Skip to content

Conversation

@ryoppippi
Copy link

What does this PR do?

add assert to bun test

How did you verify your code works?

vitest has assert function, which is useful especially if you use union types

Re-export node:assert as "assert" from bun:test for vitest
compatibility. This allows users to import { assert } from "bun:test"
and use Node.js assertion methods alongside Jest-style expect().

The assert function also works as a TypeScript type guard for type
narrowing, making it useful for refining union types in tests.
Add TypeScript type definition for the new assert export in bun:test.
The type is a re-export of node:assert, providing full type support
for all assertion methods including the type guard behaviour.
Add comprehensive tests verifying that all node:assert methods work
correctly when imported from bun:test. Tests cover:
- Basic assert() function
- Equality methods (strictEqual, deepStrictEqual, etc.)
- Error handling (throws, doesNotThrow, rejects, doesNotReject)
- String matching (match, doesNotMatch)
- Utility methods (fail, ifError, ok)
Add documentation explaining how to use the assert export from
bun:test. Covers basic usage, type narrowing with TypeScript,
available methods, and integration with expect().
@ryoppippi ryoppippi requested a review from alii as a code owner November 27, 2025 02:21
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

Walkthrough

Adds support for Node.js assert module export in Bun's test runner alongside Jest-style expect(). Includes documentation, TypeScript type definitions, native implementation to load the assert module, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Documentation
docs/guides/test/assert.mdx
New guide explaining how to use Bun's assert export, including examples of basic assertions, type narrowing, Node.js assertion methods, and mixed assertion patterns alongside expect().
Type Definitions
packages/bun-types/test.d.ts
Adds public export of assert re-exported from require("node:assert") to the bun:test module with JSDoc usage examples.
Runtime Implementation
src/bun.js/bindings/ZigGlobalObject.cpp
Loads Node's assert module from internal registry during test module initialization and exports it as "assert" property on the test module object.
Tests
test/js/bun/test/assert-export.test.ts
New test suite validating assert export presence and behavior, covering basic assertions, error handling, async assertions, equality checks, and helper methods.

Suggested reviewers

  • Jarred-Sumner
  • nektro

Pre-merge checks

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers both required sections but lacks detail. The 'What does this PR do?' section is minimal, and the 'How did you verify your code works?' lacks specific testing methodology. Expand 'What does this PR do?' with concrete details about the assert export functionality. Provide specific testing steps or test results rather than just referencing Vitest's usefulness.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(test): export assert from bun:test' clearly and specifically describes the main change: adding an assert export to the bun:test module.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 908ab9c and 2f62188.

📒 Files selected for processing (4)
  • docs/guides/test/assert.mdx (1 hunks)
  • packages/bun-types/test.d.ts (1 hunks)
  • src/bun.js/bindings/ZigGlobalObject.cpp (1 hunks)
  • test/js/bun/test/assert-export.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.{cpp,zig}

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: C++ code for JavaScriptCore bindings and Web APIs should be placed in src/bun.js/bindings/*.cpp
When implementing JavaScript classes in C++, create three classes if there's a public constructor: class Foo : public JSC::JSDestructibleObject, class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
When implementing JavaScript classes in C++, define properties using HashTableValue arrays and add iso subspaces for classes with C++ fields, caching structures in ZigGlobalObject

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
src/**/*.{ts,zig,cpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Always use absolute paths in file operations

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
test/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts,jsx,tsx}: Write tests as JavaScript and TypeScript files using Jest-style APIs (test, describe, expect) and import from bun:test
Use test.each and data-driven tests to reduce boilerplate when testing multiple similar cases

Files:

  • test/js/bun/test/assert-export.test.ts
test/js/bun/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

For Bun-specific API tests, use the test/js/bun/ directory (for http, crypto, ffi, shell, etc.)

Files:

  • test/js/bun/test/assert-export.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.test.{ts,tsx}: For single-file tests, prefer using -e flag over tempDir
For multi-file tests, prefer using tempDir from harness and Bun.spawn over other temporary directory creation methods
Always use port: 0 for network tests and do not hardcode ports or use custom random port number functions
Use normalizeBunSnapshot to normalize snapshot output of tests
Never write tests that check for no 'panic' or 'uncaught exception' or similar in the test output - that is NOT a valid test
Use tempDir from harness to create temporary directories, do not use tmpdirSync or fs.mkdtempSync
When spawning processes in tests, check stdout/stderr expectations BEFORE checking exit code to get more useful error messages on test failure
Do not write flaky tests - do not use setTimeout in tests, instead await the condition to be met as you are testing the CONDITION not the TIME PASSING
Verify your test fails with USE_SYSTEM_BUN=1 bun test <file> and passes with bun bd test <file> - your test is NOT VALID if it passes with USE_SYSTEM_BUN=1

