diff --git a/.changeset/khaki-ravens-fail.md b/.changeset/khaki-ravens-fail.md new file mode 100644 index 00000000..fc617139 --- /dev/null +++ b/.changeset/khaki-ravens-fail.md @@ -0,0 +1,5 @@ +--- +'@inox-tools/astro-tests': minor +--- + +Allow file edits and resets to create and delete files in the source code diff --git a/docs/src/content/docs/astro-tests.mdx b/docs/src/content/docs/astro-tests.mdx index 7525a769..01bbfa17 100644 --- a/docs/src/content/docs/astro-tests.mdx +++ b/docs/src/content/docs/astro-tests.mdx @@ -208,22 +208,32 @@ Checks whether the given path exists on the build output (`outDir` config). ### `readFile`

- **Type:** `(path: string) => Promise` + **Type:** `(path: string) => Promise`

-Read a file from the build (relative to `outDir` config). +Read a file from the build (relative to the `outDir` config). Returns null if the file doesn't exist. + +### `readSrcFile` + +

+ **Type:** `(path: string) => Promise` +

+ +Read a file from the project (relative to the `root` config). Returns null if the file doesn't exist. ### `editFile`

- **Type:** `(path: string, updater: string | ((content: string) => string)) => Promise<() => void>` + **Type:** `(path: string, updater: string | null | ((content: string | null) => string | null)) => Promise<() => void>`

Edit a file in the fixture directory. The first parameter is a path relative to the root of the fixture. -The second parameter can be the new content of the file or a function that takes the current content and returns the new content. +The second parameter can be the new content of the file or a function that takes the current content and returns the new content. The content passed to the function will be null if the file doesn't exist. + +If the given content or content returned from the given function is null, the file will be deleted. This function returns a Promise that resolves to another function. This resolved function can be called to revert the changes. diff --git a/packages/astro-tests/src/astroFixture.ts b/packages/astro-tests/src/astroFixture.ts index aab65fbb..7298415f 100644 --- a/packages/astro-tests/src/astroFixture.ts +++ b/packages/astro-tests/src/astroFixture.ts @@ -93,17 +93,31 @@ type Fixture = { pathExists: (path: string) => boolean; /** * Read a file from the build. + * + * Returns null if the file doesn't exist. + */ + readFile: (path: string) => Promise; + /** + * Read a file from the project. + * + * Returns null if the file doesn't exist. */ - readFile: (path: string) => Promise; + readSrcFile: (path: string) => Promise; /** * Edit a file in the fixture. * * The second parameter can be the new content of the file * or a function that takes the current content and returns the new content. * + * The content passed to the function will be null if the file doesn't exist. + * If the returned content is null, the file will be deleted. + * * Returns a function that can be called to revert the edit. */ - editFile: (path: string, updater: string | ((content: string) => string)) => Promise<() => void>; + editFile: ( + path: string, + updater: string | null | ((content: string | null) => string | null) + ) => Promise<() => void>; /** * Reset all changes made with .editFile() */ @@ -224,14 +238,17 @@ export async function loadFixture(inlineConfig: InlineConfig): Promise const onNextChange = () => devServer ? new Promise((resolve) => - // TODO: Implement filter to only resolve on changes to a given file. - devServer.watcher.once('change', () => resolve()) - ) + // TODO: Implement filter to only resolve on changes to a given file. + devServer.watcher.once('change', () => resolve()) + ) : Promise.reject(new Error('No dev server running')); // Also do it on process exit, just in case. process.on('exit', resetAllFiles); + const resolveOutPath = (path: string) => new URL(path.replace(/^\//, ''), config.outDir); + const resolveProjectPath = (path: string) => new URL(path.replace(/^\//, ''), config.root); + return { config, startDevServer: async (extraInlineConfig = {}) => { @@ -274,7 +291,7 @@ export async function loadFixture(inlineConfig: InlineConfig): Promise }); debug(`Removing node_modules/.astro`); - await fs.promises.rm(new URL('./node_modules/.astro', config.root), { + await fs.promises.rm(resolveProjectPath('./node_modules/.astro'), { maxRetries: 10, recursive: true, force: true, @@ -288,7 +305,7 @@ export async function loadFixture(inlineConfig: InlineConfig): Promise }); debug(`Removing .astro`); - await fs.promises.rm(new URL('./.astro', config.root), { + await fs.promises.rm(resolveProjectPath('./.astro'), { maxRetries: 10, recursive: true, force: true, @@ -340,16 +357,39 @@ export async function loadFixture(inlineConfig: InlineConfig): Promise } }, - pathExists: (p) => fs.existsSync(new URL(p.replace(/^\//, ''), config.outDir)), - readFile: (filePath) => - fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.outDir), 'utf8'), + pathExists: (p) => fs.existsSync(resolveOutPath(p)), + readFile: async (filePath) => { + const path = resolveOutPath(filePath); + + if (!fs.existsSync(path)) { + return null; + } + + return fs.promises.readFile(path, 'utf8'); + }, + readSrcFile: async (filePath) => { + const path = resolveProjectPath(filePath); + + if (!fs.existsSync(path)) { + return null; + } + + return fs.promises.readFile(path, 'utf8'); + }, editFile: async (filePath, newContentsOrCallback) => { - const fileUrl = new URL(filePath.replace(/^\//, ''), config.root); - const contents = await fs.promises.readFile(fileUrl, 'utf-8'); + const fileUrl = resolveProjectPath(filePath); + + const contents = fs.existsSync(fileUrl) ? await fs.promises.readFile(fileUrl, 'utf-8') : null; + const reset = () => { debug(`Resetting ${filePath}`); - fs.writeFileSync(fileUrl, contents); + if (contents) { + fs.writeFileSync(fileUrl, contents); + } else { + fs.rmSync(fileUrl, { force: true }); + } }; + // Only save this reset if not already in the map, in case multiple edits happen // to the same file. if (!fileEdits.has(fileUrl.toString())) { @@ -357,12 +397,21 @@ export async function loadFixture(inlineConfig: InlineConfig): Promise } debug(`Editing ${filePath}`); + const newContents = typeof newContentsOrCallback === 'function' ? newContentsOrCallback(contents) : newContentsOrCallback; + const nextChange = devServer ? onNextChange() : Promise.resolve(); - await fs.promises.writeFile(fileUrl, newContents); + + if (newContents) { + await fs.promises.mkdir(path.dirname(fileURLToPath(fileUrl)), { recursive: true }); + await fs.promises.writeFile(fileUrl, newContents); + } else { + await fs.promises.rm(fileUrl, { force: true }); + } + await nextChange; return reset; },