diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..fc1b79b7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm install --legacy-peer-deps + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm test + + bun-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install + + - name: Run Bun tests + run: bun test source/bun.test.js diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 96b4fabf..00000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: node_js -node_js: - - 18 -install: - - npm install --legacy-peer-deps -before_script: - - export NODE_OPTIONS=–max_old_space_size=8192 \ No newline at end of file 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/bin/riteway b/bin/riteway.js similarity index 100% rename from bin/riteway rename to bin/riteway.js diff --git a/bin/riteway.test.js b/bin/riteway.test.js index 3027fd61..e0f2b33b 100644 --- a/bin/riteway.test.js +++ b/bin/riteway.test.js @@ -1,3 +1,4 @@ +// @ts-nocheck import { describe } from '../source/riteway.js'; import { execSync } from 'child_process'; @@ -8,7 +9,7 @@ import { createIgnoreMatcher, resolveTestFiles, runTests -} from './riteway'; +} from './riteway.js'; // Test utilities const testSubprocessExit = (command, { cwd = process.cwd() } = {}) => { @@ -96,7 +97,7 @@ describe('createIgnoreMatcher()', async assert => { { // Test in subprocess since createIgnoreMatcher calls process.exit() const { exitCode, stderr } = testSubprocessExit( - 'node -e "import(\'./bin/riteway\').then(m => m.createIgnoreMatcher({ ignore: \'nonexistent.ignore\', cwd: process.cwd() }))"' + 'node -e "import(\'./bin/riteway.js\').then(m => m.createIgnoreMatcher({ ignore: \'nonexistent.ignore\', cwd: process.cwd() }))"' ); assert({ 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..cd3a181f 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" @@ -62,12 +66,12 @@ } }, "bin": { - "riteway": "bin/riteway" + "riteway": "bin/riteway.js" }, "scripts": { "lint": "eslint source && echo 'Lint complete.'", "lint-fix": "eslint --fix source && eslint --fix ./*.js", - "typecheck": "npx -p typescript tsc --esModuleInterop --rootDir . source/test.js --allowJs --checkJs --noEmit --lib es6 --jsx react && npx -p typescript tsc index.d.ts --noEmit && echo 'TypeScript check complete.'", + "typecheck": "npx -p typescript tsc --esModuleInterop --rootDir . source/test.js --allowJs --checkJs --noEmit --lib es6 --jsx react --skipLibCheck && npx -p typescript tsc index.d.ts --noEmit --skipLibCheck && echo 'TypeScript check complete.'", "ts": "npm run -s typecheck", "test": "node source/test.js && vitest run", "esm": "cp source/*.js esm/ && cp source/*.jsx esm/ && cp esm/riteway.js esm/index.js && echo 'esm complete.'", 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' }] }, + }); + }); +}); diff --git a/vitest.config.js b/vitest.config.js index d06ba34d..13b26eb8 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -9,7 +9,9 @@ export default defineConfig({ '**/.{idea,git,cache,output,temp}/**', '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*', // Exclude bin/riteway.test.js as it uses Tape instead of Vitest - '**/bin/riteway.test.js' + '**/bin/riteway.test.js', + // Exclude bun.test.js as it uses bun:test instead of Vitest + '**/bun.test.js' ] } });