feat(test): unit tests for core/config module (#4760)

* feat(test): enable tests in packages

* fix: wait error hook

* test: entry and hookable in core

* fix(test): options snapshot in windows

* refactor(test): simpilify jest.fn

* test: module in core

* test: complete module test

* test: add test for resolver in core

* test: update config snapshot

* test: nuxt in core module
This commit is contained in:
Xin Du (Clark) 2019-01-19 12:00:51 +00:00 committed by Sébastien Chopin
parent deadc48f19
commit a616c09b22
15 changed files with 1258 additions and 14 deletions

View File

@ -19,7 +19,7 @@
"test:fixtures": "jest test/fixtures --forceExit", "test:fixtures": "jest test/fixtures --forceExit",
"test:e2e": "jest -i test/e2e --forceExit", "test:e2e": "jest -i test/e2e --forceExit",
"test:lint": "yarn lint", "test:lint": "yarn lint",
"test:unit": "jest test/unit --forceExit", "test:unit": "jest test/unit packages --forceExit",
"test:types": "tsc -p test/types", "test:types": "tsc -p test/types",
"postinstall": "lerna link && yarn dev", "postinstall": "lerna link && yarn dev",
"release": "./scripts/release" "release": "./scripts/release"

View File

@ -8,7 +8,7 @@ describe('cli', () => {
test('calls expected method', async () => { test('calls expected method', async () => {
const mockedCommand = { const mockedCommand = {
run: jest.fn().mockImplementation(() => Promise.resolve({})) run: jest.fn(() => Promise.resolve({}))
} }
getCommand.mockImplementationOnce(() => Promise.resolve(mockedCommand)) getCommand.mockImplementationOnce(() => Promise.resolve(mockedCommand))

View File

@ -5,7 +5,7 @@ import listCommands from '../../src/list'
import getCommand from '../../src/commands' import getCommand from '../../src/commands'
import { indent, foldLines, colorize } from '../../src/utils/formatting' import { indent, foldLines, colorize } from '../../src/utils/formatting'
jest.mock('chalk', () => ({ green: jest.fn().mockImplementation(text => text) })) jest.mock('chalk', () => ({ green: jest.fn(text => text) }))
jest.mock('../../src/commands') jest.mock('../../src/commands')
jest.mock('../../src/utils/formatting') jest.mock('../../src/utils/formatting')

View File

@ -138,7 +138,6 @@ export function getNuxtConfig(_options) {
options.build._publicPath = options.build._publicPath.replace(/([^/])$/, '$1/') options.build._publicPath = options.build._publicPath.replace(/([^/])$/, '$1/')
// Ignore publicPath on dev // Ignore publicPath on dev
/* istanbul ignore if */
if (options.dev && isUrl(options.build.publicPath)) { if (options.dev && isUrl(options.build.publicPath)) {
options.build.publicPath = options.build._publicPath options.build.publicPath = options.build._publicPath
} }
@ -263,7 +262,6 @@ export function getNuxtConfig(_options) {
defaultsDeep(options, modePreset || options.modes.universal) defaultsDeep(options, modePreset || options.modes.universal)
// If no server-side rendering, add appear true transition // If no server-side rendering, add appear true transition
/* istanbul ignore if */
if (options.render.ssr === false && options.transition) { if (options.render.ssr === false && options.transition) {
options.transition.appear = true options.transition.appear = true
} }

View File

@ -159,6 +159,7 @@ Object {
"generate": Object { "generate": Object {
"concurrency": 500, "concurrency": 500,
"dir": "/var/nuxt/test/dist", "dir": "/var/nuxt/test/dist",
"exclude": Array [],
"fallback": "200.html", "fallback": "200.html",
"interval": 0, "interval": 0,
"routes": Array [], "routes": Array [],

View File

@ -145,6 +145,7 @@ Object {
"generate": Object { "generate": Object {
"concurrency": 500, "concurrency": 500,
"dir": "dist", "dir": "dist",
"exclude": Array [],
"fallback": "200.html", "fallback": "200.html",
"interval": 0, "interval": 0,
"routes": Array [], "routes": Array [],
@ -467,6 +468,7 @@ Object {
"generate": Object { "generate": Object {
"concurrency": 500, "concurrency": 500,
"dir": "dist", "dir": "dist",
"exclude": Array [],
"fallback": "200.html", "fallback": "200.html",
"interval": 0, "interval": 0,
"routes": Array [], "routes": Array [],

View File

@ -1,11 +1,11 @@
import { getDefaultNuxtConfig, getNuxtConfig } from '../src' import { getDefaultNuxtConfig, getNuxtConfig } from '../src'
jest.mock('../src/options', () => ({ jest.mock('../src/options', () => ({
getNuxtConfig: jest.fn().mockReturnValue('nuxt config') getNuxtConfig: jest.fn(() => 'nuxt config')
})) }))
jest.mock('../src/config', () => ({ jest.mock('../src/config', () => ({
getDefaultNuxtConfig: jest.fn().mockReturnValue('default nuxt config') getDefaultNuxtConfig: jest.fn(() => 'default nuxt config')
})) }))
describe('config: entry', () => { describe('config: entry', () => {

View File

@ -22,10 +22,12 @@ jest.mock('@nuxt/utils', () => ({
describe('config: options', () => { describe('config: options', () => {
test('should return default nuxt config', () => { test('should return default nuxt config', () => {
jest.spyOn(process, 'cwd').mockReturnValue('/var/nuxt/test') jest.spyOn(process, 'cwd').mockReturnValue('/var/nuxt/test')
jest.spyOn(path, 'resolve').mockImplementation((...args) => args.join('/').replace(/\\+/, '/'))
expect(getNuxtConfig({})).toMatchSnapshot() expect(getNuxtConfig({})).toMatchSnapshot()
process.cwd.mockRestore() process.cwd.mockRestore()
path.resolve.mockRestore()
}) })
test('should prevent duplicate calls with same options', () => { test('should prevent duplicate calls with same options', () => {
@ -93,6 +95,11 @@ describe('config: options', () => {
expect(render.ssr).toEqual(true) expect(render.ssr).toEqual(true)
}) })
test('should add appear true in transition when no ssr', () => {
const { transition } = getNuxtConfig({ render: { ssr: false } })
expect(transition.appear).toEqual(true)
})
test('should return 404.html as default generate.fallback', () => { test('should return 404.html as default generate.fallback', () => {
const { generate: { fallback } } = getNuxtConfig({ generate: { fallback: true } }) const { generate: { fallback } } = getNuxtConfig({ generate: { fallback: true } })
expect(fallback).toEqual('404.html') expect(fallback).toEqual('404.html')

View File

@ -28,7 +28,6 @@ export default class ModuleContainer {
} }
addTemplate(template) { addTemplate(template) {
/* istanbul ignore if */
if (!template) { if (!template) {
throw new Error('Invalid template: ' + JSON.stringify(template)) throw new Error('Invalid template: ' + JSON.stringify(template))
} }
@ -36,7 +35,7 @@ export default class ModuleContainer {
// Validate & parse source // Validate & parse source
const src = template.src || template const src = template.src || template
const srcPath = path.parse(src) const srcPath = path.parse(src)
/* istanbul ignore if */
if (typeof src !== 'string' || !fs.existsSync(src)) { if (typeof src !== 'string' || !fs.existsSync(src)) {
throw new Error('Template src not found: ' + src) throw new Error('Template src not found: ' + src)
} }
@ -133,7 +132,6 @@ export default class ModuleContainer {
} }
// Validate handler // Validate handler
/* istanbul ignore if */
if (typeof handler !== 'function') { if (typeof handler !== 'function') {
throw new Error('Module should export a function: ' + src) throw new Error('Module should export a function: ' + src)
} }
@ -153,7 +151,6 @@ export default class ModuleContainer {
if (options === undefined) { if (options === undefined) {
options = {} options = {}
} }
const result = await handler.call(this, options) const result = await handler.call(this, options)
return result return result
} }

View File

@ -77,7 +77,6 @@ export default class Nuxt extends Hookable {
async close(callback) { async close(callback) {
await this.callHook('close', this) await this.callHook('close', this)
/* istanbul ignore if */
if (typeof callback === 'function') { if (typeof callback === 'function') {
await callback() await callback()
} }

View File

@ -0,0 +1,162 @@
import consola from 'consola'
import Hookable from '../src/hookable'
describe('core: hookable', () => {
beforeEach(() => {
consola.debug.mockClear()
consola.log.mockClear()
consola.error.mockClear()
consola.fatal.mockClear()
})
test('should construct hook object', () => {
const hook = new Hookable()
expect(hook._hooks).toEqual({})
expect(hook._deprecatedHooks).toEqual({})
expect(hook.hook).toBeInstanceOf(Function)
expect(hook.callHook).toBeInstanceOf(Function)
})
test('should register hook successfully', () => {
const hook = new Hookable()
hook.hook('test:hook', () => {})
hook.hook('test:hook', () => {})
expect(hook._hooks['test:hook']).toHaveLength(2)
expect(hook._hooks['test:hook']).toBeInstanceOf(Array)
expect(hook._hooks['test:hook']).toEqual([expect.any(Function), expect.any(Function)])
})
test('should ignore empty hook name', () => {
const hook = new Hookable()
hook.hook(0, () => {})
hook.hook('', () => {})
hook.hook(undefined, () => {})
expect(hook._hooks[0]).toBeUndefined()
expect(hook._hooks['']).toBeUndefined()
expect(hook._hooks[undefined]).toBeUndefined()
})
test('should ignore non-function hook', () => {
const hook = new Hookable()
hook.hook('test:hook', '')
hook.hook('test:hook', undefined)
expect(hook._hooks['test:hook']).toBeUndefined()
})
test('should convert and display deprecated hook', () => {
const hook = new Hookable()
hook._deprecatedHooks['test:hook'] = 'test:before'
hook.hook('test:hook', () => {})
expect(consola.warn).toBeCalledWith('test:hook hook has been deprecated, please use test:before')
expect(hook._hooks['test:hook']).toBeUndefined()
expect(hook._hooks['test:before']).toEqual([expect.any(Function)])
})
test('should call registered hook', async () => {
const hook = new Hookable()
hook.hook('test:hook', () => consola.log('test:hook called'))
await hook.callHook('test:hook')
expect(consola.debug).toBeCalledWith('Call test:hook hooks (1)')
expect(consola.log).toBeCalledWith('test:hook called')
})
test('should ignore unregistered hook', async () => {
const hook = new Hookable()
await hook.callHook('test:hook')
expect(consola.debug).not.toBeCalled()
})
test('should report hook error', async () => {
const hook = new Hookable()
const error = new Error('Hook Error')
hook.hook('test:hook', () => { throw error })
await hook.callHook('test:hook')
expect(consola.fatal).toBeCalledWith(error)
})
test('should call error hook', async () => {
const hook = new Hookable()
const error = new Error('Hook Error')
hook.hook('error', jest.fn())
hook.hook('test:hook', () => { throw error })
await hook.callHook('test:hook')
expect(hook._hooks.error[0]).toBeCalledWith(error)
expect(consola.fatal).toBeCalledWith(error)
})
test('should clear registered hooks', () => {
const hook = new Hookable()
hook.hook('test:hook', () => {})
expect(hook._hooks['test:hook']).toHaveLength(1)
expect(hook._hooks['test:before']).toBeUndefined()
hook.clearHook('test:hook')
hook.clearHook('test:before')
expect(hook._hooks['test:hook']).toBeUndefined()
expect(hook._hooks['test:before']).toBeUndefined()
})
test('should clear all registered hooks', () => {
const hook = new Hookable()
hook.hook('test:hook', () => {})
expect(hook._hooks['test:hook']).toHaveLength(1)
expect(hook._hooks['test:before']).toBeUndefined()
hook.clearHooks()
expect(hook._hooks['test:hook']).toBeUndefined()
expect(hook._hooks['test:before']).toBeUndefined()
expect(hook._hooks).toEqual({})
})
test('should return flat hooks', () => {
const hook = new Hookable()
const hooks = hook.flatHooks({
test: {
hook: () => {},
before: () => {}
}
})
expect(hooks).toEqual({
'test:hook': expect.any(Function),
'test:before': expect.any(Function)
})
})
test('should add object hooks', () => {
const hook = new Hookable()
hook.addHooks({
test: {
hook: () => {},
before: () => {},
after: null
}
})
expect(hook._hooks).toEqual({
'test:hook': expect.any(Array),
'test:before': expect.any(Array),
'test:after': undefined
})
expect(hook._hooks['test:hook']).toHaveLength(1)
expect(hook._hooks['test:before']).toHaveLength(1)
})
})

View File

@ -0,0 +1,17 @@
import { Module, Nuxt, Resolver } from '../src'
jest.mock('../src/module', () => ({
module: true
})).mock('../src/nuxt', () => ({
nuxt: true
})).mock('../src/resolver', () => ({
resolver: true
}))
describe('core: entry', () => {
test('should export Module, Nuxt and Resolver', () => {
expect(Module.module).toEqual(true)
expect(Nuxt.nuxt).toEqual(true)
expect(Resolver.resolver).toEqual(true)
})
})

View File

@ -0,0 +1,389 @@
import fs from 'fs'
import path from 'path'
import consola from 'consola'
import { chainFn } from '@nuxt/utils'
import ModuleContainer from '../src/module'
jest.mock('fs', () => ({
existsSync: Boolean
}))
jest.mock('hash-sum', () => src => `hash(${src})`)
jest.mock('@nuxt/utils', () => ({
...jest.requireActual('@nuxt/utils'),
chainFn: jest.fn(() => 'chainedFn')
}))
describe('core: module', () => {
const requireModule = jest.fn(src => options => Promise.resolve({ src, options }))
beforeEach(() => {
consola.debug.mockClear()
consola.log.mockClear()
consola.warn.mockClear()
consola.error.mockClear()
consola.fatal.mockClear()
chainFn.mockClear()
requireModule.mockClear()
})
test('should construct module container', () => {
const nuxt = jest.fn()
nuxt.options = jest.fn()
const module = new ModuleContainer(nuxt)
expect(module.nuxt).toBe(nuxt)
expect(module.options).toBe(nuxt.options)
expect(module.requiredModules).toEqual({})
})
test('should call hooks and addModule when ready', async () => {
const nuxt = {
options: {
modules: [jest.fn(), jest.fn()]
},
callHook: jest.fn()
}
const module = new ModuleContainer(nuxt)
module.addModule = jest.fn()
await module.ready()
expect(nuxt.callHook).toBeCalledTimes(2)
expect(nuxt.callHook).nthCalledWith(1, 'modules:before', module, module.options.modules)
expect(nuxt.callHook).nthCalledWith(2, 'modules:done', module)
expect(module.addModule).toBeCalledTimes(2)
expect(module.addModule).nthCalledWith(1, module.options.modules[0])
expect(module.addModule).nthCalledWith(2, module.options.modules[1])
})
test('should display deprecated message for addVendor', () => {
new ModuleContainer({}).addVendor()
expect(consola.warn).toBeCalledTimes(1)
expect(consola.warn).toBeCalledWith('addVendor has been deprecated due to webpack4 optimization')
})
test('should add string template', () => {
const module = new ModuleContainer({
options: {
build: {
templates: []
}
}
})
const template = module.addTemplate('/var/nuxt/test')
const expected = {
'dst': 'nuxt.test.hash(/var/nuxt/test)',
'options': undefined,
'src': '/var/nuxt/test'
}
expect(template).toEqual(expected)
expect(module.options.build.templates).toEqual([expected])
})
test('should add object template', () => {
const module = new ModuleContainer({
options: {
build: {
templates: []
}
}
})
const template = module.addTemplate({
src: '/var/nuxt/test',
options: { test: true }
})
const expected = {
'dst': 'nuxt.test.hash(/var/nuxt/test)',
'options': { test: true },
'src': '/var/nuxt/test'
}
expect(template).toEqual(expected)
expect(module.options.build.templates).toEqual([expected])
})
test('should use filename in preference to calculation', () => {
const module = new ModuleContainer({
options: {
build: {
templates: []
}
}
})
const template = module.addTemplate({
src: '/var/nuxt/test',
fileName: '/var/nuxt/dist/test'
})
const expected = {
'dst': '/var/nuxt/dist/test',
'options': undefined,
'src': '/var/nuxt/test'
}
expect(template).toEqual(expected)
expect(module.options.build.templates).toEqual([expected])
})
test('should throw error when template invalid', () => {
const module = new ModuleContainer({})
expect(() => module.addTemplate()).toThrow('Invalid template: undefined')
})
test('should throw error when template not found', () => {
const module = new ModuleContainer({})
fs.existsSync = jest.fn(() => false)
expect(() => module.addTemplate('/var/nuxt/test')).toThrow('Template src not found: /var/nuxt/test')
fs.existsSync = jest.fn(Boolean)
})
test('should add plugin into module', () => {
const module = new ModuleContainer({
options: {
buildDir: '/var/nuxt/build',
plugins: []
}
})
module.addTemplate = jest.fn(() => ({ dst: 'nuxt.test.template' }))
module.addPlugin({ ssr: false, mode: 'client' })
expect(module.options.plugins).toEqual([{
src: path.join('/var/nuxt/build', 'nuxt.test.template'),
ssr: false,
mode: 'client'
}])
})
test('should add layout into module', () => {
const module = new ModuleContainer({
options: {
layouts: {}
}
})
module.addTemplate = jest.fn(() => ({
dst: 'nuxt.test.template',
src: '/var/nuxt/src'
}))
module.addLayout({}, 'test-layout')
expect(module.options.layouts).toEqual({ 'test-layout': './nuxt.test.template' })
})
test('should display deprecated message when registration is duplicate', () => {
const module = new ModuleContainer({
options: {
layouts: {
'test-layout': 'test.template'
}
}
})
module.addTemplate = jest.fn(() => ({
dst: 'nuxt.test.template',
src: '/var/nuxt/src'
}))
module.addLayout({}, 'test-layout')
expect(consola.warn).toBeCalledTimes(1)
expect(consola.warn).toBeCalledWith('Duplicate layout registration, "test-layout" has been registered as "test.template"')
})
test('should register error layout at same time', () => {
const module = new ModuleContainer({
options: {
layouts: {}
}
})
module.addErrorLayout = jest.fn()
module.addTemplate = jest.fn(() => ({
dst: 'nuxt.test.template',
src: '/var/nuxt/src'
}))
module.addLayout({}, 'error')
expect(module.options.layouts).toEqual({ 'error': './nuxt.test.template' })
expect(module.addErrorLayout).toBeCalledTimes(1)
expect(module.addErrorLayout).toBeCalledWith('nuxt.test.template')
})
test('should add error layout', () => {
const module = new ModuleContainer({
options: {
rootDir: '/var/nuxt',
buildDir: '/var/nuxt/build',
layouts: {}
}
})
module.addErrorLayout('error.template')
expect(module.options.ErrorPage).toEqual('~/build/error.template')
})
test('should add server middleware', () => {
const module = new ModuleContainer({
options: {
serverMiddleware: []
}
})
module.addServerMiddleware(() => {})
module.addServerMiddleware(() => {})
expect(module.options.serverMiddleware).toHaveLength(2)
expect(module.options.serverMiddleware).toEqual([expect.any(Function), expect.any(Function)])
})
test('should chain build.extend', () => {
const extend = () => {}
const module = new ModuleContainer({
options: {
build: { extend }
}
})
const newExtend = () => {}
module.extendBuild(newExtend)
expect(chainFn).toBeCalledTimes(1)
expect(chainFn).toBeCalledWith(extend, newExtend)
expect(module.options.build.extend).toEqual('chainedFn')
})
test('should chain router.extendRoutes', () => {
const extendRoutes = () => {}
const module = new ModuleContainer({
options: {
router: { extendRoutes }
}
})
const newExtendRoutes = () => {}
module.extendRoutes(newExtendRoutes)
expect(chainFn).toBeCalledTimes(1)
expect(chainFn).toBeCalledWith(extendRoutes, newExtendRoutes)
expect(module.options.router.extendRoutes).toEqual('chainedFn')
})
test('should call addModule when require module', () => {
const module = new ModuleContainer({
options: {}
})
module.addModule = jest.fn()
const moduleOpts = {}
module.requireModule(moduleOpts)
expect(module.addModule).toBeCalledTimes(1)
expect(module.addModule).toBeCalledWith(moduleOpts, true)
})
test('should add string module', async () => {
const module = new ModuleContainer({
resolver: { requireModule },
options: {}
})
const result = await module.addModule('moduleTest')
expect(requireModule).toBeCalledTimes(1)
expect(requireModule).toBeCalledWith('moduleTest')
expect(module.requiredModules).toEqual({
moduleTest: {
handler: expect.any(Function),
options: undefined,
src: 'moduleTest'
}
})
expect(result).toEqual({ src: 'moduleTest', options: {} })
})
test('should add array module', async () => {
const module = new ModuleContainer({
resolver: { requireModule },
options: {}
})
const result = await module.addModule(['moduleTest', { test: true }])
expect(requireModule).toBeCalledTimes(1)
expect(requireModule).toBeCalledWith('moduleTest')
expect(module.requiredModules).toEqual({
moduleTest: {
handler: expect.any(Function),
options: {
test: true
},
src: 'moduleTest'
}
})
expect(result).toEqual({ src: 'moduleTest', options: { test: true } })
})
test('should add object module', async () => {
const module = new ModuleContainer({
resolver: { requireModule },
options: {}
})
const result = await module.addModule({
src: 'moduleTest',
options: { test: true },
handler: function objectModule(options) {
return Promise.resolve(options)
}
})
expect(requireModule).not.toBeCalled()
expect(module.requiredModules).toEqual({
objectModule: {
handler: expect.any(Function),
options: {
test: true
},
src: 'moduleTest'
}
})
expect(result).toEqual({ test: true })
})
test('should throw error when handler is not function', () => {
const module = new ModuleContainer({
resolver: { requireModule: () => false },
options: {}
})
expect(module.addModule('moduleTest')).rejects.toThrow('Module should export a function: moduleTest')
})
test('should prevent multiple adding when requireOnce is enabled', async () => {
const module = new ModuleContainer({
resolver: { requireModule },
options: {}
})
const handler = jest.fn(() => true)
handler.meta = {
name: 'moduleTest'
}
const first = await module.addModule({ handler }, true)
const second = await module.addModule({ handler }, true)
expect(first).toEqual(true)
expect(second).toBeUndefined()
expect(handler).toBeCalledTimes(1)
expect(module.requiredModules.moduleTest).toBeDefined()
})
})

View File

@ -0,0 +1,203 @@
import consola from 'consola'
import { defineAlias } from '@nuxt/utils'
import { getNuxtConfig } from '@nuxt/config'
import { Server } from '@nuxt/server'
import Nuxt from '../src/nuxt'
import ModuleContainer from '../src/module'
import Hookable from '../src/hookable'
import Resolver from '../src/resolver'
import { version } from '../package.json'
jest.mock('@nuxt/utils')
jest.mock('@nuxt/config', () => ({
getNuxtConfig: jest.fn(() => ({}))
}))
jest.mock('@nuxt/server')
describe('core: nuxt', () => {
beforeEach(() => {
jest.clearAllMocks()
jest.spyOn(Nuxt.prototype, 'ready').mockImplementation(() => Promise.resolve())
})
afterEach(() => {
if (Nuxt.prototype.ready.mockRestore) {
Nuxt.prototype.ready.mockRestore()
}
})
test('should construct nuxt with options', () => {
const options = {}
const nuxt = new Nuxt(options)
expect(nuxt).toBeInstanceOf(Hookable)
expect(getNuxtConfig).toBeCalledTimes(1)
expect(getNuxtConfig).toBeCalledWith(options)
expect(nuxt.resolver).toBeInstanceOf(Resolver)
expect(nuxt.moduleContainer).toBeInstanceOf(ModuleContainer)
expect(nuxt.server).toBeInstanceOf(Server)
expect(nuxt._deprecatedHooks).toEqual({
'render:context': 'render:routeContext',
'showReady': 'webpack:done'
})
expect(defineAlias).toBeCalledTimes(2)
expect(defineAlias).nthCalledWith(1, nuxt, nuxt.server, ['renderRoute', 'renderAndGetWindow', 'listen'])
expect(defineAlias).nthCalledWith(2, nuxt, nuxt.resolver, ['resolveAlias', 'resolvePath'])
expect(nuxt.renderer).toBe(nuxt.server)
expect(nuxt.render).toBe(nuxt.server.app)
expect(nuxt.showReady).toBeInstanceOf(Function)
expect(nuxt.initialized).toEqual(false)
expect(nuxt.ready).toBeCalledTimes(1)
})
test('should call hook webpack:done in showReady', () => {
const nuxt = new Nuxt()
nuxt.callHook = jest.fn()
nuxt.showReady()
expect(nuxt.callHook).toBeCalledTimes(1)
expect(nuxt.callHook).toBeCalledWith('webpack:done')
})
test('should display fatal message if ready failed', async () => {
const err = new Error('nuxt ready failed')
Nuxt.prototype.ready.mockImplementation(() => Promise.reject(err))
const nuxt = new Nuxt()
await nuxt._ready
expect(consola.fatal).toBeCalledTimes(1)
expect(consola.fatal).toBeCalledWith(err)
})
test('should return nuxt version from package.json', () => {
expect(Nuxt.version).toEqual(`v${version}`)
})
test('should return nuxt version from global.__NUXT', () => {
global.__NUXT = {
version: 'latest'
}
expect(Nuxt.version).toEqual('latest')
delete global.__NUXT
})
test('should call module/server ready in nuxt.ready', async () => {
const nuxt = new Nuxt()
delete nuxt._ready
Nuxt.prototype.ready.mockRestore()
nuxt.callHook = jest.fn()
nuxt.server = { ready: jest.fn() }
nuxt.moduleContainer = { ready: jest.fn() }
const result = await nuxt.ready()
expect(result).toBe(nuxt)
expect(nuxt.moduleContainer.ready).toBeCalledTimes(1)
expect(nuxt.server.ready).toBeCalledTimes(1)
expect(nuxt.initialized).toEqual(true)
expect(nuxt.callHook).toBeCalledTimes(1)
expect(nuxt.callHook).toBeCalledWith('ready', nuxt)
})
test('should ignore ready when _ready exists', async () => {
const nuxt = new Nuxt()
Nuxt.prototype.ready.mockRestore()
const _ready = nuxt._ready = jest.fn()
nuxt.server = { ready: jest.fn() }
const result = await nuxt.ready()
expect(result).toBe(_ready)
expect(nuxt.server.ready).not.toBeCalled()
})
test('should add object hooks', async () => {
const hooks = {}
getNuxtConfig.mockReturnValueOnce({ hooks })
const nuxt = new Nuxt()
delete nuxt._ready
Nuxt.prototype.ready.mockRestore()
nuxt.addHooks = jest.fn()
nuxt.server = { ready: jest.fn() }
nuxt.moduleContainer = { ready: jest.fn() }
await nuxt.ready()
expect(nuxt.addHooks).toBeCalledTimes(1)
expect(nuxt.addHooks).toBeCalledWith(hooks)
})
test('should add function hooks', async () => {
const hooks = jest.fn()
getNuxtConfig.mockReturnValueOnce({ hooks })
const nuxt = new Nuxt()
delete nuxt._ready
Nuxt.prototype.ready.mockRestore()
nuxt.addHooks = jest.fn()
nuxt.server = { ready: jest.fn() }
nuxt.moduleContainer = { ready: jest.fn() }
await nuxt.ready()
expect(nuxt.addHooks).not.toBeCalled()
expect(hooks).toBeCalledTimes(1)
})
test('should close nuxt with hook triggered', async () => {
const nuxt = new Nuxt()
nuxt.callHook = jest.fn()
nuxt.clearHooks = jest.fn()
const cb = jest.fn()
await nuxt.close(cb)
expect(cb).toBeCalledTimes(1)
expect(nuxt.callHook).toBeCalledTimes(1)
expect(nuxt.callHook).toBeCalledWith('close', nuxt)
expect(nuxt.clearHooks).toBeCalledTimes(1)
})
test('should ignore non-function callback in close', async () => {
const nuxt = new Nuxt()
delete nuxt._ready
Nuxt.prototype.ready.mockRestore()
nuxt.callHook = jest.fn()
nuxt.server = { ready: jest.fn() }
nuxt.moduleContainer = { ready: jest.fn() }
const result = await nuxt.ready()
expect(result).toBe(nuxt)
expect(nuxt.moduleContainer.ready).toBeCalledTimes(1)
expect(nuxt.server.ready).toBeCalledTimes(1)
expect(nuxt.initialized).toEqual(true)
expect(nuxt.callHook).toBeCalledTimes(1)
expect(nuxt.callHook).toBeCalledWith('ready', nuxt)
})
test('should ignore non-function callback in close', async () => {
const nuxt = new Nuxt()
nuxt.callHook = jest.fn()
nuxt.clearHooks = jest.fn()
const cb = {}
await nuxt.close(cb)
expect(nuxt.callHook).toBeCalledTimes(1)
expect(nuxt.clearHooks).toBeCalledTimes(1)
})
})

View File

@ -0,0 +1,469 @@
import Module from 'module'
import path from 'path'
import esm from 'esm'
import fs from 'fs-extra'
import consola from 'consola'
import { startsWithRootAlias, startsWithSrcAlias } from '@nuxt/utils'
import Resolver from '../src/resolver'
jest.mock('module')
jest.mock('path')
jest.mock('esm', () => jest.fn(() => jest.fn()))
jest.mock('fs-extra')
jest.mock('@nuxt/utils')
describe('core: resolver', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('should construct resolver', () => {
const nuxt = jest.fn()
nuxt.options = jest.fn()
const resolver = new Resolver(nuxt)
expect(resolver.nuxt).toBe(nuxt)
expect(resolver.options).toBe(nuxt.options)
expect(resolver.resolvePath).toBeInstanceOf(Function)
expect(resolver.resolveAlias).toBeInstanceOf(Function)
expect(resolver.resolveModule).toBeInstanceOf(Function)
expect(resolver.requireModule).toBeInstanceOf(Function)
expect(resolver.esm).toEqual(expect.any(Function))
expect(esm).toBeCalledTimes(1)
expect(esm).toBeCalledWith(expect.any(Object), {})
})
test('should call _resolveFilename in resolveModule', () => {
const resolver = new Resolver({
options: { modulesDir: '/var/nuxt/node_modules' }
})
Module._resolveFilename = jest.fn(() => '/var/nuxt/resolver/module')
const modulePath = resolver.resolveModule('/var/nuxt/resolver')
expect(modulePath).toEqual('/var/nuxt/resolver/module')
expect(Module._resolveFilename).toBeCalledTimes(1)
expect(Module._resolveFilename).toBeCalledWith('/var/nuxt/resolver', { paths: '/var/nuxt/node_modules' })
})
test('should return undefined when module is not found', () => {
const resolver = new Resolver({
options: { modulesDir: '/var/nuxt/node_modules' }
})
Module._resolveFilename = jest.fn(() => {
const err = new Error()
err.code = 'MODULE_NOT_FOUND'
throw err
})
const modulePath = resolver.resolveModule('/var/nuxt/resolver')
expect(modulePath).toBeUndefined()
expect(Module._resolveFilename).toBeCalledTimes(1)
})
test('should throw error when _resolveFilename failed', () => {
const resolver = new Resolver({
options: { modulesDir: '/var/nuxt/node_modules' }
})
Module._resolveFilename = jest.fn(() => {
throw new Error('resolve failed')
})
expect(() => resolver.resolveModule('/var/nuxt/resolver')).toThrow('resolve failed')
})
test('should throw error when _resolveFilename failed', () => {
const resolver = new Resolver({
options: { modulesDir: '/var/nuxt/node_modules' }
})
Module._resolveFilename = jest.fn(() => {
throw new Error('resolve failed')
})
expect(() => resolver.resolveModule('/var/nuxt/resolver')).toThrow('resolve failed')
})
test('should resolve root alias', () => {
const resolver = new Resolver({
options: { rootDir: '/var/nuxt' }
})
startsWithRootAlias.mockReturnValue(true)
const aliasPath = { substr: jest.fn(p => String(p)) }
resolver.resolveAlias(aliasPath)
expect(path.join).toBeCalledTimes(1)
expect(path.join).toBeCalledWith('/var/nuxt', '2')
expect(aliasPath.substr).toBeCalledTimes(1)
expect(aliasPath.substr).toBeCalledWith(2)
})
test('should resolve src alias', () => {
const resolver = new Resolver({
options: { srcDir: '/var/nuxt/src' }
})
startsWithRootAlias.mockReturnValue(false)
startsWithSrcAlias.mockReturnValue(true)
const aliasPath = { substr: jest.fn(p => String(p)) }
resolver.resolveAlias(aliasPath)
expect(path.join).toBeCalledTimes(1)
expect(path.join).toBeCalledWith('/var/nuxt/src', '1')
expect(aliasPath.substr).toBeCalledTimes(1)
expect(aliasPath.substr).toBeCalledWith(1)
})
test('should resolve other alias', () => {
const resolver = new Resolver({
options: { srcDir: '/var/nuxt/src' }
})
startsWithRootAlias.mockReturnValue(false)
startsWithSrcAlias.mockReturnValue(false)
const aliasPath = { substr: jest.fn(p => String(p)) }
resolver.resolveAlias(aliasPath)
expect(path.resolve).toBeCalledTimes(1)
expect(path.resolve).toBeCalledWith('/var/nuxt/src', aliasPath)
})
describe('core: resolver resolvePath', () => {
test('should resolve existed path', () => {
const resolver = new Resolver({
options: {}
})
fs.existsSync = jest.fn(() => true)
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver/file')
expect(fs.existsSync).toBeCalledTimes(1)
expect(fs.existsSync).toBeCalledWith('/var/nuxt/resolver/file')
expect(resolvedPath).toEqual('/var/nuxt/resolver/file')
})
test('should resolve a module path', () => {
const resolver = new Resolver({
options: {}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/module')
fs.lstatSync = jest.fn(() => ({ isDirectory: () => false }))
resolver.resolveModule = jest.fn(() => '/var/nuxt/resolver/module')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver')
expect(fs.existsSync).toBeCalledTimes(2)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/module')
expect(fs.lstatSync).toBeCalledTimes(1)
expect(fs.lstatSync).nthCalledWith(1, '/var/nuxt/resolver/module')
expect(resolvedPath).toEqual('/var/nuxt/resolver/module')
})
test('should resolve a alias path', () => {
const resolver = new Resolver({
options: {}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/alias')
fs.lstatSync = jest.fn(() => ({
isDirectory: () => false
}))
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver')
expect(fs.existsSync).toBeCalledTimes(2)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/alias')
expect(fs.lstatSync).toBeCalledTimes(1)
expect(fs.lstatSync).nthCalledWith(1, '/var/nuxt/resolver/alias')
expect(resolvedPath).toEqual('/var/nuxt/resolver/alias')
})
test('should resolve path with extension', () => {
const resolver = new Resolver({
options: {
extensions: ['js']
}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/file.js')
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => false)
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver/file')
expect(fs.existsSync).toBeCalledTimes(3)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver/file')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/file')
expect(fs.existsSync).nthCalledWith(3, '/var/nuxt/resolver/file.js')
expect(resolvedPath).toEqual('/var/nuxt/resolver/file.js')
})
test('should resolve module path with extension', () => {
const resolver = new Resolver({
options: {
extensions: ['js']
}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/module.js')
resolver.resolveModule = jest.fn(() => '/var/nuxt/resolver/module')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver/file')
expect(fs.existsSync).toBeCalledTimes(3)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver/file')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/module')
expect(fs.existsSync).nthCalledWith(3, '/var/nuxt/resolver/module.js')
expect(resolvedPath).toEqual('/var/nuxt/resolver/module.js')
})
test('should resolve alias path with extension', () => {
const resolver = new Resolver({
options: {
extensions: ['js']
}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/alias.js')
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver/file')
expect(fs.existsSync).toBeCalledTimes(3)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver/file')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/alias')
expect(fs.existsSync).nthCalledWith(3, '/var/nuxt/resolver/alias.js')
expect(resolvedPath).toEqual('/var/nuxt/resolver/alias.js')
})
test('should resolve index.[ext] when path is directory', () => {
const resolver = new Resolver({
options: {
extensions: ['js']
}
})
fs.existsSync = jest.fn(path => ['/var/nuxt/resolver/alias', '/var/nuxt/resolver/alias/index.js'].includes(path))
fs.lstatSync = jest.fn(() => ({ isDirectory: () => true }))
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver')
expect(fs.existsSync).toBeCalledTimes(3)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/alias')
expect(fs.existsSync).nthCalledWith(3, '/var/nuxt/resolver/alias/index.js')
expect(resolvedPath).toEqual('/var/nuxt/resolver/alias/index.js')
})
test('should resolve style path', () => {
const resolver = new Resolver({
options: {
extensions: ['js'],
styleExtensions: ['css', 'scss']
}
})
fs.existsSync = jest.fn(path => ['/var/nuxt/resolver/alias', '/var/nuxt/resolver/alias/index.scss'].includes(path))
fs.lstatSync = jest.fn(path => ({ isDirectory: () => path === '/var/nuxt/resolver/alias' }))
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver', { isStyle: true })
expect(fs.existsSync).toBeCalledTimes(4)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/alias')
expect(fs.existsSync).nthCalledWith(3, '/var/nuxt/resolver/alias/index.css')
expect(fs.existsSync).nthCalledWith(4, '/var/nuxt/resolver/alias/index.scss')
expect(resolvedPath).toEqual('/var/nuxt/resolver/alias/index.scss')
})
test('should resolve the directory path if no file', () => {
const resolver = new Resolver({
options: {
extensions: ['js', 'vue']
}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/alias')
fs.lstatSync = jest.fn(() => ({ isDirectory: () => true }))
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver')
expect(fs.existsSync).toBeCalledTimes(4)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/alias')
expect(fs.existsSync).nthCalledWith(3, '/var/nuxt/resolver/alias/index.js')
expect(fs.existsSync).nthCalledWith(4, '/var/nuxt/resolver/alias/index.vue')
expect(resolvedPath).toEqual('/var/nuxt/resolver/alias')
})
test('should throw error if no dir and file', () => {
const resolver = new Resolver({
options: {
extensions: ['js', 'vue']
}
})
fs.existsSync = jest.fn(() => false)
fs.lstatSync = jest.fn(() => ({ isDirectory: () => false }))
resolver.resolveModule = jest.fn(() => false)
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const errMsg = 'Cannot resolve "/var/nuxt/resolver/file" from "/var/nuxt/resolver/alias"'
expect(() => resolver.resolvePath('/var/nuxt/resolver/file')).toThrow(errMsg)
})
test('should ignore module resolve if isModule is false', () => {
const resolver = new Resolver({
options: {}
})
fs.existsSync = jest.fn(path => path === '/var/nuxt/resolver/alias')
resolver.resolveModule = jest.fn(() => '/var/nuxt/resolver/module')
resolver.resolveAlias = jest.fn(() => '/var/nuxt/resolver/alias')
const resolvedPath = resolver.resolvePath('/var/nuxt/resolver/file', { isModule: false })
expect(fs.existsSync).toBeCalledTimes(2)
expect(fs.existsSync).nthCalledWith(1, '/var/nuxt/resolver/file')
expect(fs.existsSync).nthCalledWith(2, '/var/nuxt/resolver/alias')
expect(resolver.resolveModule).not.toBeCalled()
expect(resolvedPath).toEqual('/var/nuxt/resolver/alias')
})
test('should display deprecated alias options', () => {
const resolver = new Resolver({
options: {}
})
fs.existsSync = jest.fn(() => true)
resolver.resolvePath('/var/nuxt/resolver/file', { alias: true })
const warnMsg = 'Using alias is deprecated and will be removed in Nuxt 3. Use `isAlias` instead.'
expect(consola.warn).toBeCalledTimes(1)
expect(consola.warn).toBeCalledWith(warnMsg)
})
test('should display deprecated module options', () => {
const resolver = new Resolver({
options: {}
})
fs.existsSync = jest.fn(() => true)
resolver.resolvePath('/var/nuxt/resolver/file', { module: true })
const warnMsg = 'Using module is deprecated and will be removed in Nuxt 3. Use `isModule` instead.'
expect(consola.warn).toBeCalledTimes(1)
expect(consola.warn).toBeCalledWith(warnMsg)
})
})
describe('core: resolver resolveModule', () => {
test('should require es modules with default export', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn()
resolver.esm = jest.fn(() => ({ default: 'resolved module' }))
const resolvedModule = resolver.requireModule('/var/nuxt/resolver/module')
expect(resolvedModule).toEqual('resolved module')
})
test('should require es modules without default export', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn()
resolver.esm = jest.fn(() => 'resolved module')
const resolvedModule = resolver.requireModule('/var/nuxt/resolver/module')
expect(resolvedModule).toEqual('resolved module')
})
test('should require es modules without default export when intropDefault is disabled', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn()
resolver.esm = jest.fn(() => ({ default: 'resolved module' }))
const resolvedModule = resolver.requireModule('/var/nuxt/resolver/module', { intropDefault: false })
expect(resolvedModule).toEqual({ default: 'resolved module' })
})
test('should require common module', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn(() => 'path')
resolver.esm = jest.fn(() => ({ default: 'resolved module' }))
const resolvedModule = resolver.requireModule('path', { esm: false })
expect(resolvedModule).toBe(path)
})
test('should resolve with commonjs for ts module', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn(() => '/var/nuxt/resolver/module.ts')
resolver.esm = jest.fn(() => ({ default: 'resolved ts module' }))
expect(() => resolver.requireModule('/var/nuxt/resolver/module')).toThrow(
"Cannot find module '/var/nuxt/resolver/module.ts'"
)
})
test('should throw error if resolvePath failed', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn(() => { throw new Error('resolve failed') })
resolver.esm = jest.fn(() => undefined)
expect(() => resolver.requireModule('/var/nuxt/resolver/module')).toThrow('resolve failed')
})
test('should throw last error', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn(() => { throw new Error('resolve failed') })
resolver.esm = jest.fn(() => { throw new Error('resolve esm failed') })
expect(() => resolver.requireModule('/var/nuxt/resolver/module')).toThrow('resolve esm failed')
})
test('should display deprecated alias options', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn()
resolver.esm = jest.fn()
resolver.requireModule('/var/nuxt/resolver/file', { alias: true })
const warnMsg = 'Using alias is deprecated and will be removed in Nuxt 3. Use `isAlias` instead.'
expect(consola.warn).toBeCalledTimes(1)
expect(consola.warn).toBeCalledWith(warnMsg)
})
test('should display deprecated esm options', () => {
const resolver = new Resolver({
options: {}
})
resolver.resolvePath = jest.fn()
resolver.esm = jest.fn()
resolver.requireModule('/var/nuxt/resolver/file', { esm: true })
const warnMsg = 'Using esm is deprecated and will be removed in Nuxt 3. Use `useESM` instead.'
expect(consola.warn).toBeCalledTimes(1)
expect(consola.warn).toBeCalledWith(warnMsg)
})
})
})