@cloudpss/template
Version:
String and object template engine for Node.js and the browser.
309 lines (302 loc) • 12.1 kB
JavaScript
import { randomBytes, randomFillSync } from 'node:crypto';
import { setTimeout as timeout } from 'node:timers/promises';
import { template } from '@cloudpss/template';
describe('template', () => {
it('should handle primitives', () => {
expect(template('')()).toEqual('');
expect(template('Hello, world!')()).toEqual('Hello, world!');
expect(template(1)()).toEqual(1);
expect(template(1.1)()).toEqual(1.1);
expect(template(Number.NaN)()).toEqual(Number.NaN);
expect(template(Number.POSITIVE_INFINITY)()).toEqual(Number.POSITIVE_INFINITY);
expect(template(Number.NEGATIVE_INFINITY)()).toEqual(Number.NEGATIVE_INFINITY);
expect(template(Number.EPSILON)()).toEqual(Number.EPSILON);
expect(template(true)()).toEqual(true);
expect(template(false)()).toEqual(false);
expect(template(null)()).toEqual(null);
expect(template(undefined)()).toEqual(undefined);
expect(template(1n)()).toEqual(1n);
expect(
template({
string: 'Hello, world!',
number: 1,
boolean: true,
null: null,
undefined: undefined,
bigint: 1n,
})(),
).toEqual({
string: 'Hello, world!',
number: 1,
boolean: true,
null: null,
undefined: undefined,
bigint: 1n,
});
});
it('should ignore symbols', () => {
expect(template(Symbol(''))()).toEqual(undefined);
expect(
template({
key: Symbol(''),
[Symbol('')]: 1,
})(),
).toEqual({
key: undefined,
});
});
it('should ignore functions', () => {
expect(template(() => 1)()).toEqual(undefined);
expect(
template({
key: () => 1,
})(),
).toEqual({
key: undefined,
});
});
it('should copy arrays', () => {
const arr = [1, 2, {}];
const result = template(arr)();
expect(result).toEqual(arr);
expect(result).not.toBe(arr);
expect(result[2]).not.toBe(arr[2]);
});
it('should copy objects', () => {
const obj = { a: 1, b: 2, c: {} };
const result = template(obj)();
expect(result).toEqual(obj);
expect(result).not.toBe(obj);
expect(result.c).not.toBe(obj.c);
});
it('should copy dates', () => {
const date = new Date();
const result = template(date)();
expect(result).toEqual(date);
expect(result).not.toBe(date);
});
it('should copy regexps', () => {
const regexp = /./gi;
const result = template(regexp)();
expect(result).toEqual(regexp);
expect(result).not.toBe(regexp);
});
describe('should copy errors', () => {
it('Error', () => {
const error = new Error('test error');
const result = template(error)();
expect(result).toBeInstanceOf(Error);
expect(result.name).toBe(error.name);
expect(result.message).toBe(error.message);
expect(result).not.toBe(error);
});
it('TypeError', () => {
const error = new TypeError('test error');
const result = template(error)();
expect(result).toBeInstanceOf(TypeError);
expect(result.name).toBe(error.name);
expect(result.message).toBe(error.message);
expect(result).not.toBe(error);
});
it('SyntaxError with custom name', () => {
const error = new SyntaxError('test error');
error.name = 'CustomError';
const result = template(error)();
expect(result).toBeInstanceOf(SyntaxError);
expect(result.name).toBe(error.name);
expect(result.message).toBe(error.message);
expect(result).not.toBe(error);
});
it('DOMException', () => {
const error = new DOMException('test error', 'AbortError');
const result = template(error)();
expect(result).toBeInstanceOf(DOMException);
expect(result.name).toBe(error.name);
expect(result.message).toBe(error.message);
expect(result.code).toBe(error.code);
expect(result).not.toBe(error);
});
it('CustomError', () => {
const error = new (class CustomError extends Error {})('test error');
const result = template(error)();
expect(result).toBeInstanceOf(Error);
expect(result.name).toBe(error.name);
expect(result.message).toBe(error.message);
expect(result).not.toBe(error);
});
});
describe('should copy ArrayBuffers', () => {
it('ArrayBuffer', () => {
const { buffer } = randomBytes(16);
const t = template(buffer);
const result1 = t();
const result2 = t();
expect(new Uint8Array(result1)).toEqual(new Uint8Array(buffer));
expect(new Uint8Array(result2)).toEqual(new Uint8Array(buffer));
expect(result1).not.toBe(buffer);
expect(result2).not.toBe(buffer);
expect(result1).not.toBe(result2);
});
it('SharedArrayBuffer', () => {
const buffer = new SharedArrayBuffer(16);
randomFillSync(new Uint8Array(buffer));
const result = template(buffer)();
expect(new Uint8Array(result)).toEqual(new Uint8Array(buffer));
expect(result).toBeInstanceOf(SharedArrayBuffer);
expect(result).not.toBe(buffer);
});
});
describe('should copy ArrayBufferViews', () => {
it('DataView', () => {
const { buffer } = randomBytes(64);
const view = new DataView(buffer, 32, 16);
const result = template(view)();
expect(result).toEqual(view);
expect(result).not.toBe(view);
expect(new Uint8Array(result.buffer)).toEqual(new Uint8Array(buffer, view.byteOffset, view.byteLength));
expect(result.buffer).not.toBe(buffer);
});
it('Uint8Array', () => {
const { buffer } = randomBytes(64);
const view = new Uint8Array(buffer, 32, 16);
const result = template(view)();
expect(result).toEqual(view);
expect(result).not.toBe(view);
expect(new Uint8Array(result.buffer)).toEqual(new Uint8Array(buffer, view.byteOffset, view.byteLength));
expect(result.buffer).not.toBe(buffer);
});
it('Float64Array', () => {
const { buffer } = randomBytes(64);
const view = new Float64Array(buffer, 32, 1);
const result = template(view)();
expect(result).toEqual(view);
expect(result).not.toBe(view);
expect(new Uint8Array(result.buffer)).toEqual(new Uint8Array(buffer, view.byteOffset, view.byteLength));
expect(result.buffer).not.toBe(buffer);
});
it('BigInt64Array', () => {
const { buffer } = randomBytes(64);
const view = new BigInt64Array(buffer, 32, 1);
const t = template(view);
const result1 = t();
const result2 = t();
expect(result1).toEqual(view);
expect(result1).not.toBe(view);
expect(result2).toEqual(view);
expect(result2).not.toBe(view);
expect(result2).not.toBe(result1);
expect(new Uint8Array(result1.buffer)).toEqual(new Uint8Array(buffer, view.byteOffset, view.byteLength));
expect(new Uint8Array(result2.buffer)).toEqual(new Uint8Array(buffer, view.byteOffset, view.byteLength));
expect(result1.buffer).not.toBe(buffer);
expect(result2.buffer).not.toBe(buffer);
expect(result1.buffer).not.toBe(result2.buffer);
});
});
it('should build interpolated strings', () => {
const t = template({ hello: '$Hello, ${name}!', not: 'Hello $name', array: ['$$name', '$not'] });
const result = t({
name: 'world',
});
expect(result).toEqual({
hello: 'Hello, world!',
not: 'Hello $name',
array: ['world', 'not'],
});
});
it('should ignore properties on the prototype chain', () => {
const obj = Object.create({ prototype: 'chain' });
obj.own = 'property';
const result = template(obj)();
expect(result).toEqual({ own: 'property' });
});
it('should ignore non-enumerable properties', () => {
const obj = Object.create(null, {
enumerable: {
value: 'property',
enumerable: false,
},
});
const result = template(obj)();
expect(result).toEqual({});
});
describe('should work while objectKeyMode is', () => {
it('template', () => {
const obj = { $: 1, _: 2, '=x': 3, $$y: 4 };
const result = template(obj, { objectKeyMode: 'template' })({ x: 11n, y: 12 });
expect(result).toEqual({
$: 1,
_: 2,
11: 3,
12: 4,
});
});
it('ignore', () => {
const obj = { $: 1, _: 2, '=x': 3, $$y: 4 };
const result = template(obj, { objectKeyMode: 'ignore' })({ x: 11n, y: 12 });
expect(result).toEqual({
$: 1,
_: 2,
'=x': 3,
$$y: 4,
});
});
});
describe('should work with default evaluator', () => {
it('with formula', () => {
const obj = { a: '=a', b: '=b' };
const result = template(obj)({ a: 1 });
expect(result).toEqual({ a: 1, b: undefined });
});
it('with interpolation', () => {
const obj = { a: '$$a', b: '$$b' };
const result = template(obj)({ a: 1 });
expect(result).toEqual({ a: '1', b: '' });
});
});
describe('should work with custom evaluator', () => {
it('with formula', () => {
const obj = { a: '=a', b: '=b' };
const result = template(obj, {
evaluator: {
inject: { a: 'abcd', b: 11n },
compile: (expression) => `evaluator.${expression}`,
},
})({ a: 1 });
expect(result).toEqual({ a: 'abcd', b: 11n });
});
it('with interpolation', () => {
const obj = { a: '$$a', b: '$$b' };
const result = template(obj, {
evaluator: {
inject: { a: 'abcd', b: 11n },
compile: (expression) => `evaluator.${expression}`,
},
})({ a: 1 });
expect(result).toEqual({ a: 'abcd', b: '11' });
});
});
describe('should work with async evaluator', () => {
it('with formula', async () => {
const obj = { a: '=a', b: '=b' };
const result = template(obj, {
evaluator: {
inject: { a: 'abcd', b: 11n, timeout },
async: true,
compile: (expression) => `await evaluator.timeout(100, evaluator.${expression})`,
},
})({ a: 1 });
await expect(result).resolves.toEqual({ a: 'abcd', b: 11n });
});
it('with interpolation', async () => {
const obj = { a: '$$a', b: '$$b' };
const result = template(obj, {
evaluator: {
inject: { a: 'abcd', b: 11n, timeout },
async: true,
compile: (expression) => `await evaluator.timeout(100, evaluator.${expression})`,
},
})({ a: 1 });
await expect(result).resolves.toEqual({ a: 'abcd', b: '11' });
});
});
});