Files
vtj/packages/coder/tests/utils.test.ts

305 lines
8.7 KiB
TypeScript

import { expect, describe, test } from 'vitest';
import {
isJSExpression,
isJSFunction,
isJSCode,
JSCodeToString,
replaceThis,
replaceContext,
parseValue,
replaceComputedValue,
replaceFunctionTag,
parsePlainObjectValue,
getModifiers,
jsonToStyle,
skipUniComponents,
encodeDataSource,
decodeDataSource
} from '../src/utils';
describe('isJSExpression', () => {
test('should return true for valid JSExpression', () => {
expect(isJSExpression({ type: 'JSExpression', value: '1 + 1' })).toBe(true);
});
test('should return falsy for null', () => {
expect(isJSExpression(null)).toBeFalsy();
expect(isJSExpression(undefined)).toBeFalsy();
});
test('should return false for plain objects', () => {
expect(isJSExpression({})).toBe(false);
expect(isJSExpression({ type: 'other' })).toBe(false);
});
});
describe('isJSFunction', () => {
test('should return true for valid JSFunction', () => {
expect(isJSFunction({ type: 'JSFunction', value: '() => {}' })).toBe(true);
});
test('should return falsy for null', () => {
expect(isJSFunction(null)).toBeFalsy();
expect(isJSFunction(undefined)).toBeFalsy();
});
});
describe('isJSCode', () => {
test('should return true for JSExpression or JSFunction', () => {
expect(isJSCode({ type: 'JSExpression', value: '' })).toBe(true);
expect(isJSCode({ type: 'JSFunction', value: '' })).toBe(true);
});
test('should return falsy for null', () => {
expect(isJSCode(null)).toBeFalsy();
expect(isJSCode('string')).toBe(false);
expect(isJSCode(123)).toBe(false);
});
});
describe('JSCodeToString', () => {
test('should return value for JS code types', () => {
expect(JSCodeToString({ type: 'JSExpression', value: 'a + b' })).toBe(
'a + b'
);
expect(JSCodeToString({ type: 'JSFunction', value: '() => {}' })).toBe(
'() => {}'
);
});
test('should stringify plain values', () => {
expect(JSCodeToString(42)).toBe('42');
expect(JSCodeToString('hello')).toBe('"hello"');
expect(JSCodeToString({ key: 'val' })).toBe('{"key":"val"}');
expect(JSCodeToString([1, 2])).toBe('[1,2]');
});
});
describe('replaceThis', () => {
test('should replace this. with empty string', () => {
expect(replaceThis('this.count')).toBe('count');
expect(replaceThis('this.state.foo')).toBe('state.foo');
});
test('should replace multiple occurrences', () => {
expect(replaceThis('this.a + this.b')).toBe('a + b');
});
test('should handle empty string', () => {
expect(replaceThis('')).toBe('');
});
});
describe('replaceContext', () => {
test('should replace this.context?. with empty string', () => {
expect(replaceContext('this.context?.item')).toBe('item');
expect(replaceContext('this.context?.item.title')).toBe('item.title');
});
test('should handle this.context. without optional chaining', () => {
expect(replaceContext('this.context.item')).toBe('item');
});
test('should not affect other this. references', () => {
const result = replaceContext('this.state.foo + this.context?.item');
expect(result).toBe('this.state.foo + item');
});
});
describe('parseValue', () => {
const computedKeys = ['total'];
test('should parse JSExpression value', () => {
const result = parseValue({
type: 'JSExpression',
value: 'this.count + 1'
});
expect(result).toBe('count + 1');
});
test('should parse JSFunction value', () => {
const result = parseValue({ type: 'JSFunction', value: '() => {}' });
expect(result).toBe('() => {}');
});
test('should replace double quotes with single quotes for JS code when quote=true', () => {
const result = parseValue({
type: 'JSExpression',
value: '"hello world"'
});
expect(result).toBe("'hello world'");
});
test('should stringify plain values', () => {
expect(parseValue(42)).toBe('42');
expect(parseValue('hello')).toBe('"hello"');
});
test('should stringify as plain string when stringify=false for non-JS string', () => {
const result = parseValue(42, true);
expect(result).toBe('42');
});
test('should keep this. when noThis=false', () => {
const result = parseValue(
{ type: 'JSExpression', value: 'this.count' },
true,
false
);
expect(result).toBe('this.count');
});
test('should replace computed .value references', () => {
const result = parseValue(
{ type: 'JSExpression', value: 'this.total.value' },
true,
true,
computedKeys
);
expect(result).toBe('total');
});
test('should trim trailing semicolon', () => {
const result = parseValue({ type: 'JSExpression', value: 'count + 1;' });
expect(result).toBe('count + 1');
});
test('should not quote when quote=false', () => {
const result = parseValue(
{ type: 'JSExpression', value: '"hello"' },
true,
true,
[],
false
);
expect(result).toBe('"hello"');
});
});
describe('replaceComputedValue', () => {
const keys = ['total', 'count'];
test('should replace this.key.value with this.key', () => {
const result = replaceComputedValue('this.total.value + 1', keys);
expect(result).toBe('this.total + 1');
});
test('should replace multiple computed keys', () => {
const result = replaceComputedValue(
'this.total.value + this.count.value',
keys
);
expect(result).toBe('this.total + this.count');
});
test('should handle empty content', () => {
expect(replaceComputedValue('', keys)).toBe('');
});
});
describe('replaceFunctionTag', () => {
test('should handle bracket-wrapped async function', () => {
const result = replaceFunctionTag('(async function() { return 1 })');
expect(result).toBe('async() { return 1 }');
});
test('should handle bracket-wrapped function', () => {
const result = replaceFunctionTag('(function() { return 1 })');
expect(result).toBe('() { return 1 }');
});
test('should handle arrow function in brackets', () => {
const result = replaceFunctionTag('(() => { return 1 })');
expect(result).toBe('() { return 1 }');
});
test('should handle async arrow function in brackets', () => {
const result = replaceFunctionTag('(async () => { return 1 })');
expect(result).toBe('async () { return 1 }');
});
test('should handle expression-bodied arrow function', () => {
const result = replaceFunctionTag('(() => 1)');
expect(result).toBe('() { return 1 }');
});
test('should return handler starting with { as-is', () => {
const result = replaceFunctionTag('{ console.log("test") }');
expect(result).toBe('{ console.log("test") }');
});
});
describe('parsePlainObjectValue', () => {
test('should parse object entries', () => {
const result = parsePlainObjectValue({
name: { type: 'JSExpression', value: 'this.getName()' },
age: 18
});
expect(result).toContain('"name": getName()');
expect(result).toContain('"age": 18');
});
test('should handle empty object', () => {
expect(parsePlainObjectValue({})).toEqual([]);
});
});
describe('getModifiers', () => {
test('should return modifier keys', () => {
expect(getModifiers({ prevent: true, stop: true })).toEqual([
'prevent',
'stop'
]);
});
test('should return string modifiers with dots', () => {
expect(getModifiers({ prevent: true, stop: true }, true)).toEqual([
'.prevent',
'.stop'
]);
});
test('should handle empty modifiers', () => {
expect(getModifiers({})).toEqual([]);
});
});
describe('jsonToStyle', () => {
test('should convert JSON to CSS string', () => {
const result = jsonToStyle({ color: 'red', 'font-size': '14px' });
expect(result).toBe('color: red;font-size: 14px;');
});
test('should handle empty object', () => {
expect(jsonToStyle({})).toBe('');
});
});
describe('skipUniComponents', () => {
test('should filter out uni components', () => {
const components = ['div', 'span', 'View', 'Text'];
const uniComponents = ['View', 'Text', 'ScrollView'];
const result = skipUniComponents(components, uniComponents);
expect(result).toEqual(['div', 'span']);
});
test('should return all if no uni components', () => {
expect(skipUniComponents(['div', 'span'], [])).toEqual(['div', 'span']);
});
});
describe('encodeDataSource / decodeDataSource', () => {
test('should encode and decode data source', () => {
const schema = { type: 'api', url: '/test', method: 'GET' };
const encoded = encodeDataSource(schema as any);
expect(typeof encoded).toBe('string');
const decoded = decodeDataSource(encoded);
expect(decoded).toEqual(schema);
});
test('should return null for invalid encoded data', () => {
expect(decodeDataSource('invalid-base64!!!')).toBeNull();
});
});