Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ on:
branches:
- main
pull_request:
branches:
- main

permissions:
contents: read
Expand Down Expand Up @@ -36,9 +34,21 @@ jobs:
- name: Check public feature builds
run: npm run check:features

- name: Verify packed consumers
run: npm run check:consumer

- name: Run negative compiler consumers
run: npm run test:negative

- name: Run tests
run: npm test

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Run browser tests
run: npm run test:browser

- name: Build documentation
run: npm run build:docs

Expand Down
141 changes: 110 additions & 31 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ name: Release

on:
pull_request:
branches:
- main
workflow_dispatch:
inputs:
dry_run:
description: Run npm publish without publishing the package
description: Validate and dry-run without publishing
required: true
default: true
type: boolean
Expand All @@ -16,11 +14,8 @@ permissions:
contents: read

jobs:
publish:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Checkout repository
Expand All @@ -30,7 +25,6 @@ jobs:
uses: actions/setup-node@v7
with:
node-version: "26"
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Setup npm
Expand All @@ -48,39 +42,124 @@ jobs:
- name: Check public feature builds
run: npm run check:features

- name: Verify packed consumers
run: npm run check:consumer

- name: Run negative compiler consumers
run: npm run test:negative

- name: Run tests
run: npm test

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Run browser tests
run: npm run test:browser

- name: Build documentation
run: npm run build:docs

- name: get-npm-version
id: package-version
uses: martinbeentjes/npm-get-version-action@v1.3.1
- name: Validate explicit alpha version and unpublished state
run: npm run check:release

- name: Package release artifact
id: package
run: |
mkdir -p .release
npm pack --json --pack-destination .release > .release/metadata.json
artifact="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(".release/metadata.json", "utf8"))[0].filename)')"
echo "artifact=.release/$artifact" >> "$GITHUB_OUTPUT"

- name: Verify packed consumers and tarball contents
run: npm run check:consumer -- "${{ steps.package.outputs.artifact }}"

- name: Get short commit hash
id: vars
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Dry-run publish the validated artifact
run: npm publish "${{ steps.package.outputs.artifact }}" --access public --tag alpha --dry-run

- name: Update package version to -<commit-hash>
env:
PACKAGE_VERSION: ${{ steps.package-version.outputs.current-version }}-alpha-${{ env.COMMIT_HASH }}
- name: Reject real publication from non-main branches
if: github.event_name == 'workflow_dispatch' && !inputs.dry_run && github.ref != 'refs/heads/main'
run: |
node <<'NODE'
const fs = require("node:fs");
const packageJsonPath = "package.json";
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
packageJson.version = process.env.PACKAGE_VERSION;
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
echo "Real alpha publication is permitted only from main."
exit 1

- name: Upload validated release artifact
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && !inputs.dry_run
uses: actions/upload-artifact@v4
with:
name: alpha-release-artifact
path: ${{ steps.package.outputs.artifact }}
if-no-files-found: error
retention-days: 1

publish:
needs: validate
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && !inputs.dry_run
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "26"
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Setup npm
run: npm install --global npm@11.5.1

- name: Download validated release artifact
uses: actions/download-artifact@v4
with:
name: alpha-release-artifact
path: .release

- name: Verify downloaded artifact metadata
run: |
artifact=".release/rescript-webapi-$(node -p 'require("./package.json").version').tgz"
test -f "$artifact"
node --input-type=module <<'NODE'
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
const { name, version, publishConfig } = JSON.parse(readFileSync("package.json", "utf8"));
const artifact = `.release/rescript-webapi-${version}.tgz`;
const packed = JSON.parse(execFileSync("tar", ["-xOf", artifact, "package/package.json"], { encoding: "utf8" }));
if (packed.name !== name || packed.version !== version || !/^1\.0\.0-alpha\.[1-9]\d*$/.test(version)) {
throw new Error("Downloaded artifact does not match the numbered alpha package metadata.");
}
if (publishConfig.access !== "public" || publishConfig.provenance !== true) {
throw new Error("Downloaded artifact is missing public/provenance publishing metadata.");
}
const publishLifecycleScripts = ["prepare", "prepack", "prepublishOnly", "publish", "postpublish"];
if (publishLifecycleScripts.some((script) => packed.scripts?.[script] !== undefined)) {
throw new Error("Downloaded artifact contains a publish lifecycle script.");
}
console.log(`Verified ${name}@${version} artifact before publishing with the alpha tag.`);
NODE

- name: Package npm package
run: npm pack
- name: Verify publication guard
run: npm run check:release -- --publish

- name: Dry-run npm publish
if: github.event_name == 'pull_request' || inputs.dry_run
run: npm publish ./rescript-webapi-*.tgz --access public --tag alpha --dry-run
- name: Publish alpha artifact
run: npm publish ".release/rescript-webapi-$(node -p 'require("./package.json").version').tgz" --access public --tag alpha --provenance

