diff --git a/docs/userGuide/makingTheSiteSearchable.md b/docs/userGuide/makingTheSiteSearchable.md index 709939541f..a1e92e96f4 100644 --- a/docs/userGuide/makingTheSiteSearchable.md +++ b/docs/userGuide/makingTheSiteSearchable.md @@ -101,34 +101,58 @@ In your `site.json`: This tells Pagefind to exclude any element with the `algolia-no-index` class (or containing it in a space-separated list) from the search index, similar to using `data-pagefind-ignore`. +For more details, see the [Pagefind documentation on exclude selector configuration option](https://pagefind.app/docs/config-options/#exclude-selectors). + #### Limiting Which Pages Are Searchable -You can use the `glob` option to limit which pages are indexed by Pagefind. This is useful when you want search results to only show pages from specific sections of your site. +Pagefind uses the existing `searchable` property in your `pages` configuration to determine which pages should be indexed. This provides a seamless way to control search indexing without additional configuration. In your `site.json`: ```json { - "pagefind": { - "glob": [ - "devGuide", - "userGuide/*" - ] - } + "pages": [ + { + "src": "index.md" + }, + { + "src": "internal/notes.md", + "searchable": "no" + }, + { + "glob": "devGuide/**/*.md", + "searchable": "no" + } + ] } ``` -MarkBind supports glob patterns and will automatically append `.html` to your patterns if not specified. For example: -- `"devGuide"` becomes `"devGuide/**/*.html"` -- `"devGuide/*"` becomes `"devGuide/*.html"` -- `"**/devGuide/**"` becomes `"**/devGuide/**/*.html"` -- `"*.html"` remains `"*.html"` (no change needed) +- Pages with `searchable: "no"` (or `false`) will not appear in search results +- By default, all pages are searchable (`searchable: "yes"`) + +For more details on the `searchable` property, see [site.json file documentation](siteJsonFile.html#pages). + + + +MarkBind controls page indexing through the `searchable` property, which determines whether pages are passed to Pagefind for indexing. However, Pagefind also provides native attributes that can affect indexing: -Only pages matching these glob patterns will appear in search results. This can be particularly useful for: -- Multi-site setups where you want to search only specific sections -- Including only certain directories from search results +- [**`data-pagefind-body`**](https://pagefind.app/docs/indexing/#limiting-what-sections-of-a-page-are-indexed): Marks a specific element as the search content container. When this attribute exists on ANY page of your site, pages WITHOUT this attribute will not be indexed. +- [**`data-pagefind-ignore`**](https://pagefind.app/docs/indexing/#removing-individual-elements-from-the-index): Excludes specific elements from the search index. -For more details on glob patterns, see the [Pagefind documentation](https://pagefind.app/docs/config-options/#glob). +**How MarkBind handles this:** + +1. Pages with `searchable: "no"` are NOT passed to Pagefind at all (they are never indexed). +2. Pages with `searchable: "yes"` (default) ARE passed to Pagefind for indexing. + +**Interactions to be aware of:** + +- If you add `data-pagefind-body` to a searchable page, it works as expected - the page is indexed. However, only pages with this attribute will be searchable. +- If you add `data-pagefind-body` to a non-searchable page, MarkBind will still NOT index it (because it's filtered before being passed to Pagefind). +- Adding `data-pagefind-ignore` to a searchable page will NOT prevent it from being indexed - the page is still added via MarkBind's indexing, but the content within that element will be ignored by Pagefind. + +**Recommendation:** Use MarkBind's `searchable` property in site.json to control which pages are indexed & use `data-pagefind-body` attribute to exlcude specific elements within a page from being searchable. Avoid using `data-pagefind-body` as it is redundant and may lead to confusion. + + diff --git a/docs/userGuide/siteJsonFile.md b/docs/userGuide/siteJsonFile.md index d1a740baea..4f4b15546f 100644 --- a/docs/userGuide/siteJsonFile.md +++ b/docs/userGuide/siteJsonFile.md @@ -135,7 +135,7 @@ _(Optional)_ **The styling options to be applied to the site.** This includes: * **`globExclude`**: An array of file patterns to be excluded from rendering when using `glob`, also defined in the glob syntax. * **`title`**: The page `` for the generated web page. Titles specified here take priority over titles specified in the [frontmatter](tweakingThePageStructure.html#frontmatter) of individual pages. * **`layout`**: The [layout](tweakingThePageStructure.html#layouts) to be used by the page. Default: `default`. -* **`searchable`**: Specifies that the page(s) should be excluded from searching. Default: `yes`. +* **`searchable`**: Specifies whether the page(s) should be included in search indexing. This applies to both the built-in search and Pagefind (if enabled). Set to `"no"` or `false` to exclude the page(s) from search results. Default: `yes`. * **`externalScripts`**: An array of external scripts to be referenced on the page. Scripts referenced will be run before the layout script. * **`frontmatter`**: Specifies properties to add to the frontmatter of a page or glob of pages. Overrides any existing properties if they have the same name, and overrides any frontmatter properties specified in `globalOverride`. * **`fileExtension`**: A string that specifies the output file extension (e.g., `".json"`, `".txt"`) for the generated file. If not specified, defaults to `".html"`. Note that non-HTML files do not support frontmatter or scripts. diff --git a/packages/core/src/Page/page.njk b/packages/core/src/Page/page.njk index 7a96999e7d..b305315a95 100644 --- a/packages/core/src/Page/page.njk +++ b/packages/core/src/Page/page.njk @@ -34,7 +34,9 @@ <script> const baseUrl = '{{ baseUrl }}' </script> -<body {% if hasPageNavHeadings %} data-bs-spy="scroll" data-bs-target="#mb-page-nav" data-bs-offset="100" {% endif %} data-code-theme="{{ codeTheme }}"> +<body + {% if hasPageNavHeadings %} data-bs-spy="scroll" data-bs-target="#mb-page-nav" data-bs-offset="100" {% endif %} + data-code-theme="{{ codeTheme }}"> {{ content }} </body> {{- pageUserScriptsAndStyles -}} diff --git a/packages/core/src/Site/SiteConfig.ts b/packages/core/src/Site/SiteConfig.ts index 0190ed7a86..5e68f62e8a 100644 --- a/packages/core/src/Site/SiteConfig.ts +++ b/packages/core/src/Site/SiteConfig.ts @@ -66,7 +66,6 @@ export class SiteConfig { pagefind?: { exclude_selectors?: string[]; - glob?: string | string[]; }; /** diff --git a/packages/core/src/Site/SiteGenerationManager.ts b/packages/core/src/Site/SiteGenerationManager.ts index fcc40c6737..a5fce2f302 100644 --- a/packages/core/src/Site/SiteGenerationManager.ts +++ b/packages/core/src/Site/SiteGenerationManager.ts @@ -871,74 +871,6 @@ export class SiteGenerationManager { 1000, ); - /** - * Validates that a glob pattern is safe and won't traverse outside the output directory. - * - * @param pattern - The glob pattern to validate - * @returns true if the pattern is safe, false otherwise - */ - // eslint-disable-next-line class-methods-use-this - private isValidGlobPattern(pattern: string): boolean { - if (pattern.includes('..')) { - return false; - } - - const isUnixAbsolutePath = pattern.startsWith('/'); - const isWindowsAbsolutePath = /^[a-zA-Z]:[\\/]/.test(pattern); - if (isUnixAbsolutePath || isWindowsAbsolutePath) { - return false; - } - - return true; - } - - /** - * Normalizes a gitignore-style glob pattern to a valid Wax/Pagefind pattern - * by appending .html extension if not already present. - * Invalid patterns (e.g., path traversal attempts) are logged and return empty string. - * - * @param pattern - The glob pattern from user config (gitignore-style) - * @returns A valid Wax/Pagefind glob pattern, or empty string if invalid - */ - private normalizeGlobPattern(pattern: string): string { - const normalizedPattern = pattern.replace(/\\/g, '/'); - - if (!this.isValidGlobPattern(pattern)) { - logger.error(`Invalid glob pattern rejected (potential path traversal): ${pattern}`); - return ''; - } - - if (normalizedPattern.endsWith('.html')) { - return normalizedPattern; - } - - if (normalizedPattern.endsWith('/**')) { - return `${normalizedPattern}/*.html`; - } - - if (normalizedPattern.endsWith('/*')) { - return `${normalizedPattern}.html`; - } - - if (normalizedPattern.endsWith('/')) { - return `${normalizedPattern}**/*.html`; - } - - return `${normalizedPattern}/**/*.html`; - } - - /** - * Indexes all HTML files in the output directory and logs any errors. - * @param index - The pagefind index instance - * @returns The number of pages indexed - */ - // eslint-disable-next-line class-methods-use-this - private async indexAllHtmlFiles(index: any): Promise<number> { - const result = await index.addDirectory({ path: this.outputPath }); - result.errors.forEach((error: string) => logger.error(error)); - return result.page_count; - } - /** * Indexes all the pages of the site using pagefind. * @returns true if indexing succeeded and pagefind assets were written, false otherwise. @@ -963,40 +895,43 @@ export class SiteGenerationManager { const { index } = await createIndex(createIndexOptions); if (index) { - // Handle glob patterns - support both single string and array of strings - const globValue = pagefindConfig.glob; - const value = globValue ?? []; - const globPatterns = Array.isArray(value) ? value : [value]; + // Filter pages that should be indexed (searchable !== false) + const searchablePages = this.sitePages.pages.filter( + page => page.pageConfig.searchable, + ); let totalPageCount = 0; - if (globPatterns.length > 0) { - const normalizedPatterns = globPatterns - .map(pattern => this.normalizeGlobPattern(pattern)) - .filter(pattern => pattern !== ''); - - if (normalizedPatterns.length > 0) { - const results = await Promise.all( - normalizedPatterns.map(async (normalizedPattern) => { - logger.info(`Pagefind indexing with glob: ${normalizedPattern}`); - const result = await index.addDirectory({ - path: this.outputPath, - glob: normalizedPattern, - }); - - result.errors.forEach((error: string) => logger.error(error)); - - return result.page_count; - }), - ); - - totalPageCount += results.reduce((acc, count) => acc + count, 0); - } else { - logger.warn('All glob patterns were invalid, falling back to indexing all HTML files'); - totalPageCount = await this.indexAllHtmlFiles(index); - } + if (searchablePages.length === 0) { + logger.info('No pages configured for search indexing'); } else { - totalPageCount = await this.indexAllHtmlFiles(index); + // Add each searchable page to the index using addHTMLFile + const indexingResults = await Promise.all( + searchablePages.map(async (page) => { + try { + const content = await fs.readFile(page.pageConfig.resultPath, 'utf8'); + const relativePath = path.relative(this.outputPath, page.pageConfig.resultPath); + + return index.addHTMLFile({ + sourcePath: relativePath, + content, + }); + } catch (error) { + logger.error(`Failed to index ${page.pageConfig.resultPath}: ${error}`); + return null; + } + }), + ); + + // Count successful indexings + totalPageCount = indexingResults.filter(r => r !== null).length; + + // Log any errors from indexing results + indexingResults.forEach((result) => { + if (result && result.errors) { + result.errors.forEach((error: string) => logger.error(error)); + } + }); } const endTime = new Date(); diff --git a/packages/core/test/unit/Site/SiteGenerationManager.test.ts b/packages/core/test/unit/Site/SiteGenerationManager.test.ts index 4e9cdb0559..02745f543b 100644 --- a/packages/core/test/unit/Site/SiteGenerationManager.test.ts +++ b/packages/core/test/unit/Site/SiteGenerationManager.test.ts @@ -32,7 +32,11 @@ jest.mock('../../../src/Site/SiteAssetsManager', () => ({ jest.mock('../../../src/Site/SitePagesManager', () => ({ SitePagesManager: jest.fn().mockImplementation(function (this: any) { this.baseUrlMap = new Set(); - this.collectAddressablePages = jest.fn(); + this.addressablePages = []; + this.pages = []; + this.collectAddressablePages = jest.fn().mockImplementation(() => { + // Do nothing - preserve the manually set addressablePages for testing + }); this.setBaseUrlMap = jest.fn().mockImplementation((map) => { this.baseUrlMap = map; }); @@ -80,45 +84,6 @@ describe('SiteGenerationManager', () => { generationManager.configure(siteAssets, sitePages); }); - describe('normalizeGlobPattern', () => { - const prototypeMethod = (SiteGenerationManager.prototype as any).normalizeGlobPattern; - - test('should return pattern as-is if it ends with .html', () => { - const result = prototypeMethod.call(generationManager, 'page.html'); - expect(result).toBe('page.html'); - }); - - test('should append /*.html if pattern ends with /**', () => { - const result = prototypeMethod.call(generationManager, 'dir/**'); - expect(result).toBe('dir/**/*.html'); - }); - - test('should append .html if pattern ends with /*', () => { - const result = prototypeMethod.call(generationManager, 'dir/*'); - expect(result).toBe('dir/*.html'); - }); - - test('should append **/*.html if pattern ends with /', () => { - const result = prototypeMethod.call(generationManager, 'dir/'); - expect(result).toBe('dir/**/*.html'); - }); - - test('should append /**/*.html for plain directory names', () => { - const result = prototypeMethod.call(generationManager, 'dir'); - expect(result).toBe('dir/**/*.html'); - }); - - test('should return empty string for invalid path traversal patterns', () => { - const result = prototypeMethod.call(generationManager, '../../../etc/**'); - expect(result).toBe(''); - }); - - test('should return empty string for absolute paths', () => { - const result = prototypeMethod.call(generationManager, '/etc/passwd'); - expect(result).toBe(''); - }); - }); - describe('indexSiteWithPagefind', () => { beforeEach(() => { const json = { @@ -154,42 +119,6 @@ describe('SiteGenerationManager', () => { }); }); - test('should read glob configuration as string', async () => { - const customSiteJson = createSiteJsonWithPagefind({ - pagefind: { - glob: '**/docs/*.html', - }, - }); - mockFs.vol.fromJSON({ - ...PAGE_NJK, - 'site.json': customSiteJson, - '_site/index.html': '<html><body>Test</body></html>', - }, rootPath); - - await generationManager.readSiteConfig(); - expect(generationManager.siteConfig.pagefind).toEqual({ - glob: '**/docs/*.html', - }); - }); - - test('should read glob configuration as array', async () => { - const customSiteJson = createSiteJsonWithPagefind({ - pagefind: { - glob: ['**/docs/*.html', '**/guide/*.html'], - }, - }); - mockFs.vol.fromJSON({ - ...PAGE_NJK, - 'site.json': customSiteJson, - '_site/index.html': '<html><body>Test</body></html>', - }, rootPath); - - await generationManager.readSiteConfig(); - expect(generationManager.siteConfig.pagefind).toEqual({ - glob: ['**/docs/*.html', '**/guide/*.html'], - }); - }); - test('should index site with default configuration', async () => { const json = { ...PAGE_NJK, @@ -245,70 +174,66 @@ describe('SiteGenerationManager', () => { pagefindSpy.mockRestore(); }); - test('should handle glob pattern as string', async () => { - const customSiteJson = createSiteJsonWithPagefind({ - pagefind: { - glob: '**/docs/*.html', - }, - }); - mockFs.vol.fromJSON({ + test('should index searchable pages using addHTMLFile', async () => { + const json = { ...PAGE_NJK, - 'site.json': customSiteJson, + 'site.json': SITE_JSON_DEFAULT, '_site/index.html': '<html><body>Test</body></html>', - }, rootPath); + }; + mockFs.vol.fromJSON(json, rootPath); - const mockIndex = createMockIndex({ page_count: 3, errors: [] }); + const mockIndex = createMockIndex({ page_count: 1, errors: [] }, { errors: [] }); const mockPagefindInstance = createMockPagefind(mockIndex, true); const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( mockPagefindInstance.createIndex({}) as any, ); - await generationManager.readSiteConfig(); + generationManager.siteConfig = { enableSearch: true, pages: [] } as any; + const pageConfig = { resultPath: path.join(outputPath, 'index.html'), searchable: true }; + generationManager.sitePages.pages = [{ pageConfig }] as any; + await generationManager.indexSiteWithPagefind(); - expect(mockIndex.addDirectory).toHaveBeenCalledWith({ - path: outputPath, - glob: '**/docs/*.html', + expect(mockIndex.addHTMLFile).toHaveBeenCalledWith({ + sourcePath: 'index.html', + content: '<html><body>Test</body></html>', }); pagefindSpy.mockRestore(); }); - test('should handle glob pattern as array', async () => { - const customSiteJson = createSiteJsonWithPagefind({ - pagefind: { - glob: ['**/docs/*.html', '**/guide/*.html'], - }, - }); - mockFs.vol.fromJSON({ + test('should log errors from addHTMLFile', async () => { + const json = { ...PAGE_NJK, - 'site.json': customSiteJson, + 'site.json': SITE_JSON_DEFAULT, '_site/index.html': '<html><body>Test</body></html>', - }, rootPath); + }; + mockFs.vol.fromJSON(json, rootPath); - const mockIndex = createMockIndex({ page_count: 2, errors: [] }); + const mockIndex = createMockIndex( + { page_count: 0, errors: [] }, + { errors: ['Error 1', 'Error 2'] }, + ); const mockPagefindInstance = createMockPagefind(mockIndex, true); const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( mockPagefindInstance.createIndex({}) as any, ); + const errorSpy = jest.spyOn(logger, 'error').mockImplementation(); + + generationManager.siteConfig = { enableSearch: true, pages: [] } as any; + const pageConfig2 = { resultPath: path.join(outputPath, 'index.html'), searchable: true }; + generationManager.sitePages.pages = [{ pageConfig: pageConfig2 }] as any; - await generationManager.readSiteConfig(); await generationManager.indexSiteWithPagefind(); - expect(mockIndex.addDirectory).toHaveBeenCalledTimes(2); - expect(mockIndex.addDirectory).toHaveBeenNthCalledWith(1, { - path: outputPath, - glob: '**/docs/*.html', - }); - expect(mockIndex.addDirectory).toHaveBeenNthCalledWith(2, { - path: outputPath, - glob: '**/guide/*.html', - }); + expect(errorSpy).toHaveBeenCalledWith('Error 1'); + expect(errorSpy).toHaveBeenCalledWith('Error 2'); pagefindSpy.mockRestore(); + errorSpy.mockRestore(); }); - test('should index all HTML files when no glob specified', async () => { + test('should skip indexing when pagefind import fails', async () => { const json = { ...PAGE_NJK, 'site.json': SITE_JSON_DEFAULT, @@ -316,23 +241,21 @@ describe('SiteGenerationManager', () => { }; mockFs.vol.fromJSON(json, rootPath); - const mockIndex = createMockIndex({ page_count: 10, errors: [] }); - const mockPagefindInstance = createMockPagefind(mockIndex, true); - const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( - mockPagefindInstance.createIndex({}) as any, + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockRejectedValue( + new Error('Module not found'), ); + const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(); await generationManager.readSiteConfig(); await generationManager.indexSiteWithPagefind(); - expect(mockIndex.addDirectory).toHaveBeenCalledWith({ - path: outputPath, - }); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Pagefind indexing skipped')); pagefindSpy.mockRestore(); + warnSpy.mockRestore(); }); - test('should log errors from addDirectory', async () => { + test('should handle when createIndex returns null index', async () => { const json = { ...PAGE_NJK, 'site.json': SITE_JSON_DEFAULT, @@ -340,8 +263,7 @@ describe('SiteGenerationManager', () => { }; mockFs.vol.fromJSON(json, rootPath); - const mockIndex = createMockIndex({ page_count: 1, errors: ['Error 1', 'Error 2'] }); - const mockPagefindInstance = createMockPagefind(mockIndex, true); + const mockPagefindInstance = createMockPagefindNullIndex(); const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( mockPagefindInstance.createIndex({}) as any, ); @@ -350,56 +272,98 @@ describe('SiteGenerationManager', () => { await generationManager.readSiteConfig(); await generationManager.indexSiteWithPagefind(); - expect(errorSpy).toHaveBeenCalledWith('Error 1'); - expect(errorSpy).toHaveBeenCalledWith('Error 2'); + expect(errorSpy).toHaveBeenCalledWith('Pagefind failed to create index'); pagefindSpy.mockRestore(); errorSpy.mockRestore(); }); - test('should skip indexing when pagefind import fails', async () => { + test('should calculate searchable page count from addressablePages', async () => { const json = { ...PAGE_NJK, 'site.json': SITE_JSON_DEFAULT, - '_site/index.html': '<html><body>Test</body></html>', + '_site/index.html': '<html><body>Test page</body></html>', + '_site/page1.html': '<html><body>Page 1</body></html>', + '_site/page2.html': '<html><body>Page 2</body></html>', }; mockFs.vol.fromJSON(json, rootPath); - const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockRejectedValue( - new Error('Module not found'), + const mockIndex = createMockIndex({ page_count: 3, errors: [] }, { errors: [] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, ); - const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(); + const infoSpy = jest.spyOn(logger, 'info').mockImplementation(); + + generationManager.siteConfig = { enableSearch: true, pages: [] } as any; + generationManager.sitePages.pages = [ + { pageConfig: { resultPath: path.join(outputPath, 'index.html'), searchable: true } }, + { pageConfig: { resultPath: path.join(outputPath, 'page1.html'), searchable: true } }, + { pageConfig: { resultPath: path.join(outputPath, 'page2.html'), searchable: false } }, + ] as any; - await generationManager.readSiteConfig(); await generationManager.indexSiteWithPagefind(); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Pagefind indexing skipped')); + expect(infoSpy).toHaveBeenCalledWith(expect.stringMatching(/Pagefind indexed 2 pages in/)); pagefindSpy.mockRestore(); - warnSpy.mockRestore(); + infoSpy.mockRestore(); }); - test('should handle when createIndex returns null index', async () => { + test('should handle searchable as string "no"', async () => { const json = { ...PAGE_NJK, 'site.json': SITE_JSON_DEFAULT, - '_site/index.html': '<html><body>Test</body></html>', + '_site/index.html': '<html><body>Test page</body></html>', + '_site/page1.html': '<html><body>Page 1</body></html>', }; mockFs.vol.fromJSON(json, rootPath); - const mockPagefindInstance = createMockPagefindNullIndex(); + const mockIndex = createMockIndex({ page_count: 1, errors: [] }, { errors: [] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( mockPagefindInstance.createIndex({}) as any, ); - const errorSpy = jest.spyOn(logger, 'error').mockImplementation(); + const infoSpy = jest.spyOn(logger, 'info').mockImplementation(); + + generationManager.siteConfig = { enableSearch: true, pages: [] } as any; + generationManager.sitePages.pages = [ + { pageConfig: { resultPath: path.join(outputPath, 'index.html'), searchable: false } }, + { pageConfig: { resultPath: path.join(outputPath, 'page1.html'), searchable: true } }, + ] as any; - await generationManager.readSiteConfig(); await generationManager.indexSiteWithPagefind(); - expect(errorSpy).toHaveBeenCalledWith('Pagefind failed to create index'); + expect(infoSpy).toHaveBeenCalledWith(expect.stringMatching(/Pagefind indexed 1 pages in/)); pagefindSpy.mockRestore(); - errorSpy.mockRestore(); + infoSpy.mockRestore(); + }); + + test('should handle all pages non-searchable', async () => { + const json = { + ...PAGE_NJK, + 'site.json': SITE_JSON_DEFAULT, + '_site/index.html': '<html><body>Test page</body></html>', + }; + mockFs.vol.fromJSON(json, rootPath); + + const mockIndex = createMockIndex({ page_count: 1, errors: [] }); + const mockPagefindInstance = createMockPagefind(mockIndex, true); + const pagefindSpy = jest.spyOn(pagefind, 'createIndex').mockResolvedValue( + mockPagefindInstance.createIndex({}) as any, + ); + const infoSpy = jest.spyOn(logger, 'info').mockImplementation(); + + generationManager.siteConfig = { enableSearch: true } as any; + generationManager.sitePages.addressablePages = [{ src: 'index.md', searchable: false }]; + + await generationManager.indexSiteWithPagefind(); + + expect(infoSpy).toHaveBeenCalledWith(expect.stringMatching(/Pagefind indexed 0 pages in/)); + + pagefindSpy.mockRestore(); + infoSpy.mockRestore(); }); }); diff --git a/packages/core/test/unit/utils/data.ts b/packages/core/test/unit/utils/data.ts index 7d223f4942..65762818b6 100644 --- a/packages/core/test/unit/utils/data.ts +++ b/packages/core/test/unit/utils/data.ts @@ -102,8 +102,14 @@ export interface MockAddDirectoryResult { errors?: string[]; } +export interface MockAddHTMLFileResult { + file?: { path: string }; + errors?: string[]; +} + export interface MockIndex { addDirectory: ReturnType<JestFn>; + addHTMLFile: ReturnType<JestFn>; writeFiles: ReturnType<JestFn>; } @@ -115,12 +121,15 @@ export interface MockPagefind { /** * Creates a mock pagefind index * @param result - The result to return from addDirectory + * @param htmlFileResult - The result to return from addHTMLFile */ export function createMockIndex( result: MockAddDirectoryResult = { page_count: 1, errors: [] }, + htmlFileResult: MockAddHTMLFileResult = { errors: [] }, ): MockIndex { return { addDirectory: jest.fn().mockResolvedValue(result), + addHTMLFile: jest.fn().mockResolvedValue(htmlFileResult), writeFiles: jest.fn().mockResolvedValue(undefined), }; }