This commit is contained in:
2025-09-18 11:20:17 -06:00
parent f27ef4f341
commit 530a74147b
3 changed files with 133 additions and 2 deletions

View File

@@ -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"
} }
} }

View 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: '' });
}
});
});

View File

@@ -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