- name: Publish npm package
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && !inputs.dry_run
run: npm publish ./rescript-webapi-*.tgz --access public --tag alpha
- name: Verify alpha dist-tag
run: |
node <<'NODE'
const { execFileSync } = require("node:child_process");
const { name, version } = require("./package.json");
const tags = JSON.parse(execFileSync("npm", ["view", name, "dist-tags", "--json"], { encoding: "utf8" }));
if (tags.alpha !== version) {
throw new Error(`Expected alpha dist-tag ${version}, received ${tags.alpha ?? "missing"}.`);
}
if (tags.latest === version) {
throw new Error(`Alpha publication unexpectedly advanced latest to ${version}.`);
}
console.log(`Verified ${name}@${version} is tagged alpha and not latest.`);
NODE
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
.bsb.lock
.astro
dist/
.release/
tmp/
docs/public/llm.txt
.playwright/
/test-results/
/playwright-report/
.npm-cache/
*.tgz
*~
Expand Down
50 changes: 38 additions & 12 deletions docs/content/docs/contributing/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@ description: Learn more about testing the bindings for @rescript/webapi.
slug: "06-testing"
---

import { Aside, FileTree } from "@astrojs/starlight/components";
import { FileTree } from "@astrojs/starlight/components";

Adding some unit tests is a great way to ensure that the bindings work as expected.
It illustrates how the bindings are supposed to be used and can help to catch regressions.

## Testing the bindings

Create a new help in the `test` folder with the same name as the module you want to test, followed by `_test.res`.
Add a ReScript fixture under `tests/`, following the source module's directory structure. Register
each fixture in exactly one category in `tests/fixtures.json`: `compileOnly`,
`documentationExamples`, `nodeRuntime`, or `browserRuntime`. `npm test` fails if a fixture is missing
from the manifest or appears in multiple categories, compiles all fixtures, and executes only the
Node runtime modules. Compile-only fixtures and documentation examples are never executed.

<FileTree>

- src
- DOMAPI.res
- DOMAPI
- HTMLCanvasElement.res
- fetch
- Headers.res
- tests
- DOMAPI
- HTMLCanvasElement_test.res
- FetchAPI
- Headers\_\_test.res

</FileTree>

Expand All @@ -33,12 +36,35 @@ git add tests
npm test
```

If any of the existing tests have different ouput JavaScript, you will see a diff in the output and the test will fail.
Run the focused checks that match your fixture:

<Aside>
Depending on your use-case, it might be okay to have slightly different output JavaScript. The
maintainers will help you to decide if the change is acceptable.
</Aside>
```shell
npm test # compile fixtures and execute Node-compatible runtime tests
npm run test:browser # execute native-browser regressions in Chromium
npm run test:negative # verify expected compiler failures and diagnostics
```

Use the browser suite for APIs that require a real browser. Node's built-in Fetch support is useful
for Node runtime coverage, but is not a substitute for Chromium validation; DOM emulators are not
browser evidence either. Keep examples that need special setup, such as a GET request with a body,
compile-only unless they are deliberately configured as a runtime fixture.
Browser fixtures with asynchronous behavior must await all assertions and callback completion before
the ReScript module finishes evaluating; the browser runner awaits module evaluation and treats
rejections and uncaught page errors as failures.

Put expected-failure consumer sources under `scripts/fixtures/negative/`, outside the recursively
compiled `tests/` tree. Register each in `scripts/negative-fixtures.json` with its feature set and a
distinctive compiler diagnostic substring. The negative command must observe both compilation failure
and that expected diagnostic; an unrelated compiler failure does not pass the check.

## Installed package consumers

Run `npm run check:consumer` to pack the current package, install that artifact with the supported
ReScript compiler in a fresh temporary project, and compile representative feature consumers.
This verifies the installed tarball rather than copied source files or local compiler links.
The development compiler and the exact version exercised by this check are ReScript
`13.0.0-alpha.5`. The peer range `>=13.0.0-alpha.5 <13.0.0` admits later `13.0.0` prereleases;
it does not claim support for earlier compilers or stable `13.0.0`.

## Why add tests?

Expand Down
6 changes: 2 additions & 4 deletions docs/llm.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,9 @@ files.sort();
const pages = await Promise.all(files.map(processFile));
const packageJson = await fs.readFile(path.join(import.meta.dirname, "../package.json"), "utf-8");
const version = JSON.parse(packageJson).version;
const sha = await execAsync("git rev-parse --short HEAD").then(({ stdout }) => stdout.trim());
const fullVersion = `${version}-alpha-${sha}`;
const header = `Experimental ReScript WebAPI Documentation ${fullVersion}
const header = `Experimental ReScript WebAPI Documentation ${version}

This is the API documentation for the experimental WebAPI module version ${fullVersion}.
This is the API documentation for the experimental WebAPI module version ${version}.
More information can be found on https://rescript-lang.github.io/experimental-rescript-webapi/

`;
Expand Down
Loading
Loading