diff --git a/client-src/default/index.js b/client-src/default/index.js index dede6f0763..1cbf5372d9 100644 --- a/client-src/default/index.js +++ b/client-src/default/index.js @@ -137,6 +137,7 @@ const onSocketMessage = { }, close() { log.error('[WDS] Disconnected!'); + sendMessage('Close'); }, }; diff --git a/lib/Server.js b/lib/Server.js index 4043b354ee..0935f09655 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -678,7 +678,9 @@ class Server { this.hostname = hostname; return this.listeningApp.listen(port, hostname, (err) => { - this.createSocketServer(); + if (this.options.hot || this.options.liveReload) { + this.createSocketServer(); + } if (this.options.bonjour) { runBonjour(this.options); @@ -923,7 +925,7 @@ class Server { const watcher = chokidar.watch(watchPath, watchOptions); // disabling refreshing on changing the content - if (this.options.liveReload !== false) { + if (this.options.liveReload) { watcher.on('change', () => { this.sockWrite(this.sockets, 'content-changed'); }); diff --git a/test/client/__snapshots__/index.test.js.snap b/test/client/__snapshots__/index.test.js.snap index cc267a2ce7..9768edb49f 100644 --- a/test/client/__snapshots__/index.test.js.snap +++ b/test/client/__snapshots__/index.test.js.snap @@ -1,5 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`index should run onSocketMessage.close (hot enabled) 1`] = `"[WDS] Disconnected!"`; + +exports[`index should run onSocketMessage.close (hot enabled) 2`] = `"Close"`; + +exports[`index should run onSocketMessage.close (liveReload enabled) 1`] = `"[WDS] Disconnected!"`; + +exports[`index should run onSocketMessage.close (liveReload enabled) 2`] = `"Close"`; + exports[`index should run onSocketMessage.close 1`] = `"[WDS] Disconnected!"`; exports[`index should run onSocketMessage.close 2`] = `"Close"`; diff --git a/test/client/index.test.js b/test/client/index.test.js index ba402a2dd9..f2196febae 100644 --- a/test/client/index.test.js +++ b/test/client/index.test.js @@ -214,4 +214,20 @@ describe('index', () => { expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); }); + + test('should run onSocketMessage.close (hot enabled)', () => { + // enabling hot + onSocketMessage.hot(); + onSocketMessage.close(); + expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); + expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); + }); + + test('should run onSocketMessage.close (liveReload enabled)', () => { + // enabling liveReload + onSocketMessage.liveReload(); + onSocketMessage.close(); + expect(log.log.error.mock.calls[0][0]).toMatchSnapshot(); + expect(sendMessage.mock.calls[0][0]).toMatchSnapshot(); + }); }); diff --git a/test/e2e/ClientOptions.test.js b/test/e2e/ClientOptions.test.js index 4847f2a6a6..e9d66e3b46 100644 --- a/test/e2e/ClientOptions.test.js +++ b/test/e2e/ClientOptions.test.js @@ -365,6 +365,12 @@ describe('Client console.log', () => { port: port2, host: '0.0.0.0', }; + const transportModes = [ + {}, + { transportMode: 'sockjs' }, + { transportMode: 'ws' }, + ]; + const cases = [ { title: 'hot disabled', @@ -390,6 +396,13 @@ describe('Client console.log', () => { liveReload: true, }, }, + { + title: 'liveReload & hot are disabled', + options: { + liveReload: false, + hot: false, + }, + }, // TODO: make clientLogLevel work as expected for HMR logs { title: 'clientLogLevel is silent', @@ -399,48 +412,29 @@ describe('Client console.log', () => { }, ]; - cases.forEach(({ title, options }) => { - it(title, (done) => { - const res = []; + transportModes.forEach(async (mode) => { + await cases.forEach(async ({ title, options }) => { + title += ` (${ + Object.keys(mode).length ? mode.transportMode : 'default' + })`; + options = { ...mode, ...options }; const testOptions = Object.assign({}, baseOptions, options); - - // TODO: use async/await when Node.js v6 support is dropped - Promise.resolve() - .then(() => { - return new Promise((resolve) => { - testServer.startAwaitingCompilation(config, testOptions, resolve); - }); - }) - .then(() => { - // make sure the previous Promise is not passing along strange arguments to runBrowser - return runBrowser(); - }) - .then(({ page, browser }) => { - return new Promise((resolve) => { - page.goto(`http://localhost:${port2}/main`); - page.on('console', ({ _text }) => { - res.push(_text); - }); - // wait for load before closing the browser - page.waitForNavigation({ waitUntil: 'load' }).then(() => { - page.waitFor(beforeBrowserCloseDelay).then(() => { - browser.close().then(() => { - resolve(); - }); - }); - }); - }); - }) - .then(() => { - return new Promise((resolve) => { - testServer.close(resolve); - }); - }) - .then(() => { - // Order doesn't matter, maybe we should improve that in future - expect(res.sort()).toMatchSnapshot(); - done(); + await it(title, async (done) => { + await testServer.startAwaitingCompilation(config, testOptions); + const res = []; + const { page, browser } = await runBrowser(); + page.goto(`http://localhost:${port2}/main`); + page.on('console', ({ _text }) => { + res.push(_text); }); + // wait for load before closing the browser + await page.waitForNavigation({ waitUntil: 'load' }); + await page.waitFor(beforeBrowserCloseDelay); + await browser.close(); + // Order doesn't matter, maybe we should improve that in future + await expect(res.sort()).toMatchSnapshot(); + await testServer.close(done); + }); }); }); }); diff --git a/test/e2e/Socket-injection.test.js b/test/e2e/Socket-injection.test.js new file mode 100644 index 0000000000..2005af6796 --- /dev/null +++ b/test/e2e/Socket-injection.test.js @@ -0,0 +1,403 @@ +'use strict'; + +/* eslint-disable + no-unused-vars +*/ + +const express = require('express'); +const request = require('supertest'); +const testServer = require('../helpers/test-server'); +const runBrowser = require('../helpers/run-browser'); +const port = require('../ports-map').WebsocketClient; +const config = require('../fixtures/client-config/webpack.config'); +const { beforeBrowserCloseDelay } = require('../helpers/puppeteer-constants'); + +const transportModeWSValidOptions = [{}, { transportMode: 'ws' }]; + +describe('ws websocket client injection', () => { + for (const wsOption of transportModeWSValidOptions) { + let req; + let server; + const errorMsg = `WebSocket connection to 'ws://localhost:${port}/ws' failed: Error during WebSocket handshake: Unexpected response code: 404`; + const res = []; + + describe('testing default settings', () => { + beforeAll((done) => { + const options = { + port, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(false); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + + describe('testing when liveReload is enabled', () => { + beforeAll((done) => { + const options = { + port, + liveReload: true, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(false); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + + describe('testing when liveReload is disabled', () => { + beforeAll((done) => { + const options = { + port, + liveReload: false, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(false); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + + describe('testing when hot is enabled', () => { + beforeAll((done) => { + const options = { + port, + hot: true, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(false); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + + describe('testing when hot is enabled and liveReload is disabled', () => { + beforeAll((done) => { + const options = { + port, + hot: true, + liveReload: false, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(false); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + + describe('testing when hot is disabled and liveReload is enabled', () => { + beforeAll((done) => { + const options = { + port, + hot: false, + liveReload: true, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(false); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + + describe('testing when hot and liveReload are disabled', () => { + beforeAll((done) => { + const options = { + port, + hot: false, + liveReload: false, + ...wsOption, + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should not be injected', (done) => { + // TODO: listen for websocket requestType via puppeteer when added + // to puppeteer: https://github.com/puppeteer/puppeteer/issues/2974 + runBrowser().then(({ page, browser }) => { + page.on('console', ({ _text }) => { + res.push(_text); + }); + page.waitForNavigation({ waitUntil: 'load' }).then(() => { + page.waitFor(beforeBrowserCloseDelay).then(() => { + browser.close().then(() => { + // if the error msg doesn't exist that means the ws websocket is working. + expect(res.includes(errorMsg)).toBe(true); + done(); + }); + }); + }); + page.goto(`http://localhost:${port}/main`); + }); + }); + }); + } +}); + +describe('sockjs websocket client injection', () => { + let req; + let server; + + describe('testing default settings', () => { + beforeAll((done) => { + const options = { + port, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + req.get('/ws').expect(200, 'Welcome to SockJS!\n', done); + }); + }); + + describe('testing when liveReload is enabled', () => { + beforeAll((done) => { + const options = { + port, + liveReload: true, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + req.get('/ws').expect(200, 'Welcome to SockJS!\n', done); + }); + }); + + describe('testing when liveReload is disabled', () => { + beforeAll((done) => { + const options = { + port, + liveReload: false, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + req.get('/ws').expect(200, 'Welcome to SockJS!\n', done); + }); + }); + + describe('testing when hot is enabled', () => { + beforeAll((done) => { + const options = { + port, + hot: true, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + req.get('/ws').expect(200, 'Welcome to SockJS!\n', done); + }); + }); + + describe('testing when hot is enabled and liveReload is disabled', () => { + beforeAll((done) => { + const options = { + port, + hot: true, + liveReload: false, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + req.get('/ws').expect(200, 'Welcome to SockJS!\n', done); + }); + }); + + describe('testing when hot is disabled and liveReload is enabled', () => { + beforeAll((done) => { + const options = { + port, + hot: false, + liveReload: true, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should be injected', (done) => { + req.get('/ws').expect(200, 'Welcome to SockJS!\n', done); + }); + }); + + describe('testing when hot and liveReload are disabled', () => { + beforeAll((done) => { + const options = { + port, + hot: false, + liveReload: false, + transportMode: 'sockjs', + }; + server = testServer.start(config, options, done); + req = request(`http://localhost:${port}`); + }); + + afterAll(testServer.close); + + it('should not be injected', (done) => { + req + .get('/ws') + .expect(404) + .then(({ res }) => { + expect(res.text.includes('Cannot GET /ws')).toBe(true); + done(); + }); + }); + }); +}); diff --git a/test/e2e/__snapshots__/ClientOptions.test.js.snap b/test/e2e/__snapshots__/ClientOptions.test.js.snap index d3040d3f2c..72d313bf8a 100644 --- a/test/e2e/__snapshots__/ClientOptions.test.js.snap +++ b/test/e2e/__snapshots__/ClientOptions.test.js.snap @@ -1,20 +1,48 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Client console.log clientLogLevel is silent 1`] = ` +exports[`Client console.log clientLogLevel is silent (default) 1`] = ` Array [ "Hey.", "[HMR] Waiting for update signal from WDS...", ] `; -exports[`Client console.log hot disabled 1`] = ` +exports[`Client console.log clientLogLevel is silent (sockjs) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`Client console.log clientLogLevel is silent (ws) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`Client console.log hot disabled (default) 1`] = ` Array [ "Hey.", "[WDS] Live Reloading enabled.", ] `; -exports[`Client console.log hot enabled 1`] = ` +exports[`Client console.log hot disabled (sockjs) 1`] = ` +Array [ + "Hey.", + "[WDS] Live Reloading enabled.", +] +`; + +exports[`Client console.log hot disabled (ws) 1`] = ` +Array [ + "Hey.", + "[WDS] Live Reloading enabled.", +] +`; + +exports[`Client console.log hot enabled (default) 1`] = ` Array [ "Hey.", "[HMR] Waiting for update signal from WDS...", @@ -23,15 +51,98 @@ Array [ ] `; -exports[`Client console.log liveReload disabled 1`] = ` +exports[`Client console.log hot enabled (sockjs) 1`] = ` Array [ "Hey.", "[HMR] Waiting for update signal from WDS...", "[WDS] Hot Module Replacement enabled.", + "[WDS] Live Reloading enabled.", +] +`; + +exports[`Client console.log hot enabled (ws) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", + "[WDS] Hot Module Replacement enabled.", + "[WDS] Live Reloading enabled.", +] +`; + +exports[`Client console.log liveReload & hot are disabled (default) 1`] = ` +Array [ + "Hey.", + "WebSocket connection to 'ws://localhost:8096/ws' failed: Error during WebSocket handshake: Unexpected response code: 404", + "WebSocket connection to 'ws://localhost:8096/ws' failed: Error during WebSocket handshake: Unexpected response code: 404", + "[WDS] Disconnected!", + "[WDS] JSHandle@object", + "[WDS] JSHandle@object", +] +`; + +exports[`Client console.log liveReload & hot are disabled (sockjs) 1`] = ` +Array [ + "Failed to load resource: the server responded with a status of 404 (Not Found)", + "Failed to load resource: the server responded with a status of 404 (Not Found)", + "Hey.", + "[WDS] Disconnected!", +] +`; + +exports[`Client console.log liveReload & hot are disabled (ws) 1`] = ` +Array [ + "Hey.", + "WebSocket connection to 'ws://localhost:8096/ws' failed: Error during WebSocket handshake: Unexpected response code: 404", + "WebSocket connection to 'ws://localhost:8096/ws' failed: Error during WebSocket handshake: Unexpected response code: 404", + "[WDS] Disconnected!", + "[WDS] JSHandle@object", + "[WDS] JSHandle@object", +] +`; + +exports[`Client console.log liveReload disabled (default) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", + "[WDS] Hot Module Replacement enabled.", +] +`; + +exports[`Client console.log liveReload disabled (sockjs) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", + "[WDS] Hot Module Replacement enabled.", +] +`; + +exports[`Client console.log liveReload disabled (ws) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", + "[WDS] Hot Module Replacement enabled.", +] +`; + +exports[`Client console.log liveReload enabled (default) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", + "[WDS] Hot Module Replacement enabled.", + "[WDS] Live Reloading enabled.", +] +`; + +exports[`Client console.log liveReload enabled (sockjs) 1`] = ` +Array [ + "Hey.", + "[HMR] Waiting for update signal from WDS...", + "[WDS] Hot Module Replacement enabled.", + "[WDS] Live Reloading enabled.", ] `; -exports[`Client console.log liveReload enabled 1`] = ` +exports[`Client console.log liveReload enabled (ws) 1`] = ` Array [ "Hey.", "[HMR] Waiting for update signal from WDS...",