/** * scaffold-doc — SOURCE mode: same wiring plan as before, but LOUD. * * A wiring target that findFirst cannot locate is now a hard error (errors[]) * instead of a silent `file: null` instruction — the degradation that let the * skill "succeed" on projects missing the docs subsystem. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { generate } from '../generate.js' import { makeSourceProject, sourceSpec, read, SOURCE_WEB } from './fixtures.js' let root: string beforeEach(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'sdoc-source-')) }) afterEach(() => { fs.rmSync(root, { recursive: true, force: true }) }) describe('scaffold-doc source mode', () => { it('emits the full 4-instruction wiring plan on a complete monorepo (user type)', async () => { makeSourceProject(root) const res = await generate(sourceSpec(root)) expect(res.errors).toEqual([]) expect(res.mode).toBe('source') expect(res.effectiveNamespace).toBe('docsAdministrationUsers') expect(res.wiring.map((w) => w.kind)).toEqual(['i18n-config', 'doc-routes', 'doc-panel-mapping', 'user-index']) for (const w of res.wiring) { expect(w.severity).toBe('required') expect(w.file).not.toBeNull() } }) it('writes the i18n files (kebab of the camel namespace) and upserts the manifest', async () => { makeSourceProject(root) const res = await generate(sourceSpec(root)) expect(res.errors).toEqual([]) const frRel = [...SOURCE_WEB, 'src', 'i18n', 'locales', 'fr', 'docs-administration-users.json'].join('/') expect(res.filesCreated).toContain(frRel) const manifest = JSON.parse(read(root, [...SOURCE_WEB, 'src', 'pages', 'docs', 'docs-manifest.json'].join('/'))) expect(manifest.modules).toHaveLength(1) expect(manifest.modules[0].i18nNamespace).toBe('docsAdministrationUsers') }) it('a missing wiring target is a HARD error, not a silent file:null instruction', async () => { makeSourceProject(root, { without: ['docPanel'] }) const res = await generate(sourceSpec(root)) expect(res.errors.join('\n')).toContain('**/DocPanelContext.tsx') expect(res.errors.join('\n')).toContain('"mode":"client"') // The other 3 targets still yield instructions; none carries file: null. expect(res.wiring.map((w) => w.kind)).toEqual(['i18n-config', 'doc-routes', 'user-index']) for (const w of res.wiring) expect(w.file).not.toBeNull() }) it('a missing docs-manifest.json is a HARD error in source mode', async () => { makeSourceProject(root, { without: ['manifest'] }) const res = await generate(sourceSpec(root)) expect(res.errors.join('\n')).toContain('docs-manifest.json not found') }) it('DocRenderer types skip the user-index instruction (unchanged contract)', async () => { makeSourceProject(root) const res = await generate( sourceSpec(root, { type: 'developer', namespace: 'docsToolsPostman', routePath: 'developer/tools/postman', pageImportPath: '@/pages/docs/developer/tools/postman' }), ) expect(res.errors).toEqual([]) expect(res.wiring.map((w) => w.kind)).toEqual(['i18n-config', 'doc-routes', 'doc-panel-mapping']) // DocRenderer i18n is nested under the namespace key. const fr = JSON.parse(read(root, [...SOURCE_WEB, 'src', 'i18n', 'locales', 'fr', 'docs-tools-postman.json'].join('/'))) expect(fr.docsToolsPostman).toBeDefined() expect(fr.title).toBeUndefined() }) })