You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vitepress/__tests__/unit/node/utils/fnSerialize.test.ts

99 lines
2.9 KiB

import {
deserializeFunctions,
serializeFunctions
} from 'node/utils/fnSerialize'
// runs the exact code shape that plugin.ts / build.ts emit into the site-data
// module and the metadata script — the revived value must come back without
// the deserializer ever compiling a string (new Function is used here only to
// stand in for the browser executing the emitted file)
function emitAndRevive(data: any): any {
const fns: string[] = []
const serialized = serializeFunctions(data, fns)
const script = `${deserializeFunctions};return deserializeFunctions(JSON.parse(${JSON.stringify(
JSON.stringify(serialized)
)}),[${fns.join(',')}])`
return new Function(script)()
}
describe('node/utils/fnSerialize', () => {
test('emitted deserializer does not rely on unsafe-eval', () => {
expect(deserializeFunctions).not.toContain('new Function')
expect(deserializeFunctions).not.toContain('eval')
})
test('serializes functions as indexed markers', () => {
const fns: string[] = []
const serialized = serializeFunctions(
{ a: (x: number) => x, b: { c: (x: number) => x * 2 } },
fns
)
expect(serialized).toEqual({ a: '_vp-fn_0', b: { c: '_vp-fn_1' } })
expect(fns).toHaveLength(2)
})
test('revives functions nested in objects and arrays', () => {
const data = {
search: {
options: {
miniSearch: {
options: {
tokenize: (text: string) => text.split(/\s+/)
},
searchOptions: {
boostDocument: (id: string) => (id === 'index.md' ? 2 : 1)
}
}
}
},
list: [(n: number) => n + 1, 'plain', 42]
}
const revived = emitAndRevive(data)
expect(revived.search.options.miniSearch.options.tokenize('a b')).toEqual([
'a',
'b'
])
expect(
revived.search.options.miniSearch.searchOptions.boostDocument('index.md')
).toBe(2)
expect(revived.list[0](1)).toBe(2)
expect(revived.list[1]).toBe('plain')
expect(revived.list[2]).toBe(42)
})
test('revives method shorthand and async functions', () => {
const data = {
tokenize(text: string) {
return text.toUpperCase()
},
async extractField(doc: { id: string }) {
return doc.id
}
}
const revived = emitAndRevive(data)
expect(revived.tokenize('abc')).toBe('ABC')
return expect(revived.extractField({ id: 'x' })).resolves.toBe('x')
})
test('drops underscore-prefixed keys', () => {
const revived = emitAndRevive({ _render: () => '', keep: 1 })
expect(revived).toEqual({ keep: 1 })
})
test('leaves data strings resembling markers untouched', () => {
const data = {
fn: (x: number) => x,
note: '_vp-fn_alert(1)'
}
const revived = emitAndRevive(data)
expect(revived.fn(1)).toBe(1)
expect(revived.note).toBe('_vp-fn_alert(1)')
})
})