Files
MetonaSqlark/tests/engine/indexeddb.test.ts
T
thzxx e2a590c5b1 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%覆盖率
- 零运行时依赖
2026-07-26 15:00:01 +08:00

324 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* IndexedDBEngine 完整测试(使用 fake-indexeddb
* 每次测试使用独立的数据库名避免版本冲突
*/
import 'fake-indexeddb/auto';
import { IndexedDBEngine } from '../../src/engine/indexeddb';
import { createSchema } from '../../src/table/schema';
let dbCounter = 0;
describe('IndexedDBEngine', () => {
let engine: IndexedDBEngine;
let dbName: string;
const userSchema = createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
age: { type: 'number', default: 0 },
email: { type: 'string', unique: true },
});
beforeEach(async () => {
dbName = `test-idb-${++dbCounter}`;
engine = new IndexedDBEngine();
await engine.open(dbName, 1);
});
afterEach(async () => {
if (engine.isOpen()) {
await engine.close();
}
// 清理:删除 IndexedDB 数据库
try {
indexedDB.deleteDatabase(dbName);
} catch {}
});
// ---- 生命周期 ----
describe('生命周期', () => {
it('打开和关闭', async () => {
expect(engine.isOpen()).toBe(true);
await engine.close();
expect(engine.isOpen()).toBe(false);
});
it('未打开时操作抛出错误', async () => {
await engine.close();
await expect(engine.count('users')).rejects.toThrow('not opened');
});
it('可以重新打开', async () => {
await engine.close();
await engine.open(dbName, 2);
expect(engine.isOpen()).toBe(true);
});
});
// ---- 表管理 ----
describe('表管理', () => {
it('创建和删除表', async () => {
await engine.createTable(userSchema);
expect(await engine.hasTable('users')).toBe(true);
await engine.dropTable('users');
expect(await engine.hasTable('users')).toBe(false);
});
it('表名列表', async () => {
await engine.createTable(userSchema);
const names = await engine.getTableNames();
expect(names).toContain('users');
});
it('获取表结构', async () => {
await engine.createTable(userSchema);
const schema = await engine.getTableSchema('users');
expect(schema).not.toBeNull();
expect(schema!.name).toBe('users');
});
it('获取不存在的表结构返回 null', async () => {
const schema = await engine.getTableSchema('nonexistent');
expect(schema).toBeNull();
});
});
// ---- 插入 ----
describe('插入', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
});
it('插入单行', async () => {
const pks = await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
]);
expect(pks).toEqual(['1']);
expect(await engine.count('users')).toBe(1);
});
it('插入多行', async () => {
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'a@t.com' },
{ id: '2', name: 'Bob', age: 25, email: 'b@t.com' },
]);
expect(await engine.count('users')).toBe(2);
});
it('必填字段缺失抛出错误', async () => {
await expect(
engine.insert('users', [{ id: '1', email: 'a@t.com' }]),
).rejects.toThrow('required');
});
});
// ---- 查询 ----
describe('查询', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
{ id: '2', name: 'Bob', age: 25, email: 'bob@test.com' },
{ id: '3', name: 'Charlie', age: 35, email: 'charlie@test.com' },
]);
});
it('查询所有', async () => {
const rows = await engine.find('users', { table: 'users' });
expect(rows).toHaveLength(3);
});
it('WHERE 条件', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $gt: 28 } },
});
expect(rows).toHaveLength(2);
});
it('ORDER BY + LIMIT', async () => {
const rows = await engine.find('users', {
table: 'users',
orderBy: [{ column: 'age', direction: 'desc' }],
limit: 2,
});
expect(rows.map(r => r.age)).toEqual([35, 30]);
});
it('列选择', async () => {
const rows = await engine.find('users', {
table: 'users',
columns: ['id'],
where: { id: '1' },
});
expect(Object.keys(rows[0])).toEqual(['id']);
});
});
// ---- 更新 ----
describe('更新', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'a@t.com' },
{ id: '2', name: 'Bob', age: 25, email: 'b@t.com' },
]);
});
it('更新匹配行', async () => {
const count = await engine.update(
'users',
{ table: 'users', where: { id: '1' } },
{ age: 31 },
);
expect(count).toBe(1);
const rows = await engine.find('users', { table: 'users', where: { id: '1' } });
expect(rows[0].age).toBe(31);
});
it('更新所有行', async () => {
const count = await engine.update(
'users',
{ table: 'users' },
{ age: 100 },
);
expect(count).toBe(2);
});
});
// ---- 删除 ----
describe('删除', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'a@t.com' },
{ id: '2', name: 'Bob', age: 25, email: 'b@t.com' },
]);
});
it('条件删除', async () => {
const count = await engine.delete('users', {
table: 'users',
where: { id: '1' },
});
expect(count).toBe(1);
expect(await engine.count('users')).toBe(1);
});
it('清空表', async () => {
await engine.clear('users');
expect(await engine.count('users')).toBe(0);
// 表结构仍存在
expect(await engine.hasTable('users')).toBe(true);
});
it('删除不存在的表抛出错误', async () => {
await expect(
engine.delete('nonexistent', { table: 'nonexistent' }),
).rejects.toThrow();
});
});
// ---- Count ----
describe('Count', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'a@t.com' },
{ id: '2', name: 'Bob', age: 25, email: 'b@t.com' },
]);
});
it('count 全部', async () => {
expect(await engine.count('users')).toBe(2);
});
it('count with where', async () => {
expect(await engine.count('users', {
table: 'users',
where: { age: { $gt: 28 } },
})).toBe(1);
});
});
// ---- 复杂 Where 条件 ----
describe('复杂 Where', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'a@t.com' },
{ id: '2', name: 'Bob', age: 25, email: 'b@t.com' },
{ id: '3', name: 'Charlie', age: 35, email: 'c@t.com' },
]);
});
it('$like 模糊匹配', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { name: { $like: 'A%' } },
});
expect(rows).toHaveLength(1);
});
it('$in 列表', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { id: { $in: ['1', '3'] } },
});
expect(rows).toHaveLength(2);
});
it('$ne 不等于', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $ne: 25 } },
});
expect(rows).toHaveLength(2);
});
it('$gte $lte 范围', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $gte: 25, $lte: 30 } },
});
expect(rows).toHaveLength(2);
});
});
// ---- 并发/多次操作 ----
describe('多次操作', () => {
it('连续创建删除表', async () => {
await engine.createTable(userSchema);
expect(await engine.hasTable('users')).toBe(true);
await engine.dropTable('users');
expect(await engine.hasTable('users')).toBe(false);
await engine.createTable(userSchema);
expect(await engine.hasTable('users')).toBe(true);
});
it('插入后查询再更新再查询', async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'a@t.com' },
]);
let rows = await engine.find('users', { table: 'users' });
expect(rows[0].age).toBe(30);
await engine.update('users', { table: 'users' }, { age: 31 });
rows = await engine.find('users', { table: 'users' });
expect(rows[0].age).toBe(31);
});
});
});