# fs

Temporary directories that delete themselves, and fixture trees written into them from data the spec declares.

## Usage

[`useTempDir`](https://www.hyperfrontend.dev/docs/libraries/testing/fs/#api-useTempDir) registers the `node:test` hooks that give every test in the suite a fresh directory and delete it afterwards. The handle it returns always points at the current test's directory, so capture it once in the [`describe`](https://www.hyperfrontend.dev/docs/libraries/testing/#api-describe) body:

```typescript
import { describe, expect, it } from '@hyperfrontend/testing'
import { useTempDir } from '@hyperfrontend/testing/fs'
import { loadConfig } from './load-config'

describe('loadConfig', () => {
  const dir = useTempDir({
    tree: {
      'package.json': { name: 'app' },
      'src/index.ts': 'export const ready = true\n',
    },
  })

  it('reads a config file beside the manifest', () => {
    dir.write('hf.config.json', { strict: true })
    expect(loadConfig(dir.path)).toEqual({ strict: true })
  })
})
```

Pass `scope: 'all'` to share one directory across the suite instead. For a directory a single test owns, [`createTempDir`](https://www.hyperfrontend.dev/docs/libraries/testing/fs/#api-createTempDir) creates it straight away and leaves removal to the caller: a matching [`after`](https://nodejs.org/api/test.html#afterfn-options) hook at module or suite scope, or a `finally` inside the one test that owns it. [`writeTree`](https://www.hyperfrontend.dev/docs/libraries/testing/fs/#api-writeTree) and [`writeFile`](https://www.hyperfrontend.dev/docs/libraries/testing/fs/#api-writeFile) write into any directory you already have.

## File content

A [`FileTree`](https://www.hyperfrontend.dev/docs/libraries/testing/fs/#api-FileTree) maps relative paths to content. Parent directories are created as needed.

| Value           | Written as                                |
| --------------- | ----------------------------------------- |
| A string        | The text, unchanged                       |
| A `Uint8Array`  | The bytes, unchanged                      |
| Any other value | Pretty-printed JSON followed by a newline |

## Design

Fixtures held as data in the spec, rather than checked in as files, never look like a real manifest or lockfile to a dependency scanner, and the spec states every file it depends on.

The directory's path is reported with symbolic links resolved. On macOS the system temporary directory is a symbolic link, and on Windows it can be an 8.3 short name, so an unresolved path would differ from what `process.cwd()` or a resolved real path report inside the code under test. Writes refuse a path that is absolute or climbs out of the directory, since anything written there would survive the cleanup. Removal retries while Windows still holds a handle open inside the directory.

## API Reference

### Functions

#### createTempDir

```typescript
function createTempDir(options: TempDirOptions): TempDir
```

Creates a temporary directory now, for the caller to remove.

Use it at module or suite scope with a matching `after`, or inside a single test with a
`finally` that removes it. For a directory every test in a suite gets, `useTempDir`
registers the hooks itself.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| `options` | `TempDirOptions` | Name prefix, parent and initial files. (default: `{}`) |

**Returns** `TempDir`: The directory and its file operations.

**Example: A directory one test owns**

```ts
it('reads the manifest', () => {
  const dir = createTempDir({ tree: { 'package.json': { name: 'app' } } })
  try {
    expect(readManifest(dir.path).name).toBe('app')
  } finally {
    dir.remove()
  }
})
```

#### useTempDir

```typescript
function useTempDir(options: UseTempDirOptions): TempDir
```

Gives every test in the enclosing suite a temporary directory, created before and deleted
after, by registering the `node:test` hooks that do it.

Call it in a `describe` body or at the top of a spec file. The returned handle reads the
directory current at the time of each call, so it can be captured once and used from any
test or hook. Reading it outside a test throws, since no directory exists then.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| `options` | `UseTempDirOptions` | Name prefix, parent, initial files, and whether tests share one directory. (default: `{}`) |

**Returns** `TempDir`: A handle to the current directory and its file operations.

**Example: A fresh directory for every test**

```ts
describe('loadConfig', () => {
  const dir = useTempDir({ tree: { 'package.json': {} } })

  it('finds a config file beside the manifest', () => {
    dir.write('hf.config.json', { strict: true })
    expect(loadConfig(dir.path)).toEqual({ strict: true })
  })
})
```

#### writeFile

```typescript
function writeFile(root: string, relativePath: string, content: FileContent): string
```

Writes one file under a root, creating its parent directories.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| `root` | `string` | The directory to write under. |
| `relativePath` | `string` | Where the file goes, relative to the root. |
| `content` | `FileContent` | What the file holds. |

**Returns** `string`: The absolute path of the written file.

**Example: Writing a manifest as JSON**

```ts
writeFile(root, 'packages/app/package.json', { name: 'app' })
// => '<root>/packages/app/package.json', holding '{\n  "name": "app"\n}\n'
```

#### writeTree

```typescript
function writeTree(root: string, tree: FileTree): void
```

Writes a tree of files under a root, creating every directory the paths name.

Fixtures held as data in a spec, rather than checked in as files, never look like a real
manifest or lockfile to a dependency scanner, and a spec states exactly the files it
depends on. Every path must stay inside the root.

**Parameters**

| Name | Type | Description |
| --- | --- | --- |
| `root` | `string` | The directory to write under. It is created when missing. |
| `tree` | `FileTree` | The files to write. |

**Example: Laying out a small workspace**

```ts
writeTree(root, {
  'package.json': { name: 'workspace', workspaces: ['packages/*'] },
  'packages/app/package.json': { name: 'app' },
  'packages/app/src/index.ts': 'export const ready = true\n',
})
```

### Interfaces

#### UseTempDirOptions

When `useTempDir` creates and removes its directory.

**Properties**

| Name | Type | Description |
| --- | --- | --- |
| `prefix?` | `string` | Start of the directory's name, before the random suffix. Defaults to `hf-test-`. |
| `parent?` | `string` | Directory to create it in. Defaults to the system temporary directory. |
| `tree?` | `FileTree` | Files written into it as soon as it is created. |
| `scope?` | `"each" \| "all"` | `each` gives every test a fresh directory; `all` shares one across the suite. Defaults to `each`. |

### Types

#### FileContent

What a file in a tree holds: text as written, bytes as given, or any other value as
pretty-printed JSON with a trailing newline.

```typescript
type FileContent = string | Uint8Array | object
```

#### FileTree

Files to write, keyed by path relative to the directory they are written into. A key
may name nested directories with `/`, which are created as needed.

```typescript
type FileTree = Record<string, FileContent>
```

#### TempDir

A temporary directory and the file operations a spec runs against it, every path relative
to the directory.

```typescript
type TempDir = unknown
```

#### TempDirOptions

Where a temporary directory is created and what it starts with.

```typescript
type TempDirOptions = unknown
```

---

Canonical page: https://www.hyperfrontend.dev/docs/libraries/testing/fs/
This file: https://www.hyperfrontend.dev/docs/libraries/testing/fs.md
Documentation index: https://www.hyperfrontend.dev/llms.txt
