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;
},