diff --git a/packages/autodoc/src/parsers/utils.ts b/packages/autodoc/src/parsers/utils.ts index 0320844..e109450 100644 --- a/packages/autodoc/src/parsers/utils.ts +++ b/packages/autodoc/src/parsers/utils.ts @@ -234,7 +234,7 @@ export function getCodeBlock( export function getExampleInfo( sourcePath: string, inferFallback: (content: string, path: string) => string, -): Record | undefined { +): ExampleInfo | undefined { const content = fs.readFileSync(sourcePath, "utf-8"); if (//.test(content)) return undefined; @@ -242,6 +242,7 @@ export function getExampleInfo( let title: string; const sentinelMatch = content.match(//); + const hasCustomTitle = !!sentinelMatch; if (sentinelMatch) { title = sentinelMatch[1].trim(); } else { @@ -250,9 +251,7 @@ export function getExampleInfo( title = titleTagMatch[1].trim(); } - return { - [title]: { path: sourcePath, code: getCodeBlock(content, sourcePath, inferFallback) }, - }; + return { title, hasCustomTitle, path: sourcePath, displayPath: sourcePath, code: getCodeBlock(content, sourcePath, inferFallback) }; } /** Collects example info from a directory or single HTML file. */ @@ -266,14 +265,18 @@ export function collectExamples( const stat = fs.statSync(examplePath); const htmlFiles: string[] = []; + let isDirectory = false; if (stat.isDirectory()) { - htmlFiles.push( - ...fs - .readdirSync(examplePath) - .filter((f) => f.endsWith(".html")) - .map((f) => path.join(examplePath, f)), - ); + isDirectory = true; + const collectHtml = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) collectHtml(full); + else if (entry.name.endsWith(".html")) htmlFiles.push(full); + } + }; + collectHtml(examplePath); } else if (stat.isFile()) { if (!examplePath.endsWith(".html")) { throw new Error(`Example file must be an HTML file: ${examplePath}`); @@ -284,9 +287,30 @@ export function collectExamples( } const result: Record = {}; + const titleCounts = new Map(); for (const file of htmlFiles) { - const exampleInfo = getExampleInfo(file, inferFallback); - if (exampleInfo) Object.assign(result, exampleInfo); + try { + const info = getExampleInfo(file, inferFallback); + if (info) { + info.displayPath = isDirectory ? path.relative(examplePath, info.path) : path.basename(info.path); + if (info.hasCustomTitle) { + const baseTitle = info.title; + const count = titleCounts.get(baseTitle) ?? 0; + titleCounts.set(baseTitle, count + 1); + if (count > 0) { + info.title = `${baseTitle} (${count + 1})`; + console.warn(`Warning: duplicate example title "${baseTitle}" in ${file}, renamed to "${info.title}"`); + } + } + result[info.path] = info; + } + } catch (e) { + if (htmlFiles.length > 1) { + console.warn(`Warning: skipping ${file}: ${e instanceof Error ? e.message : e}`); + } else { + throw e; + } + } } return result; } diff --git a/packages/autodoc/src/renderers/extension.ts b/packages/autodoc/src/renderers/extension.ts index 1b2917f..d5f7fe7 100644 --- a/packages/autodoc/src/renderers/extension.ts +++ b/packages/autodoc/src/renderers/extension.ts @@ -99,8 +99,8 @@ ${renderFunctionGroup(info.functions)} if (Object.keys(info.examples).length === 0) return ""; const sections = Object.entries(info.examples) .map( - ([title, example]) => - `### ${title} (${example.path}) + ([, example]) => + `### ${example.title}${example.hasCustomTitle ? "" : ` (${example.displayPath})`} \`\`\`js ${example.code} diff --git a/packages/autodoc/src/renderers/plugin.ts b/packages/autodoc/src/renderers/plugin.ts index 9ac4a4d..b6651d7 100644 --- a/packages/autodoc/src/renderers/plugin.ts +++ b/packages/autodoc/src/renderers/plugin.ts @@ -67,8 +67,8 @@ ${renderFunctionGroup(info.functions)} if (Object.keys(info.examples).length === 0) return ""; const sections = Object.entries(info.examples) .map( - ([title, example]) => - `### ${title} (${example.path}) + ([, example]) => + `### ${example.title}${example.hasCustomTitle ? "" : ` (${example.displayPath})`} \`\`\`js ${example.code} diff --git a/packages/autodoc/src/renderers/timeline.ts b/packages/autodoc/src/renderers/timeline.ts index e47000a..ecfaa75 100644 --- a/packages/autodoc/src/renderers/timeline.ts +++ b/packages/autodoc/src/renderers/timeline.ts @@ -124,8 +124,8 @@ ${sections}`; if (Object.keys(info.examples).length === 0) return ""; const sections = Object.entries(info.examples) .map( - ([title, example]) => - `### ${title} (${example.path}) + ([, example]) => + `### ${example.title}${example.hasCustomTitle ? "" : ` (${example.displayPath})`} \`\`\`js ${example.code} diff --git a/packages/autodoc/src/types/info.ts b/packages/autodoc/src/types/info.ts index 3f8526d..675f45e 100644 --- a/packages/autodoc/src/types/info.ts +++ b/packages/autodoc/src/types/info.ts @@ -76,7 +76,10 @@ export interface ParameterInfo { /** name is attached via record */ export interface ExampleInfo { + title: string; + hasCustomTitle: boolean; path: string; + displayPath: string; code: string; } diff --git a/packages/autodoc/tests/fixtures/extension/title-filepath-collision-tests/path1/duplicated_filename.html b/packages/autodoc/tests/fixtures/extension/title-filepath-collision-tests/path1/duplicated_filename.html new file mode 100644 index 0000000..b93d42f --- /dev/null +++ b/packages/autodoc/tests/fixtures/extension/title-filepath-collision-tests/path1/duplicated_filename.html @@ -0,0 +1,13 @@ + + + + TestExtension Example + + + + + diff --git a/packages/autodoc/tests/fixtures/extension/title-filepath-collision-tests/path2/duplicated_filename.html b/packages/autodoc/tests/fixtures/extension/title-filepath-collision-tests/path2/duplicated_filename.html new file mode 100644 index 0000000..89d88f7 --- /dev/null +++ b/packages/autodoc/tests/fixtures/extension/title-filepath-collision-tests/path2/duplicated_filename.html @@ -0,0 +1,13 @@ + + + + TestExtension Example + + + + + diff --git a/packages/autodoc/tests/fixtures/extension/title-sentinel-collision-tests/file-a.html b/packages/autodoc/tests/fixtures/extension/title-sentinel-collision-tests/file-a.html new file mode 100644 index 0000000..e37da2d --- /dev/null +++ b/packages/autodoc/tests/fixtures/extension/title-sentinel-collision-tests/file-a.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/autodoc/tests/fixtures/extension/title-sentinel-collision-tests/file-b.html b/packages/autodoc/tests/fixtures/extension/title-sentinel-collision-tests/file-b.html new file mode 100644 index 0000000..91b24be --- /dev/null +++ b/packages/autodoc/tests/fixtures/extension/title-sentinel-collision-tests/file-b.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/autodoc/tests/fixtures/extension/title-tag-collision-tests/file-a.html b/packages/autodoc/tests/fixtures/extension/title-tag-collision-tests/file-a.html new file mode 100644 index 0000000..9895d2c --- /dev/null +++ b/packages/autodoc/tests/fixtures/extension/title-tag-collision-tests/file-a.html @@ -0,0 +1,13 @@ + + + + My Example + + + + + diff --git a/packages/autodoc/tests/fixtures/extension/title-tag-collision-tests/file-b.html b/packages/autodoc/tests/fixtures/extension/title-tag-collision-tests/file-b.html new file mode 100644 index 0000000..b59965c --- /dev/null +++ b/packages/autodoc/tests/fixtures/extension/title-tag-collision-tests/file-b.html @@ -0,0 +1,13 @@ + + + + My Example + + + + + diff --git a/packages/autodoc/tests/parsers/extension.test.ts b/packages/autodoc/tests/parsers/extension.test.ts index 517ac33..14c1988 100644 --- a/packages/autodoc/tests/parsers/extension.test.ts +++ b/packages/autodoc/tests/parsers/extension.test.ts @@ -3,6 +3,7 @@ import ts from 'typescript'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { jest } from '@jest/globals'; import { identifyPackageType } from '../../src/utils.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -147,8 +148,8 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { const filePath = path.join(inferTestsDir, 'non-trial-name-with-extension.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['non trial name example']).toBeDefined(); - const code = info.examples['non trial name example'].code; + expect(info.examples[filePath]).toBeDefined(); + const code = info.examples[filePath].code; expect(code).toContain('myBlock'); expect(code).toContain('initJsPsych'); }); @@ -157,8 +158,8 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { const filePath = path.join(inferTestsDir, 'trial-name-with-extension.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['trial name example']).toBeDefined(); - const code = info.examples['trial name example'].code; + expect(info.examples[filePath]).toBeDefined(); + const code = info.examples[filePath].code; expect(code).toContain('const trial'); expect((code.match(/const trial\b/g) ?? []).length).toBe(1); }); @@ -167,8 +168,8 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { const filePath = path.join(inferTestsDir, 'trial-name-no-extension.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['trial name no extension example']).toBeDefined(); - const code = info.examples['trial name no extension example'].code; + expect(info.examples[filePath]).toBeDefined(); + const code = info.examples[filePath].code; expect(code).toContain('const trial'); expect(code).toContain('initJsPsych'); }); @@ -176,7 +177,7 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { it('collects local dependencies of a trial found by name (no extensions property)', () => { const filePath = path.join(inferTestsDir, 'trial-name-no-extension.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); - const code = info.examples['trial name no extension example'].code; + const code = info.examples[filePath].code; expect(code).toContain('const stimulus'); expect(code.indexOf('const stimulus')).toBeLessThan(code.indexOf('const trial')); }); @@ -185,7 +186,7 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { const filePath = path.join(inferTestsDir, 'mixed-detection.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - const code = info.examples['mixed detection example'].code; + const code = info.examples[filePath].code; expect(code).toContain('const trial'); expect(code).toContain('const myBlock'); }); @@ -194,7 +195,7 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { const filePath = path.join(inferTestsDir, 'trial-name-non-object.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - const code = info.examples['trial name non object example'].code; + const code = info.examples[filePath].code; expect(code).toContain('const trial'); expect(code).toContain('"experiment stimulus text"'); }); @@ -203,7 +204,7 @@ describe('inferCodeBlock (via getExtensionInfoAndExamples)', () => { const filePath = path.join(inferTestsDir, 'sentinel-bypass.html'); const info = getExtensionInfoAndExamples(fixtureSource, classNode, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['sentinel bypass example']).toBeDefined(); + expect(info.examples[filePath]).toBeDefined(); }); }); @@ -215,38 +216,89 @@ describe('getExtensionInfoAndExamples', () => { const { mainNode: classNode } = identifyPackageType(fixtureSource); const info = getExtensionInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['simple sentinel example']).toBeDefined(); - expect(info.examples['simple sentinel example'].path).toBe(filePath); - expect(info.examples['simple sentinel example'].code).toBe( + expect(info.examples[filePath]).toBeDefined(); + expect(info.examples[filePath].title).toBe('simple sentinel example'); + expect(info.examples[filePath].path).toBe(filePath); + expect(info.examples[filePath].code).toBe( 'var trial = {\n type: jsPsychTestPlugin,\n stimulus: "hello",\n extensions: [\n {type: jsPsychTestExtension, params: {test: "hi"}}\n ]\n};' ); }); + it('produces unique titles when two files share the same jspsych-autodoc:title sentinel', () => { + const collisionDir = path.resolve(__dirname, '../fixtures/extension/title-sentinel-collision-tests'); + const { mainNode: classNode } = identifyPackageType(fixtureSource); + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const info = getExtensionInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, collisionDir); + const titles = Object.values(info.examples).map((e) => e.title); + expect(titles).toHaveLength(2); + expect(new Set(titles).size).toBe(2); + expect(titles[0]).toBe("my example"); + expect(titles[1]).toBe("my example (2)"); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('my example')); + warnSpy.mockRestore(); + }); + + it('keeps both examples when two files share the same tag without renaming', () => { + const collisionDir = path.resolve(__dirname, '../fixtures/extension/title-tag-collision-tests'); + const { mainNode: classNode } = identifyPackageType(fixtureSource); + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const info = getExtensionInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, collisionDir); + const examples = Object.values(info.examples); + expect(examples).toHaveLength(2); + // titles are NOT renamed — displayPath in the heading handles uniqueness at render time + expect(examples.every((e) => e.title === 'My Example')).toBe(true); + const displayPaths = examples.map((e) => e.displayPath); + // Example headings will be unique due to the inclusion of file names/paths + expect(new Set(displayPaths).size).toBe(2); + expect(warnSpy).not.toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('keeps both examples when two files have the same default title but different paths', () => { + const collisionDir = path.resolve(__dirname, '../fixtures/extension/title-filepath-collision-tests'); + const { mainNode: classNode } = identifyPackageType(fixtureSource); + const info = getExtensionInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, collisionDir); + expect(Object.values(info.examples)).toHaveLength(2); + const displayPaths = Object.values(info.examples).map((e) => e.displayPath); + expect(displayPaths).toContain('path1/duplicated_filename.html'); + expect(displayPaths).toContain('path2/duplicated_filename.html'); + const titles = Object.values(info.examples).map((e) => e.title); + expect(titles).toHaveLength(2); + // duplicate titles remain the same — source/displayPath appended at render time keeps the full headings unique + expect(new Set(titles).size).toBe(1); + expect(titles[0]).toBe('TestExtension Example'); + expect(titles[1]).toBe('TestExtension Example'); + }); + it('should extract examples from a provided directory', () => { const { mainNode: classNode } = identifyPackageType(fixtureSource); const info = getExtensionInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, examplesDir); expect(Object.keys(info.examples)).toHaveLength(4); - expect(info.examples['ignored example']).toBeUndefined(); + expect(info.examples[path.join(examplesDir, 'ignored-example.html')]).toBeUndefined(); - const simpleSentinelExample = info.examples['simple sentinel example']; + const simpleSentinelExample = info.examples[path.join(examplesDir, 'simple-sentinel-example.html')]; + expect(simpleSentinelExample.title).toBe('simple sentinel example'); expect(simpleSentinelExample.path).toBe(path.join(examplesDir, 'simple-sentinel-example.html')); expect(simpleSentinelExample.code).toBe( 'var trial = {\n type: jsPsychTestPlugin,\n stimulus: "hello",\n extensions: [\n {type: jsPsychTestExtension, params: {test: "hi"}}\n ]\n};' ); - const complexSentinelExample = info.examples['complex sentinel example']; + const complexSentinelExample = info.examples[path.join(examplesDir, 'complex-sentinel-example.html')]; + expect(complexSentinelExample.title).toBe('complex sentinel example'); expect(complexSentinelExample.path).toBe(path.join(examplesDir, 'complex-sentinel-example.html')); expect(complexSentinelExample.code).toBe( 'var jsPsych = initJsPsych({\n extensions: [\n {type: jsPsychTestExtension}\n ]\n});\n\nvar helloTrial = {\n type: jsPsychTestPlugin,\n stimulus: "Hello",\n extensions: [\n {type: jsPsychTestExtension, params: {test: "hi"}}\n ]\n};\n\nvar goodbyeTrial = {\n type: jsPsychTestPlugin,\n stimulus: "Goodbye",\n extensions: [\n {type: jsPsychTestExtension, params: {test: "bye"}}\n ]\n};' ); - const simpleInferredExample = info.examples['simple inferred example']; + const simpleInferredExample = info.examples[path.join(examplesDir, 'simple-inferred-example.html')]; + expect(simpleInferredExample.title).toBe('simple inferred example'); expect(simpleInferredExample.path).toBe(path.join(examplesDir, 'simple-inferred-example.html')); expect(simpleInferredExample.code).toBe( 'var jsPsych = initJsPsych({\n extensions: [\n {type: jsPsychTestExtension, params: {test: "inferred"}}\n ]\n});\n\nvar trial = {\n type: jsPsychTestPlugin,\n stimulus: "World",\n extensions: [\n {type: jsPsychTestExtension, params: {test: "trial-level inferred"}}\n ]\n};' ); - const complexInferredExample = info.examples['complex inferred example']; + const complexInferredExample = info.examples[path.join(examplesDir, 'complex-inferred-example.html')]; + expect(complexInferredExample.title).toBe('complex inferred example'); expect(complexInferredExample.path).toBe(path.join(examplesDir, 'complex-inferred-example.html')); expect(complexInferredExample.code).toBe( 'var jsPsych = initJsPsych();\n\nvar stimulus = "Hello, world!";\n\nvar duration = 1000;\n\nvar choices = ["f", "j"];\n\nvar trial = {\n type: jsPsychTestPlugin,\n stimulus: stimulus,\n trial_duration: duration,\n choices: choices,\n extensions: [\n {type: jsPsychTestExtension, params: {test: "inferred complex"}}\n ]\n};' diff --git a/packages/autodoc/tests/parsers/plugin.test.ts b/packages/autodoc/tests/parsers/plugin.test.ts index 37f3658..7df65e1 100644 --- a/packages/autodoc/tests/parsers/plugin.test.ts +++ b/packages/autodoc/tests/parsers/plugin.test.ts @@ -124,9 +124,10 @@ describe('getPluginInfoAndExamples', () => { const { mainNode: classNode } = identifyPackageType(fixtureSource); const info = getPluginInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['simple sentinel example']).toBeDefined(); - expect(info.examples['simple sentinel example'].path).toBe(filePath); - expect(info.examples['simple sentinel example'].code).toBe( + expect(info.examples[filePath]).toBeDefined(); + expect(info.examples[filePath].title).toBe('simple sentinel example'); + expect(info.examples[filePath].path).toBe(filePath); + expect(info.examples[filePath].code).toBe( 'var trial = {\n type: jsPsychTestPlugin,\n stimulus: "hello"\n};' ); }); @@ -135,27 +136,31 @@ describe('getPluginInfoAndExamples', () => { const { mainNode: classNode } = identifyPackageType(fixtureSource); const info = getPluginInfoAndExamples(fixtureSource, classNode as ts.ClassDeclaration, examplesDir); expect(Object.keys(info.examples)).toHaveLength(4); - expect(info.examples['ignored example']).toBeUndefined(); + expect(info.examples[path.join(examplesDir, 'ignored-example.html')]).toBeUndefined(); - const simpleSentinelExample = info.examples['simple sentinel example']; + const simpleSentinelExample = info.examples[path.join(examplesDir, 'simple-sentinel-example.html')]; + expect(simpleSentinelExample.title).toBe('simple sentinel example'); expect(simpleSentinelExample.path).toBe(path.join(examplesDir, 'simple-sentinel-example.html')); expect(simpleSentinelExample.code).toBe( 'var trial = {\n type: jsPsychTestPlugin,\n stimulus: "hello"\n};' ); - const complexSentinelExample = info.examples['complex sentinel example']; + const complexSentinelExample = info.examples[path.join(examplesDir, 'complex-sentinel-example.html')]; + expect(complexSentinelExample.title).toBe('complex sentinel example'); expect(complexSentinelExample.path).toBe(path.join(examplesDir, 'complex-sentinel-example.html')); expect(complexSentinelExample.code).toBe( 'var fixationTrial = {\n type: jsPsychTestPlugin,\n stimulus: "+"\n};\n\nvar stimulusTrial = {\n type: jsPsychTestPlugin,\n stimulus: "Hello"\n};\n\nvar feedbackTrial = {\n type: jsPsychTestPlugin,\n stimulus: "Correct!"\n};' ); - const inferredExample = info.examples['simple inferred example']; + const inferredExample = info.examples[path.join(examplesDir, 'simple-inferred-example.html')]; + expect(inferredExample.title).toBe('simple inferred example'); expect(inferredExample.path).toBe(path.join(examplesDir, 'simple-inferred-example.html')); expect(inferredExample.code).toBe( 'var trial = {\n type: jsPsychTestPlugin,\n stimulus: "World"\n};' ); - const complexInferredExample = info.examples['complex inferred example']; + const complexInferredExample = info.examples[path.join(examplesDir, 'complex-inferred-example.html')]; + expect(complexInferredExample.title).toBe('complex inferred example'); expect(complexInferredExample.path).toBe(path.join(examplesDir, 'complex-inferred-example.html')); expect(complexInferredExample.code).toBe( 'var stimulus = "Hello, world!";\n\nvar duration = 1000;\n\nvar choices = ["f", "j"];\n\nvar trial = {\n type: jsPsychTestPlugin,\n stimulus: stimulus,\n trial_duration: duration,\n choices: choices\n};' diff --git a/packages/autodoc/tests/parsers/timeline.test.ts b/packages/autodoc/tests/parsers/timeline.test.ts index 37e18b7..b5f81f1 100644 --- a/packages/autodoc/tests/parsers/timeline.test.ts +++ b/packages/autodoc/tests/parsers/timeline.test.ts @@ -174,9 +174,10 @@ describe('getTimelineInfoAndExamples', () => { const filePath = path.join(examplesDir, 'simple-sentinel-example.html'); const info = getTimelineInfoAndExamples(fixturePath, filePath); expect(Object.keys(info.examples)).toHaveLength(1); - expect(info.examples['simple sentinel example']).toBeDefined(); - expect(info.examples['simple sentinel example'].path).toBe(filePath); - expect(info.examples['simple sentinel example'].code).toBe( + expect(info.examples[filePath]).toBeDefined(); + expect(info.examples[filePath].title).toBe('simple sentinel example'); + expect(info.examples[filePath].path).toBe(filePath); + expect(info.examples[filePath].code).toBe( 'const config = {\n testParam: 1,\n testParam2: "hello hello"\n}\n\nconst timeline = jsPsychTestTimeline.createTimeline(jsPsych, config);' ); }); @@ -184,27 +185,31 @@ describe('getTimelineInfoAndExamples', () => { it('should extract examples from a provided directory', () => { const info = getTimelineInfoAndExamples(fixturePath, examplesDir); expect(Object.keys(info.examples)).toHaveLength(4); - expect(info.examples['ignored example']).toBeUndefined(); + expect(info.examples[path.join(examplesDir, 'ignored-example.html')]).toBeUndefined(); - const simpleSentinelExample = info.examples['simple sentinel example']; + const simpleSentinelExample = info.examples[path.join(examplesDir, 'simple-sentinel-example.html')]; + expect(simpleSentinelExample.title).toBe('simple sentinel example'); expect(simpleSentinelExample.path).toBe(path.join(examplesDir, 'simple-sentinel-example.html')); expect(simpleSentinelExample.code).toBe( 'const config = {\n testParam: 1,\n testParam2: "hello hello"\n}\n\nconst timeline = jsPsychTestTimeline.createTimeline(jsPsych, config);' ); - const complexSentinelExample = info.examples['complex sentinel example']; + const complexSentinelExample = info.examples[path.join(examplesDir, 'complex-sentinel-example.html')]; + expect(complexSentinelExample.title).toBe('complex sentinel example'); expect(complexSentinelExample.path).toBe(path.join(examplesDir, 'complex-sentinel-example.html')); expect(complexSentinelExample.code).toBe( 'var fixationTrial = jsPsychTestTimeline.createFixationTrial("+");\n\nvar stimulusTrial = jsPsychTestTimeline.createStimulusTrial(\n "hello",\n 12,\n true\n)\n\nvar feedbackTrial = jsPsychTestTimeline.createFeedbackTrial(false);' ); - const simpleInferredExample = info.examples['simple inferred example']; + const simpleInferredExample = info.examples[path.join(examplesDir, 'simple-inferred-example.html')]; + expect(simpleInferredExample.title).toBe('simple inferred example'); expect(simpleInferredExample.path).toBe(path.join(examplesDir, 'simple-inferred-example.html')); expect(simpleInferredExample.code).toBe( 'const config = {\n testParam: 1,\n testParam2: "hello hello"\n}\n\nconst timeline = jsPsychTestTimeline.createTimeline(jsPsych, config);' ); - const complexInferredExample = info.examples['complex inferred example']; + const complexInferredExample = info.examples[path.join(examplesDir, 'complex-inferred-example.html')]; + expect(complexInferredExample.title).toBe('complex inferred example'); expect(complexInferredExample.path).toBe(path.join(examplesDir, 'complex-inferred-example.html')); expect(complexInferredExample.code).toBe( 'const fixationConfig = {\n fixation: "+",\n duration: 1000,\n}\n\nconst fixationTrial = jsPsychTestTimeline.timelineUnits.createFixation(fixationConfig);\n\nconst stimulusConfig = {\n stimuli: ["hello", "cheese wheel"],\n duration: [250, 1000],\n}\n\nconst stimulusTrial = jsPsychTestTimeline.timelineUnits.createStimulus({\n ...stimulusConfig, \n reverse: true\n})\n\nlet feedbackMessage = "dog";\n\nconst feedbackTrial = jsPsychTestTimeline.timelineUnits.createFeedbackTrial({\n feedback: feedbackMessage,\n showCreature: jsPsychTestTimeline.utils.canShowCreature("maybe")\n})' diff --git a/packages/autodoc/tests/renderers/__snapshots__/extension.test.ts.snap b/packages/autodoc/tests/renderers/__snapshots__/extension.test.ts.snap index 220e30e..a11b54a 100644 --- a/packages/autodoc/tests/renderers/__snapshots__/extension.test.ts.snap +++ b/packages/autodoc/tests/renderers/__snapshots__/extension.test.ts.snap @@ -12,7 +12,7 @@ exports[`extension renderer (default template) matches the rendered snapshot 1`] "examples": "<!-- jspsych-autodocs:examples:start --> ## Examples -### Basic example (examples/basic.html) +### Basic example \`\`\`js initJsPsych({ extensions: [...] }); diff --git a/packages/autodoc/tests/renderers/__snapshots__/plugin.test.ts.snap b/packages/autodoc/tests/renderers/__snapshots__/plugin.test.ts.snap index 6f1e937..2259514 100644 --- a/packages/autodoc/tests/renderers/__snapshots__/plugin.test.ts.snap +++ b/packages/autodoc/tests/renderers/__snapshots__/plugin.test.ts.snap @@ -15,7 +15,7 @@ In addition to the [default data collected by all plugins](https://www.jspsych.o "examples": "<!-- jspsych-autodocs:examples:start --> ## Examples -### Basic example (examples/basic.html) +### Basic example \`\`\`js const trial = { type: jsPsychTestPlugin }; diff --git a/packages/autodoc/tests/renderers/__snapshots__/timeline.test.ts.snap b/packages/autodoc/tests/renderers/__snapshots__/timeline.test.ts.snap index 751f2e7..2ae070e 100644 --- a/packages/autodoc/tests/renderers/__snapshots__/timeline.test.ts.snap +++ b/packages/autodoc/tests/renderers/__snapshots__/timeline.test.ts.snap @@ -20,7 +20,7 @@ Builds the timeline. "examples": "<!-- jspsych-autodocs:examples:start --> ## Examples -### Basic example (examples/basic.html) +### Basic example \`\`\`js createTimeline(jsPsych); diff --git a/packages/autodoc/tests/renderers/extension.test.ts b/packages/autodoc/tests/renderers/extension.test.ts index 4e27975..880f88a 100644 --- a/packages/autodoc/tests/renderers/extension.test.ts +++ b/packages/autodoc/tests/renderers/extension.test.ts @@ -25,7 +25,7 @@ const info: ExtensionInfo = { }, }, examples: { - "Basic example": { path: "examples/basic.html", code: "initJsPsych({ extensions: [...] });" }, + "examples/basic.html": { title: "Basic example", hasCustomTitle: true, path: "examples/basic.html", displayPath: "basic.html", code: "initJsPsych({ extensions: [...] });" }, }, }; @@ -62,6 +62,32 @@ describe("extension renderer (default template)", () => { expect(docs["data"]).not.toContain("ParameterType"); }); + it("renders both examples with their subdirectory path in the section heading", () => { + const infoWithPathCollision: ExtensionInfo = { + ...info, + examples: { + "examples/path1/duplicated_filename.html": { + title: "TestExtension Example", + hasCustomTitle: false, + path: "examples/path1/duplicated_filename.html", + displayPath: "path1/duplicated_filename.html", + code: "const trial1 = {};", + }, + "examples/path2/duplicated_filename.html": { + title: "TestExtension Example", + hasCustomTitle: false, + path: "examples/path2/duplicated_filename.html", + displayPath: "path2/duplicated_filename.html", + code: "const trial2 = {};", + }, + }, + }; + const docs = getExtensionDocs(infoWithPathCollision); + expect(docs.examples).toContain("### TestExtension Example (path1/duplicated_filename.html)"); + expect(docs.examples).toContain("### TestExtension Example (path2/duplicated_filename.html)"); + expect(docs.examples).not.toContain("### TestExtension Example (duplicated_filename.html)"); + }); + it("matches the rendered snapshot", () => { expect(getExtensionDocs(info)).toMatchSnapshot(); }); diff --git a/packages/autodoc/tests/renderers/plugin.test.ts b/packages/autodoc/tests/renderers/plugin.test.ts index 4acd29d..755a845 100644 --- a/packages/autodoc/tests/renderers/plugin.test.ts +++ b/packages/autodoc/tests/renderers/plugin.test.ts @@ -29,7 +29,7 @@ const info: PluginInfo = { }, }, examples: { - "Basic example": { path: "examples/basic.html", code: "const trial = { type: jsPsychTestPlugin };" }, + "examples/basic.html": { title: "Basic example", hasCustomTitle: true, path: "examples/basic.html", displayPath: "basic.html", code: "const trial = { type: jsPsychTestPlugin };" }, }, }; @@ -61,7 +61,7 @@ describe("plugin renderer (default template)", () => { it("renders data rows and examples", () => { expect(docs.data).toContain("Response time in ms."); - expect(docs.examples).toContain("examples/basic.html"); + expect(docs.examples).toContain("Basic example"); }); it("maps ParameterType values to human-readable strings in the data table", () => { diff --git a/packages/autodoc/tests/renderers/timeline.test.ts b/packages/autodoc/tests/renderers/timeline.test.ts index 54ca60d..f417a36 100644 --- a/packages/autodoc/tests/renderers/timeline.test.ts +++ b/packages/autodoc/tests/renderers/timeline.test.ts @@ -17,7 +17,7 @@ const info: TimelineInfo = { utils: {}, interfaces: {}, examples: { - "Basic example": { path: "examples/basic.html", code: "createTimeline(jsPsych);" }, + "examples/basic.html": { title: "Basic example", hasCustomTitle: true, path: "examples/basic.html", displayPath: "basic.html", code: "createTimeline(jsPsych);" }, }, };