UNPKG

@mintlify/previewing

Version:

Preview Mintlify docs locally

103 lines (102 loc) 6.12 kB
import { getFileListSync } from '@mintlify/prebuild'; import fse from 'fs-extra'; import { initializeImportCache, updateImportCacheForFile, removeFromImportCache, getImportedFilesFromCache, getFilesImportingPathFromCache, getTransitiveImportersFromCache, } from '../local-preview/listener/importCache.js'; vi.mock('fs-extra', () => ({ default: { readFile: vi.fn() }, })); vi.mock('@mintlify/prebuild', async () => { const actual = await vi.importActual('@mintlify/prebuild'); return { ...actual, getFileListSync: vi.fn().mockReturnValue([]), }; }); const mockReadFile = vi.mocked(fse.readFile); const mockGetFileListSync = vi.mocked(getFileListSync); describe('importCache', () => { beforeEach(async () => { vi.clearAllMocks(); mockGetFileListSync.mockReturnValue([]); await initializeImportCache('/base'); }); it('starts with empty cache', () => { expect(getImportedFilesFromCache()).toEqual(new Set()); }); it('tracks imports when file is updated', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import Foo from './foo.mdx';\n\n<Foo />`)); await updateImportCacheForFile('/base', 'page.mdx'); expect(getImportedFilesFromCache()).toEqual(new Set(['/foo.mdx'])); }); it('returns newlyImported when import is added', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import Bar from '/bar.mdx';\n\n<Bar />`)); const result = await updateImportCacheForFile('/base', 'page.mdx'); expect(result.newlyImported).toEqual(['/bar.mdx']); }); it('returns noLongerImported when file is removed', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import X from './x.mdx';\n\n<X />`)); await updateImportCacheForFile('/base', 'a.mdx'); const result = removeFromImportCache('a.mdx'); expect(result.noLongerImported).toEqual(['/x.mdx']); }); it('ignores non-mdx files', async () => { const result = await updateImportCacheForFile('/base', 'file.js'); expect(result).toEqual({ newlyImported: [], noLongerImported: [] }); }); it('keeps import in cache if still referenced by another file', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import S from './shared.mdx';\n\n<S />`)); await updateImportCacheForFile('/base', 'a.mdx'); await updateImportCacheForFile('/base', 'b.mdx'); const result = removeFromImportCache('a.mdx'); expect(result.noLongerImported).toEqual([]); expect(getImportedFilesFromCache()).toEqual(new Set(['/shared.mdx'])); }); it('tracks parent-directory relative imports', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import Shared from '../shared/snippet.mdx';\n\n<Shared />`)); await updateImportCacheForFile('/base', 'docs/page.mdx'); expect(getImportedFilesFromCache()).toEqual(new Set(['/shared/snippet.mdx'])); }); it('tracks explicit snippet folder imports', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import Callout from '/snippets/callout.mdx';\n\n<Callout />`)); await updateImportCacheForFile('/base', 'docs/page.mdx'); expect(getImportedFilesFromCache()).toEqual(new Set(['/snippets/callout.mdx'])); }); it('detects when imports change in a file', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import Old from './old.mdx';\n\n<Old />`)); await updateImportCacheForFile('/base', 'page.mdx'); mockReadFile.mockResolvedValue(Buffer.from(`import New from './new.mdx';\n\n<New />`)); const result = await updateImportCacheForFile('/base', 'page.mdx'); expect(result.newlyImported).toEqual(['/new.mdx']); expect(result.noLongerImported).toEqual(['/old.mdx']); }); it('returns files that directly import a path', async () => { mockReadFile.mockResolvedValueOnce(Buffer.from(`import S from './shared.mdx';\n\n<S />`)); await updateImportCacheForFile('/base', 'a.mdx'); mockReadFile.mockResolvedValueOnce(Buffer.from(`import S from './shared.mdx';\n\n<S />`)); await updateImportCacheForFile('/base', 'b.mdx'); expect(getFilesImportingPathFromCache('/shared.mdx')).toEqual(new Set(['/a.mdx', '/b.mdx'])); }); it('preserves importer path casing from file updates', async () => { mockReadFile.mockResolvedValue(Buffer.from(`import S from '../Shared.mdx';\n\n<S />`)); await updateImportCacheForFile('/base', 'Docs/MyPage.mdx'); expect(getFilesImportingPathFromCache('/Shared.mdx')).toEqual(new Set(['/Docs/MyPage.mdx'])); }); it('preserves importer path casing when initialized from a prebuild import map', async () => { mockGetFileListSync.mockReturnValue(['Docs/MyPage.mdx']); await initializeImportCache('/base', new Map([['/docs/mypage.mdx', new Set(['/shared.mdx'])]])); expect(getFilesImportingPathFromCache('/shared.mdx')).toEqual(new Set(['/Docs/MyPage.mdx'])); }); it('returns transitive importers for a path', async () => { mockReadFile.mockResolvedValueOnce(Buffer.from(`import S from './shared.mdx';\n\n<S />`)); await updateImportCacheForFile('/base', 'snippet.mdx'); mockReadFile.mockResolvedValueOnce(Buffer.from(`import Snippet from './snippet.mdx';\n\n<Snippet />`)); await updateImportCacheForFile('/base', 'page.mdx'); expect(getTransitiveImportersFromCache('/shared.mdx')).toEqual(new Set(['/snippet.mdx', '/page.mdx'])); }); it('preserves transitive importer path casing', async () => { mockReadFile.mockResolvedValueOnce(Buffer.from(`import S from '../Shared.mdx';\n\n<S />`)); await updateImportCacheForFile('/base', 'Snippets/Card.mdx'); mockReadFile.mockResolvedValueOnce(Buffer.from(`import Card from '../Snippets/Card.mdx';\n\n<Card />`)); await updateImportCacheForFile('/base', 'Docs/Page.mdx'); expect(getTransitiveImportersFromCache('/Shared.mdx')).toEqual(new Set(['/Snippets/Card.mdx', '/Docs/Page.mdx'])); }); });