diff --git a/src/hmr/hotModuleReplacement.js b/src/hmr/hotModuleReplacement.js
index 6d6611b1..802c0a12 100644
--- a/src/hmr/hotModuleReplacement.js
+++ b/src/hmr/hotModuleReplacement.js
@@ -120,7 +120,11 @@ function updateCss(el, url) {
newEl.href = `${url}?${Date.now()}`;
- el.parentNode.appendChild(newEl);
+ if (el.nextSibling) {
+ el.parentNode.insertBefore(newEl, el.nextSibling);
+ } else {
+ el.parentNode.appendChild(newEl);
+ }
}
function getReloadUrl(href, src) {
@@ -160,6 +164,7 @@ function reloadStyle(src) {
if (url) {
updateCss(el, url);
+
loaded = true;
}
});
@@ -182,18 +187,8 @@ function reloadAll() {
function isUrlRequest(url) {
// An URL is not an request if
- // 1. It's an absolute url
- if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {
- return false;
- }
-
- // 2. It's a protocol-relative
- if (/^\/\//.test(url)) {
- return false;
- }
-
- // 3. Its a `#` link
- if (/^#/.test(url)) {
+ // It is not http or https
+ if (!/^https?:/i.test(url)) {
return false;
}
diff --git a/test/HMR.test.js b/test/HMR.test.js
new file mode 100644
index 00000000..be56e01d
--- /dev/null
+++ b/test/HMR.test.js
@@ -0,0 +1,284 @@
+/* eslint-env browser */
+/* eslint-disable no-console */
+
+import hotModuleReplacement from '../src/hmr/hotModuleReplacement';
+
+function getLoadEvent() {
+ const event = document.createEvent('Event');
+
+ event.initEvent('load', false, false);
+
+ return event;
+}
+
+function getErrorEvent() {
+ const event = document.createEvent('Event');
+
+ event.initEvent('error', false, false);
+
+ return event;
+}
+
+describe('HMR', () => {
+ let consoleMock = null;
+
+ beforeEach(() => {
+ consoleMock = jest.spyOn(console, 'log').mockImplementation(() => () => {});
+
+ jest.spyOn(Date, 'now').mockImplementation(() => 1479427200000);
+
+ document.head.innerHTML = '';
+ document.body.innerHTML = '';
+ });
+
+ afterEach(() => {
+ consoleMock.mockClear();
+ });
+
+ it('should works', (done) => {
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+
+ done();
+ }, 100);
+ });
+
+ it('should works with multiple updates', (done) => {
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+
+ jest.spyOn(Date, 'now').mockImplementation(() => 1479427200001);
+
+ const update2 = hotModuleReplacement('./src/style.css', {});
+
+ update2();
+
+ setTimeout(() => {
+ const links2 = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links2[0].visited).toBe(true);
+ expect(links2[0].isLoaded).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links2[1].dispatchEvent(getLoadEvent());
+
+ expect(links2[1].isLoaded).toBe(true);
+
+ done();
+ }, 100);
+ }, 100);
+ });
+
+ it('should reloads with locals', (done) => {
+ const update = hotModuleReplacement('./src/style.css', {
+ locals: { foo: 'bar' },
+ });
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+
+ done();
+ }, 100);
+ });
+
+ it('should reloads with reloadAll option', (done) => {
+ const update = hotModuleReplacement('./src/style.css', {
+ reloadAll: true,
+ });
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+
+ done();
+ }, 100);
+ });
+
+ it('should reloads with non http/https link href', (done) => {
+ document.head.innerHTML =
+ '';
+
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+ expect(links[2].visited).toBeUndefined();
+
+ done();
+ }, 100);
+ });
+
+ it('should reloads with # link href', (done) => {
+ document.head.innerHTML =
+ '';
+
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+ expect(links[2].visited).toBeUndefined();
+
+ done();
+ }, 100);
+ });
+
+ it('should reloads with link without href', (done) => {
+ document.head.innerHTML =
+ '';
+
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+ expect(links[2].visited).toBeUndefined();
+
+ done();
+ }, 100);
+ });
+
+ it('should reloads with absolute remove url', (done) => {
+ document.head.innerHTML =
+ '';
+
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getLoadEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+ expect(links[2].visited).toBeUndefined();
+
+ done();
+ }, 100);
+ });
+
+ it('should handle error event', (done) => {
+ const update = hotModuleReplacement('./src/style.css', {});
+
+ update();
+
+ setTimeout(() => {
+ expect(console.log.mock.calls[0][0]).toMatchSnapshot();
+
+ const links = Array.prototype.slice.call(
+ document.querySelectorAll('link')
+ );
+
+ expect(links[0].visited).toBe(true);
+ expect(document.head.innerHTML).toMatchSnapshot();
+
+ links[1].dispatchEvent(getErrorEvent());
+
+ expect(links[1].isLoaded).toBe(true);
+
+ done();
+ }, 100);
+ });
+});
diff --git a/test/__snapshots__/HMR.test.js.snap b/test/__snapshots__/HMR.test.js.snap
new file mode 100644
index 00000000..269260d0
--- /dev/null
+++ b/test/__snapshots__/HMR.test.js.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`HMR should handle error event 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should handle error event 2`] = `""`;
+
+exports[`HMR should reloads with # link href 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should reloads with # link href 2`] = `""`;
+
+exports[`HMR should reloads with absolute remove url 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should reloads with absolute remove url 2`] = `""`;
+
+exports[`HMR should reloads with link without href 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should reloads with link without href 2`] = `""`;
+
+exports[`HMR should reloads with locals 1`] = `"[HMR] Detected local css modules. Reload all css"`;
+
+exports[`HMR should reloads with locals 2`] = `""`;
+
+exports[`HMR should reloads with non http/https link href 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should reloads with non http/https link href 2`] = `""`;
+
+exports[`HMR should reloads with reloadAll option 1`] = `"[HMR] Reload all css"`;
+
+exports[`HMR should reloads with reloadAll option 2`] = `""`;
+
+exports[`HMR should works 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should works 2`] = `""`;
+
+exports[`HMR should works with multiple updates 1`] = `"[HMR] css reload %s"`;
+
+exports[`HMR should works with multiple updates 2`] = `""`;
+
+exports[`HMR should works with multiple updates 3`] = `""`;