import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import test from 'node:test'; const planStore = () => readFileSync(join(process.cwd(), 'stores/useVersionPlanStore.ts'), 'utf8'); const taskStore = () => readFileSync(join(process.cwd(), 'stores/useDevTaskStore.ts'), 'utf8'); const versionDetailPage = () => readFileSync(join(process.cwd(), 'app/versions/[id]/page.tsx'), 'utf8'); const workspacePage = () => readFileSync(join(process.cwd(), 'app/workspace/page.tsx'), 'utf8'); const devTaskTab = () => readFileSync(join(process.cwd(), 'components/dev-task/DevTaskTab.tsx'), 'utf8'); const projectListPage = () => readFileSync(join(process.cwd(), 'app/projects/page.tsx'), 'utf8'); const projectDetailPage = () => readFileSync(join(process.cwd(), 'app/projects/[id]/page.tsx'), 'utf8'); function storeMethodBody(text: string, name: string) { const implementationStart = text.indexOf('export const'); assert.notEqual(implementationStart, -1, 'missing store implementation'); const start = text.indexOf(` ${name}:`, implementationStart); assert.notEqual(start, -1, `missing store method ${name}`); let depth = 0; let sawFirstBrace = false; for (let i = start; i < text.length; i += 1) { const char = text[i]; if (char === '{') { depth += 1; sawFirstBrace = true; } if (char === '}') { depth -= 1; if (sawFirstBrace && depth === 0) return text.slice(start, i + 1); } } throw new Error(`could not extract store method ${name}`); } test('version plan store uses domain APIs for version-scoped writes', () => { const text = planStore(); assert.match(text, /from '@\/lib\/domain-api'/); assert.match(storeMethodBody(text, 'createPlan'), /createVersionPlanByVersionId\(plan\.versionId,/); assert.match(storeMethodBody(text, 'updatePlan'), /updateVersionPlanByVersionId\(versionId, id,/); assert.match(storeMethodBody(text, 'completePlan'), /completeVersionPlanByVersionId\(versionId, id,/); assert.doesNotMatch(storeMethodBody(text, 'createPlan'), /saveServerData\('version-plans'/); assert.doesNotMatch(storeMethodBody(text, 'updatePlan'), /saveServerData\('version-plans'/); assert.doesNotMatch(text, /saveServerData\('version-plans'/); assert.doesNotMatch(text, /saveVersionPlansFallback/); }); test('version plan domain API preserves research direction tasks', () => { const text = readFileSync(join(process.cwd(), 'lib/domain-api.ts'), 'utf8'); assert.match(text, /data\.tasks !== undefined && \{ tasks: data\.tasks \}/); assert.match(text, /tasks: asArray\(row\.tasks\)/); }); test('version plan reads for version detail use version-scoped relation data', () => { const store = planStore(); const fetchPlans = storeMethodBody(store, 'fetchPlans'); const page = versionDetailPage(); assert.match(fetchPlans, /listVersionPlansByVersionId\(options\.versionId\)/); assert.match(fetchPlans, /!options\?\.versionId && !options\?\.force/); assert.match(fetchPlans, /mergeVersionPlansForVersion/); assert.doesNotMatch(fetchPlans, /listVersionPlansByVersionId\(options\.versionId\)\.catch\(loadStored\)/); assert.match(page, /fetchPlans\(\{ versionId \}\);/); assert.match(page, /v22Scope,\s*\}\)/); assert.doesNotMatch(page, /versionWriteStoresReady \? null : v22Scope/); assert.match(page, /const displayRequirements = scopedVersionData\.requirements/); assert.match(page, /const displayRequirementIds = scopedVersionData\.requirementIds/); assert.match(page, /const displayPlans = scopedVersionData\.plans/); assert.match(page, /const displayDevTasks = scopedVersionData\.devTasks/); assert.match(page, /const displayTestCases = scopedVersionData\.testCases/); assert.match(page, /const displayBugs = scopedVersionData\.bugs/); }); test('workspace plan drawer loads version-scoped relation data', () => { const page = workspacePage(); assert.match(page, /fetchPlans\(\{ versionId: item\.versionId \}\)/); assert.doesNotMatch(page, /&& !plansLoaded\) \{\s*pendingLoads\.push\(fetchPlans\(\{ versionId: item\.versionId \}\)\);/); assert.doesNotMatch(page, /pendingLoads\.push\(fetchPlans\(\)\);/); }); test('dev task store uses domain APIs for version-scoped writes', () => { const text = taskStore(); const fetchTasks = storeMethodBody(text, 'fetchTasks'); assert.match(text, /from '@\/lib\/domain-api'/); assert.match(fetchTasks, /listDevTasksByVersionId\(options\.versionId\)/); assert.match(fetchTasks, /!options\?\.versionId && !options\?\.force/); assert.match(storeMethodBody(text, 'createTask'), /createDevTaskByVersionId\(task\.versionId,/); assert.match(storeMethodBody(text, 'updateTask'), /updateDevTaskByVersionId\(versionId, id,/); assert.match(storeMethodBody(text, 'changeStatus'), /updateDevTaskStatusByVersionId\(task\.versionId,/); assert.match(storeMethodBody(text, 'setBlocked'), /setDevTaskBlockedByVersionId\(task\.versionId,/); assert.doesNotMatch(storeMethodBody(text, 'createTask'), /saveServerData\('dev-tasks'/); assert.doesNotMatch(storeMethodBody(text, 'updateTask'), /saveServerData\('dev-tasks'/); }); test('workspace dev task drawer loads version-scoped relation data', () => { const page = workspacePage(); assert.match(page, /fetchTasks\(\{ versionId: item\.versionId \}\)/); assert.doesNotMatch(page, /item\.type === 'devTask' && !devTasksLoaded/); assert.doesNotMatch(page, /pendingLoads\.push\(fetchTasks\(\)\);/); }); test('dev task tab fallback fetches the current version relation partition', () => { const text = devTaskTab(); assert.match(text, /fetchTasks\(\{ versionId \}\)/); assert.doesNotMatch(text, /if \(!scopedVersionTasks\) fetchTasks\(\);/); }); test('project pages hydrate version summaries through version-scoped relation partitions', () => { const listPage = projectListPage(); const detailPage = projectDetailPage(); assert.match(listPage, /fetchPlans\(\{ versionId: version\.id \}\)/); assert.match(listPage, /fetchDevTasks\(\{ versionId: version\.id \}\)/); assert.match(listPage, /fetchTestCases\(\{ versionId: version\.id \}\)/); assert.doesNotMatch(listPage, /fetchPlans\(\);/); assert.doesNotMatch(listPage, /fetchDevTasks\(\);/); assert.doesNotMatch(listPage, /fetchTestCases\(\);/); assert.match(detailPage, /fetchPlans\(\{ versionId: version\.id \}\)/); assert.match(detailPage, /fetchDevTasks\(\{ versionId: version\.id \}\)/); assert.match(detailPage, /fetchTestCases\(\{ versionId: version\.id \}\)/); assert.match(detailPage, /fetchBugs\(\{ versionId: version\.id \}\)/); assert.doesNotMatch(detailPage, /fetchPlans\(\);/); assert.doesNotMatch(detailPage, /fetchDevTasks\(\);/); assert.doesNotMatch(detailPage, /fetchTestCases\(\);/); assert.doesNotMatch(detailPage, /fetchBugs\(\);/); });