Files:

  • test/js/bun/test/assert-export.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test with files that end in *.test.{ts,js,jsx,tsx,mjs,cjs}
Do not write flaky tests. Never wait for time to pass in tests; always wait for the condition to be met instead of using an arbitrary amount of time
Never use hardcoded port numbers in tests. Always use port: 0 to get a random port
Prefer concurrent tests over sequential tests using test.concurrent or describe.concurrent when multiple tests spawn processes or write files, unless it's very difficult to make them concurrent
When spawning Bun processes in tests, use bunExe and bunEnv from harness to ensure the same build of Bun is used and debug logging is silenced
Use -e flag for single-file tests when spawning Bun processes
Use tempDir() from harness to create temporary directories with files for multi-file tests instead of creating files manually
Prefer async/await over callbacks in tests
When callbacks must be used and it's just a single callback, use Promise.withResolvers to create a promise that can be resolved or rejected from a callback
Do not set a timeout on tests. Bun already has timeouts
Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests, as ''.repeat is very slow in debug JavaScriptCore builds
Use describe blocks for grouping related tests
Always use await using or using to ensure proper resource cleanup in tests for APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc
Always check exit codes and test error scenarios in error tests
Use describe.each() for parameterized tests
Use toMatchSnapshot() for snapshot testing
Use beforeAll(), afterEach(), beforeEach() for setup/teardown in tests
Track resources (servers, clients) in arrays for cleanup in afterEach()

Files:

  • test/js/bun/test/assert-export.test.ts
