Files
bun.sh/test/js/bun/globals.test.js
Dylan Conway ddfc8555f7 crypto: fix test-crypto-random.js (#18044)
Co-authored-by: Meghan Denny <meghan@bun.sh>
2025-03-11 18:21:20 -07:00

260 lines
7.9 KiB
JavaScript

import { describe, expect, it } from "bun:test";
import { bunEnv, bunExe } from "harness";
import path from "path";
it("ERR_INVALID_THIS", () => {
try {
Request.prototype.formData.call(undefined);
expect.unreachable();
} catch (e) {
expect(e.code).toBe("ERR_INVALID_THIS");
expect(e.name).toBe("TypeError");
expect(e.message).toBe("Expected this to be instanceof Request");
}
try {
Request.prototype.formData.call(null);
expect.unreachable();
} catch (e) {
expect(e.code).toBe("ERR_INVALID_THIS");
expect(e.name).toBe("TypeError");
expect(e.message).toBe("Expected this to be instanceof Request, but received null");
}
try {
Request.prototype.formData.call(new (class Boop {})());
expect.unreachable();
} catch (e) {
expect(e.code).toBe("ERR_INVALID_THIS");
expect(e.name).toBe("TypeError");
expect(e.message).toBe("Expected this to be instanceof Request, but received an instance of Boop");
}
try {
Request.prototype.formData.call("hellooo");
expect.unreachable();
} catch (e) {
expect(e.code).toBe("ERR_INVALID_THIS");
expect(e.name).toBe("TypeError");
expect(e.message).toBe(`Expected this to be instanceof Request, but received type string ('hellooo')`);
}
});
it("extendable", () => {
const classes = [Blob, TextDecoder, TextEncoder, Request, Response, Headers, HTMLRewriter, Bun.Transpiler, Buffer];
for (let Class of classes) {
var Foo = class extends Class {};
var bar = Class === Request ? new Request({ url: "https://example.com" }) : new Foo();
expect(bar instanceof Class).toBe(true);
expect(!!Class.prototype).toBe(true);
expect(typeof Class.prototype).toBe("object");
}
expect(true).toBe(true);
});
it("writable", () => {
const classes = [
["TextDecoder", TextDecoder],
["Request", Request],
["Response", Response],
["Headers", Headers],
["Buffer", Buffer],
["Event", Event],
["DOMException", DOMException],
["EventTarget", EventTarget],
["ErrorEvent", ErrorEvent],
["CustomEvent", CustomEvent],
["CloseEvent", CloseEvent],
["File", File],
];
for (let [name, Class] of classes) {
globalThis[name] = 123;
expect(globalThis[name]).toBe(123);
globalThis[name] = Class;
expect(globalThis[name]).toBe(Class);
}
});
it("name", () => {
const classes = [
["Blob", Blob],
["TextDecoder", TextDecoder],
["TextEncoder", TextEncoder],
["Request", Request],
["Response", Response],
["Headers", Headers],
["HTMLRewriter", HTMLRewriter],
["Transpiler", Bun.Transpiler],
["Buffer", Buffer],
["File", File],
];
for (let [name, Class] of classes) {
expect(Class.name).toBe(name);
}
});
describe("File", () => {
it("constructor", () => {
const file = new File(["foo"], "bar.txt", { type: "text/plain;charset=utf-8" });
expect(file.name).toBe("bar.txt");
expect(file.type).toBe("text/plain;charset=utf-8");
expect(file.size).toBe(3);
expect(file.lastModified).toBeGreaterThan(0);
});
it("constructor with empty array", () => {
const file = new File([], "empty.txt", { type: "text/plain;charset=utf-8" });
expect(file.name).toBe("empty.txt");
expect(file.size).toBe(0);
expect(file.type).toBe("text/plain;charset=utf-8");
});
it("constructor with lastModified", () => {
const file = new File(["foo"], "bar.txt", { type: "text/plain;charset=utf-8", lastModified: 123 });
expect(file.name).toBe("bar.txt");
expect(file.type).toBe("text/plain;charset=utf-8");
expect(file.size).toBe(3);
expect(file.lastModified).toBe(123);
});
it("constructor with undefined name", () => {
const file = new File(["foo"], undefined);
expect(file.name).toBe("undefined");
expect(file.type).toBe("");
expect(file.size).toBe(3);
expect(file.lastModified).toBeGreaterThan(0);
});
it("constructor throws invalid args", () => {
const invalid = [[], [undefined], [null], [Symbol(), "foo"], [Symbol(), Symbol(), Symbol()]];
for (let args of invalid) {
expect(() => new File(...args)).toThrow();
}
});
it("constructor without new", () => {
const result = () => File();
expect(result).toThrow({
name: "TypeError",
message: "Class constructor File cannot be invoked without 'new'",
});
});
it("instanceof", () => {
const file = new File(["foo"], "bar.txt", { type: "text/plain;charset=utf-8" });
expect(file instanceof File).toBe(true);
expect(file instanceof Blob).toBe(true);
expect(file instanceof Object).toBe(true);
expect(file instanceof Function).toBe(false);
const blob = new Blob(["foo"], { type: "text/plain;charset=utf-8" });
expect(blob instanceof File).toBe(false);
});
it("extendable", async () => {
class Foo extends File {
constructor(...args) {
super(...args);
}
bar() {
return true;
}
text() {
return super.text();
}
}
const foo = new Foo(["foo"], "bar.txt", { type: "text/plain;charset=utf-8" });
expect(foo instanceof File).toBe(true);
expect(foo instanceof Blob).toBe(true);
expect(foo instanceof Object).toBe(true);
expect(foo instanceof Function).toBe(false);
expect(foo instanceof Foo).toBe(true);
expect(foo.bar()).toBe(true);
expect(foo.name).toBe("bar.txt");
expect(foo.type).toBe("text/plain;charset=utf-8");
expect(foo.size).toBe(3);
expect(foo.lastModified).toBeGreaterThanOrEqual(0);
expect(await foo.text()).toBe("foo");
});
});
it("globals are deletable", () => {
const { stdout, exitCode } = Bun.spawnSync({
cmd: [bunExe(), "run", path.join(import.meta.dir, "deletable-globals-fixture.js")],
env: bunEnv,
stderr: "inherit",
});
expect(stdout.toString().trim().endsWith("--pass--")).toBe(true);
expect(exitCode).toBe(0);
});
it("self is a getter", () => {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "self");
expect(descriptor.get).toBeInstanceOf(Function);
expect(descriptor.set).toBeInstanceOf(Function);
expect(descriptor.enumerable).toBe(true);
expect(descriptor.configurable).toBe(true);
expect(globalThis.self).toBe(globalThis);
});
it("errors thrown by native code should be TypeError", async () => {
expect(() => Bun.dns.prefetch()).toThrowError(TypeError);
expect(async () => await fetch("http://localhost", { body: "123" })).toThrowError(TypeError);
});
describe("globalThis.gc", () => {
/**
* @param {string} expr
* @param {string[]} args
* @returns {string}
*/
const runAndPrint = (expr, ...args) => {
const result = Bun.spawnSync([bunExe(), ...args, "--print", expr], {
env: bunEnv,
});
if (!result.success) throw new Error(result.stderr.toString("utf8"));
return result.stdout.toString("utf8").trim();
};
describe("when --expose-gc is not passed", () => {
it("globalThis.gc === undefined", () => {
expect(runAndPrint("typeof globalThis.gc")).toEqual("undefined");
});
it(".gc does not take up a property slot", () => {
expect(runAndPrint("'gc' in globalThis")).toEqual("false");
});
});
describe("when --expose-gc is passed", () => {
it("is a function", () => {
expect(runAndPrint("typeof globalThis.gc", "--expose-gc")).toEqual("function");
});
it("gc is the same as globalThis.gc", () => {
expect(runAndPrint("gc === globalThis.gc", "--expose-gc")).toEqual("true");
});
it("cleans up memory", () => {
const src = /* js */ `
let arr = []
for (let i = 0; i < 100; i++) {
arr.push(new Array(100_000));
}
arr.length = 0;
const before = process.memoryUsage().heapUsed;
globalThis.gc();
const after = process.memoryUsage().heapUsed;
return before - after;
`;
const expr = /* js */ `(function() { ${src} })()`;
const delta = Number.parseInt(runAndPrint(expr, "--expose-gc"));
expect(delta).not.toBeNaN();
expect(delta).toBeGreaterThanOrEqual(0);
});
});
});