fix: v0.7.3 数据正确性与边界窗口收尾 — INSERT 语句级原子(三引擎+Aria PK 批内重复)/ 索引列 IS NULL 恒空 / delete RESTRICT 破坏索引 / queryStream 子查询静默空结果 / ALTER DROP 索引残留 / UNIQUE INDEX 存量校验 / SELECT * 别名投影 / WAL BEGIN/ROLLBACK 事务边界 / aria $in 与级联重复扫描性能 / $and 等值下推 / ANALYZE 索引统计 / React-Vue hooks 生命周期 / 迁移主键兜底 + 58 回归
CI / test (18.x) (push) Successful in 17m43s
CI / test (22.x) (push) Successful in 13m45s
CI / test (20.x) (push) Successful in 15m33s
CI / test (24.x) (push) Successful in 24m46s
CI / e2e (push) Successful in 52s

This commit is contained in:
thzxx
2026-08-14 22:53:46 +08:00
parent f8f8d1b2ff
commit 50468b9b0e
29 changed files with 2374 additions and 650 deletions
+59 -3
View File
@@ -16,6 +16,9 @@ jest.mock('react', () => {
const stateStore: any[] = [];
const registeredEffects = new Set<number>();
const effectQueue: Array<() => void | Promise<void>> = [];
// v0.7.3: 依赖数组指纹 + cleanup 支持(useDatabase config 变更重建测试用)
const effectDeps = new Map<number, string>();
const effectCleanups = new Map<number, () => void>();
let cursor = 0;
return {
useState: (init: any) => {
@@ -28,12 +31,26 @@ jest.mock('react', () => {
},
];
},
// 按 hook 调用位置去重:同一位置的 effect 只在首次 render 注册
useEffect: (fn: any, _deps: any[]) => {
// 按 hook 调用位置注册:同一位置首次注册入队;deps 指纹变化时
// 先执行旧 cleanup 再重跑 effect(模拟 React 依赖更新语义)
useEffect: (fn: any, _deps: any[] = []) => {
const idx = cursor;
const key = JSON.stringify(_deps ?? []);
if (!registeredEffects.has(idx)) {
registeredEffects.add(idx);
effectQueue.push(fn);
effectDeps.set(idx, key);
effectQueue.push(() => {
const cleanup = fn();
if (typeof cleanup === 'function') effectCleanups.set(idx, cleanup);
});
} else if (effectDeps.get(idx) !== key) {
effectDeps.set(idx, key);
effectQueue.push(() => {
const c = effectCleanups.get(idx);
if (c) { c(); effectCleanups.delete(idx); }
const cleanup = fn();
if (typeof cleanup === 'function') effectCleanups.set(idx, cleanup);
});
}
},
useCallback: (fn: any) => fn,
@@ -49,6 +66,8 @@ jest.mock('react', () => {
cursor = 0;
effectQueue.length = 0;
registeredEffects.clear();
effectDeps.clear();
effectCleanups.clear();
},
};
}, { virtual: true });
@@ -197,4 +216,41 @@ describe('useDatabase', () => {
initSpy.mockRestore();
});
test('config 变更时关闭旧实例并重建(v0.7.3)', async () => {
const closeSpy = jest.spyOn(MetonaSqlark.prototype, 'close').mockResolvedValue();
// 首次挂载(真实异步 init
const mount = (config: { name: string }) => useDatabase(config);
render(() => mount({ name: 'hook-a' }));
await flushEffects();
// 轮询等待真实 init 完成(stateStore 经 re-render 读取最新值)
for (let i = 0; i < 100 && render(() => mount({ name: 'hook-a' })).db === null; i++) {
await new Promise((r) => setTimeout(r, 5));
}
const first = render(() => mount({ name: 'hook-a' }));
expect(first.db).not.toBeNull();
const firstDb = first.db;
// 同名 config 再渲染 → 不重建(相同实例)
const same = render(() => mount({ name: 'hook-a' }));
expect(same.db).toBe(firstDb);
// config 变更渲染 → cleanup 关闭旧实例 + 重建新实例
render(() => mount({ name: 'hook-b' }));
await flushEffects();
for (let i = 0; i < 100; i++) {
const cur = render(() => mount({ name: 'hook-b' }));
if (cur.db !== null && cur.db !== firstDb && cur.ready) break;
await new Promise((r) => setTimeout(r, 5));
}
const afterChange = render(() => mount({ name: 'hook-b' }));
expect(closeSpy).toHaveBeenCalled();
expect(afterChange.db).not.toBeNull();
expect(afterChange.db).not.toBe(firstDb);
expect(afterChange.ready).toBe(true);
closeSpy.mockRestore();
});
});