From 10d79f9be7cbc763be3b43f1058735be5b8d1fb9 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 27 Jan 2026 10:58:03 +0100 Subject: [PATCH] feat: add Bun test runner support Add riteway/bun adapter that provides the familiar assert API with Bun's fast, built-in test runner. Uses expect.extend to create a custom matcher for proper error messages with given/should context. Includes: - setupRitewayBun() to register the custom matcher - assert() function with required keys validation - TypeScript definitions - Tests and README documentation Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 12 +++++ README.md | 94 +++++++++++++++++++++++++++++++++++++ bun.d.ts | 19 ++++++++ package.json | 4 ++ source/bun.js | 43 +++++++++++++++++ source/bun.test.js | 50 ++++++++++++++++++++ 6 files changed, 222 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 bun.d.ts create mode 100644 source/bun.js create mode 100644 source/bun.test.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..cabbe58e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "Bash(git checkout:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(gh pr create:*)", + "Bash(bun test:*)" + ] + } +} diff --git a/README.md b/README.md index 4ca20dbe..b6758e78 100644 --- a/README.md +++ b/README.md @@ -456,3 +456,97 @@ describe('sum()', () => { }); }); ``` + +## Bun + +[Bun](https://bun.sh/) has a fast, built-in test runner that is Jest-compatible. Riteway provides a Bun adapter so you can use the familiar `assert` API with Bun's test runner. + +### Installing + +First, make sure you have Bun installed. Then install Riteway into your project: + +```shell +bun add --dev riteway +``` + +### Setup + +Before using `assert`, you need to call `setupRitewayBun()` once to register the custom matcher. We recommend doing this in a global setup file using Bun's `preload` option. + +Create a setup file (e.g., `test/setup.ts`): + +```ts +import { setupRitewayBun } from 'riteway/bun'; + +setupRitewayBun(); +``` + +Then configure Bun to preload it. Add to your `bunfig.toml`: + +```toml +[test] +preload = ["./test/setup.ts"] +``` + +Or specify it via CLI: + +```shell +bun test --preload ./test/setup.ts +``` + +### Usage + +In your test files, import `test`, `describe`, and `assert` from `riteway/bun`: + +```ts +import { test, describe, assert } from 'riteway/bun'; +``` + +Then run your tests with `bun test`: + +```shell +bun test +``` + +### Example + +```ts +import { test, describe, assert } from 'riteway/bun'; + +// a function to test +const sum = (...args) => { + if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN'); + return args.reduce((acc, n) => acc + n, 0); +}; + +describe('sum()', () => { + test('given: no arguments, should: return 0', () => { + assert({ + given: 'no arguments', + should: 'return 0', + actual: sum(), + expected: 0 + }); + }); + + test('given: two numbers, should: return the correct sum', () => { + assert({ + given: 'two numbers', + should: 'return the correct sum', + actual: sum(2, 3), + expected: 5 + }); + }); +}); +``` + +### Failure Output + +When a test fails, the error message includes the `given` and `should` context: + +``` +error: Given two different numbers: should be equal + +Expected: 43 +Received: 42 +``` diff --git a/bun.d.ts b/bun.d.ts new file mode 100644 index 00000000..6e27f0ae --- /dev/null +++ b/bun.d.ts @@ -0,0 +1,19 @@ +declare module 'riteway/bun' { + interface Assertion { + readonly given: string; + readonly should: string; + readonly actual: T; + readonly expected: T; + } + + export function assert(assertion: Assertion): void; + + /** + * Setup function to extend Bun's expect with a custom RITEway matcher. + * Call this once in your test setup file or at the top of test files. + */ + export function setupRitewayBun(): void; + + // Re-export test and describe from bun:test + export { test, describe } from 'bun:test'; +} diff --git a/package.json b/package.json index 5fe52155..9afce9df 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "import": "./source/vitest.js", "types": "./vitest.d.ts" }, + "./bun": { + "import": "./source/bun.js", + "types": "./bun.d.ts" + }, "./match": { "import": "./source/match.js", "types": "./match.d.ts" diff --git a/source/bun.js b/source/bun.js new file mode 100644 index 00000000..2bdb805e --- /dev/null +++ b/source/bun.js @@ -0,0 +1,43 @@ +import { expect, test, describe } from 'bun:test'; + +export { test, describe }; + +const requiredKeys = ['given', 'should', 'actual', 'expected']; + +/** + * Setup function to extend Bun's expect with a custom RITEway matcher. + * Call this once in your test setup file or at the top of test files. + */ +export const setupRitewayBun = () => { + expect.extend({ + toRitewayEqual(received, expected, given, should) { + const pass = this.equals(received, expected); + + if (pass) { + return { pass: true }; + } + + return { + pass: false, + message: () => + `Given ${given}: should ${should}\n\nExpected: ${this.utils.printExpected(expected)}\nReceived: ${this.utils.printReceived(received)}`, + }; + }, + }); +}; + +/** + * Assert function compatible with Bun's expect, using the custom matcher. + * @param {Object} args - Assertion object with given, should, actual, expected. + */ +export const assert = (args = {}) => { + const missing = requiredKeys.filter((k) => !Object.keys(args).includes(k)); + if (missing.length) { + throw new Error( + `The following parameters are required by \`assert\`: ${missing.join(', ')}` + ); + } + + const { given, should, actual, expected } = args; + expect(actual).toRitewayEqual(expected, given, should); +}; diff --git a/source/bun.test.js b/source/bun.test.js new file mode 100644 index 00000000..870257f2 --- /dev/null +++ b/source/bun.test.js @@ -0,0 +1,50 @@ +import { test, describe, assert, setupRitewayBun } from './bun.js'; + +setupRitewayBun(); + +describe('riteway/bun', () => { + test('given: matching primitives, should: pass', () => { + assert({ + given: 'two identical numbers', + should: 'be equal', + actual: 42, + expected: 42, + }); + }); + + test('given: matching strings, should: pass', () => { + assert({ + given: 'two identical strings', + should: 'be equal', + actual: 'hello', + expected: 'hello', + }); + }); + + test('given: matching objects, should: pass', () => { + assert({ + given: 'two identical objects', + should: 'be deeply equal', + actual: { name: 'Bun', version: 1.1 }, + expected: { name: 'Bun', version: 1.1 }, + }); + }); + + test('given: matching arrays, should: pass', () => { + assert({ + given: 'two identical arrays', + should: 'be deeply equal', + actual: [1, 2, 3], + expected: [1, 2, 3], + }); + }); + + test('given: nested structures, should: pass', () => { + assert({ + given: 'two identical nested structures', + should: 'be deeply equal', + actual: { users: [{ name: 'Alice' }, { name: 'Bob' }] }, + expected: { users: [{ name: 'Alice' }, { name: 'Bob' }] }, + }); + }); +});