import path from 'path' import connect from 'connect' import consola from 'consola' import serveStatic from 'serve-static' import servePlaceholder from 'serve-placeholder' import launchMiddleware from 'launch-editor-middleware' import { determineGlobals, isUrl } from '@nuxt/utils' import { VueRenderer } from '@nuxt/vue-renderer' import Server from '../src/server' import Listener from '../src/listener' import ServerContext from '../src/context' import renderAndGetWindow from '../src/jsdom' import nuxtMiddleware from '../src/middleware/nuxt' import errorMiddleware from '../src/middleware/error' import createTimingMiddleware from '../src/middleware/timing' jest.mock('connect') jest.mock('serve-static') jest.mock('serve-placeholder') jest.mock('launch-editor-middleware') jest.mock('@nuxt/utils') jest.mock('@nuxt/vue-renderer') jest.mock('../src/listener') jest.mock('../src/context') jest.mock('../src/jsdom') jest.mock('../src/middleware/nuxt') jest.mock('../src/middleware/error') jest.mock('../src/middleware/timing') describe('server: server', () => { const createNuxt = () => ({ options: { dir: { static: 'var/nuxt/static' }, srcDir: '/var/nuxt/src', buildDir: '/var/nuxt/build', globalName: 'test-global-name', globals: { id: 'test-globals' }, build: { publicPath: '__nuxt_test' }, router: { base: '/foo/' }, render: { id: 'test-render', dist: { id: 'test-render-dist' }, static: { id: 'test-render-static', prefix: 'test-render-static-prefix' } }, server: {}, serverMiddleware: [] }, hook: jest.fn(), ready: jest.fn(), callHook: jest.fn(), resolver: { requireModule: jest.fn() } }) beforeAll(() => { jest.spyOn(path, 'join').mockImplementation((...args) => `join(${args.join(', ')})`) jest.spyOn(path, 'resolve').mockImplementation((...args) => `resolve(${args.join(', ')})`) connect.mockReturnValue({ use: jest.fn() }) serveStatic.mockImplementation(dir => ({ id: 'test-serve-static', dir })) nuxtMiddleware.mockImplementation(options => ({ id: 'test-nuxt-middleware', ...options })) errorMiddleware.mockImplementation(options => ({ id: 'test-error-middleware', ...options })) createTimingMiddleware.mockImplementation(options => ({ id: 'test-timing-middleware', ...options })) launchMiddleware.mockImplementation(options => ({ id: 'test-open-in-editor-middleware', ...options })) servePlaceholder.mockImplementation(options => ({ key: 'test-serve-placeholder', ...options })) }) afterAll(() => { path.join.mockRestore() path.resolve.mockRestore() }) beforeEach(() => { jest.clearAllMocks() }) test('should construct server', () => { const nuxt = createNuxt() determineGlobals.mockReturnValueOnce({ ...nuxt.options.globals, name: nuxt.options.globalName }) let server = new Server(nuxt) expect(server.nuxt).toBe(nuxt) expect(server.options).toBe(nuxt.options) expect(server.publicPath).toBe('__nuxt_test') expect(server.resources).toEqual({}) expect(server.listeners).toEqual([]) expect(connect).toBeCalledTimes(1) expect(server.nuxt.hook).toBeCalledTimes(1) expect(server.nuxt.hook).toBeCalledWith('close', expect.any(Function)) const closeHook = server.nuxt.hook.mock.calls[0][1] server.close = jest.fn() expect(server.close).not.toBeCalled() closeHook() expect(server.close).toBeCalledTimes(1) nuxt.options.build._publicPath = 'http://localhost:3000/test' isUrl.mockReturnValueOnce(true) server = new Server(nuxt) expect(server.publicPath).toBe(nuxt.options.build._publicPath) }) test('should be ready for listening', async () => { const nuxt = createNuxt() const server = new Server(nuxt) const renderer = { ready: jest.fn() } const context = jest.fn() VueRenderer.mockImplementationOnce(() => renderer) ServerContext.mockImplementationOnce(() => context) server.setupMiddleware = jest.fn() path.join.mockRestore() path.resolve.mockRestore() await server.ready() expect(server.nuxt.callHook).toBeCalledTimes(2) expect(server.nuxt.callHook).nthCalledWith(1, 'render:before', server, server.options.render) expect(server.nuxt.callHook).nthCalledWith(2, 'render:done', server) expect(ServerContext).toBeCalledTimes(1) expect(ServerContext).toBeCalledWith(server) expect(VueRenderer).toBeCalledTimes(1) expect(VueRenderer).toBeCalledWith(context) expect(server.renderer).toBe(renderer) expect(renderer.ready).toBeCalledTimes(1) expect(server.setupMiddleware).toBeCalledTimes(1) jest.spyOn(path, 'join').mockImplementation((...args) => `join(${args.join(', ')})`) jest.spyOn(path, 'resolve').mockImplementation((...args) => `resolve(${args.join(', ')})`) }) test('should setup middleware', async () => { const nuxt = createNuxt() const server = new Server(nuxt) server.useMiddleware = jest.fn() server.serverContext = { id: 'test-server-context' } await server.setupMiddleware() expect(server.nuxt.callHook).toBeCalledTimes(2) expect(server.nuxt.callHook).nthCalledWith(1, 'render:setupMiddleware', server.app) expect(server.nuxt.callHook).nthCalledWith(2, 'render:errorMiddleware', server.app) expect(server.useMiddleware).toBeCalledTimes(4) expect(serveStatic).toBeCalledTimes(2) expect(serveStatic).nthCalledWith(1, 'resolve(/var/nuxt/src, var/nuxt/static)', server.options.render.static) expect(server.useMiddleware).nthCalledWith(1, { dir: 'resolve(/var/nuxt/src, var/nuxt/static)', id: 'test-serve-static', prefix: 'test-render-static-prefix' }) expect(serveStatic).nthCalledWith(2, 'resolve(/var/nuxt/build, dist, client)', server.options.render.dist) expect(server.useMiddleware).nthCalledWith(2, { handler: { dir: 'resolve(/var/nuxt/build, dist, client)', id: 'test-serve-static' }, path: '__nuxt_test' }) const nuxtMiddlewareOpts = { options: server.options, nuxt: server.nuxt, renderRoute: expect.any(Function), resources: server.resources } expect(nuxtMiddleware).toBeCalledTimes(1) expect(nuxtMiddleware).toBeCalledWith(nuxtMiddlewareOpts) expect(server.useMiddleware).nthCalledWith(3, { id: 'test-nuxt-middleware', ...nuxtMiddlewareOpts }) const errorMiddlewareOpts = { resources: server.resources, options: server.options } expect(errorMiddleware).toBeCalledTimes(1) expect(errorMiddleware).toBeCalledWith(errorMiddlewareOpts) expect(server.useMiddleware).nthCalledWith(4, { id: 'test-error-middleware', ...errorMiddlewareOpts }) }) test('should setup compressor middleware', async () => { const nuxt = createNuxt() nuxt.options.render.compressor = jest.fn() const server = new Server(nuxt) server.useMiddleware = jest.fn() server.renderer = { context: { id: 'test-server-context' } } await server.setupMiddleware() expect(server.useMiddleware).nthCalledWith(1, nuxt.options.render.compressor) server.useMiddleware.mockClear() nuxt.options.render.compressor = { id: 'test-render-compressor' } nuxt.resolver.requireModule.mockImplementationOnce(name => jest.fn(options => ({ name, ...options }))) await server.setupMiddleware() expect(nuxt.resolver.requireModule).nthCalledWith(1, 'compression') expect(server.useMiddleware).nthCalledWith(1, { id: 'test-render-compressor', name: 'compression' }) }) test('should setup timing middleware', async () => { const nuxt = createNuxt() nuxt.options.server.timing = { id: 'test-server-timing' } const server = new Server(nuxt) server.useMiddleware = jest.fn() server.renderer = { context: { id: 'test-server-context' } } await server.setupMiddleware() expect(createTimingMiddleware).nthCalledWith(1, { id: 'test-server-timing' }) expect(server.useMiddleware).nthCalledWith(1, { id: 'test-server-timing' }) }) test('should setup open-in-editor middleware', async () => { const nuxt = createNuxt() nuxt.options.dev = true nuxt.options.debug = true nuxt.options.editor = { id: 'test-editor' } const server = new Server(nuxt) server.useMiddleware = jest.fn() server.renderer = { context: { id: 'test-server-context' } } await server.setupMiddleware() expect(launchMiddleware).toBeCalledTimes(1) expect(launchMiddleware).toBeCalledWith({ id: 'test-editor' }) expect(server.useMiddleware).nthCalledWith(3, { handler: { id: 'test-editor' }, path: '__open-in-editor' }) }) test('should setup server middleware', async () => { const nuxt = createNuxt() nuxt.options.serverMiddleware = [ { id: 'test-server-middleware-1' }, { id: 'test-server-middleware-2' } ] const server = new Server(nuxt) server.useMiddleware = jest.fn() server.renderer = { context: { id: 'test-server-context' } } await server.setupMiddleware() expect(server.useMiddleware).nthCalledWith(3, { id: 'test-server-middleware-1' }) expect(server.useMiddleware).nthCalledWith(4, { id: 'test-server-middleware-2' }) }) test('should setup fallback middleware', async () => { const nuxt = createNuxt() nuxt.options.render.fallback = { dist: { id: 'test-render-fallback-dist' }, static: { id: 'test-render-fallback-static' } } const server = new Server(nuxt) server.useMiddleware = jest.fn() server.renderer = { context: { id: 'test-server-context' } } await server.setupMiddleware() expect(servePlaceholder).toBeCalledTimes(2) expect(server.useMiddleware).nthCalledWith(3, { handler: { id: 'test-render-fallback-dist', key: 'test-serve-placeholder' }, path: '__nuxt_test' }) expect(server.useMiddleware).nthCalledWith(4, { handler: { id: 'test-render-fallback-static', key: 'test-serve-placeholder' }, path: '/' }) servePlaceholder.mockClear() nuxt.options.render.fallback = {} await server.setupMiddleware() expect(servePlaceholder).not.toBeCalled() }) test('should use object middleware', () => { const nuxt = createNuxt() nuxt.options.router = { base: '/' } const server = new Server(nuxt) const handler = jest.fn() server.useMiddleware({ handler }) expect(nuxt.resolver.requireModule).not.toBeCalled() expect(server.app.use).toBeCalledTimes(1) expect(server.app.use).toBeCalledWith(nuxt.options.router.base, handler) }) test('should use function module middleware', () => { const nuxt = createNuxt() nuxt.options.router = { base: '/' } const server = new Server(nuxt) const handler = jest.fn() nuxt.resolver.requireModule.mockReturnValueOnce(handler) server.useMiddleware('test-middleware') expect(nuxt.resolver.requireModule).toBeCalledTimes(1) expect(nuxt.resolver.requireModule).toBeCalledWith('test-middleware') expect(server.app.use).toBeCalledTimes(1) expect(server.app.use).toBeCalledWith(nuxt.options.router.base, handler) }) test('should use object module middleware', () => { const nuxt = createNuxt() nuxt.options.router = { base: '/' } const server = new Server(nuxt) const handler = jest.fn() nuxt.resolver.requireModule.mockReturnValueOnce({ handler, prefix: false, path: '//middleware' }) server.useMiddleware('test-middleware') expect(nuxt.resolver.requireModule).toBeCalledTimes(1) expect(nuxt.resolver.requireModule).toBeCalledWith('test-middleware') expect(server.app.use).toBeCalledTimes(1) expect(server.app.use).toBeCalledWith('/middleware', handler) }) test('should throw error when module resolves failed', () => { const nuxt = createNuxt() nuxt.options.router = { base: '/' } const server = new Server(nuxt) const error = Error('middleware resolves failed') nuxt.resolver.requireModule.mockImplementationOnce(() => { throw error }) expect(() => server.useMiddleware('test-middleware')).toThrow(error) expect(consola.error).toBeCalledTimes(1) expect(consola.error).toBeCalledWith(error) }) test('should only log error when module resolves failed in dev mode', () => { const nuxt = createNuxt() nuxt.options.dev = true nuxt.options.router = { base: '/' } const server = new Server(nuxt) const error = Error('middleware resolves failed') nuxt.resolver.requireModule.mockImplementationOnce(() => { throw error }) server.useMiddleware('test-middleware') expect(consola.error).toBeCalledTimes(1) expect(consola.error).toBeCalledWith(error) }) test('should render route via renderer', () => { const nuxt = createNuxt() const server = new Server(nuxt) server.renderer = { renderRoute: jest.fn() } server.renderRoute('test-render-route') expect(server.renderer.renderRoute).toBeCalledTimes(1) expect(server.renderer.renderRoute).toBeCalledWith('test-render-route') }) test('should load resources via renderer', () => { const nuxt = createNuxt() const server = new Server(nuxt) server.renderer = { loadResources: jest.fn() } server.loadResources('test-load-resources') expect(server.renderer.loadResources).toBeCalledTimes(1) expect(server.renderer.loadResources).toBeCalledWith('test-load-resources') }) test('should render and get window', () => { const nuxt = createNuxt() const globals = { ...nuxt.options.globals, name: nuxt.options.globalName, loadedCallback: jest.fn() } determineGlobals.mockReturnValueOnce(globals) const server = new Server(nuxt) server.renderAndGetWindow('/render/window') expect(renderAndGetWindow).toBeCalledTimes(1) expect(renderAndGetWindow).toBeCalledWith('/render/window', {}, { loadedCallback: globals.loadedCallback, ssr: nuxt.options.render.ssr, globals: globals }) }) test('should listen server', async () => { const nuxt = createNuxt() const server = new Server(nuxt) const listener = { listen: jest.fn(), server: jest.fn() } Listener.mockImplementationOnce(() => { return listener }) await server.listen(3000, 'localhost', '/var/nuxt/unix.socket') expect(Listener).toBeCalledWith({ port: 3000, host: 'localhost', socket: '/var/nuxt/unix.socket', https: undefined, app: server.app, dev: server.options.dev, baseURL: '/foo/' }) expect(listener.listen).toBeCalledTimes(1) expect(server.listeners).toEqual([ listener ]) expect(server.nuxt.callHook).toBeCalledTimes(1) expect(server.nuxt.callHook).toBeCalledWith('listen', listener.server, listener) }) test('should listen server via options.server', async () => { const nuxt = createNuxt() nuxt.options.server = { host: 'localhost', port: '3000', socket: '/var/nuxt/unix.socket', https: true } const server = new Server(nuxt) await server.listen() expect(Listener).toBeCalledWith({ ...nuxt.options.server, app: server.app, dev: server.options.dev, baseURL: '/foo/' }) }) test('should close server', async () => { const removeAllListeners = jest.fn() connect.mockReturnValueOnce({ use: jest.fn(), removeAllListeners }) const nuxt = createNuxt() const server = new Server(nuxt) const listener = { close: jest.fn() } server.listeners = [ listener ] server.renderer = { close: jest.fn() } server.resources = { id: 'test-resources' } await server.close() expect(server.__closed).toEqual(true) expect(listener.close).toBeCalledTimes(1) expect(server.listeners).toEqual([]) expect(server.renderer.close).toBeCalledTimes(1) expect(removeAllListeners).toBeCalledTimes(1) expect(server.app).toBeNull() expect(server.resources).toEqual({}) }) test('should prevent closing server multiple times', async () => { const removeAllListeners = jest.fn() connect.mockReturnValueOnce({ use: jest.fn(), removeAllListeners }) const nuxt = createNuxt() const server = new Server(nuxt) server.renderer = {} await server.close() expect(server.__closed).toEqual(true) expect(removeAllListeners).toBeCalledTimes(1) removeAllListeners.mockClear() await server.close() expect(server.__closed).toEqual(true) expect(removeAllListeners).not.toBeCalled() }) })