🧠 Learnings (32)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/**/*.{js,ts,jsx,tsx} : Write tests as JavaScript and TypeScript files using Jest-style APIs (`test`, `describe`, `expect`) and import from `bun:test`
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.205Z
Learning: Add tests for new Bun runtime functionality
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.069Z
Learning: Applies to **/*.test.{ts,tsx} : Verify your test fails with `USE_SYSTEM_BUN=1 bun test <file>` and passes with `bun bd test <file>` - your test is NOT VALID if it passes with `USE_SYSTEM_BUN=1`
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.069Z
Learning: Applies to test/js/bun/**/*.test.{ts,tsx} : For Bun-specific API tests, use the `test/js/bun/` directory (for http, crypto, ffi, shell, etc.)
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` with files that end in `*.test.{ts,js,jsx,tsx,mjs,cjs}`
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use `$debug()` for debug logging and `$assert()` for assertions; both are stripped in release builds
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Unit tests for specific features are organized by module (e.g., `/test/js/bun/`, `/test/js/node/`)
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:08.612Z
Learning: Applies to test/bake/**/*.test.ts : Assert console messages using `c.expectMessage()` with single or multiple arguments; any unasserted logs fail the test to catch unexpected re-evaluations or reloads
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/**/*.{js,ts,jsx,tsx} : Write tests as JavaScript and TypeScript files using Jest-style APIs (`test`, `describe`, `expect`) and import from `bun:test`

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:36:33.069Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.069Z
Learning: Applies to test/js/bun/**/*.test.{ts,tsx} : For Bun-specific API tests, use the `test/js/bun/` directory (for http, crypto, ffi, shell, etc.)

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:35:39.205Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.205Z
Learning: Add tests for new Bun runtime functionality

Applied to files:

  • docs/guides/test/assert.mdx
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.

Applied to files:

  • docs/guides/test/assert.mdx
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: Applies to test/cli/**/*.{js,ts,jsx,tsx} : When testing Bun as a CLI, use the `spawn` API from `bun` with the `bunExe()` and `bunEnv` from `harness` to execute Bun commands and validate exit codes, stdout, and stderr

Applied to files:

  • docs/guides/test/assert.mdx
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:35:08.612Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:08.612Z
Learning: Applies to test/bake/**/*.test.ts : Assert console messages using `c.expectMessage()` with single or multiple arguments; any unasserted logs fail the test to catch unexpected re-evaluations or reloads

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use `$debug()` for debug logging and `$assert()` for assertions; both are stripped in release builds

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:36:33.069Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.069Z
Learning: Applies to **/*.test.{ts,tsx} : Verify your test fails with `USE_SYSTEM_BUN=1 bun test <file>` and passes with `bun bd test <file>` - your test is NOT VALID if it passes with `USE_SYSTEM_BUN=1`

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` with files that end in `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : When spawning Bun processes in tests, use `bunExe` and `bunEnv` from `harness` to ensure the same build of Bun is used and debug logging is silenced

Applied to files:

  • docs/guides/test/assert.mdx
  • packages/bun-types/test.d.ts
  • test/js/bun/test/assert-export.test.ts
📚 Learning: 2025-10-19T02:52:37.412Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/tsconfig.json:1-15
Timestamp: 2025-10-19T02:52:37.412Z
Learning: In the Bun repository, packages under packages/ (e.g., bun-otel) can follow a TypeScript-first pattern where package.json exports point directly to .ts files (not compiled .js files). Bun natively runs TypeScript, so consumers import .ts sources directly and receive full type information without needing compiled .d.ts declaration files. For such packages, adding "declaration": true or "outDir" in tsconfig.json is unnecessary and would break the export structure.
<!-- [remove_learning]
ceedde95-980e-4898-a2c6-40ff73913664

Applied to files:

  • packages/bun-types/test.d.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Register new V8 API test functions in the Init method using NODE_SET_METHOD with exports object

Applied to files:

  • packages/bun-types/test.d.ts
  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:35:50.422Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-11-24T18:35:50.422Z
Learning: See `test/harness.ts` for common test utilities and helpers

Applied to files:

  • packages/bun-types/test.d.ts
📚 Learning: 2025-11-24T18:36:33.069Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.069Z
Learning: Applies to test/js/node/**/*.test.{ts,tsx} : For Node.js compatibility tests, use the `test/js/node/` directory

Applied to files:

  • packages/bun-types/test.d.ts
📚 Learning: 2025-11-24T18:35:25.883Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.883Z
Learning: Applies to *.cpp : For classes, prototypes, and constructors: add `JSC::LazyClassStructure` to ZigGlobalObject.h, initialize in `GlobalObject::finishCreation()`, visit in `GlobalObject::visitChildrenImpl()`, and implement a setup function that creates prototype, constructor, and main class structures

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:35:25.883Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.883Z
Learning: Applies to *.cpp : For classes without Constructor, use `JSC::LazyProperty<JSGlobalObject, Structure>` instead of `JSC::LazyClassStructure` in ZigGlobalObject.h, initialize in `GlobalObject::finishCreation()`, and visit in `GlobalObject::visitChildrenImpl()`

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:33.069Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.069Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : When implementing JavaScript classes in C++, define properties using HashTableValue arrays and add iso subspaces for classes with C++ fields, caching structures in ZigGlobalObject

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to **/*.zig : Use consistent parameter name `globalObject` instead of `ctx` in Zig constructor and method implementations

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to **/*.zig : Use `JSC.markBinding(src())` in finalize methods for debugging purposes before calling `deinit()`

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Include new class bindings in `src/bun.js/bindings/generated_classes_list.zig` to register them with the code generator

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:35:25.883Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.883Z
Learning: Applies to *.cpp : To create JavaScript objects from Zig, implement C++ functions following the `Bun__ClassName__toJS(Zig::GlobalObject*, NativeType*)` convention that construct and return the JavaScript object as an encoded JSValue

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:35:25.883Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.883Z
Learning: Applies to *.cpp : Expose C++ class constructors to Zig using `extern "C"` functions following the pattern `Bun__JSClassName(Zig::GlobalObject*)` that return the encoded JSValue constructor

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:35:25.883Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.883Z
Learning: Applies to *.cpp : When constructing JavaScript objects, retrieve the structure from the global object using `zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject)` or similar pattern

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Create test functions in test/v8/v8-module/main.cpp that take FunctionCallbackInfo<Value> parameter, use the test V8 API, print results for comparison with Node.js, and return Undefined

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:36:08.558Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.558Z
Learning: Applies to **/*.zig : Implement getter functions with naming pattern `get<PropertyName>` in Zig that accept `this` and `globalObject` parameters and return `JSC.JSValue`

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-11-24T18:37:30.259Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:30.259Z
Learning: Unit tests for specific features are organized by module (e.g., `/test/js/bun/`, `/test/js/node/`)

Applied to files:

  • test/js/bun/test/assert-export.test.ts
🧬 Code graph analysis (1)
src/bun.js/bindings/ZigGlobalObject.cpp (2)
src/bun.js/bindings/ZigGlobalObject.h (1)
  • globalObject (127-127)
src/bun.js/bindings/ScriptExecutionContext.cpp (2)
  • globalObject (93-96)
  • globalObject (93-93)
🔇 Additional comments (2)
packages/bun-types/test.d.ts (1)

2375-2391: Type-level re‑export of assert from node:assert looks correct

The ambient export import assert = require("node:assert"); cleanly ties bun:test’s assert to the existing Node typings, so users get the proper asserts-style type narrowing and full API surface without duplicating types. This matches the runtime wiring and the docs examples.

Please run your TypeScript/typecheck pipeline (or tsc for packages/bun-types) to confirm there are no module name / lib conflicts around the "node:assert" typings in this repo’s configuration.

docs/guides/test/assert.mdx (1)

1-93: Clear, focused documentation for assert in bun:test

The page accurately describes assert as a node:assert re‑export, shows realistic TS narrowing usage, covers core methods (including async helpers), and explains mixing it with expect(). The “See also” links correctly point users to Node’s docs and Bun’s test guide. No changes needed.

Comment on lines +1815 to +1827
auto* globalObject = jsCast<Zig::GlobalObject*>(init.owner);
auto& vm = init.vm;

JSValue result = JSValue::decode(Bun__Jest__createTestModuleObject(globalObject));
init.set(result.toObject(globalObject));
JSObject* testModule = result.toObject(globalObject);

// Add node:assert as "assert" export for vitest compatibility
JSValue assertModule = globalObject->internalModuleRegistry()->requireId(globalObject, vm, Bun::InternalModuleRegistry::Field::NodeAssert);
if (assertModule && !assertModule.isUndefinedOrNull()) {
testModule->putDirect(vm, Identifier::fromString(vm, "assert"_s), assertModule);
}

init.set(testModule);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Guard lazy test module initializer with a ThrowScope when loading node:assert

Inside m_lazyTestModuleObject.initLater you now:

  • Decode the Jest test module object.
  • Call internalModuleRegistry()->requireId(..., Field::NodeAssert).
  • putDirect the returned value as "assert".

Unlike the nearby m_utilInspectFunction initializer, this path doesn’t use DECLARE_THROW_SCOPE / RETURN_IF_EXCEPTION, so any unexpected exception from Bun__Jest__createTestModuleObject, requireId, or putDirect would escape without a controlled early return from the initializer.

You can align this with existing patterns and make it more robust with a small change:

    m_lazyTestModuleObject.initLater(
        [](const Initializer<JSObject>& init) {
-            auto* globalObject = jsCast<Zig::GlobalObject*>(init.owner);
-            auto& vm = init.vm;
-
-            JSValue result = JSValue::decode(Bun__Jest__createTestModuleObject(globalObject));
-            JSObject* testModule = result.toObject(globalObject);
-
-            // Add node:assert as "assert" export for vitest compatibility
-            JSValue assertModule = globalObject->internalModuleRegistry()->requireId(globalObject, vm, Bun::InternalModuleRegistry::Field::NodeAssert);
-            if (assertModule && !assertModule.isUndefinedOrNull()) {
-                testModule->putDirect(vm, Identifier::fromString(vm, "assert"_s), assertModule);
-            }
-
-            init.set(testModule);
+            auto* globalObject = jsCast<Zig::GlobalObject*>(init.owner);
+            auto& vm = init.vm;
+            auto scope = DECLARE_THROW_SCOPE(vm);
+
+            JSValue result = JSValue::decode(Bun__Jest__createTestModuleObject(globalObject));
+            JSObject* testModule = result.toObject(globalObject);
+            RETURN_IF_EXCEPTION(scope, );
+
+            // Add node:assert as "assert" export for vitest compatibility
+            JSValue assertModule =
+                globalObject->internalModuleRegistry()->requireId(globalObject, vm, Bun::InternalModuleRegistry::Field::NodeAssert);
+            RETURN_IF_EXCEPTION(scope, );
+            if (assertModule && !assertModule.isUndefinedOrNull()) {
+                testModule->putDirect(vm, Identifier::fromString(vm, "assert"_s), assertModule);
+                RETURN_IF_EXCEPTION(scope, );
+            }
+
+            init.set(testModule);
        });

This way a failure to load node:assert simply means the assert export is absent, without leaving a stray pending exception from a lazy initializer.

Comment on lines +1 to +149
import { assert, expect, test } from "bun:test";

test("assert is exported from bun:test", () => {
expect(typeof assert).toBe("function");
});

test("assert(condition) works", () => {
assert(true);
assert(1);
assert("non-empty string");

expect(() => assert(false)).toThrow();
expect(() => assert(0)).toThrow();
expect(() => assert("")).toThrow();
});

test("assert with message works", () => {
assert(true, "should not throw");

expect(() => assert(false, "custom error message")).toThrow(/custom error message/);
});

test("assert.ok works", () => {
assert.ok(true);
assert.ok(1);

expect(() => assert.ok(false)).toThrow();
});

test("assert.strictEqual works", () => {
assert.strictEqual(1, 1);
assert.strictEqual("hello", "hello");

expect(() => assert.strictEqual(1, 2)).toThrow();
expect(() => assert.strictEqual(1, "1")).toThrow();
});

test("assert.deepStrictEqual works", () => {
assert.deepStrictEqual({ a: 1 }, { a: 1 });
assert.deepStrictEqual([1, 2, 3], [1, 2, 3]);

expect(() => assert.deepStrictEqual({ a: 1 }, { a: 2 })).toThrow();
});

test("assert.notStrictEqual works", () => {
assert.notStrictEqual(1, 2);
assert.notStrictEqual(1, "1");

expect(() => assert.notStrictEqual(1, 1)).toThrow();
});

test("assert.throws works", () => {
assert.throws(() => {
throw new Error("test error");
});

expect(() =>
assert.throws(() => {
// does not throw
}),
).toThrow();
});

test("assert.doesNotThrow works", () => {
assert.doesNotThrow(() => {
// does not throw
});

expect(() =>
assert.doesNotThrow(() => {
throw new Error("test error");
}),
).toThrow();
});

test("assert.rejects works", async () => {
await assert.rejects(async () => {
throw new Error("async error");
});

await expect(
assert.rejects(async () => {
// does not reject
}),
).rejects.toThrow();
});

test("assert.doesNotReject works", async () => {
await assert.doesNotReject(async () => {
// does not reject
});

await expect(
assert.doesNotReject(async () => {
throw new Error("async error");
}),
).rejects.toThrow();
});

test("assert.equal works (loose equality)", () => {
assert.equal(1, 1);
assert.equal(1, "1"); // loose equality allows this

expect(() => assert.equal(1, 2)).toThrow();
});

test("assert.notEqual works (loose inequality)", () => {
assert.notEqual(1, 2);

expect(() => assert.notEqual(1, 1)).toThrow();
});

test("assert.deepEqual works", () => {
assert.deepEqual({ a: 1 }, { a: 1 });
assert.deepEqual([1, 2], [1, 2]);

expect(() => assert.deepEqual({ a: 1 }, { a: 2 })).toThrow();
});

test("assert.notDeepEqual works", () => {
assert.notDeepEqual({ a: 1 }, { a: 2 });

expect(() => assert.notDeepEqual({ a: 1 }, { a: 1 })).toThrow();
});

test("assert.fail works", () => {
expect(() => assert.fail()).toThrow();
expect(() => assert.fail("custom message")).toThrow(/custom message/);
});

test("assert.ifError works", () => {
assert.ifError(null);
assert.ifError(undefined);

expect(() => assert.ifError(new Error("test"))).toThrow();
expect(() => assert.ifError("some error")).toThrow();
});

test("assert.match works", () => {
assert.match("hello world", /world/);

expect(() => assert.match("hello", /world/)).toThrow();
});

test("assert.doesNotMatch works", () => {
assert.doesNotMatch("hello", /world/);

expect(() => assert.doesNotMatch("hello world", /world/)).toThrow();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Comprehensive coverage of the assert export; consider also asserting identity with node:assert

These tests do a solid job of exercising the assert export from bun:test across the core Node APIs (sync, async, loose/strict, deep vs non‑deep, and helper methods). They align with Bun’s testing style guidelines and should quickly catch regressions in the wiring to node:assert.

If you want to further lock in the “re‑export” guarantee (not just behavioral similarity), you could add a small identity check:

-import { assert, expect, test } from "bun:test";
+import { assert, expect, test } from "bun:test";
+import nodeAssert from "node:assert";
+
+test("assert is the node:assert export", () => {
+  expect(assert).toBe(nodeAssert);
+});

Optional, but it would guarantee that bun:test and node:assert stay in sync at the object level.

🤖 Prompt for AI Agents
In test/js/bun/test/assert-export.test.ts around lines 1 to 149, add a small
identity check to ensure the exported assert from bun:test is the same object
exported by node:assert; require or import node:assert (e.g., import * as
nodeAssert from "node:assert" or require("node:assert")) and add an expectation
that assert === nodeAssert (or otherwise assert strict identity), so the test
verifies a re-export rather than only behavioral equivalence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant