# wgsl-test

Test WGSL and WESL shaders with the CLI or vitest.

- **Native WESL** (`@test`) - Unit test shader functions, assertions run on GPU
- **CLI** (`wgsl-test run`) - Run tests from the command line
- **TypeScript-driven** - Integration tests, visual regression, custom
  validation

## Installation

```bash
npm install wgsl-test
```

## Native WESL Testing

Write tests directly in WESL with the `@test` attribute. Minimal boilerplate,
assertions run on the GPU:

```wgsl
/// interp_test.wesl
import package::interp::smootherstep; // source fn to test
import wgsl_test::expectNear; // expectations from wgsl-test lib

@test  // tag each test fn
fn smootherstepQuarter() {
  expectNear(smootherstep(0.0, 1.0, 0.25), 0.103516);
}
...
```

### CLI

Run tests directly from the command line:

```bash
wgsl-test run                      # discover **/*.test.wesl, run all
wgsl-test run path/to/test.wesl    # run specific file(s)
wgsl-test run --projectDir ./foo   # specify project root
```

### Vitest Integration

Or run with a minimal TypeScript wrapper:

```typescript
import { getGPUDevice, testWesl } from "wgsl-test";

const device = await getGPUDevice();

// runs all tests in interp_test
await testWesl({ device, moduleName: "interp_test" });
```

**[See API.md for assertion functions →](https://github.com/webgpu-tools/wesl-js/blob/main/packages/wgsl-test/API.md#assertion-functions)**

## Annotated Resources

Declare GPU resources for a test inline with `@buffer`, `@test_texture`, and
`@sampler`. wgsl-test assigns bindings and builds the bind group automatically:

```wgsl
import wgsl_test::expectNear;

@buffer var<storage, read_write> data: array<f32, 4>;

@test fn write_and_read() {
  data[0] = 42.0;
  expectNear(data[0], 42.0);
}
```

Fragment snapshots can combine textures and samplers the same way:

```wgsl
@test_texture(checkerboard, 256, 256, 16) var tex: texture_2d<f32>;
@sampler(nearest)                         var samp: sampler;

@fragment @extent(256, 256) @snapshot
fn test_checker(@builtin(position) pos: vec4f) -> @location(0) vec4f {
  let uv = pos.xy / 256.0;
  return textureSampleLevel(tex, samp, uv, 0.0);
}
```

Built-in texture sources: `checkerboard`, `gradient`, `radial_gradient`,
`color_bars`, `edge_pattern`, `noise`, `solid`, `lemur`. Sampler filter options:
`linear`, `nearest`.

## Visual Regression Testing

Test complete rendered images in vitest:

```typescript
import { expectFragmentImage } from "wgsl-test";
import { imageMatcher } from "vitest-image-snapshot";

imageMatcher(); // Setup once

test("blur shader matches snapshot", async () => {
  await expectFragmentImage(device, "effects/blur.wgsl"); 
  // Snapshot automatically compared against __image_snapshots__/effects-blur.png
});
```

Update snapshots with `vitest -u` as needed.

**[See API.md for snapshot workflow and visual regression testing →](https://github.com/webgpu-tools/wesl-js/blob/main/packages/wgsl-test/API.md#visual-regression-testing)**

## Testing Compute Shaders

For more control, use `testCompute()`. Declare each output as a `@buffer`; the
contents come back keyed by var name:

```typescript
import { testCompute, getGPUDevice } from "wgsl-test";

const device = await getGPUDevice();

const src = `
  import package::hash::lowbias32;

  @buffer var<storage, read_write> results: array<u32, 2>;

  @compute @workgroup_size(1)
  fn main() {
    results[0] = lowbias32(0u);
    results[1] = lowbias32(42u);
  }
`;

const { results } = await testCompute({ device, src });
// results = [0, 388445122]
```

**[See API.md for complete API documentation →](https://github.com/webgpu-tools/wesl-js/blob/main/packages/wgsl-test/API.md#testcompute)**

## Testing Fragment Shaders

Some functions only make sense in fragment shaders.

Use `testFragment()` to test fragment shader rendering.

```rs
/// shaders/foo.wesl
fn bar(p: vec4f) -> vec4f {
  return 2 * sqrt(p);
}
```

```typescript
const src = `
  @fragment
  fn fs_main(@builtin(position) pos:vec4f) -> @location(0) vec4f {
    return foo::bar(pos * 2);
  }
`;

const result = await testFragment({ device, src });
// result = [2.828, 1.414, 0.0, 2.0]  // vec4f color at pixel (0,0)
```

**[See API.md for derivatives, input textures, uniforms, and more →](https://github.com/webgpu-tools/wesl-js/blob/main/packages/wgsl-test/API.md#testfragment)**

## API Documentation

-
  **[API.md](https://github.com/webgpu-tools/wesl-js/blob/main/packages/wgsl-test/API.md)**
  - Complete API reference with detailed examples
-
  **[API.md#complete-test-example](https://github.com/webgpu-tools/wesl-js/blob/main/packages/wgsl-test/API.md#complete-test-example)**
  - Full vitest test setup with beforeAll/afterAll
- **[Examples](https://github.com/webgpu-tools/wesl-js/tree/main/examples)**
  - Tiny standalone examples

## Future

File an [issue](https://github.com/webgpu-tools/wesl-js/issues) or talk about
your ideas on the tooling group [discord chat](https://discord.gg/5UhkaSu4dt).