Nuxt/packages/server/test/server.test.js

543 lines
16 KiB
JavaScript

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
})
})
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()
})
})