Skip to content

Commit

Permalink
Merge pull request #61 from luxass/feat/metadata-helpers
Browse files Browse the repository at this point in the history
feat: add metadata helper functions
  • Loading branch information
luxass authored Dec 27, 2024
2 parents 722e9b0 + 0a7865c commit 246d278
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 158 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.0.0

- name: seyup node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fetch-depth: 0

- name: setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v4.0.0

- name: setup node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
Expand Down
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ npm install vitest-testdirs --save-dev
## 🚀 Usage

```js
import { readFileSync } from "node:fs";
// index.test.ts
import { readFileSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { describe, expect, vi } from "vitest";
import { describe, expect, it } from "vitest";
import { testdir, testdirSync } from "vitest-testdirs";

describe("testdir", () => {
Expand Down Expand Up @@ -52,6 +52,52 @@ describe("testdirSync", () => {
});
```

## Metadata on Windows

When you are using `withMetadata` on a Windows filesystem, the permission is not set correctly on directories. This is not something that can be fixed, in this library as the issue is either coming from node or libuv.

It mostly happens on Directories. You can try out the following example to see the issue.

```ts
import { readFile, writeFile } from "node:fs/promises";
import { expect, it } from "vitest";
import { testdir, withMetadata } from "../src";

it("windows", async () => {
const path = await testdir({
"file.txt": withMetadata("Hello, World!", { mode: 0o444 }), // This works
"nested": withMetadata({
"file.txt": "Hello, World!",
}, { mode: 0o555 }), // This doesn't work.
});

try {
await writeFile(`${path}/file.txt`, "Hello, Vitest!");
// This should throw an error
} catch (err) {
console.log(err);
}

const content = await readFile(`${path}/file.txt`, "utf8");

expect(content).not.toBe("Hello, Vitest!");
expect(content).toBe("Hello, World!");

try {
await writeFile(`${path}/nested/file.txt`, "Hello, Vitest!");
// This should throw an error, but not on Windows
} catch (err) {
console.log(err);
}

const nestedContent = await readFile(`${path}/nested/file.txt`, "utf8");
// The content is now changed, but it should not be possible to write to the file

expect(nestedContent).not.toBe("Hello, Vitest!");
expect(nestedContent).toBe("Hello, World!");
});
```

## 📄 License

Published under [MIT License](./LICENSE).
Expand Down
10 changes: 9 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@ export const FIXTURE_TYPE_LINK_SYMBOL = Symbol("testdir-link");
export const FIXTURE_TYPE_SYMLINK_SYMBOL = Symbol("testdir-symlink");

/**
* Symbol representing the original file path of a test fixture directory.
* Symbol representing the original file path of a test fixture definition.
* Used internally to track and restore the original paths of test directories.
*
* @const {symbol}
*/
export const FIXTURE_ORIGINAL_PATH = Symbol("testdir-original-path");

/**
* Symbol representing the metadata of a test fixture definition.
* Used internally to store and retrieve metadata about test definitions.
*
* @const {symbol}
*/
export const FIXTURE_METADATA = Symbol("testdir-metadata");
32 changes: 23 additions & 9 deletions src/file-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* ```
*/

import type { DirectoryContent, DirectoryJSON, TestdirLink, TestdirSymlink } from "./types";
import type { DirectoryContent, DirectoryJSON, TestdirLink, TestdirMetadata, TestdirSymlink } from "./types";
import {
linkSync,
mkdirSync,
Expand All @@ -28,8 +28,8 @@ import {
} from "node:fs";
import { link, mkdir, symlink, writeFile } from "node:fs/promises";
import { dirname, normalize, sep as pathSeparator, resolve } from "node:path";
import { FIXTURE_ORIGINAL_PATH } from "./constants";
import { isLink, isSymlink } from "./utils";
import { FIXTURE_METADATA, FIXTURE_ORIGINAL_PATH } from "./constants";
import { hasMetadata, isLink, isSymlink } from "./utils";

/**
* Creates a file tree at the specified path using the provided files object.
Expand All @@ -44,6 +44,9 @@ export async function createFileTree(
): Promise<void> {
for (let filename in files) {
let data = files[filename];
const metadata = hasMetadata(data) ? data[FIXTURE_METADATA] : undefined;
data = hasMetadata(data) ? data.content : data;

filename = resolve(path, filename);

// check if file is a object with the link symbol
Expand Down Expand Up @@ -86,6 +89,7 @@ export async function createFileTree(

if (isPrimitive(data) || data instanceof Uint8Array) {
const dir = dirname(filename);

await mkdir(dir, {
recursive: true,
});
Expand All @@ -100,13 +104,16 @@ export async function createFileTree(
data = String(data);
}

await writeFile(filename, data);
await writeFile(filename, data, {
...metadata,
});
} else {
await mkdir(filename, {
recursive: true,
...(metadata?.mode ? { mode: metadata.mode } : {}),
});

await createFileTree(filename, data);
await createFileTree(filename, data as DirectoryJSON);
}
}
}
Expand All @@ -121,6 +128,9 @@ export async function createFileTree(
export function createFileTreeSync(path: string, files: DirectoryJSON): void {
for (let filename in files) {
let data = files[filename];
const metadata = hasMetadata(data) ? data[FIXTURE_METADATA] : undefined;
data = hasMetadata(data) ? data.content : data;

filename = resolve(path, filename);

// check if file is a object with the link symbol
Expand Down Expand Up @@ -163,6 +173,7 @@ export function createFileTreeSync(path: string, files: DirectoryJSON): void {

if (isPrimitive(data) || data instanceof Uint8Array) {
const dir = dirname(filename);

mkdirSync(dir, {
recursive: true,
});
Expand All @@ -177,13 +188,16 @@ export function createFileTreeSync(path: string, files: DirectoryJSON): void {
data = String(data);
}

writeFileSync(filename, data);
writeFileSync(filename, data, {
...metadata,
});
} else {
mkdirSync(filename, {
recursive: true,
...(metadata?.mode ? { mode: metadata.mode } : {}),
});

createFileTreeSync(filename, data);
createFileTreeSync(filename, data as DirectoryJSON);
}
}
}
Expand All @@ -193,9 +207,9 @@ export function createFileTreeSync(path: string, files: DirectoryJSON): void {
*
* @internal
* @param {unknown} data - The data to be checked.
* @returns {data is Exclude<DirectoryContent, TestdirSymlink | TestdirLink | DirectoryJSON>} `true` if the data is a primitive value, `false` otherwise.
* @returns {data is Exclude<DirectoryContent, TestdirSymlink | TestdirLink | DirectoryJSON | TestdirMetadata>} `true` if the data is a primitive value, `false` otherwise.
*/
function isPrimitive(data: unknown): data is Exclude<DirectoryContent, TestdirSymlink | TestdirLink | DirectoryJSON> {
function isPrimitive(data: unknown): data is Exclude<DirectoryContent, TestdirSymlink | TestdirLink | DirectoryJSON | TestdirMetadata> {
return (
typeof data === "string"
|| typeof data === "number"
Expand Down
21 changes: 18 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,44 @@
* ```
*/

