console/test: serialize DOM nodes as markup in Bun.inspect and matcher utils
What
Bun.inspect, console.log, this.utils.printReceived/printExpected/stringify, and toMatchSnapshot now serialize DOM nodes (from jsdom / happy-dom) as markup instead of dumping the full object graph.
Received element is not disabled:
<button />
instead of
Received element is not disabled:
HTMLButtonElement {
[Symbol(listeners)]: {},
[Symbol(listenerOptions)]: {},
[Symbol(isConnected)]: false,
[Symbol(parentNode)]: null,
[Symbol(rootNode)]: null,
[Symbol(ownerDocument)]: HTMLDocument {
...hundreds more lines...
Fixes #10886. Fixes #5540. Fixes #39768.
Not related to #29709 beyond the issue link: that PR fixes a native stack overflow when printing circular JSX / deep Proxy chains, while the #10886 report is a happy-dom element (a plain object to the formatter) producing a bounded but enormous dump, which is what this PR changes. Both still reproduce on current main; this branch has been merged with main.
Reproduction
import * as matchers from "@testing-library/jest-dom/matchers";
import { render, screen } from "@testing-library/react";
expect.extend(matchers);
it("test", () => {
render(<button>test</button>);
expect(screen.getByRole("button")).toBeDisabled();
});
Before this change the failure message from toBeDisabled() printed the whole happy-dom HTMLButtonElement tree (the ownerDocument → window graph), which users describe as "infinite". Jest and Vitest print <button /> because this.utils.printReceived is backed by pretty-format with the DOMElement plugin.
Cause
ExpectMatcherUtils.printReceived / stringify route through ConsoleObject::Formatter, which had no handling for DOM nodes (there is no native DOM in bun; these objects come from jsdom / happy-dom as plain JS class instances). The formatter already has a JSX tag for React elements but nothing for Element / Text / Comment / DocumentFragment, so they fell through to Tag::Object and were walked as ordinary objects. The snapshot serializer in pretty_format.rs had the same gap.
Fix
Add a DOMNode tag to both ConsoleObject::Formatter and the test-runner pretty_format that mirrors the pretty-format DOMElement plugin:
- Detection: an
Object/FinalObjectis a DOM node when its class name matches/^((HTML|SVG)\w*)?Element$/,Text,Comment, orDocumentFragmentand itsnodeTypeagrees with that class. The name check runs first sonodeType(a prototype getter) is never invoked on unrelated objects, and a two-pointer prototype probe bails out immediately for plain{}/Object.create(null)so the common path does not pay for acalculatedClassNamewalk. - Printing: elements render as
<tag attr="v">...children...</tag>(attributes sorted, self-closing when empty,…leaf atmax_depth);Textas itsdata,Commentas<!--data-->,DocumentFragmentas<DocumentFragment>…</DocumentFragment>.
This matches what browsers do for console.log(element) and what Jest does for printReceived / snapshots. util.inspect is unchanged (still Node-compatible).
Verification
bun bd test test/js/bun/util/inspect.test.js -t "DOM nodes": 12 new tests covering HTML/SVG elements, Text/Comment/DocumentFragment, nested depth, printReceived/stringify in a custom matcher, and the negative cases (wrong nodeType, non-DOM class name, throwing nodeType getter). All fail on current release and pass with this change.
Checked against happy-dom 15/20 and jsdom.
no test proof · iteration 6 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/util/inspect.test.js