diff --git a/lib/utils/getSocketServerImplementation.js b/lib/utils/getSocketServerImplementation.js index 6df634085f..c60376a9ba 100644 --- a/lib/utils/getSocketServerImplementation.js +++ b/lib/utils/getSocketServerImplementation.js @@ -9,6 +9,9 @@ function getSocketServerImplementation(options) { if (options.serverMode === 'sockjs') { // eslint-disable-next-line global-require ServerImplementation = require('../servers/SockJSServer'); + } else if (options.serverMode === 'ws') { + // eslint-disable-next-line global-require + ServerImplementation = require('../servers/WebsocketServer'); } else { try { // eslint-disable-next-line global-require, import/no-dynamic-require @@ -29,7 +32,7 @@ function getSocketServerImplementation(options) { if (!serverImplFound) { throw new Error( - "serverMode must be a string denoting a default implementation (e.g. 'sockjs'), a full path to " + + "serverMode must be a string denoting a default implementation (e.g. 'sockjs', 'ws'), a full path to " + 'a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer) ' + 'via require.resolve(...), or the class itself which extends BaseServer' ); diff --git a/test/server/serverMode-option.test.js b/test/server/serverMode-option.test.js index b0ef7357fb..a87d814794 100644 --- a/test/server/serverMode-option.test.js +++ b/test/server/serverMode-option.test.js @@ -7,134 +7,216 @@ const request = require('supertest'); const sockjs = require('sockjs'); const SockJSServer = require('../../lib/servers/SockJSServer'); const config = require('../fixtures/simple-config/webpack.config'); -const testServer = require('../helpers/test-server'); const BaseServer = require('../../lib/servers/BaseServer'); const port = require('../ports-map')['serverMode-option']; describe('serverMode option', () => { + let mockedTestServer; + let testServer; let server; let req; + let getSocketServerImplementation; + + const serverModes = [ + { + title: 'as a string ("sockjs")', + serverMode: 'sockjs', + }, + { + title: 'as a path ("sockjs")', + serverMode: require.resolve('../../lib/servers/SockJSServer'), + }, + { + title: 'as a string ("ws")', + serverMode: 'ws', + }, + { + title: 'as a path ("ws")', + serverMode: require.resolve('../../lib/servers/WebsocketServer'), + }, + { + title: 'as a class (custom implementation)', + serverMode: class CustomServer {}, + }, + { + title: 'as a nonexistent path', + serverMode: '/bad/path/to/implementation', + }, + ]; + + describe('is passed to getSocketServerImplementation correctly', () => { + beforeEach(() => { + jest.mock('../../lib/utils/getSocketServerImplementation'); + // eslint-disable-next-line global-require + getSocketServerImplementation = require('../../lib/utils/getSocketServerImplementation'); + getSocketServerImplementation.mockImplementation(() => { + return class MockServer { + // eslint-disable-next-line no-empty-function + onConnection() {} + }; + }); + }); - afterEach((done) => { - testServer.close(done); - req = null; - server = null; - }); + afterEach((done) => { + jest.resetAllMocks(); + jest.resetModules(); - describe('as a string ("sockjs")', () => { - beforeEach((done) => { - server = testServer.start( - config, - { - serverMode: 'sockjs', - port, - }, - done - ); - req = request(`http://localhost:${port}`); + mockedTestServer.close(done); }); - it('sockjs path responds with a 200', (done) => { - req.get('/sockjs-node').expect(200, done); + serverModes.forEach((data) => { + it(data.title, (done) => { + // eslint-disable-next-line global-require + mockedTestServer = require('../helpers/test-server'); + mockedTestServer.start( + config, + { + serverMode: data.serverMode, + port, + }, + () => { + expect(getSocketServerImplementation.mock.calls.length).toEqual(1); + expect(getSocketServerImplementation.mock.calls[0].length).toEqual( + 1 + ); + expect( + getSocketServerImplementation.mock.calls[0][0].serverMode + ).toEqual(data.serverMode); + done(); + } + ); + }); }); }); - describe('as a path ("sockjs")', () => { - beforeEach((done) => { - server = testServer.start( - config, - { - serverMode: require.resolve('../../lib/servers/SockJSServer'), - port, - }, - done - ); - req = request(`http://localhost:${port}`); + describe('passed to server', () => { + beforeAll(() => { + jest.unmock('../../lib/utils/getSocketServerImplementation'); + // eslint-disable-next-line global-require + testServer = require('../helpers/test-server'); }); - it('sockjs path responds with a 200', (done) => { - req.get('/sockjs-node').expect(200, done); + afterEach((done) => { + testServer.close(done); + req = null; + server = null; }); - }); - describe('as a class ("sockjs")', () => { - beforeEach((done) => { - server = testServer.start( - config, - { - serverMode: SockJSServer, - port, - }, - done - ); - req = request(`http://localhost:${port}`); + describe('as a string ("sockjs")', () => { + beforeEach((done) => { + server = testServer.start( + config, + { + serverMode: 'sockjs', + port, + }, + done + ); + req = request(`http://localhost:${port}`); + }); + + it('sockjs path responds with a 200', (done) => { + req.get('/sockjs-node').expect(200, done); + }); }); - it('sockjs path responds with a 200', (done) => { - req.get('/sockjs-node').expect(200, done); + describe('as a path ("sockjs")', () => { + beforeEach((done) => { + server = testServer.start( + config, + { + serverMode: require.resolve('../../lib/servers/SockJSServer'), + port, + }, + done + ); + req = request(`http://localhost:${port}`); + }); + + it('sockjs path responds with a 200', (done) => { + req.get('/sockjs-node').expect(200, done); + }); }); - }); - describe('as a class (custom "sockjs" implementation)', () => { - it('uses supplied server implementation', (done) => { - server = testServer.start( - config, - { - port, - sockPath: '/foo/test/bar/', - serverMode: class MySockJSServer extends BaseServer { - constructor(serv) { - super(serv); - this.socket = sockjs.createServer({ - // Use provided up-to-date sockjs-client - sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js', - // Limit useless logs - log: (severity, line) => { - if (severity === 'error') { - this.server.log.error(line); - } else { - this.server.log.debug(line); - } - }, - }); - - this.socket.installHandlers(this.server.listeningApp, { - prefix: this.server.sockPath, - }); - - expect(server.options.sockPath).toEqual('/foo/test/bar/'); - } - - send(connection, message) { - connection.write(message); - } - - close(connection) { - connection.close(); - } - - onConnection(f) { - this.socket.on('connection', f); - } + describe('as a class ("sockjs")', () => { + beforeEach((done) => { + server = testServer.start( + config, + { + serverMode: SockJSServer, + port, }, - }, - done - ); + done + ); + req = request(`http://localhost:${port}`); + }); + + it('sockjs path responds with a 200', (done) => { + req.get('/sockjs-node').expect(200, done); + }); }); - }); - describe('as a path with nonexistent path', () => { - it('should throw an error', () => { - expect(() => { + describe('as a class (custom "sockjs" implementation)', () => { + it('uses supplied server implementation', (done) => { server = testServer.start( config, { - serverMode: '/bad/path/to/implementation', port, + sockPath: '/foo/test/bar/', + serverMode: class MySockJSServer extends BaseServer { + constructor(serv) { + super(serv); + this.socket = sockjs.createServer({ + // Use provided up-to-date sockjs-client + sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js', + // Limit useless logs + log: (severity, line) => { + if (severity === 'error') { + this.server.log.error(line); + } else { + this.server.log.debug(line); + } + }, + }); + + this.socket.installHandlers(this.server.listeningApp, { + prefix: this.server.sockPath, + }); + + expect(server.options.sockPath).toEqual('/foo/test/bar/'); + } + + send(connection, message) { + connection.write(message); + } + + close(connection) { + connection.close(); + } + + onConnection(f) { + this.socket.on('connection', f); + } + }, }, - () => {} + done ); - }).toThrow(/serverMode must be a string/); + }); + }); + + describe('as a path with nonexistent path', () => { + it('should throw an error', () => { + expect(() => { + server = testServer.start( + config, + { + serverMode: '/bad/path/to/implementation', + port, + }, + () => {} + ); + }).toThrow(/serverMode must be a string/); + }); }); }); }); diff --git a/test/server/utils/getSocketServerImplementation.test.js b/test/server/utils/getSocketServerImplementation.test.js index 0a157c39a9..1898b8addf 100644 --- a/test/server/utils/getSocketServerImplementation.test.js +++ b/test/server/utils/getSocketServerImplementation.test.js @@ -2,9 +2,10 @@ const getSocketServerImplementation = require('../../../lib/utils/getSocketServerImplementation'); const SockJSServer = require('../../../lib/servers/SockJSServer'); +const WebsocketServer = require('../../../lib/servers/WebsocketServer'); describe('getSocketServerImplementation util', () => { - it("should works with string serverMode ('sockjs')", () => { + it("should work with string serverMode ('sockjs')", () => { let result; expect(() => { @@ -16,7 +17,7 @@ describe('getSocketServerImplementation util', () => { expect(result).toEqual(SockJSServer); }); - it('should works with serverMode (SockJSServer class)', () => { + it('should work with serverMode (SockJSServer class)', () => { let result; expect(() => { @@ -40,7 +41,43 @@ describe('getSocketServerImplementation util', () => { expect(result).toEqual(SockJSServer); }); - it('should throws with serverMode (bad path)', () => { + it("should work with string serverMode ('ws')", () => { + let result; + + expect(() => { + result = getSocketServerImplementation({ + serverMode: 'ws', + }); + }).not.toThrow(); + + expect(result).toEqual(WebsocketServer); + }); + + it('should work with serverMode (WebsocketServer class)', () => { + let result; + + expect(() => { + result = getSocketServerImplementation({ + serverMode: WebsocketServer, + }); + }).not.toThrow(); + + expect(result).toEqual(WebsocketServer); + }); + + it('should work with serverMode (WebsocketServer full path)', () => { + let result; + + expect(() => { + result = getSocketServerImplementation({ + serverMode: require.resolve('../../../lib/servers/WebsocketServer'), + }); + }).not.toThrow(); + + expect(result).toEqual(WebsocketServer); + }); + + it('should throw with serverMode (bad path)', () => { expect(() => { getSocketServerImplementation({ serverMode: '/bad/path/to/implementation',