diff --git a/docs/guides/write-file/unlink.md b/docs/guides/write-file/unlink.md index ba0cbe8b1d3611..ef2fc5fb2c92e8 100644 --- a/docs/guides/write-file/unlink.md +++ b/docs/guides/write-file/unlink.md @@ -2,22 +2,10 @@ name: Delete a file --- -To synchronously delete a file with Bun, use the `unlinkSync` function from the [`node:fs`](https://nodejs.org/api/fs.html#fs_fs_unlink_path_callback) module. (Currently, there is no `Bun` API for deleting files.) +To delete a file in Bun, use the `delete` method. ```ts -import { unlinkSync } from "node:fs"; +import { file } from "bun"; -const path = "/path/to/file.txt"; -unlinkSync(path); -``` - ---- - -To remove a file asynchronously, use the `unlink` function from the [`node:fs/promises`](https://nodejs.org/api/fs.html#fs_fspromises_unlink_path) module. - -```ts -import { unlink } from "node:fs/promises"; - -const path = "/path/to/file.txt"; -await unlink(path); +await file("./path-to-file.txt").delete(); ``` diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index e7ef78f9a97746..a2c2cc914fe7eb 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1230,6 +1230,16 @@ declare module "bun" { * Deletes the file. */ unlink(): Promise; + + /** + * Deletes the file. ( same as unlink ) + */ + delete(): Promise + + /** + * Provides useful information about the file. + */ + stat(): Promise } interface NetworkSink extends FileSink { /** diff --git a/packages/bun-types/html-rewriter.d.ts b/packages/bun-types/html-rewriter.d.ts index 4db17decb630da..6ff37fa0fe080b 100644 --- a/packages/bun-types/html-rewriter.d.ts +++ b/packages/bun-types/html-rewriter.d.ts @@ -50,7 +50,7 @@ declare namespace HTMLRewriterTypes { interface Element { tagName: string; - readonly attributes: IterableIterator; + readonly attributes: IterableIterator<[string, string]>; readonly removed: boolean; /** Whether the element is explicitly self-closing, e.g. `` */ readonly selfClosing: boolean; diff --git a/src/bun.js/bindings/JSPropertyIterator.cpp b/src/bun.js/bindings/JSPropertyIterator.cpp index 89e64a8ac88121..c7e3ae8fed8589 100644 --- a/src/bun.js/bindings/JSPropertyIterator.cpp +++ b/src/bun.js/bindings/JSPropertyIterator.cpp @@ -45,6 +45,10 @@ extern "C" JSPropertyIterator* Bun__JSPropertyIterator__create(JSC::JSGlobalObje auto scope = DECLARE_THROW_SCOPE(vm); JSC::PropertyNameArray array(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); + if (UNLIKELY(object->hasNonReifiedStaticProperties())) { + object->reifyAllStaticProperties(globalObject); + } + #if OS(WINDOWS) if (UNLIKELY(object->type() == JSC::ProxyObjectType)) { // Check if we're actually iterating through the JSEnvironmentVariableMap's proxy. @@ -75,7 +79,7 @@ extern "C" JSPropertyIterator* Bun__JSPropertyIterator__create(JSC::JSGlobalObje if (only_non_index_properties) { object->getOwnNonIndexPropertyNames(globalObject, array, DontEnumPropertiesMode::Exclude); } else { - object->getOwnPropertyNames(object, globalObject, array, DontEnumPropertiesMode::Exclude); + object->methodTable()->getOwnPropertyNames(object, globalObject, array, DontEnumPropertiesMode::Exclude); } } else { object->getPropertyNames(globalObject, array, DontEnumPropertiesMode::Exclude); @@ -161,7 +165,7 @@ extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIte auto& vm = iter->vm; auto scope = DECLARE_THROW_SCOPE(vm); PropertySlot slot(object, PropertySlot::InternalMethodType::GetOwnProperty); - if (!object->getOwnPropertySlot(object, globalObject, prop, slot)) { + if (!object->methodTable()->getOwnPropertySlot(object, globalObject, prop, slot)) { return {}; } RETURN_IF_EXCEPTION(scope, {}); diff --git a/src/js/internal/util/inspect.js b/src/js/internal/util/inspect.js index a67c9dd49cd026..f7dbe104e2e3b6 100644 --- a/src/js/internal/util/inspect.js +++ b/src/js/internal/util/inspect.js @@ -2607,14 +2607,18 @@ function getStringWidth(str, removeControlChars = true) { } // Regex used for ansi escape code splitting -// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js -// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore +// Ref: https://github.com/chalk/ansi-regex/blob/f338e1814144efb950276aac84135ff86b72dc8e/index.js +// License: MIT by Sindre Sorhus // Matches all ansi escape code sequences in a string -const ansiPattern = +const ansiPattern = new RegExp( "[\\u001B\\u009B][[\\]()#;?]*" + - "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" + - "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" + - "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"; + "(?:(?:(?:(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]+)*" + + "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]*)*)?" + + "(?:\\u0007|\\u001B\\u005C|\\u009C))" + + "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?" + + "[\\dA-PR-TZcf-nq-uy=><~]))", + "g", +); const ansi = new RegExp(ansiPattern, "g"); /** Remove all VT control characters. Use to estimate displayed string width. */ function stripVTControlCharacters(str) { diff --git a/src/js/node/util.ts b/src/js/node/util.ts index a9ddd44135d437..8023c24647c4ce 100644 --- a/src/js/node/util.ts +++ b/src/js/node/util.ts @@ -31,10 +31,12 @@ const formatWithOptions = utl.formatWithOptions; const format = utl.format; const stripVTControlCharacters = utl.stripVTControlCharacters; +const codesWarned = new Set(); function deprecate(fn, msg, code) { if (process.noDeprecation === true) { return fn; } + if (code !== undefined) validateString(code, "code"); var warned = false; function deprecated() { @@ -46,7 +48,15 @@ function deprecate(fn, msg, code) { } else if (process.traceDeprecation) { console.trace(msg); } else { - console.error(msg); + if (code !== undefined) { + // only warn for each code once + if (codesWarned.has(code)) { + process.emitWarning(msg, "DeprecationWarning", code); + } + codesWarned.add(code); + } else { + process.emitWarning(msg, "DeprecationWarning"); + } } warned = true; } @@ -149,7 +159,13 @@ var inherits = function inherits(ctor, superCtor) { if (superCtor.prototype === undefined) { throw $ERR_INVALID_ARG_TYPE("superCtor.prototype", "object", superCtor.prototype); } - ctor.super_ = superCtor; + Object.defineProperty(ctor, "super_", { + // @ts-ignore + __proto__: null, + value: superCtor, + writable: true, + configurable: true, + }); Object.setPrototypeOf(ctor.prototype, superCtor.prototype); }; var _extend = function (origin, add) { @@ -172,30 +188,40 @@ function callbackifyOnRejected(reason, cb) { return cb(reason); } function callbackify(original) { - if (typeof original !== "function") { - throw new TypeError('The "original" argument must be of type Function'); - } - function callbackified() { - var args = Array.prototype.slice.$call(arguments); - var maybeCb = args.pop(); - if (typeof maybeCb !== "function") { - throw new TypeError("The last argument must be of type Function"); - } - var self = this; - var cb = function () { - return maybeCb.$apply(self, arguments); - }; + const { validateFunction } = require("internal/validators"); + validateFunction(original, "original"); + + // We DO NOT return the promise as it gives the user a false sense that + // the promise is actually somehow related to the callback's execution + // and that the callback throwing will reject the promise. + function callbackified(...args) { + const maybeCb = Array.prototype.pop.$call(args); + validateFunction(maybeCb, "last argument"); + const cb = Function.prototype.bind.$call(maybeCb, this); + // In true node style we process the callback on `nextTick` with all the + // implications (stack, `uncaughtException`, `async_hooks`) original.$apply(this, args).then( - function (ret) { - process.nextTick(cb, null, ret); - }, - function (rej) { - process.nextTick(callbackifyOnRejected, rej, cb); - }, + ret => process.nextTick(cb, null, ret), + rej => process.nextTick(callbackifyOnRejected, rej, cb), ); } - Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original)); - Object.defineProperties(callbackified, getOwnPropertyDescriptors(original)); + + const descriptors = Object.getOwnPropertyDescriptors(original); + // It is possible to manipulate a functions `length` or `name` property. This + // guards against the manipulation. + if (typeof descriptors.length.value === "number") { + descriptors.length.value++; + } + if (typeof descriptors.name.value === "string") { + descriptors.name.value += "Callbackified"; + } + const propertiesValues = Object.values(descriptors); + for (let i = 0; i < propertiesValues.length; i++) { + // We want to use null-prototype objects to not rely on globally mutable + // %Object.prototype%. + Object.setPrototypeOf(propertiesValues[i], null); + } + Object.defineProperties(callbackified, descriptors); return callbackified; } var toUSVString = input => { diff --git a/test/bun.lockb b/test/bun.lockb index fb2b70b1f29064..2d9775c983b086 100755 Binary files a/test/bun.lockb and b/test/bun.lockb differ diff --git a/test/js/node/test/parallel/test-util-callbackify.js b/test/js/node/test/parallel/test-util-callbackify.js new file mode 100644 index 00000000000000..7f7afb6407b2a3 --- /dev/null +++ b/test/js/node/test/parallel/test-util-callbackify.js @@ -0,0 +1,296 @@ +'use strict'; +const common = require('../common'); + +// This test checks that the semantics of `util.callbackify` are as described in +// the API docs + +const assert = require('assert'); +const { callbackify } = require('util'); +const { execFile } = require('child_process'); +const fixtures = require('../common/fixtures'); + +const values = [ + 'hello world', + null, + undefined, + false, + 0, + {}, + { key: 'value' }, + Symbol('I am a symbol'), + function ok() {}, + ['array', 'with', 4, 'values'], + new Error('boo'), +]; + +{ + // Test that the resolution value is passed as second argument to callback + for (const value of values) { + // Test and `async function` + async function asyncFn() { + return value; + } + + const cbAsyncFn = callbackify(asyncFn); + cbAsyncFn(common.mustSucceed((ret) => { + assert.strictEqual(ret, value); + })); + + // Test Promise factory + function promiseFn() { + return Promise.resolve(value); + } + + const cbPromiseFn = callbackify(promiseFn); + cbPromiseFn(common.mustSucceed((ret) => { + assert.strictEqual(ret, value); + })); + + // Test Thenable + function thenableFn() { + return { + then(onRes, onRej) { + onRes(value); + } + }; + } + + const cbThenableFn = callbackify(thenableFn); + cbThenableFn(common.mustSucceed((ret) => { + assert.strictEqual(ret, value); + })); + } +} + +{ + // Test that rejection reason is passed as first argument to callback + for (const value of values) { + // Test an `async function` + async function asyncFn() { + return Promise.reject(value); + } + + const cbAsyncFn = callbackify(asyncFn); + assert.strictEqual(cbAsyncFn.length, 1); + assert.strictEqual(cbAsyncFn.name, 'asyncFnCallbackified'); + cbAsyncFn(common.mustCall((err, ret) => { + assert.strictEqual(ret, undefined); + if (err instanceof Error) { + if ('reason' in err) { + assert(!value); + assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION'); + assert.strictEqual(err.reason, value); + } else { + assert.strictEqual(String(value).endsWith(err.message), true); + } + } else { + assert.strictEqual(err, value); + } + })); + + // Test a Promise factory + function promiseFn() { + return Promise.reject(value); + } + const obj = {}; + Object.defineProperty(promiseFn, 'name', { + value: obj, + writable: false, + enumerable: false, + configurable: true + }); + + const cbPromiseFn = callbackify(promiseFn); + assert.strictEqual(promiseFn.name, obj); + cbPromiseFn(common.mustCall((err, ret) => { + assert.strictEqual(ret, undefined); + if (err instanceof Error) { + if ('reason' in err) { + assert(!value); + assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION'); + assert.strictEqual(err.reason, value); + } else { + assert.strictEqual(String(value).endsWith(err.message), true); + } + } else { + assert.strictEqual(err, value); + } + })); + + // Test Thenable + function thenableFn() { + return { + then(onRes, onRej) { + onRej(value); + } + }; + } + + const cbThenableFn = callbackify(thenableFn); + cbThenableFn(common.mustCall((err, ret) => { + assert.strictEqual(ret, undefined); + if (err instanceof Error) { + if ('reason' in err) { + assert(!value); + assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION'); + assert.strictEqual(err.reason, value); + } else { + assert.strictEqual(String(value).endsWith(err.message), true); + } + } else { + assert.strictEqual(err, value); + } + })); + } +} + +{ + // Test that arguments passed to callbackified function are passed to original + for (const value of values) { + async function asyncFn(arg) { + assert.strictEqual(arg, value); + return arg; + } + + const cbAsyncFn = callbackify(asyncFn); + assert.strictEqual(cbAsyncFn.length, 2); + assert.notStrictEqual( + Object.getPrototypeOf(cbAsyncFn), + Object.getPrototypeOf(asyncFn) + ); + assert.strictEqual(Object.getPrototypeOf(cbAsyncFn), Function.prototype); + cbAsyncFn(value, common.mustSucceed((ret) => { + assert.strictEqual(ret, value); + })); + + function promiseFn(arg) { + assert.strictEqual(arg, value); + return Promise.resolve(arg); + } + const obj = {}; + Object.defineProperty(promiseFn, 'length', { + value: obj, + writable: false, + enumerable: false, + configurable: true + }); + + const cbPromiseFn = callbackify(promiseFn); + assert.strictEqual(promiseFn.length, obj); + cbPromiseFn(value, common.mustSucceed((ret) => { + assert.strictEqual(ret, value); + })); + } +} + +{ + // Test that `this` binding is the same for callbackified and original + for (const value of values) { + const iAmThis = { + fn(arg) { + assert.strictEqual(this, iAmThis); + return Promise.resolve(arg); + }, + }; + iAmThis.cbFn = callbackify(iAmThis.fn); + iAmThis.cbFn(value, common.mustSucceed(function(ret) { + assert.strictEqual(ret, value); + assert.strictEqual(this, iAmThis); + })); + + const iAmThat = { + async fn(arg) { + assert.strictEqual(this, iAmThat); + return arg; + }, + }; + iAmThat.cbFn = callbackify(iAmThat.fn); + iAmThat.cbFn(value, common.mustSucceed(function(ret) { + assert.strictEqual(ret, value); + assert.strictEqual(this, iAmThat); + })); + } +} + +{ + // Test that callback that throws emits an `uncaughtException` event + const fixture = fixtures.path('uncaught-exceptions', 'callbackify1.js'); + execFile( + process.execPath, + [fixture], + common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err.code, 1); + assert.strictEqual(Object.getPrototypeOf(err).name, 'Error'); + assert.strictEqual(stdout, ''); + const errLines = stderr.trim().split(/[\r\n]+/); + const errLine = errLines.find((l) => /^error/.exec(l)); + assert.strictEqual(errLine, `error: ${fixture}`); + }) + ); +} + +{ + // Test that handled `uncaughtException` works and passes rejection reason + const fixture = fixtures.path('uncaught-exceptions', 'callbackify2.js'); + execFile( + process.execPath, + [fixture], + common.mustSucceed((stdout, stderr) => { + assert.strictEqual( + stdout.trim(), + `ifError got unwanted exception: ${fixture}`); + assert.strictEqual(stderr, ''); + }) + ); +} + +{ + // Verify that non-function inputs throw. + ['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => { + assert.throws(() => { + callbackify(value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "original" argument must be of type function.' + + common.invalidArgTypeHelper(value) + }); + }); +} + +{ + async function asyncFn() { + return 42; + } + + const cb = callbackify(asyncFn); + const args = []; + + // Verify that the last argument to the callbackified function is a function. + ['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => { + args.push(value); + assert.throws(() => { + cb(...args); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + }); +} + +{ + // Test Promise factory + function promiseFn(value) { + return Promise.reject(value); + } + + const cbPromiseFn = callbackify(promiseFn); + + cbPromiseFn(null, (err) => { + assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION'); + assert.strictEqual(err.reason, null); + // skipped, bun doesn't hide callbackifyOnRejected from the stack trace + // const stack = err.stack.split(/[\r\n]+/); + // assert.match(stack[1], /at process\.processTicksAndRejections/); + }); +} diff --git a/test/js/node/test/parallel/test-util-deprecate-invalid-code.js b/test/js/node/test/parallel/test-util-deprecate-invalid-code.js new file mode 100644 index 00000000000000..7e68c18817c0ca --- /dev/null +++ b/test/js/node/test/parallel/test-util-deprecate-invalid-code.js @@ -0,0 +1,14 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const util = require('util'); + +[1, true, false, null, {}].forEach((notString) => { + assert.throws(() => util.deprecate(() => {}, 'message', notString), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "code" argument must be of type string.' + + common.invalidArgTypeHelper(notString) + }); +}); diff --git a/test/js/node/test/parallel/test-util-deprecate.js b/test/js/node/test/parallel/test-util-deprecate.js new file mode 100644 index 00000000000000..1b4a5e76623743 --- /dev/null +++ b/test/js/node/test/parallel/test-util-deprecate.js @@ -0,0 +1,57 @@ +'use strict'; + +require('../common'); + +// Tests basic functionality of util.deprecate(). + +const assert = require('assert'); +const util = require('util'); + +const expectedWarnings = new Map(); + +// Emits deprecation only once if same function is called. +{ + const msg = 'fhqwhgads'; + const fn = util.deprecate(() => {}, msg); + expectedWarnings.set(msg, { code: undefined, count: 1 }); + fn(); + fn(); +} + +// Emits deprecation twice for different functions. +{ + const msg = 'sterrance'; + const fn1 = util.deprecate(() => {}, msg); + const fn2 = util.deprecate(() => {}, msg); + expectedWarnings.set(msg, { code: undefined, count: 2 }); + fn1(); + fn2(); +} + +// Emits deprecation only once if optional code is the same, even for different +// functions. +{ + const msg = 'cannonmouth'; + const code = 'deprecatesque'; + const fn1 = util.deprecate(() => {}, msg, code); + const fn2 = util.deprecate(() => {}, msg, code); + expectedWarnings.set(msg, { code, count: 1 }); + fn1(); + fn2(); + fn1(); + fn2(); +} + +process.on('warning', (warning) => { + assert.strictEqual(warning.name, 'DeprecationWarning'); + assert.ok(expectedWarnings.has(warning.message)); + const expected = expectedWarnings.get(warning.message); + assert.strictEqual(warning.code, expected.code); + expected.count = expected.count - 1; + if (expected.count === 0) + expectedWarnings.delete(warning.message); +}); + +process.on('exit', () => { + assert.deepStrictEqual(expectedWarnings, new Map()); +}); diff --git a/test/js/node/test/parallel/test-util-emit-experimental-warning.js b/test/js/node/test/parallel/test-util-emit-experimental-warning.js new file mode 100644 index 00000000000000..6b6a45ec03b289 --- /dev/null +++ b/test/js/node/test/parallel/test-util-emit-experimental-warning.js @@ -0,0 +1,19 @@ +'use strict'; +// emitExperimentalWarning is a node internal not used by bun, so this test is skipped + +// // Flags: --expose-internals +// const common = require('../common'); +// const assert = require('assert'); +// const { emitExperimentalWarning } = require('internal/util'); + +// This test ensures that the emitExperimentalWarning in internal/util emits a +// warning when passed an unsupported feature and that it simply returns +// when passed the same feature multiple times. + +// process.on('warning', common.mustCall((warning) => { +// assert.match(warning.message, /is an experimental feature/); +// }, 2)); + +// emitExperimentalWarning('feature1'); +// emitExperimentalWarning('feature1'); // should not warn +// emitExperimentalWarning('feature2'); diff --git a/test/js/node/test/parallel/test-util-inherits.js b/test/js/node/test/parallel/test-util-inherits.js new file mode 100644 index 00000000000000..2ff8a844460179 --- /dev/null +++ b/test/js/node/test/parallel/test-util-inherits.js @@ -0,0 +1,105 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const { inherits } = require('util'); + +// Super constructor +function A() { + this._a = 'a'; +} +A.prototype.a = function() { return this._a; }; + +// One level of inheritance +function B(value) { + A.call(this); + this._b = value; +} +inherits(B, A); +B.prototype.b = function() { return this._b; }; + +assert.deepStrictEqual( + Object.getOwnPropertyDescriptor(B, 'super_'), + { + value: A, + enumerable: false, + configurable: true, + writable: true + } +); + +const b = new B('b'); +assert.strictEqual(b.a(), 'a'); +assert.strictEqual(b.b(), 'b'); +assert.strictEqual(b.constructor, B); + +// Two levels of inheritance +function C() { + B.call(this, 'b'); + this._c = 'c'; +} +inherits(C, B); +C.prototype.c = function() { return this._c; }; +C.prototype.getValue = function() { return this.a() + this.b() + this.c(); }; + +assert.strictEqual(C.super_, B); + +const c = new C(); +assert.strictEqual(c.getValue(), 'abc'); +assert.strictEqual(c.constructor, C); + +// Inherits can be called after setting prototype properties +function D() { + C.call(this); + this._d = 'd'; +} + +D.prototype.d = function() { return this._d; }; +inherits(D, C); + +assert.strictEqual(D.super_, C); + +const d = new D(); +assert.strictEqual(d.c(), 'c'); +assert.strictEqual(d.d(), 'd'); +assert.strictEqual(d.constructor, D); + +// ES6 classes can inherit from a constructor function +class E { + constructor() { + D.call(this); + this._e = 'e'; + } + e() { return this._e; } +} +inherits(E, D); + +assert.strictEqual(E.super_, D); + +const e = new E(); +assert.strictEqual(e.getValue(), 'abc'); +assert.strictEqual(e.d(), 'd'); +assert.strictEqual(e.e(), 'e'); +assert.strictEqual(e.constructor, E); + +// Should throw with invalid arguments +assert.throws(() => { + inherits(A, {}); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', +}); + +assert.throws(() => { + inherits(A, null); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', +}); + +assert.throws(() => { + inherits(null, A); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', +}); diff --git a/test/js/node/test/parallel/test-util-inspect-long-running.js b/test/js/node/test/parallel/test-util-inspect-long-running.js new file mode 100644 index 00000000000000..167f72ba64631a --- /dev/null +++ b/test/js/node/test/parallel/test-util-inspect-long-running.js @@ -0,0 +1,20 @@ +'use strict'; + +require('../common'); + +// Test that huge objects don't crash due to exceeding the maximum heap size. + +const util = require('util'); + +// Create a difficult to stringify object. Without the artificial limitation +// this would crash or throw an maximum string size error. +let last = {}; +const obj = last; + +for (let i = 0; i < 1000; i++) { + last.next = { circular: obj, last, obj: { a: 1, b: 2, c: true } }; + last = last.next; + obj[i] = last; +} + +util.inspect(obj, { depth: Infinity }); diff --git a/test/js/node/test/parallel/test-util-inspect-proxy.js b/test/js/node/test/parallel/test-util-inspect-proxy.js new file mode 100644 index 00000000000000..054a544d622369 --- /dev/null +++ b/test/js/node/test/parallel/test-util-inspect-proxy.js @@ -0,0 +1,183 @@ +// Flags: --expose-internals +'use strict'; + +// tests testing proxy internals are skipped, as getProxyDetails is not available in bun + +require('../common'); +const assert = require('assert'); +const util = require('util'); +// const { internalBinding } = require('internal/test/binding'); +// const processUtil = internalBinding('util'); +const opts = { showProxy: true }; + +let proxyObj; +let called = false; +const target = { + [util.inspect.custom](depth, { showProxy }) { + if (showProxy === false) { + called = true; + if (proxyObj !== this) { + throw new Error('Failed'); + } + } + return [1, 2, 3]; + } +}; +const handler = { + getPrototypeOf() { throw new Error('getPrototypeOf'); }, + setPrototypeOf() { throw new Error('setPrototypeOf'); }, + isExtensible() { throw new Error('isExtensible'); }, + preventExtensions() { throw new Error('preventExtensions'); }, + getOwnPropertyDescriptor() { throw new Error('getOwnPropertyDescriptor'); }, + defineProperty() { throw new Error('defineProperty'); }, + has() { throw new Error('has'); }, + get() { throw new Error('get'); }, + set() { throw new Error('set'); }, + deleteProperty() { throw new Error('deleteProperty'); }, + ownKeys() { throw new Error('ownKeys'); }, + apply() { throw new Error('apply'); }, + construct() { throw new Error('construct'); } +}; +proxyObj = new Proxy(target, handler); + +// Inspecting the proxy should not actually walk it's properties +util.inspect(proxyObj, opts); + +// Make sure inspecting object does not trigger any proxy traps. +util.format('%s', proxyObj); + +// getProxyDetails is an internal method, not intended for public use. +// This is here to test that the internals are working correctly. +// let details = processUtil.getProxyDetails(proxyObj, true); +// assert.strictEqual(target, details[0]); +// assert.strictEqual(handler, details[1]); + +// details = processUtil.getProxyDetails(proxyObj); +// assert.strictEqual(target, details[0]); +// assert.strictEqual(handler, details[1]); + +// details = processUtil.getProxyDetails(proxyObj, false); +// assert.strictEqual(target, details); + +// details = processUtil.getProxyDetails({}, true); +// assert.strictEqual(details, undefined); + +const r = Proxy.revocable({}, {}); +r.revoke(); + +// details = processUtil.getProxyDetails(r.proxy, true); +// assert.strictEqual(details[0], null); +// assert.strictEqual(details[1], null); + +// details = processUtil.getProxyDetails(r.proxy, false); +// assert.strictEqual(details, null); + +assert.strictEqual(util.inspect(r.proxy), ''); +assert.strictEqual( + util.inspect(r, { showProxy: true }), + '{ proxy: , revoke: [Function (anonymous)] }', +); + +assert.strictEqual(util.format('%s', r.proxy), ''); + +assert.strictEqual( + util.inspect(proxyObj, opts), + 'Proxy [\n' + + ' [ 1, 2, 3 ],\n' + + ' {\n' + + ' getPrototypeOf: [Function: getPrototypeOf],\n' + + ' setPrototypeOf: [Function: setPrototypeOf],\n' + + ' isExtensible: [Function: isExtensible],\n' + + ' preventExtensions: [Function: preventExtensions],\n' + + ' getOwnPropertyDescriptor: [Function: getOwnPropertyDescriptor],\n' + + ' defineProperty: [Function: defineProperty],\n' + + ' has: [Function: has],\n' + + ' get: [Function: get],\n' + + ' set: [Function: set],\n' + + ' deleteProperty: [Function: deleteProperty],\n' + + ' ownKeys: [Function: ownKeys],\n' + + ' apply: [Function: apply],\n' + + ' construct: [Function: construct]\n' + + ' }\n' + + ']' +); + +// // Using getProxyDetails with non-proxy returns undefined +// assert.strictEqual(processUtil.getProxyDetails({}), undefined); + +// Inspecting a proxy without the showProxy option set to true should not +// trigger any proxy handlers. +assert.strictEqual(util.inspect(proxyObj), '[ 1, 2, 3 ]'); +assert(called); + +// Yo dawg, I heard you liked Proxy so I put a Proxy +// inside your Proxy that proxies your Proxy's Proxy. +const proxy1 = new Proxy({}, {}); +const proxy2 = new Proxy(proxy1, {}); +const proxy3 = new Proxy(proxy2, proxy1); +const proxy4 = new Proxy(proxy1, proxy2); +const proxy5 = new Proxy(proxy3, proxy4); +const proxy6 = new Proxy(proxy5, proxy5); +const expected0 = '{}'; +const expected1 = 'Proxy [ {}, {} ]'; +const expected2 = 'Proxy [ Proxy [ {}, {} ], {} ]'; +const expected3 = 'Proxy [ Proxy [ Proxy [ {}, {} ], {} ], Proxy [ {}, {} ] ]'; +const expected4 = 'Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [ {}, {} ], {} ] ]'; +const expected5 = 'Proxy [\n ' + + 'Proxy [ Proxy [ Proxy [Array], {} ], Proxy [ {}, {} ] ],\n' + + ' Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [Array], {} ] ]' + + '\n]'; +const expected6 = 'Proxy [\n' + + ' Proxy [\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ],\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ]\n' + + ' ],\n' + + ' Proxy [\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ],\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ]\n' + + ' ]\n' + + ']'; +assert.strictEqual( + util.inspect(proxy1, { showProxy: 1, depth: null }), + expected1); +assert.strictEqual(util.inspect(proxy2, opts), expected2); +assert.strictEqual(util.inspect(proxy3, opts), expected3); +assert.strictEqual(util.inspect(proxy4, opts), expected4); +assert.strictEqual(util.inspect(proxy5, opts), expected5); +assert.strictEqual(util.inspect(proxy6, opts), expected6); +assert.strictEqual(util.inspect(proxy1), expected0); +assert.strictEqual(util.inspect(proxy2), expected0); +assert.strictEqual(util.inspect(proxy3), expected0); +assert.strictEqual(util.inspect(proxy4), expected0); +assert.strictEqual(util.inspect(proxy5), expected0); +assert.strictEqual(util.inspect(proxy6), expected0); + +// Just for fun, let's create a Proxy using Arrays. +const proxy7 = new Proxy([], []); +const expected7 = 'Proxy [ [], [] ]'; +assert.strictEqual(util.inspect(proxy7, opts), expected7); +assert.strictEqual(util.inspect(proxy7), '[]'); + +// Now we're just getting silly, right? +const proxy8 = new Proxy(Date, []); +const proxy9 = new Proxy(Date, String); +const expected8 = 'Proxy [ [Function: Date], [] ]'; +const expected9 = 'Proxy [ [Function: Date], [Function: String] ]'; +assert.strictEqual(util.inspect(proxy8, opts), expected8); +assert.strictEqual(util.inspect(proxy9, opts), expected9); +assert.strictEqual(util.inspect(proxy8), '[Function: Date]'); +assert.strictEqual(util.inspect(proxy9), '[Function: Date]'); + +const proxy10 = new Proxy(() => {}, {}); +const proxy11 = new Proxy(() => {}, { + get() { + return proxy11; + }, + apply() { + return proxy11; + } +}); +const expected10 = '[Function (anonymous)]'; +const expected11 = '[Function (anonymous)]'; +assert.strictEqual(util.inspect(proxy10), expected10); +assert.strictEqual(util.inspect(proxy11), expected11); diff --git a/test/js/node/test/parallel/test-util-stripvtcontrolcharacters.js b/test/js/node/test/parallel/test-util-stripvtcontrolcharacters.js new file mode 100644 index 00000000000000..a201c78976c0ac --- /dev/null +++ b/test/js/node/test/parallel/test-util-stripvtcontrolcharacters.js @@ -0,0 +1,26 @@ +'use strict'; + +require('../common'); +const util = require('util'); +const assert = require('node:assert'); + +// Ref: https://github.com/chalk/ansi-regex/blob/main/test.js +const tests = [ + // [before, expected] + ['\u001B[0m\u001B[4m\u001B[42m\u001B[31mfoo\u001B[39m\u001B[49m\u001B[24mfoo\u001B[0m', 'foofoo'], // Basic ANSI + ['\u001B[0;33;49;3;9;4mbar\u001B[0m', 'bar'], // Advanced colors + ['foo\u001B[0gbar', 'foobar'], // Clear tabs + ['foo\u001B[Kbar', 'foobar'], // Clear line + ['foo\u001B[2Jbar', 'foobar'], // Clear screen +]; + +for (const ST of ['\u0007', '\u001B\u005C', '\u009C']) { + tests.push( + [`\u001B]8;;mailto:no-replay@mail.com${ST}mail\u001B]8;;${ST}`, 'mail'], + [`\u001B]8;k=v;https://example-a.com/?a_b=1&c=2#tit%20le${ST}click\u001B]8;;${ST}`, 'click'], + ); +} + +for (const [before, expected] of tests) { + assert.strictEqual(util.stripVTControlCharacters(before), expected); +} diff --git a/test/js/node/test/parallel/test-util-types.js b/test/js/node/test/parallel/test-util-types.js new file mode 100644 index 00000000000000..755209036f15ca --- /dev/null +++ b/test/js/node/test/parallel/test-util-types.js @@ -0,0 +1,296 @@ +'use strict'; +// JSStream is disabled because it is a node internal +// vm tests are skipped because node:vm Module is not yet implemented +// // Flags: --experimental-vm-modules --expose-internals +const common = require('../common'); +const assert = require('assert'); +const { types, inspect } = require('util'); +const vm = require('vm'); +// const { internalBinding } = require('internal/test/binding'); +// const { JSStream } = internalBinding('js_stream'); + +// const external = (new JSStream())._externalStream; + +for (const [ value, _method ] of [ + // [ external, 'isExternal' ], + [ new Date() ], + [ (function() { return arguments; })(), 'isArgumentsObject' ], + [ new Boolean(), 'isBooleanObject' ], + [ new Number(), 'isNumberObject' ], + [ new String(), 'isStringObject' ], + [ Object(Symbol()), 'isSymbolObject' ], + [ Object(BigInt(0)), 'isBigIntObject' ], + [ new Error(), 'isNativeError' ], + [ new RegExp() ], + [ async function() {}, 'isAsyncFunction' ], + [ function*() {}, 'isGeneratorFunction' ], + [ (function*() {})(), 'isGeneratorObject' ], + [ Promise.resolve() ], + [ new Map() ], + [ new Set() ], + [ (new Map())[Symbol.iterator](), 'isMapIterator' ], + [ (new Set())[Symbol.iterator](), 'isSetIterator' ], + [ new WeakMap() ], + [ new WeakSet() ], + [ new ArrayBuffer() ], + [ new Uint8Array() ], + [ new Uint8ClampedArray() ], + [ new Uint16Array() ], + [ new Uint32Array() ], + [ new Int8Array() ], + [ new Int16Array() ], + [ new Int32Array() ], + [ new Float32Array() ], + [ new Float64Array() ], + [ new BigInt64Array() ], + [ new BigUint64Array() ], + [ Object.defineProperty(new Uint8Array(), + Symbol.toStringTag, + { value: 'foo' }) ], + [ new DataView(new ArrayBuffer()) ], + [ new SharedArrayBuffer() ], + [ new Proxy({}, {}), 'isProxy' ], +]) { + const method = _method || `is${value.constructor.name}`; + assert(method in types, `Missing ${method} for ${inspect(value)}`); + assert(types[method](value), `Want ${inspect(value)} to match ${method}`); + + for (const key of Object.keys(types)) { + if ((types.isArrayBufferView(value) || + types.isAnyArrayBuffer(value)) && key.includes('Array') || + key === 'isBoxedPrimitive') { + continue; + } + + assert.strictEqual(types[key](value), + key === method, + `${inspect(value)}: ${key}, ` + + `${method}, ${types[key](value)}`); + } +} + +// Check boxed primitives. +[ + new Boolean(), + new Number(), + new String(), + Object(Symbol()), + Object(BigInt(0)), +].forEach((entry) => assert(types.isBoxedPrimitive(entry))); + +{ + assert(!types.isUint8Array({ [Symbol.toStringTag]: 'Uint8Array' })); + assert(types.isUint8Array(vm.runInNewContext('new Uint8Array'))); + + assert(!types.isUint8ClampedArray({ + [Symbol.toStringTag]: 'Uint8ClampedArray' + })); + assert(types.isUint8ClampedArray( + vm.runInNewContext('new Uint8ClampedArray') + )); + + assert(!types.isUint16Array({ [Symbol.toStringTag]: 'Uint16Array' })); + assert(types.isUint16Array(vm.runInNewContext('new Uint16Array'))); + + assert(!types.isUint32Array({ [Symbol.toStringTag]: 'Uint32Array' })); + assert(types.isUint32Array(vm.runInNewContext('new Uint32Array'))); + + assert(!types.isInt8Array({ [Symbol.toStringTag]: 'Int8Array' })); + assert(types.isInt8Array(vm.runInNewContext('new Int8Array'))); + + assert(!types.isInt16Array({ [Symbol.toStringTag]: 'Int16Array' })); + assert(types.isInt16Array(vm.runInNewContext('new Int16Array'))); + + assert(!types.isInt32Array({ [Symbol.toStringTag]: 'Int32Array' })); + assert(types.isInt32Array(vm.runInNewContext('new Int32Array'))); + + assert(!types.isFloat32Array({ [Symbol.toStringTag]: 'Float32Array' })); + assert(types.isFloat32Array(vm.runInNewContext('new Float32Array'))); + + assert(!types.isFloat64Array({ [Symbol.toStringTag]: 'Float64Array' })); + assert(types.isFloat64Array(vm.runInNewContext('new Float64Array'))); + + assert(!types.isBigInt64Array({ [Symbol.toStringTag]: 'BigInt64Array' })); + assert(types.isBigInt64Array(vm.runInNewContext('new BigInt64Array'))); + + assert(!types.isBigUint64Array({ [Symbol.toStringTag]: 'BigUint64Array' })); + assert(types.isBigUint64Array(vm.runInNewContext('new BigUint64Array'))); +} + +{ + const primitive = true; + const arrayBuffer = new ArrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + const dataView = new DataView(arrayBuffer); + const uint8Array = new Uint8Array(arrayBuffer); + const uint8ClampedArray = new Uint8ClampedArray(arrayBuffer); + const uint16Array = new Uint16Array(arrayBuffer); + const uint32Array = new Uint32Array(arrayBuffer); + const int8Array = new Int8Array(arrayBuffer); + const int16Array = new Int16Array(arrayBuffer); + const int32Array = new Int32Array(arrayBuffer); + const float32Array = new Float32Array(arrayBuffer); + const float64Array = new Float64Array(arrayBuffer); + const bigInt64Array = new BigInt64Array(arrayBuffer); + const bigUint64Array = new BigUint64Array(arrayBuffer); + + const fakeBuffer = { __proto__: Buffer.prototype }; + const fakeDataView = { __proto__: DataView.prototype }; + const fakeUint8Array = { __proto__: Uint8Array.prototype }; + const fakeUint8ClampedArray = { __proto__: Uint8ClampedArray.prototype }; + const fakeUint16Array = { __proto__: Uint16Array.prototype }; + const fakeUint32Array = { __proto__: Uint32Array.prototype }; + const fakeInt8Array = { __proto__: Int8Array.prototype }; + const fakeInt16Array = { __proto__: Int16Array.prototype }; + const fakeInt32Array = { __proto__: Int32Array.prototype }; + const fakeFloat32Array = { __proto__: Float32Array.prototype }; + const fakeFloat64Array = { __proto__: Float64Array.prototype }; + const fakeBigInt64Array = { __proto__: BigInt64Array.prototype }; + const fakeBigUint64Array = { __proto__: BigUint64Array.prototype }; + + const stealthyDataView = + Object.setPrototypeOf(new DataView(arrayBuffer), Uint8Array.prototype); + const stealthyUint8Array = + Object.setPrototypeOf(new Uint8Array(arrayBuffer), ArrayBuffer.prototype); + const stealthyUint8ClampedArray = + Object.setPrototypeOf( + new Uint8ClampedArray(arrayBuffer), ArrayBuffer.prototype + ); + const stealthyUint16Array = + Object.setPrototypeOf(new Uint16Array(arrayBuffer), Uint16Array.prototype); + const stealthyUint32Array = + Object.setPrototypeOf(new Uint32Array(arrayBuffer), Uint32Array.prototype); + const stealthyInt8Array = + Object.setPrototypeOf(new Int8Array(arrayBuffer), Int8Array.prototype); + const stealthyInt16Array = + Object.setPrototypeOf(new Int16Array(arrayBuffer), Int16Array.prototype); + const stealthyInt32Array = + Object.setPrototypeOf(new Int32Array(arrayBuffer), Int32Array.prototype); + const stealthyFloat32Array = + Object.setPrototypeOf( + new Float32Array(arrayBuffer), Float32Array.prototype + ); + const stealthyFloat64Array = + Object.setPrototypeOf( + new Float64Array(arrayBuffer), Float64Array.prototype + ); + const stealthyBigInt64Array = + Object.setPrototypeOf( + new BigInt64Array(arrayBuffer), BigInt64Array.prototype + ); + const stealthyBigUint64Array = + Object.setPrototypeOf( + new BigUint64Array(arrayBuffer), BigUint64Array.prototype + ); + + const all = [ + primitive, arrayBuffer, buffer, fakeBuffer, + dataView, fakeDataView, stealthyDataView, + uint8Array, fakeUint8Array, stealthyUint8Array, + uint8ClampedArray, fakeUint8ClampedArray, stealthyUint8ClampedArray, + uint16Array, fakeUint16Array, stealthyUint16Array, + uint32Array, fakeUint32Array, stealthyUint32Array, + int8Array, fakeInt8Array, stealthyInt8Array, + int16Array, fakeInt16Array, stealthyInt16Array, + int32Array, fakeInt32Array, stealthyInt32Array, + float32Array, fakeFloat32Array, stealthyFloat32Array, + float64Array, fakeFloat64Array, stealthyFloat64Array, + bigInt64Array, fakeBigInt64Array, stealthyBigInt64Array, + bigUint64Array, fakeBigUint64Array, stealthyBigUint64Array, + ]; + + const expected = { + isArrayBufferView: [ + buffer, + dataView, stealthyDataView, + uint8Array, stealthyUint8Array, + uint8ClampedArray, stealthyUint8ClampedArray, + uint16Array, stealthyUint16Array, + uint32Array, stealthyUint32Array, + int8Array, stealthyInt8Array, + int16Array, stealthyInt16Array, + int32Array, stealthyInt32Array, + float32Array, stealthyFloat32Array, + float64Array, stealthyFloat64Array, + bigInt64Array, stealthyBigInt64Array, + bigUint64Array, stealthyBigUint64Array, + ], + isTypedArray: [ + buffer, + uint8Array, stealthyUint8Array, + uint8ClampedArray, stealthyUint8ClampedArray, + uint16Array, stealthyUint16Array, + uint32Array, stealthyUint32Array, + int8Array, stealthyInt8Array, + int16Array, stealthyInt16Array, + int32Array, stealthyInt32Array, + float32Array, stealthyFloat32Array, + float64Array, stealthyFloat64Array, + bigInt64Array, stealthyBigInt64Array, + bigUint64Array, stealthyBigUint64Array, + ], + isUint8Array: [ + buffer, uint8Array, stealthyUint8Array, + ], + isUint8ClampedArray: [ + uint8ClampedArray, stealthyUint8ClampedArray, + ], + isUint16Array: [ + uint16Array, stealthyUint16Array, + ], + isUint32Array: [ + uint32Array, stealthyUint32Array, + ], + isInt8Array: [ + int8Array, stealthyInt8Array, + ], + isInt16Array: [ + int16Array, stealthyInt16Array, + ], + isInt32Array: [ + int32Array, stealthyInt32Array, + ], + isFloat32Array: [ + float32Array, stealthyFloat32Array, + ], + isFloat64Array: [ + float64Array, stealthyFloat64Array, + ], + isBigInt64Array: [ + bigInt64Array, stealthyBigInt64Array, + ], + isBigUint64Array: [ + bigUint64Array, stealthyBigUint64Array, + ] + }; + + for (const testedFunc of Object.keys(expected)) { + const func = types[testedFunc]; + const yup = []; + for (const value of all) { + if (func(value)) { + yup.push(value); + } + } + console.log('Testing', testedFunc); + assert.deepStrictEqual(yup, expected[testedFunc]); + } +} + +// (skipped) +// (async () => { +// const m = new vm.SourceTextModule(''); +// await m.link(() => 0); +// await m.evaluate(); +// assert.ok(types.isModuleNamespaceObject(m.namespace)); +// })().then(common.mustCall()); + +{ + // eslint-disable-next-line node-core/crypto-check + if (common.hasCrypto) { + const crypto = require('crypto'); + assert.ok(!types.isKeyObject(crypto.createHash('sha1'))); + } + assert.ok(!types.isCryptoKey()); + assert.ok(!types.isKeyObject()); +} diff --git a/test/js/web/websocket/autobahn.test.ts b/test/js/web/websocket/autobahn.test.ts index 931db2794fef9a..edc472883da709 100644 --- a/test/js/web/websocket/autobahn.test.ts +++ b/test/js/web/websocket/autobahn.test.ts @@ -1,16 +1,20 @@ import { which } from "bun"; import { afterAll, describe, expect, it } from "bun:test"; import child_process from "child_process"; -import { tempDirWithFiles } from "harness"; - +import { tempDirWithFiles, isLinux } from "harness"; const dockerCLI = which("docker") as string; function isDockerEnabled(): boolean { if (!dockerCLI) { return false; } + // TODO: investigate why its not starting on Linux arm64 + if (isLinux && process.arch === "arm64") { + return false; + } + try { - const info = child_process.execSync(`${dockerCLI} info`, { stdio: "ignore" }); + const info = child_process.execSync(`${dockerCLI} info`, { stdio: ["ignore", "pipe", "inherit"] }); return info.toString().indexOf("Server Version:") !== -1; } catch { return false; @@ -19,7 +23,7 @@ function isDockerEnabled(): boolean { if (isDockerEnabled()) { describe("autobahn", async () => { - const url = "ws://localhost:9001"; + const url = "ws://localhost:9002"; const agent = encodeURIComponent("bun/1.0.0"); let docker: child_process.ChildProcessWithoutNullStreams | null = null; const { promise, resolve } = Promise.withResolvers(); @@ -29,7 +33,7 @@ if (isDockerEnabled()) { // ], const CWD = tempDirWithFiles("autobahn", { "fuzzingserver.json": `{ - "url": "ws://127.0.0.1:9001", + "url": "ws://127.0.0.1:9002", "outdir": "./", "cases": ["*"], "exclude-agent-cases": {} @@ -48,7 +52,7 @@ if (isDockerEnabled()) { "-v", `${CWD}:/reports`, "-p", - "9001:9001", + "9002:9002", "--name", "fuzzingserver", "crossbario/autobahn-testsuite", diff --git a/test/package.json b/test/package.json index 9c9d51d496c4a6..edc248dadfa608 100644 --- a/test/package.json +++ b/test/package.json @@ -20,6 +20,8 @@ "@remix-run/serve": "2.10.3", "@resvg/resvg-js": "2.4.1", "@swc/core": "1.3.38", + "@testing-library/jest-dom": "6.6.3", + "@testing-library/react": "16.1.0", "aws-cdk-lib": "2.148.0", "axios": "1.6.8", "body-parser": "1.20.2", diff --git a/test/regression/issue/16312.test.ts b/test/regression/issue/16312.test.ts new file mode 100644 index 00000000000000..15b9019c0d17e4 --- /dev/null +++ b/test/regression/issue/16312.test.ts @@ -0,0 +1,13 @@ +import { test, afterEach, expect } from "bun:test"; +import { cleanup } from "@testing-library/react"; +import * as matchers from "@testing-library/jest-dom/matchers"; + +expect.extend(matchers); +afterEach(() => { + cleanup(); +}); + +test("expect extended", () => { + // @ts-ignore + expect(expect.toBeInTheDocument).not.toBe(undefined); +});