test: unit tests for @nuxt/util (#4880)

This commit is contained in:
Xin Du (Clark) 2019-01-29 19:23:42 +00:00 committed by Pooya Parsa
parent 5101dc6aae
commit 96bab9f09c
16 changed files with 929 additions and 331 deletions

View File

@ -4,7 +4,7 @@ export const encodeHtml = function encodeHtml(str) {
export const isString = obj => typeof obj === 'string' || obj instanceof String 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) { export const isPureObject = function isPureObject(o) {
return !Array.isArray(o) && typeof o === 'object' return !Array.isArray(o) && typeof o === 'object'

View File

@ -11,7 +11,6 @@ export const startsWithRootAlias = startsWithAlias(['@@', '~~'])
export const isWindows = /^win/.test(process.platform) export const isWindows = /^win/.test(process.platform)
export const wp = function wp(p = '') { export const wp = function wp(p = '') {
/* istanbul ignore if */
if (isWindows) { if (isWindows) {
return p.replace(/\\/g, '\\\\') return p.replace(/\\/g, '\\\\')
} }
@ -19,7 +18,6 @@ export const wp = function wp(p = '') {
} }
export const wChunk = function wChunk(p = '') { export const wChunk = function wChunk(p = '') {
/* istanbul ignore if */
if (isWindows) { if (isWindows) {
return p.replace(/\//g, '_') return p.replace(/\//g, '_')
} }
@ -62,7 +60,7 @@ export const relativeTo = function relativeTo() {
// Make correct relative path // Make correct relative path
let rp = path.relative(dir, _path) let rp = path.relative(dir, _path)
if (rp[0] !== '.') { if (rp[0] !== '.') {
rp = './' + rp rp = '.' + path.sep + rp
} }
return wp(rp) return wp(rp)

View File

@ -9,7 +9,6 @@ export const flatRoutes = function flatRoutes(router, _path = '', routes = []) {
if ([':', '*'].some(c => r.path.includes(c))) { if ([':', '*'].some(c => r.path.includes(c))) {
return return
} }
/* istanbul ignore if */
if (r.children) { if (r.children) {
if (_path === '' && r.path === '/') { if (_path === '' && r.path === '/') {
routes.push('/') routes.push('/')

View File

@ -1,4 +1,3 @@
import serialize from 'serialize-javascript' import serialize from 'serialize-javascript'
export function serializeFunction(func) { export function serializeFunction(func) {

View File

@ -10,7 +10,6 @@ export const parallel = function parallel(tasks, fn) {
} }
export const chainFn = function chainFn(base, fn) { export const chainFn = function chainFn(base, fn) {
/* istanbul ignore if */
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
return base return base
} }

View File

@ -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",
},
]
`;

View File

@ -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' })
})
})

View File

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

View File

@ -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 = '<h1>Hello</h1>'
expect(encodeHtml(html)).toEqual('&lt;h1&gt;Hello&lt;/h1&gt;')
})
test('should join url', () => {
expect(urlJoin('test', '/about')).toEqual('test/about')
})
})

View File

@ -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')
})
})
})

View File

@ -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')
})
})
})

View File

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

View File

@ -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';
}
};
}
}`)
})
})

View File

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

View File

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

View File

@ -1,332 +1,8 @@
import path from 'path'
import { waitUntil } from '../utils' import { waitUntil } from '../utils'
import * as Utils from '../../packages/utils/src/index'
describe('utils', () => { describe('utils', () => {
test('encodeHtml', () => {
const html = '<h1>Hello</h1>'
expect(Utils.encodeHtml(html)).toBe('&lt;h1&gt;Hello&lt;/h1&gt;')
})
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 () => { test('waitUntil', async () => {
expect(await waitUntil(() => true, 0.1, 100)).toBe(false) expect(await waitUntil(() => true, 0.1, 100)).toBe(false)
expect(await waitUntil(() => false, 0.1, 100)).toBe(true) 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)
}) })