export { FIXTURE_ORIGINAL_PATH, FIXTURE_TYPE_LINK_SYMBOL, FIXTURE_TYPE_SYMLINK_SYMBOL } from "./constants";
export {
FIXTURE_METADATA,
FIXTURE_ORIGINAL_PATH,
FIXTURE_TYPE_LINK_SYMBOL,
FIXTURE_TYPE_SYMLINK_SYMBOL,
} from "./constants";
export type { FromFileSystemOptions } from "./file-system";
export { fromFileSystem, fromFileSystemSync } from "./file-system";
export {
fromFileSystem,
fromFileSystemSync,
} from "./file-system";

export { createFileTree, createFileTreeSync } from "./file-tree";
export {
createFileTree,
createFileTreeSync,
} from "./file-tree";

export type {
DirectoryContent,
DirectoryJSON,
FSMetadata,
TestdirLink,
TestdirMetadata,
TestdirSymlink,
} from "./types";

export {
BASE_DIR,
DIR_REGEX,
getDirNameFromTask,
hasMetadata,
isLink,
isSymlink,
link,
symlink,
testdir,
testdirSync,
withMetadata,
} from "./utils";

export type { TestdirOptions } from "./utils";
67 changes: 45 additions & 22 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,6 @@ import type {

/**
* Represents the possible content types that can be stored in a test directory.
*
* @typedef DirectoryContent
*
* Can be one of:
* - string
* - boolean
* - number
* - Uint8Array
* - null
* - undefined
* - bigint
* - symbol
* - TestdirSymlink
* - TestdirLink
*/
export type DirectoryContent =
| string
Expand All @@ -35,28 +21,36 @@ export type DirectoryContent =
| bigint
| symbol
| TestdirSymlink
| TestdirLink;
| TestdirLink
| TestdirMetadata;

/**
* Represents a symbolic link in a testing directory.
*
* @interface TestdirSymlink
* @property {symbol} [key] - Symbol property indicating that this is a symlink type
* @property {string} path - The path that the symlink points to
*/
export interface TestdirSymlink {
/**
* Internally used to mark and distinguish symlink fixtures from other fixture types.
*/
[key: symbol]: typeof FIXTURE_TYPE_SYMLINK_SYMBOL;

/**
* The path to the symlink target.
*/
path: string;
}

/**
* Represents a symbolic link in the test directory.
* @interface TestdirLink
* @property {symbol} [key] - Symbol used to identify the fixture type link.
* @property {string} path - The path of the symbolic link.
*/
export interface TestdirLink {
/**
* Internally used to mark and distinguish link fixtures from other fixture types.
*/
[key: symbol]: typeof FIXTURE_TYPE_LINK_SYMBOL;

/**
* The path to the link target.
*/
path: string;
}

Expand All @@ -68,5 +62,34 @@ export interface DirectoryJSON<T extends DirectoryContent = DirectoryContent> {
* Is only set when generated by `fromFileSystem` or `fromFileSystemSync`.
*/
[originalPath: symbol]: string | undefined;

/**
* The content of the directory.
*/
[key: string]: T | DirectoryJSON<T>;
}

/**
* Represents metadata for a file system entry.
*/
export interface FSMetadata {
/**
* The POSIX permission of the file system entry.
*/
mode: number;
}

/**
* Represents metadata for a test directory entry.
*/
export interface TestdirMetadata {
/**
* Internally used to store and retrieve metadata about test fixture definitions.
*/
[key: symbol]: FSMetadata;

/**
* The content of the definition.
*/
content: DirectoryContent | DirectoryJSON;
}
Loading

0 comments on commit 246d278

Please sign in to comment.