diff --git a/packages/utils/src/lang.js b/packages/utils/src/lang.js
index 4a3c68ca81..a98f294ea3 100644
--- a/packages/utils/src/lang.js
+++ b/packages/utils/src/lang.js
@@ -4,7 +4,7 @@ export const encodeHtml = function encodeHtml(str) {
export const isString = obj => typeof obj === 'string' || obj instanceof String
-export const isNonEmptyString = obj => obj && isString(obj)
+export const isNonEmptyString = obj => Boolean(obj && isString(obj))
export const isPureObject = function isPureObject(o) {
return !Array.isArray(o) && typeof o === 'object'
diff --git a/packages/utils/src/resolve.js b/packages/utils/src/resolve.js
index 1d63c5a880..65e0da8bad 100644
--- a/packages/utils/src/resolve.js
+++ b/packages/utils/src/resolve.js
@@ -11,7 +11,6 @@ export const startsWithRootAlias = startsWithAlias(['@@', '~~'])
export const isWindows = /^win/.test(process.platform)
export const wp = function wp(p = '') {
- /* istanbul ignore if */
if (isWindows) {
return p.replace(/\\/g, '\\\\')
}
@@ -19,7 +18,6 @@ export const wp = function wp(p = '') {
}
export const wChunk = function wChunk(p = '') {
- /* istanbul ignore if */
if (isWindows) {
return p.replace(/\//g, '_')
}
@@ -62,7 +60,7 @@ export const relativeTo = function relativeTo() {
// Make correct relative path
let rp = path.relative(dir, _path)
if (rp[0] !== '.') {
- rp = './' + rp
+ rp = '.' + path.sep + rp
}
return wp(rp)
diff --git a/packages/utils/src/route.js b/packages/utils/src/route.js
index e75bbaf09c..5bda20e779 100644
--- a/packages/utils/src/route.js
+++ b/packages/utils/src/route.js
@@ -9,7 +9,6 @@ export const flatRoutes = function flatRoutes(router, _path = '', routes = []) {
if ([':', '*'].some(c => r.path.includes(c))) {
return
}
- /* istanbul ignore if */
if (r.children) {
if (_path === '' && r.path === '/') {
routes.push('/')
diff --git a/packages/utils/src/serialize.js b/packages/utils/src/serialize.js
index d04bd31194..7428fb6a81 100644
--- a/packages/utils/src/serialize.js
+++ b/packages/utils/src/serialize.js
@@ -1,4 +1,3 @@
-
import serialize from 'serialize-javascript'
export function serializeFunction(func) {
diff --git a/packages/utils/src/task.js b/packages/utils/src/task.js
index 412096d119..b3186d135a 100644
--- a/packages/utils/src/task.js
+++ b/packages/utils/src/task.js
@@ -10,7 +10,6 @@ export const parallel = function parallel(tasks, fn) {
}
export const chainFn = function chainFn(base, fn) {
- /* istanbul ignore if */
if (typeof fn !== 'function') {
return base
}
diff --git a/packages/utils/test/__snapshots__/route.test.js.snap b/packages/utils/test/__snapshots__/route.test.js.snap
new file mode 100644
index 0000000000..7ac1ccbc04
--- /dev/null
+++ b/packages/utils/test/__snapshots__/route.test.js.snap
@@ -0,0 +1,121 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`util: route util: route create createRoutes should allow snake case routes in posix system 1`] = `
+Array [
+ Object {
+ "chunkName": "pages/parent/index",
+ "component": "/some/nuxt/app/pages/parent/index.vue",
+ "name": "parent",
+ "path": "/parent",
+ },
+ Object {
+ "chunkName": "pages/snake_case_route",
+ "component": "/some/nuxt/app/pages/snake_case_route.vue",
+ "name": "snake_case_route",
+ "path": "/snake_case_route",
+ },
+ Object {
+ "chunkName": "pages/parent/child/index",
+ "component": "/some/nuxt/app/pages/parent/child/index.vue",
+ "name": "parent-child",
+ "path": "/parent/child",
+ },
+ Object {
+ "chunkName": "pages/parent/child/test",
+ "component": "/some/nuxt/app/pages/parent/child/test.vue",
+ "name": "parent-child-test",
+ "path": "/parent/child/test",
+ },
+ Object {
+ "children": Array [
+ Object {
+ "chunkName": "pages/another_route/_id",
+ "component": "/some/nuxt/app/pages/another_route/_id.vue",
+ "name": "another_route-id",
+ "path": "",
+ },
+ ],
+ "chunkName": "pages/another_route/_id",
+ "component": "/some/nuxt/app/pages/another_route/_id.vue",
+ "path": "/another_route/:id?",
+ },
+ Object {
+ "chunkName": "pages/subpage/_param",
+ "component": "/some/nuxt/app/pages/subpage/_param.vue",
+ "name": "subpage-param",
+ "path": "/subpage/:param?",
+ },
+ Object {
+ "chunkName": "pages/index",
+ "component": "/some/nuxt/app/pages/index.vue",
+ "name": "index",
+ "path": "/",
+ },
+ Object {
+ "chunkName": "pages/_param",
+ "component": "/some/nuxt/app/pages/_param.vue",
+ "name": "param",
+ "path": "/:param",
+ },
+]
+`;
+
+exports[`util: route util: route create createRoutes should allow snake case routes in windows system 1`] = `
+Array [
+ Object {
+ "chunkName": "pages/parent/index",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\parent\\\\\\\\index.vue",
+ "name": "parent",
+ "path": "/parent",
+ },
+ Object {
+ "chunkName": "pages/snake_case_route",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\snake_case_route.vue",
+ "name": "snake_case_route",
+ "path": "/snake_case_route",
+ },
+ Object {
+ "chunkName": "pages/parent/child/index",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\parent\\\\\\\\child\\\\\\\\index.vue",
+ "name": "parent-child",
+ "path": "/parent/child",
+ },
+ Object {
+ "chunkName": "pages/parent/child/test",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\parent\\\\\\\\child\\\\\\\\test.vue",
+ "name": "parent-child-test",
+ "path": "/parent/child/test",
+ },
+ Object {
+ "children": Array [
+ Object {
+ "chunkName": "pages/another_route/_id",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\another_route\\\\\\\\_id.vue",
+ "name": "another_route-id",
+ "path": "",
+ },
+ ],
+ "chunkName": "pages/another_route/_id",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\another_route\\\\\\\\_id.vue",
+ "path": "/another_route/:id?",
+ },
+ Object {
+ "chunkName": "pages/subpage/_param",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\subpage\\\\\\\\_param.vue",
+ "name": "subpage-param",
+ "path": "/subpage/:param?",
+ },
+ Object {
+ "chunkName": "pages/index",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\index.vue",
+ "name": "index",
+ "path": "/",
+ },
+ Object {
+ "chunkName": "pages/_param",
+ "component": "\\\\\\\\\\\\\\\\some\\\\\\\\nuxt\\\\\\\\app\\\\\\\\pages\\\\\\\\_param.vue",
+ "name": "param",
+ "path": "/:param",
+ },
+]
+`;
diff --git a/packages/utils/test/context.test.js b/packages/utils/test/context.test.js
new file mode 100644
index 0000000000..bc2aa7effd
--- /dev/null
+++ b/packages/utils/test/context.test.js
@@ -0,0 +1,24 @@
+import { getContext, determineGlobals } from '../src/context'
+
+describe('util: context', () => {
+ test('should get context with req and res', () => {
+ const ctx = getContext({ a: 1 }, { b: 2 })
+
+ expect(getContext.length).toBe(2)
+ expect(typeof ctx.req).toBe('object')
+ expect(typeof ctx.res).toBe('object')
+ expect(ctx.req.a).toBe(1)
+ expect(ctx.res.b).toBe(2)
+ })
+
+ test('should get correct globals', () => {
+ const globals = {
+ foo: name => `${name}: foo`,
+ bar: name => `${name}: bar`,
+ baz: 'baz'
+ }
+ const result = determineGlobals('global', globals)
+
+ expect(result).toEqual({ bar: 'global: bar', foo: 'global: foo', baz: 'baz' })
+ })
+})
diff --git a/packages/utils/test/index.test.js b/packages/utils/test/index.test.js
new file mode 100644
index 0000000000..0ad3710d59
--- /dev/null
+++ b/packages/utils/test/index.test.js
@@ -0,0 +1,22 @@
+import * as Util from '../src'
+import * as context from '../src/context'
+import * as lang from '../src/lang'
+import * as resolve from '../src/resolve'
+import * as route from '../src/route'
+import * as serialize from '../src/serialize'
+import * as task from '../src/task'
+import * as timer from '../src/timer'
+
+describe('util: entry', () => {
+ test('should export all methods from utils folder', () => {
+ expect(Util).toEqual({
+ ...context,
+ ...lang,
+ ...resolve,
+ ...route,
+ ...serialize,
+ ...task,
+ ...timer
+ })
+ })
+})
diff --git a/packages/utils/test/lang.test.js b/packages/utils/test/lang.test.js
new file mode 100644
index 0000000000..f3132a877c
--- /dev/null
+++ b/packages/utils/test/lang.test.js
@@ -0,0 +1,55 @@
+import {
+ encodeHtml, isString, isNonEmptyString,
+ isPureObject, isUrl, urlJoin, wrapArray, stripWhitespace
+} from '../src/lang'
+
+describe('util: lang', () => {
+ test('should check if given argument is string', () => {
+ expect(isString('str')).toEqual(true)
+ expect(isString(String(100))).toEqual(true)
+ expect(isString(100)).toEqual(false)
+ expect(isString([])).toEqual(false)
+ })
+
+ test('should check if given argument is empty string', () => {
+ expect(isNonEmptyString('str')).toEqual(true)
+ expect(isNonEmptyString([])).toEqual(false)
+ expect(isNonEmptyString('')).toEqual(false)
+ })
+
+ test('should check if given argument is pure object', () => {
+ expect(isPureObject({})).toEqual(true)
+ expect(isPureObject([])).toEqual(false)
+ expect(isPureObject(Number('1'))).toEqual(false)
+ })
+
+ test('should check if given argument is url', () => {
+ expect(isUrl('http://localhost')).toEqual(true)
+ expect(isUrl('https://localhost')).toEqual(true)
+ expect(isUrl('//localhost')).toEqual(true)
+ expect(isUrl('localhost')).toEqual(false)
+ })
+
+ test('should wrap given argument with array', () => {
+ expect(wrapArray([ 'array' ])).toEqual([ 'array' ])
+ expect(wrapArray('str')).toEqual([ 'str' ])
+ })
+
+ test('should strip white spaces in given argument', () => {
+ expect(stripWhitespace('foo')).toEqual('foo')
+ expect(stripWhitespace('foo\t\r\f\n')).toEqual('foo\n')
+ expect(stripWhitespace('foo{\n\n\n')).toEqual('foo{\n')
+ expect(stripWhitespace('\n\n\n\f\r\f}')).toEqual('\n\f\r\f}')
+ expect(stripWhitespace('foo\n\n\nbar')).toEqual('foo\n\nbar')
+ expect(stripWhitespace('foo\n\n\n')).toEqual('foo\n')
+ })
+
+ test('should encode html', () => {
+ const html = '
Hello
'
+ expect(encodeHtml(html)).toEqual('<h1>Hello</h1>')
+ })
+
+ test('should join url', () => {
+ expect(urlJoin('test', '/about')).toEqual('test/about')
+ })
+})
diff --git a/packages/utils/test/resolve.posix.test.js b/packages/utils/test/resolve.posix.test.js
new file mode 100644
index 0000000000..650ccd3402
--- /dev/null
+++ b/packages/utils/test/resolve.posix.test.js
@@ -0,0 +1,85 @@
+import consola from 'consola'
+
+import {
+ startsWithAlias, startsWithSrcAlias, wp, wChunk,
+ relativeTo, defineAlias, isIndexFileAndFolder, getMainModule
+} from '../src/resolve'
+
+describe.posix('util: resolve', () => {
+ test('should check if path starts with alias', () => {
+ expect(startsWithAlias(['/var'])('/var/nuxt/src')).toEqual(true)
+ })
+
+ test('should check if path starts with root alias', () => {
+ expect(startsWithSrcAlias('@/assets')).toEqual(true)
+ expect(startsWithSrcAlias('~/pages')).toEqual(true)
+ })
+
+ test('should check if path starts with src alias', () => {
+ expect(startsWithSrcAlias('@@/src/assets')).toEqual(true)
+ expect(startsWithSrcAlias('~~/src/pages')).toEqual(true)
+ })
+
+ test('should return same path in linux', () => {
+ expect(wp('/var/nuxt\\ src/')).toEqual('/var/nuxt\\ src/')
+ })
+
+ test('should return same path in linux', () => {
+ expect(wChunk('nuxt/layout/test')).toEqual('nuxt/layout/test')
+ })
+
+ test('should define alias', () => {
+ const nuxt = {}
+ const server = {
+ name: 'nuxt',
+ bound: () => 'bound fn',
+ test: () => 'test defineAlias'
+ }
+
+ defineAlias(nuxt, server, ['name', 'bound'])
+ defineAlias(nuxt, server, ['test'], { bind: false, warn: true })
+
+ expect(nuxt.name).toEqual(server.name)
+ expect(nuxt.bound).not.toBe(server.bound)
+ expect(nuxt.bound()).toEqual('bound fn')
+ expect(nuxt.test).toBe(server.test)
+ expect(nuxt.test()).toEqual('test defineAlias')
+ expect(consola.warn).toBeCalledTimes(1)
+ expect(consola.warn).toBeCalledWith({
+ message: `'test' is deprecated'`,
+ additional: expect.any(String)
+ })
+ })
+
+ test('should check if given argument is index file or folder', () => {
+ expect(isIndexFileAndFolder(['/var/nuxt/plugins/test'])).toEqual(false)
+ expect(isIndexFileAndFolder(['/var/nuxt/plugins/test/index.js'])).toEqual(false)
+ expect(isIndexFileAndFolder(['/var/nuxt/plugins/test', '/var/nuxt/plugins/test/index.js'])).toEqual(true)
+ })
+
+ test('should return main module', () => {
+ expect(getMainModule()).toHaveProperty('children', 'exports', 'filename', 'path')
+ })
+
+ describe('relativeTo', () => {
+ const path1 = '@/foo'
+ const path2 = '@/bar'
+
+ test('should resolve alias path', () => {
+ expect(relativeTo(path1, path2)).toBe('@/bar')
+ })
+
+ test('should keep webpack inline loaders prepended', () => {
+ expect(relativeTo(path1, `loader1!loader2!${path2}`))
+ .toEqual('loader1!loader2!@/bar')
+ })
+
+ test('should check path which is not started with alias', () => {
+ expect(relativeTo('/var/nuxt/foo/bar', '/var/nuxt/foo/baz')).toBe('../baz')
+ })
+
+ test('should check path which is not started with alias ', () => {
+ expect(relativeTo('/var/nuxt/foo', '/var/nuxt/foo/bar')).toBe('./bar')
+ })
+ })
+})
diff --git a/packages/utils/test/resolve.win.test.js b/packages/utils/test/resolve.win.test.js
new file mode 100644
index 0000000000..80ca1ea474
--- /dev/null
+++ b/packages/utils/test/resolve.win.test.js
@@ -0,0 +1,90 @@
+import consola from 'consola'
+
+import {
+ wp, wChunk, r, relativeTo,
+ startsWithAlias, startsWithSrcAlias,
+ defineAlias, isIndexFileAndFolder, getMainModule
+} from '../src/resolve'
+
+describe.win('util: resolve windows', () => {
+ test('should format windows separator', () => {
+ expect(wp('c:\\nuxt\\src')).toEqual('c:\\\\nuxt\\\\src')
+ })
+
+ test('should format windows path', () => {
+ expect(wChunk('nuxt/layout/test')).toEqual('nuxt_layout_test')
+ })
+
+ test('should resolve alias path', () => {
+ expect(r('@\\layout\\test')).toEqual('@\\\\layout\\\\test')
+ })
+
+ test('should check if path starts with alias', () => {
+ expect(startsWithAlias(['#'])('#layout/test')).toEqual(true)
+ })
+
+ test('should check if path starts with root alias', () => {
+ expect(startsWithSrcAlias('@/assets')).toEqual(true)
+ expect(startsWithSrcAlias('~/pages')).toEqual(true)
+ })
+
+ test('should check if path starts with src alias', () => {
+ expect(startsWithSrcAlias('@@/src/assets')).toEqual(true)
+ expect(startsWithSrcAlias('~~/src/pages')).toEqual(true)
+ })
+
+ test('should define alias', () => {
+ const nuxt = {}
+ const server = {
+ name: 'nuxt',
+ bound: () => 'bound fn',
+ test: () => 'test defineAlias'
+ }
+
+ defineAlias(nuxt, server, ['name', 'bound'])
+ defineAlias(nuxt, server, ['test'], { bind: false, warn: true })
+
+ expect(nuxt.name).toEqual(server.name)
+ expect(nuxt.bound).not.toBe(server.bound)
+ expect(nuxt.bound()).toEqual('bound fn')
+ expect(nuxt.test).toBe(server.test)
+ expect(nuxt.test()).toEqual('test defineAlias')
+ expect(consola.warn).toBeCalledTimes(1)
+ expect(consola.warn).toBeCalledWith({
+ message: `'test' is deprecated'`,
+ additional: expect.any(String)
+ })
+ })
+
+ test('should check if given argument is index file or folder', () => {
+ expect(isIndexFileAndFolder(['/var/nuxt/plugins/test'])).toEqual(false)
+ expect(isIndexFileAndFolder(['/var/nuxt/plugins/test/index.js'])).toEqual(false)
+ expect(isIndexFileAndFolder(['/var/nuxt/plugins/test', '/var/nuxt/plugins/test/index.js'])).toEqual(true)
+ })
+
+ test('should return main module', () => {
+ expect(getMainModule()).toHaveProperty('children', 'exports', 'filename', 'path')
+ })
+
+ describe('relativeTo', () => {
+ const path1 = '@\\foo'
+ const path2 = '@\\bar'
+
+ test('should resolve alias path', () => {
+ expect(relativeTo(path1, path2)).toBe('@\\\\bar')
+ })
+
+ test('should keep webpack inline loaders prepended', () => {
+ expect(relativeTo(path1, `loader1!loader2!${path2}`))
+ .toBe('loader1!loader2!@\\\\bar')
+ })
+
+ test('should check path which is not started with alias', () => {
+ expect(relativeTo('c:\\foo\\bar', 'c:\\foo\\baz')).toBe('..\\\\baz')
+ })
+
+ test('should check path which is not started with alias ', () => {
+ expect(relativeTo('c:\\foo', 'c:\\foo\\baz')).toBe('.\\\\baz')
+ })
+ })
+})
diff --git a/packages/utils/test/route.test.js b/packages/utils/test/route.test.js
new file mode 100644
index 0000000000..e61420d7d4
--- /dev/null
+++ b/packages/utils/test/route.test.js
@@ -0,0 +1,198 @@
+import { flatRoutes, createRoutes, guardDir, promisifyRoute } from '../src/route'
+
+describe('util: route', () => {
+ test('should flat route with path', () => {
+ const routes = flatRoutes([
+ { name: 'login', path: '/login' },
+ { name: 'about', path: '/about' },
+ { name: 'posts',
+ path: '',
+ children: [
+ { name: 'posts-list', path: '' },
+ { name: 'posts-create', path: 'post' }
+ ]
+ }
+ ])
+ expect(routes).toEqual([ '/login', '/about', '', '/post' ])
+ })
+
+ test('should ignore route with * and :', () => {
+ const routes = flatRoutes([
+ { name: 'login', path: '/login' },
+ { name: 'foo', path: '/foo/:id' },
+ { name: 'bar', path: '/bar/*' }
+ ])
+ expect(routes).toEqual([ '/login' ])
+ })
+
+ test('should resolve route with /', () => {
+ const routes = flatRoutes([
+ { name: 'foo',
+ path: '/',
+ children: [
+ { name: 'foo-bar', path: 'foo/bar' },
+ { name: 'foo-baz', path: 'foo/baz' }
+ ]
+ }
+ ])
+ expect(routes).toEqual([ '/', '/foo/bar', '/foo/baz' ])
+ })
+
+ describe('util: route guard', () => {
+ test('should guard parent dir', () => {
+ expect(() => {
+ guardDir({ dir1: '/root/parent', dir2: '/root' }, 'dir1', 'dir2')
+ }).toThrow()
+ })
+
+ test('should guard same dir', () => {
+ expect(() => {
+ guardDir({ dir1: '/root/parent', dir2: '/root/parent' }, 'dir1', 'dir2')
+ }).toThrow()
+ })
+
+ test('should not guard same level dir', () => {
+ expect(() => {
+ guardDir({ dir1: '/root/parent-next', dir2: '/root/parent' }, 'dir1', 'dir2')
+ }).not.toThrow()
+ })
+
+ test('should not guard same level dir - 2', () => {
+ expect(() => {
+ guardDir({ dir1: '/root/parent', dir2: '/root/parent-next' }, 'dir1', 'dir2')
+ }).not.toThrow()
+ })
+
+ test('should not guard child dir', () => {
+ expect(() => {
+ guardDir({ dir1: '/root/parent', dir2: '/root/parent/child' }, 'dir1', 'dir2')
+ }).not.toThrow()
+ })
+ })
+
+ describe('util: route promisifyRoute', () => {
+ test('should promisify array routes', () => {
+ const array = [1]
+ const promise = promisifyRoute(array)
+ expect(typeof promise).toBe('object')
+ return promise.then((res) => {
+ expect(res).toBe(array)
+ })
+ })
+
+ test('should promisify functional routes', () => {
+ const array = [1, 2]
+ const fn = function () {
+ return array
+ }
+ const promise = promisifyRoute(fn)
+ expect(typeof promise).toBe('object')
+ return promise.then((res) => {
+ expect(res).toBe(array)
+ })
+ })
+
+ test('should promisify promisable functional routes', () => {
+ const array = [1, 2, 3]
+ const fn = function () {
+ return new Promise((resolve) => {
+ resolve(array)
+ })
+ }
+ const promise = promisifyRoute(fn)
+ expect(typeof promise).toBe('object')
+ return promise.then((res) => {
+ expect(res).toBe(array)
+ })
+ })
+
+ test('should promisify promisable functional routes with arguments', () => {
+ const fn = function (array) {
+ return new Promise((resolve) => {
+ resolve(array)
+ })
+ }
+ const array = [1, 2, 3]
+ const promise = promisifyRoute(fn, array)
+ expect(typeof promise).toBe('object')
+ return promise.then((res) => {
+ expect(res).toBe(array)
+ })
+ })
+
+ test('should promisify functional routes with error', () => {
+ const fn = function (cb) {
+ cb(new Error('Error here'))
+ }
+ const promise = promisifyRoute(fn)
+ expect(typeof promise).toBe('object')
+ return promise.catch((e) => {
+ expect(e.message).toBe('Error here')
+ })
+ })
+
+ test('should promisify functional routes with arguments and error', () => {
+ const fn = function (cb, array) {
+ cb(new Error('Error here: ' + array.join()))
+ }
+ const array = [1, 2, 3, 4]
+ const promise = promisifyRoute(fn, array)
+ expect(typeof promise).toBe('object')
+ return promise.catch((e) => {
+ expect(e.message).toBe('Error here: ' + array.join())
+ })
+ })
+
+ test('should promisify functional routes with result', () => {
+ const array = [1, 2, 3, 4]
+ const fn = function (cb) {
+ cb(null, array)
+ }
+ const promise = promisifyRoute(fn)
+ expect(typeof promise).toBe('object')
+ return promise.then((res) => {
+ expect(res).toBe(array)
+ })
+ })
+
+ test('should promisify functional routes with arguments and result', () => {
+ const fn = function (cb, array, object) {
+ cb(null, { array, object })
+ }
+ const array = [1, 2, 3, 4]
+ const object = { a: 1 }
+ const promise = promisifyRoute(fn, array, object)
+ expect(typeof promise).toBe('object')
+ return promise.then((res) => {
+ expect(res.array).toBe(array)
+ expect(res.object).toBe(object)
+ })
+ })
+ })
+
+ describe('util: route create', () => {
+ const files = [
+ 'pages/index.vue',
+ 'pages/_param.vue',
+ 'pages/subpage/_param.vue',
+ 'pages/snake_case_route.vue',
+ 'pages/another_route/_id.vue',
+ 'pages/another_route/_id.vue',
+ 'pages/parent/index.vue',
+ 'pages/parent/child/index.vue',
+ 'pages/parent/child/test.vue'
+ ]
+ const srcDir = '/some/nuxt/app'
+ const pagesDir = 'pages'
+
+ test.posix('createRoutes should allow snake case routes in posix system', () => {
+ const routesResult = createRoutes(files, srcDir, pagesDir)
+ expect(routesResult).toMatchSnapshot()
+ })
+
+ test.win('createRoutes should allow snake case routes in windows system', () => {
+ const routesResult = createRoutes(files, srcDir, pagesDir)
+ expect(routesResult).toMatchSnapshot()
+ })
+ })
+})
diff --git a/packages/utils/test/serialize.test.js b/packages/utils/test/serialize.test.js
new file mode 100644
index 0000000000..bb9c0df6c4
--- /dev/null
+++ b/packages/utils/test/serialize.test.js
@@ -0,0 +1,61 @@
+import { serializeFunction } from '../src/serialize'
+
+describe('util: serialize', () => {
+ test('should serialize normal function', () => {
+ const obj = {
+ fn: function () {}
+ }
+ expect(serializeFunction(obj.fn)).toEqual('function () {}')
+ })
+
+ test('should serialize shorthand function', () => {
+ const obj = {
+ fn() {}
+ }
+ expect(serializeFunction(obj.fn)).toEqual('function() {}')
+ })
+
+ test('should serialize arrow function', () => {
+ const obj = {
+ fn: () => {}
+ }
+ expect(serializeFunction(obj.fn)).toEqual('() => {}')
+ })
+
+ test('should not replace custom scripts', () => {
+ const obj = {
+ fn() {
+ return 'function xyz(){};a=false?true:xyz();'
+ }
+ }
+
+ expect(serializeFunction(obj.fn)).toEqual(`function () {
+ return 'function xyz(){};a=false?true:xyz();';
+ }`)
+ })
+
+ test('should serialize internal function', () => {
+ const obj = {
+ fn(arg) {
+ if (arg) {
+ return {
+ title() {
+ return 'test'
+ }
+ }
+ }
+ }
+ }
+
+ expect(serializeFunction(obj.fn)).toEqual(`function(arg) {
+ if (arg) {
+ return {
+ title: function () {
+ return 'test';
+ }
+
+ };
+ }
+ }`)
+ })
+})
diff --git a/packages/utils/test/task.test.js b/packages/utils/test/task.test.js
new file mode 100644
index 0000000000..69cbbcdecc
--- /dev/null
+++ b/packages/utils/test/task.test.js
@@ -0,0 +1,106 @@
+import consola from 'consola'
+import { sequence, parallel, chainFn } from '../src/task'
+
+describe('util: task', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ test('should call fn in sequence', async () => {
+ const fn = jest.fn(consola.log)
+ await sequence([1, 2, 3, 4], fn)
+
+ expect(fn).toBeCalledTimes(4)
+ expect(consola.log).toBeCalledTimes(4)
+ expect(consola.log).nthCalledWith(1, 1)
+ expect(consola.log).nthCalledWith(2, 2)
+ expect(consola.log).nthCalledWith(3, 3)
+ expect(consola.log).nthCalledWith(4, 4)
+ })
+
+ test('should call fn in parallel', async () => {
+ jest.spyOn(Promise, 'all')
+ jest.spyOn(Promise, 'resolve')
+
+ await parallel([1, 2, 3, 4], (num, index) => [num, index])
+
+ expect(Promise.all).toBeCalledTimes(1)
+ expect(Promise.resolve).toBeCalledTimes(4)
+ expect(Promise.resolve).nthCalledWith(1, [1, 0])
+ expect(Promise.resolve).nthCalledWith(2, [2, 1])
+ expect(Promise.resolve).nthCalledWith(3, [3, 2])
+ expect(Promise.resolve).nthCalledWith(4, [4, 3])
+
+ Promise.all.mockRestore()
+ Promise.resolve.mockRestore()
+ })
+
+ test('chainFn (mutate, mutate)', () => {
+ // Pass more than one argument to test that they're actually taken into account
+ const firstFn = function (obj, count) {
+ obj.foo = count + 1
+ }
+ const secondFn = function (obj, count) {
+ obj.bar = count + 2
+ }
+
+ const chainedFn = chainFn(firstFn, secondFn)
+ expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
+ })
+
+ test('chainFn (mutate, return)', () => {
+ const firstFn = function (obj, count) {
+ obj.foo = count + 1
+ }
+ const secondFn = function (obj, count) {
+ return Object.assign({}, obj, { bar: count + 2 })
+ }
+
+ const chainedFn = chainFn(firstFn, secondFn)
+ expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
+ })
+
+ test('chainFn (return, mutate)', () => {
+ const firstFn = function (obj, count) {
+ return Object.assign({}, obj, { foo: count + 1 })
+ }
+ const secondFn = function (obj, count) {
+ obj.bar = count + 2
+ }
+
+ const chainedFn = chainFn(firstFn, secondFn)
+ expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
+ })
+
+ test('chainFn (return, return)', () => {
+ const firstFn = function (obj, count) {
+ return Object.assign({}, obj, { foo: count + 1 })
+ }
+ const secondFn = function (obj, count) {
+ return Object.assign({}, obj, { bar: count + 2 })
+ }
+
+ const chainedFn = chainFn(firstFn, secondFn)
+ expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
+ })
+
+ test('chainFn (return, non-function)', () => {
+ const firstFn = function (obj, count) {
+ return Object.assign({}, obj, { foo: count + 1 })
+ }
+ const secondFn = ''
+
+ const chainedFn = chainFn(firstFn, secondFn)
+ expect(chainedFn).toBe(firstFn)
+ })
+
+ test('chainFn (non-function, return)', () => {
+ const firstFn = ''
+ const secondFn = function (obj, count) {
+ return Object.assign({}, obj, { bar: count + 2 })
+ }
+
+ const chainedFn = chainFn(firstFn, secondFn)
+ expect(chainedFn({}, 10)).toEqual({ bar: 12 })
+ })
+})
diff --git a/packages/utils/test/timer.test.js b/packages/utils/test/timer.test.js
new file mode 100644
index 0000000000..2a89335e8e
--- /dev/null
+++ b/packages/utils/test/timer.test.js
@@ -0,0 +1,165 @@
+import { timeout, waitFor, Timer } from '../src/timer'
+
+describe('util: timer', () => {
+ test('timeout (promise)', async () => {
+ const result = await timeout(Promise.resolve('time not run out'), 100)
+ expect(result).toEqual('time not run out')
+ })
+
+ test('timeout (async function)', async () => {
+ const result = await timeout(async () => {
+ await waitFor(10)
+ return 'time not run out'
+ }, 100)
+ expect(result).toEqual('time not run out')
+ })
+
+ test('timeout (timeout in 100ms)', async () => {
+ const call = timeout(waitFor(200), 100, 'timeout test 100ms')
+ await expect(call).rejects.toThrow('timeout test 100ms')
+ })
+
+ test('timeout (async timeout in 100ms)', async () => {
+ const call = timeout(async () => {
+ await waitFor(500)
+ }, 100, 'timeout test 100ms')
+ await expect(call).rejects.toThrow('timeout test 100ms')
+ })
+
+ test('waitFor', async () => {
+ const delay = 100
+ const s = process.hrtime()
+ await waitFor(delay)
+ const t = process.hrtime(s)
+ // Node.js makes no guarantees about the exact timing of when callbacks will fire
+ // HTML5 specifies a minimum delay of 4ms for timeouts
+ // although arbitrary, use this value to determine our lower limit
+ expect((t[0] * 1e9 + t[1]) / 1e6).not.toBeLessThan(delay - 4)
+ await waitFor()
+ })
+
+ describe('util: timer Timer', () => {
+ beforeAll(() => {
+ // jest.spyOn()
+ })
+
+ test('should construct Timer', () => {
+ const timer = new Timer()
+ expect(timer._times).toBeInstanceOf(Map)
+ })
+
+ test('should create new time record', () => {
+ const timer = new Timer()
+ timer.hrtime = jest.fn(() => 'hrtime')
+
+ const time = timer.start('test', 'test Timer')
+
+ expect(timer.hrtime).toBeCalledTimes(1)
+ expect(time).toEqual({ description: 'test Timer', name: 'test', start: 'hrtime' })
+ })
+
+ test('should stop and remove time record', () => {
+ const timer = new Timer()
+ timer.hrtime = jest.fn(() => 'hrtime')
+ timer.start('test', 'test Timer')
+
+ const time = timer.end('test')
+
+ expect(timer._times.size).toEqual(0)
+ expect(timer.hrtime).toBeCalledTimes(2)
+ expect(timer.hrtime).nthCalledWith(2, 'hrtime')
+ expect(time).toEqual({ description: 'test Timer', name: 'test', duration: 'hrtime', start: 'hrtime' })
+ })
+
+ test('should be quiet if end with nonexistent time', () => {
+ const timer = new Timer()
+
+ const time = timer.end('test')
+
+ expect(time).toBeUndefined()
+ })
+
+ test('should use bigint hrtime if supports', () => {
+ const timer = new Timer()
+ const hrtime = process.hrtime
+ process.hrtime = {
+ bigint: jest.fn(() => 'bingint hrtime')
+ }
+
+ const time = timer.hrtime()
+
+ expect(time).toEqual('bingint hrtime')
+ expect(process.hrtime.bigint).toBeCalledTimes(1)
+
+ process.hrtime = hrtime
+ })
+
+ if (BigInt) {
+ test('should calculate duration with bigint hrtime', () => {
+ const timer = new Timer()
+ const hrtime = process.hrtime
+ process.hrtime = {
+ bigint: jest.fn()
+ .mockReturnValueOnce(BigInt(100000000))
+ .mockReturnValueOnce(BigInt(213000000))
+ }
+
+ let time = timer.hrtime()
+ time = timer.hrtime(time)
+
+ expect(time).toEqual(BigInt(113))
+ expect(process.hrtime.bigint).toBeCalledTimes(2)
+
+ process.hrtime = hrtime
+ })
+ }
+
+ test('should use hrtime if bigint it not supported', () => {
+ const timer = new Timer()
+ const hrtime = process.hrtime
+ process.hrtime = jest.fn(() => 'hrtime')
+ process.hrtime.bigint = undefined
+
+ const time = timer.hrtime()
+
+ expect(time).toEqual('hrtime')
+ expect(process.hrtime).toBeCalledTimes(1)
+
+ process.hrtime = hrtime
+ })
+
+ test('should calculate duration with hrtime', () => {
+ const timer = new Timer()
+ const hrtime = process.hrtime
+ process.hrtime = jest.fn()
+ .mockReturnValueOnce([1, 500000])
+ .mockReturnValueOnce([2, 600000])
+ process.hrtime.bigint = undefined
+
+ let time = timer.hrtime()
+ time = timer.hrtime(time)
+
+ expect(time).toEqual(2000.6)
+ expect(process.hrtime).toBeCalledTimes(2)
+ expect(process.hrtime).nthCalledWith(1)
+ expect(process.hrtime).nthCalledWith(2, [1, 500000])
+
+ process.hrtime = hrtime
+ })
+
+ test('should clear all times', () => {
+ const timer = new Timer()
+ timer.hrtime = jest.fn(() => 'hrtime')
+
+ timer.start('time-1', 'test time-1')
+ timer.start('time-2', 'test time-2')
+ timer.start('time-3', 'test time-3')
+
+ expect(timer._times.size).toEqual(3)
+
+ timer.clear()
+
+ expect(timer._times.size).toEqual(0)
+ })
+ })
+})
diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js
index 84515e4712..140465218b 100644
--- a/test/unit/utils.test.js
+++ b/test/unit/utils.test.js
@@ -1,332 +1,8 @@
-import path from 'path'
import { waitUntil } from '../utils'
-import * as Utils from '../../packages/utils/src/index'
describe('utils', () => {
- test('encodeHtml', () => {
- const html = 'Hello
'
- expect(Utils.encodeHtml(html)).toBe('<h1>Hello</h1>')
- })
-
- test('getContext', () => {
- const ctx = Utils.getContext({ a: 1 }, { b: 2 })
- expect(Utils.getContext.length).toBe(2)
- expect(typeof ctx.req).toBe('object')
- expect(typeof ctx.res).toBe('object')
- expect(ctx.req.a).toBe(1)
- expect(ctx.res.b).toBe(2)
- })
-
- test('waitFor', async () => {
- const delay = 100
- const s = process.hrtime()
- await Utils.waitFor(delay)
- const t = process.hrtime(s)
- // Node.js makes no guarantees about the exact timing of when callbacks will fire
- // HTML5 specifies a minimum delay of 4ms for timeouts
- // although arbitrary, use this value to determine our lower limit
- expect((t[0] * 1e9 + t[1]) / 1e6).not.toBeLessThan(delay - 4)
- await Utils.waitFor()
- })
-
test('waitUntil', async () => {
expect(await waitUntil(() => true, 0.1, 100)).toBe(false)
expect(await waitUntil(() => false, 0.1, 100)).toBe(true)
})
-
- test('timeout (promise)', async () => {
- const result = await Utils.timeout(Promise.resolve('time not run out'), 100)
- expect(result).toBe('time not run out')
- })
-
- test('timeout (async function)', async () => {
- const result = await Utils.timeout(async () => {
- await Utils.waitFor(10)
- return 'time not run out'
- }, 100)
- expect(result).toBe('time not run out')
- })
-
- test('timeout (timeout in 100ms)', async () => {
- const timeout = Utils.timeout(Utils.waitFor(200), 100, 'timeout test 100ms')
- await expect(timeout).rejects.toThrow('timeout test 100ms')
- })
-
- test('timeout (async timeout in 100ms)', async () => {
- const timeout = Utils.timeout(async () => {
- await Utils.waitFor(500)
- }, 100, 'timeout test 100ms')
- await expect(timeout).rejects.toThrow('timeout test 100ms')
- })
-
- test('urlJoin', () => {
- expect(Utils.urlJoin('test', '/about')).toBe('test/about')
- })
-
- test('promisifyRoute (array)', () => {
- const array = [1]
- const promise = Utils.promisifyRoute(array)
- expect(typeof promise).toBe('object')
- return promise.then((res) => {
- expect(res).toBe(array)
- })
- })
-
- test('promisifyRoute (fn => array)', () => {
- const array = [1, 2]
- const fn = function () {
- return array
- }
- const promise = Utils.promisifyRoute(fn)
- expect(typeof promise).toBe('object')
- return promise.then((res) => {
- expect(res).toBe(array)
- })
- })
-
- test('promisifyRoute (fn => promise)', () => {
- const array = [1, 2, 3]
- const fn = function () {
- return new Promise((resolve) => {
- resolve(array)
- })
- }
- const promise = Utils.promisifyRoute(fn)
- expect(typeof promise).toBe('object')
- return promise.then((res) => {
- expect(res).toBe(array)
- })
- })
-
- test('promisifyRoute ((fn(args) => promise))', () => {
- const fn = function (array) {
- return new Promise((resolve) => {
- resolve(array)
- })
- }
- const array = [1, 2, 3]
- const promise = Utils.promisifyRoute(fn, array)
- expect(typeof promise).toBe('object')
- return promise.then((res) => {
- expect(res).toBe(array)
- })
- })
-
- test('promisifyRoute (fn(cb) with error)', () => {
- const fn = function (cb) {
- cb(new Error('Error here'))
- }
- const promise = Utils.promisifyRoute(fn)
- expect(typeof promise).toBe('object')
- return promise.catch((e) => {
- expect(e.message).toBe('Error here')
- })
- })
-
- test('promisifyRoute (fn(cb, args) with error)', () => {
- const fn = function (cb, array) {
- cb(new Error('Error here: ' + array.join()))
- }
- const array = [1, 2, 3, 4]
- const promise = Utils.promisifyRoute(fn, array)
- expect(typeof promise).toBe('object')
- return promise.catch((e) => {
- expect(e.message).toBe('Error here: ' + array.join())
- })
- })
-
- test('promisifyRoute (fn(cb) with result)', () => {
- const array = [1, 2, 3, 4]
- const fn = function (cb) {
- cb(null, array)
- }
- const promise = Utils.promisifyRoute(fn)
- expect(typeof promise).toBe('object')
- return promise.then((res) => {
- expect(res).toBe(array)
- })
- })
-
- test('promisifyRoute (fn(cb, args) with result)', () => {
- const fn = function (cb, array, object) {
- cb(null, { array, object })
- }
- const array = [1, 2, 3, 4]
- const object = { a: 1 }
- const promise = Utils.promisifyRoute(fn, array, object)
- expect(typeof promise).toBe('object')
- return promise.then((res) => {
- expect(res.array).toBe(array)
- expect(res.object).toBe(object)
- })
- })
-
- test('chainFn (mutate, mutate)', () => {
- // Pass more than one argument to test that they're actually taken into account
- const firstFn = function (obj, count) {
- obj.foo = count + 1
- }
- const secondFn = function (obj, count) {
- obj.bar = count + 2
- }
-
- const chainedFn = Utils.chainFn(firstFn, secondFn)
- expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
- })
-
- test('chainFn (mutate, return)', () => {
- const firstFn = function (obj, count) {
- obj.foo = count + 1
- }
- const secondFn = function (obj, count) {
- return Object.assign({}, obj, { bar: count + 2 })
- }
-
- const chainedFn = Utils.chainFn(firstFn, secondFn)
- expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
- })
-
- test('chainFn (return, mutate)', () => {
- const firstFn = function (obj, count) {
- return Object.assign({}, obj, { foo: count + 1 })
- }
- const secondFn = function (obj, count) {
- obj.bar = count + 2
- }
-
- const chainedFn = Utils.chainFn(firstFn, secondFn)
- expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
- })
-
- test('chainFn (return, return)', () => {
- const firstFn = function (obj, count) {
- return Object.assign({}, obj, { foo: count + 1 })
- }
- const secondFn = function (obj, count) {
- return Object.assign({}, obj, { bar: count + 2 })
- }
-
- const chainedFn = Utils.chainFn(firstFn, secondFn)
- expect(chainedFn({}, 10)).toEqual({ foo: 11, bar: 12 })
- })
-
- test('flatRoutes', () => {
- const routes = Utils.flatRoutes([
- { name: 'login', path: '/login' },
- { name: 'about', path: '/about' },
- { name: 'posts',
- path: '',
- children: [
- { name: 'posts-list',
- path: ''
- },
- { name: 'posts-create',
- path: 'post'
- }
- ]
- }
- ])
- expect(routes).toMatchObject([ '/login', '/about', '', '/post' ])
- })
-
- describe('relativeTo', () => {
- const path1 = path.join(path.sep, 'foo', 'bar')
- const path2 = path.join(path.sep, 'foo', 'baz')
-
- test('makes path relative to dir', () => {
- expect(Utils.relativeTo(path1, path2)).toBe(Utils.wp(`..${path.sep}baz`))
- })
-
- test('keeps webpack inline loaders prepended', () => {
- expect(Utils.relativeTo(path1, `loader1!loader2!${path2}`))
- .toBe(Utils.wp(`loader1!loader2!..${path.sep}baz`))
- })
- })
-
- describe('guardDir', () => {
- test('Parent dir is guarded', () => {
- expect(() => {
- Utils.guardDir({
- dir1: '/root/parent',
- dir2: '/root'
- }, 'dir1', 'dir2')
- }).toThrow()
- })
-
- test('Same dir is guarded', () => {
- expect(() => {
- Utils.guardDir({
- dir1: '/root/parent',
- dir2: '/root/parent'
- }, 'dir1', 'dir2')
- }).toThrow()
- })
-
- test('Same level dir is not guarded', () => {
- expect(() => {
- Utils.guardDir({
- dir1: '/root/parent-next',
- dir2: '/root/parent'
- }, 'dir1', 'dir2')
- }).not.toThrow()
- })
-
- test('Same level dir is not guarded 2', () => {
- expect(() => {
- Utils.guardDir({
- dir1: '/root/parent',
- dir2: '/root/parent-next'
- }, 'dir1', 'dir2')
- }).not.toThrow()
- })
-
- test('Child dir is not guarded', () => {
- expect(() => {
- Utils.guardDir({
- dir1: '/root/parent',
- dir2: '/root/parent/child'
- }, 'dir1', 'dir2')
- }).not.toThrow()
- })
- })
-})
-
-test('createRoutes should allow snake case routes', () => {
- const files = [
- 'pages/_param.vue',
- 'pages/subpage/_param.vue',
- 'pages/snake_case_route.vue',
- 'pages/another_route/_id.vue'
- ]
- const srcDir = '/some/nuxt/app'
- const pagesDir = 'pages'
- const routesResult = Utils.createRoutes(files, srcDir, pagesDir)
- const expectedResult = [
- {
- name: 'snake_case_route',
- path: '/snake_case_route',
- component: Utils.r('/some/nuxt/app/pages/snake_case_route.vue'),
- chunkName: 'pages/snake_case_route'
- },
- {
- name: 'another_route-id',
- path: '/another_route/:id?',
- component: Utils.r('/some/nuxt/app/pages/another_route/_id.vue'),
- chunkName: 'pages/another_route/_id'
- },
- {
- name: 'subpage-param',
- path: '/subpage/:param?',
- component: Utils.r('/some/nuxt/app/pages/subpage/_param.vue'),
- chunkName: 'pages/subpage/_param'
- },
- {
- name: 'param',
- path: '/:param?',
- component: Utils.r('/some/nuxt/app/pages/_param.vue'),
- chunkName: 'pages/_param'
- }
- ]
-
- expect(routesResult).toEqual(expectedResult)
})