T010
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"vitest": "^2.1.1"
|
"vitest": "^2.1.1",
|
||||||
|
"fake-indexeddb": "^6.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
130
packages/storage/tests/contract/schema.spec.ts
Normal file
130
packages/storage/tests/contract/schema.spec.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { describe, it, expect, beforeAll } from 'vitest';
|
||||||
|
import { indexedDB, IDBKeyRange } from 'fake-indexeddb';
|
||||||
|
|
||||||
|
// Implementation placeholder import; will fail until implemented per tasks T016, T017
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error - module not implemented yet
|
||||||
|
import { openDb } from '../../src/db';
|
||||||
|
|
||||||
|
// Attach fake IndexedDB globals so the implementation (when added) can use global indexedDB
|
||||||
|
// and our test can also open the DB by name to inspect stores/indexes
|
||||||
|
// @ts-ignore
|
||||||
|
if (!(globalThis as any).indexedDB) {
|
||||||
|
// @ts-ignore
|
||||||
|
(globalThis as any).indexedDB = indexedDB;
|
||||||
|
// @ts-ignore
|
||||||
|
(globalThis as any).IDBKeyRange = IDBKeyRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
name: 'glowtrack',
|
||||||
|
version: 1,
|
||||||
|
stores: {
|
||||||
|
settings: { keyPath: undefined, key: 'singleton', indexes: [] },
|
||||||
|
habits: { keyPath: 'id', indexes: ['by_type'] },
|
||||||
|
days: { keyPath: 'date', indexes: [] },
|
||||||
|
entries: { keyPath: 'id', indexes: ['by_date', 'by_habit'] }
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
async function getDbMeta(dbName: string) {
|
||||||
|
// Open the DB directly to inspect metadata when implementation exists
|
||||||
|
return await new Promise<IDBDatabase>((resolve, reject) => {
|
||||||
|
const req = indexedDB.open(dbName);
|
||||||
|
req.onsuccess = () => resolve(req.result);
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Contract: IndexedDB storage schema (T010)', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Ensure call occurs to create DB/migrations once impl exists
|
||||||
|
try {
|
||||||
|
await openDb();
|
||||||
|
} catch {
|
||||||
|
// Expected to fail or throw until implemented
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should define object stores and indexes per storage.schema.md', async () => {
|
||||||
|
// Open by expected name; impl should use same name
|
||||||
|
const name = expected.name;
|
||||||
|
|
||||||
|
let db: IDBDatabase | null = null;
|
||||||
|
try {
|
||||||
|
db = await getDbMeta(name);
|
||||||
|
} catch (e) {
|
||||||
|
// If DB doesn't exist yet, that's fine; we still run expectations to intentionally fail
|
||||||
|
}
|
||||||
|
|
||||||
|
// If implementation not present, construct a minimal snapshot that will fail below
|
||||||
|
const snapshot = db
|
||||||
|
? {
|
||||||
|
name: db.name,
|
||||||
|
version: db.version,
|
||||||
|
stores: Object.fromEntries(
|
||||||
|
(Array.from(((db as any).objectStoreNames as unknown as string[]))).map((storeName: string) => {
|
||||||
|
const tx = db!.transaction(storeName, 'readonly');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
const indexes = Array.from(store.indexNames);
|
||||||
|
return [
|
||||||
|
storeName,
|
||||||
|
{
|
||||||
|
keyPath: store.keyPath as string | string[] | null,
|
||||||
|
indexes
|
||||||
|
}
|
||||||
|
];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
: { name: null, version: null, stores: {} };
|
||||||
|
|
||||||
|
// Assertions — structured to produce helpful diffs
|
||||||
|
expect(snapshot.name).toBe(expected.name);
|
||||||
|
expect(snapshot.version).toBe(expected.version);
|
||||||
|
|
||||||
|
// Required stores
|
||||||
|
const storeNames = ['settings', 'habits', 'days', 'entries'] as const;
|
||||||
|
for (const s of storeNames) {
|
||||||
|
expect(Object.prototype.hasOwnProperty.call(snapshot.stores, s)).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys and indexes
|
||||||
|
if (db) {
|
||||||
|
// settings store: no keyPath, manual key 'singleton'
|
||||||
|
{
|
||||||
|
const tx = db.transaction('settings', 'readonly');
|
||||||
|
const store = tx.objectStore('settings');
|
||||||
|
// In v1 we accept keyPath null/undefined; key is provided at put time
|
||||||
|
expect(store.keyPath === null || store.keyPath === undefined).toBe(true);
|
||||||
|
expect(Array.from(store.indexNames)).toEqual([]);
|
||||||
|
}
|
||||||
|
// habits
|
||||||
|
{
|
||||||
|
const tx = db.transaction('habits', 'readonly');
|
||||||
|
const store = tx.objectStore('habits');
|
||||||
|
expect(store.keyPath).toBe('id');
|
||||||
|
expect(Array.from(store.indexNames)).toContain('by_type');
|
||||||
|
}
|
||||||
|
// days
|
||||||
|
{
|
||||||
|
const tx = db.transaction('days', 'readonly');
|
||||||
|
const store = tx.objectStore('days');
|
||||||
|
expect(store.keyPath).toBe('date');
|
||||||
|
expect(Array.from(store.indexNames)).toEqual([]);
|
||||||
|
}
|
||||||
|
// entries
|
||||||
|
{
|
||||||
|
const tx = db.transaction('entries', 'readonly');
|
||||||
|
const store = tx.objectStore('entries');
|
||||||
|
expect(store.keyPath).toBe('id');
|
||||||
|
const idx = Array.from(store.indexNames);
|
||||||
|
expect(idx).toContain('by_date');
|
||||||
|
expect(idx).toContain('by_habit');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Force failure with descriptive message until DB is created by implementation
|
||||||
|
expect({ exists: false, reason: 'DB not created yet' }).toEqual({ exists: true, reason: '' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -74,7 +74,7 @@ Contract files from /home/jawz/Development/Projects/GlowTrack/specs/001-glowtrac
|
|||||||
- Expect failure until export service implemented
|
- Expect failure until export service implemented
|
||||||
- Dependencies: T007
|
- Dependencies: T007
|
||||||
|
|
||||||
- [ ] T010 [P] Contract test: IndexedDB storage schema
|
- [X] T010 [P] Contract test: IndexedDB storage schema
|
||||||
- Create /home/jawz/Development/Projects/GlowTrack/packages/storage/tests/contract/schema.spec.ts
|
- Create /home/jawz/Development/Projects/GlowTrack/packages/storage/tests/contract/schema.spec.ts
|
||||||
- Open DB via openDb() and assert stores/indexes per /home/jawz/Development/Projects/GlowTrack/specs/001-glowtrack-a-mood/contracts/storage.schema.md
|
- Open DB via openDb() and assert stores/indexes per /home/jawz/Development/Projects/GlowTrack/specs/001-glowtrack-a-mood/contracts/storage.schema.md
|
||||||
- Expect failure until DB module/migrations implemented
|
- Expect failure until DB module/migrations implemented
|
||||||
|
|||||||
Reference in New Issue
Block a user