feat: metona-sqlark v0.1.12 — 前端TypeScript关系型数据库
- 4种存储引擎:Memory / IndexedDB / OPFS / Hybrid - 完整SQL支持:SELECT/INSERT/UPDATE/DELETE/JOIN/GROUP BY/HAVING/DISTINCT - Query Builder链式API + TypeScript泛型支持 - 聚合函数:COUNT/SUM/AVG/MIN/MAX - 事务、插件系统(14 hooks)、发布订阅、数据迁移、导入导出 - React/Vue框架集成 - 264个测试用例,93.46%覆盖率 - 零运行时依赖
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Schema 边缘场景测试
|
||||
*/
|
||||
|
||||
import { createSchema, validateRow, getPrimaryKey, checkFieldType, astColumnToColumnDef } from '../../src/table/schema';
|
||||
|
||||
describe('Schema 边缘场景', () => {
|
||||
// ---- createSchema ----
|
||||
|
||||
describe('createSchema', () => {
|
||||
it('空列表抛出错误', () => {
|
||||
expect(() => createSchema('empty', {})).toThrow('at least one column');
|
||||
});
|
||||
|
||||
it('多主键定义(取第一个标记的)', () => {
|
||||
const schema = createSchema('multi', {
|
||||
id1: { type: 'string', primaryKey: true },
|
||||
id2: { type: 'string' },
|
||||
});
|
||||
expect(getPrimaryKey(schema)).toBe('id1');
|
||||
});
|
||||
|
||||
it('大表定义', () => {
|
||||
const cols: Record<string, any> = {};
|
||||
for (let i = 0; i < 50; i++) {
|
||||
cols[`col${i}`] = { type: 'string' };
|
||||
}
|
||||
cols.id = { type: 'string', primaryKey: true };
|
||||
const schema = createSchema('big', cols);
|
||||
expect(Object.keys(schema.columns)).toHaveLength(51);
|
||||
});
|
||||
});
|
||||
|
||||
// ---- getPrimaryKey ----
|
||||
|
||||
describe('getPrimaryKey', () => {
|
||||
it('没有标记主键时返回第一列', () => {
|
||||
const schema = createSchema('test', {
|
||||
uuid: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string' },
|
||||
});
|
||||
// 重新构造不带主键标记的 schema
|
||||
const noPkSchema = { name: 'test', columns: {
|
||||
first: { type: 'string' as const },
|
||||
second: { type: 'string' as const },
|
||||
}};
|
||||
expect(getPrimaryKey(noPkSchema)).toBe('first');
|
||||
});
|
||||
});
|
||||
|
||||
// ---- validateRow ----
|
||||
|
||||
describe('validateRow', () => {
|
||||
const schema = createSchema('users', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string', required: true },
|
||||
bio: { type: 'string', maxLength: 200 },
|
||||
score: { type: 'number', min: 0, max: 100, default: 50 },
|
||||
active: { type: 'boolean', default: true },
|
||||
birthday: { type: 'date' },
|
||||
tags: { type: 'json', default: [] },
|
||||
});
|
||||
|
||||
it('跳过 undefined 字段(使用默认值)', () => {
|
||||
const row = validateRow(schema, { id: '1', name: 'Alice' });
|
||||
expect(row.id).toBe('1');
|
||||
expect(row.name).toBe('Alice');
|
||||
expect(row.score).toBe(50);
|
||||
expect(row.active).toBe(true);
|
||||
expect(row.tags).toEqual([]);
|
||||
});
|
||||
|
||||
it('null 值被视为不满足必填', () => {
|
||||
expect(() => validateRow(schema, { id: '1', name: null })).toThrow('required');
|
||||
});
|
||||
|
||||
it('json 类型接受对象', () => {
|
||||
const row = validateRow(schema, { id: '1', name: 'Alice', tags: { key: 'value' } });
|
||||
expect(row.tags).toEqual({ key: 'value' });
|
||||
});
|
||||
|
||||
it('json 类型接受数组', () => {
|
||||
const row = validateRow(schema, { id: '1', name: 'Alice', tags: [1, 2, 3] });
|
||||
expect(row.tags).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('date 类型接受有效日期字符串', () => {
|
||||
const row = validateRow(schema, { id: '1', name: 'Alice', birthday: '2024-01-15' });
|
||||
expect(row.birthday).toBe('2024-01-15');
|
||||
});
|
||||
|
||||
it('date 类型拒绝无效格式', () => {
|
||||
expect(() => validateRow(schema, { id: '1', name: 'Alice', birthday: 'not-a-date' }))
|
||||
.toThrow('expects valid date');
|
||||
expect(() => validateRow(schema, { id: '1', name: 'Alice', birthday: 123 }))
|
||||
.toThrow('expects valid date');
|
||||
});
|
||||
|
||||
it('boolean 类型', () => {
|
||||
const row = validateRow(schema, { id: '1', name: 'Alice', active: false });
|
||||
expect(row.active).toBe(false);
|
||||
});
|
||||
|
||||
it('boolean 拒绝非布尔值', () => {
|
||||
expect(() => validateRow(schema, { id: '1', name: 'Alice', active: 'yes' }))
|
||||
.toThrow('expects boolean');
|
||||
});
|
||||
|
||||
it('number 拒绝字符串', () => {
|
||||
expect(() => validateRow(schema, { id: '1', name: 'Alice', score: 'high' }))
|
||||
.toThrow('expects number');
|
||||
});
|
||||
|
||||
it('string 拒绝数字', () => {
|
||||
expect(() => validateRow(schema, { id: 123, name: 'Alice' }))
|
||||
.toThrow('expects string');
|
||||
});
|
||||
});
|
||||
|
||||
// ---- checkFieldType ----
|
||||
|
||||
describe('checkFieldType', () => {
|
||||
it('string 类型通过', () => {
|
||||
expect(() => checkFieldType('t', 'c', 'string', 'hello')).not.toThrow();
|
||||
});
|
||||
|
||||
it('string 类型拒绝', () => {
|
||||
expect(() => checkFieldType('t', 'c', 'string', 123)).toThrow('expects string');
|
||||
});
|
||||
|
||||
it('number 类型通过', () => {
|
||||
expect(() => checkFieldType('t', 'c', 'number', 42)).not.toThrow();
|
||||
expect(() => checkFieldType('t', 'c', 'number', 3.14)).not.toThrow();
|
||||
expect(() => checkFieldType('t', 'c', 'number', 0)).not.toThrow();
|
||||
});
|
||||
|
||||
it('boolean 类型通过', () => {
|
||||
expect(() => checkFieldType('t', 'c', 'boolean', true)).not.toThrow();
|
||||
expect(() => checkFieldType('t', 'c', 'boolean', false)).not.toThrow();
|
||||
});
|
||||
|
||||
it('json 拒绝非对象', () => {
|
||||
expect(() => checkFieldType('t', 'c', 'json', 'not-object')).toThrow('expects object');
|
||||
expect(() => checkFieldType('t', 'c', 'json', 123)).toThrow('expects object');
|
||||
});
|
||||
});
|
||||
|
||||
// ---- astColumnToColumnDef ----
|
||||
|
||||
describe('astColumnToColumnDef', () => {
|
||||
it('转换基础列', () => {
|
||||
const result = astColumnToColumnDef({ name: 'id', type: 'string' });
|
||||
expect(result.type).toBe('string');
|
||||
});
|
||||
|
||||
it('转换带修饰符的列', () => {
|
||||
const result = astColumnToColumnDef({
|
||||
name: 'id', type: 'string', primaryKey: true, unique: true, required: true,
|
||||
});
|
||||
expect(result.primaryKey).toBe(true);
|
||||
expect(result.unique).toBe(true);
|
||||
expect(result.required).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user