diff options
-rw-r--r-- | test/js/deno/abort/abort-controller.test.ts | 66 | ||||
-rw-r--r-- | test/js/deno/harness.ts | 226 | ||||
-rw-r--r-- | test/js/deno/html/blob.test.ts | 117 | ||||
-rw-r--r-- | test/js/deno/resources/imports.json | 4 | ||||
-rw-r--r-- | test/js/deno/resources/tests.json | 10 | ||||
-rw-r--r-- | test/js/deno/scripts/postinstall.ts | 31 | ||||
-rw-r--r-- | test/tsconfig.json | 4 |
7 files changed, 457 insertions, 1 deletions
diff --git a/test/js/deno/abort/abort-controller.test.ts b/test/js/deno/abort/abort-controller.test.ts new file mode 100644 index 000000000..ba386d021 --- /dev/null +++ b/test/js/deno/abort/abort-controller.test.ts @@ -0,0 +1,66 @@ +// Updated: Wed, 08 Mar 2023 00:55:15 GMT +// URL: https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/abort_controller_test.ts +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assert, assertEquals } from "deno:harness"; + +Deno.test(function basicAbortController() { + const controller = new AbortController(); + assert(controller); + const { signal } = controller; + assert(signal); + assertEquals(signal.aborted, false); + controller.abort(); + assertEquals(signal.aborted, true); +}); + +Deno.test(function signalCallsOnabort() { + const controller = new AbortController(); + const { signal } = controller; + let called = false; + signal.onabort = (evt) => { + assert(evt); + assertEquals(evt.type, "abort"); + called = true; + }; + controller.abort(); + assert(called); +}); + +Deno.test(function signalEventListener() { + const controller = new AbortController(); + const { signal } = controller; + let called = false; + signal.addEventListener("abort", function (ev) { + assert(this === signal); + assertEquals(ev.type, "abort"); + called = true; + }); + controller.abort(); + assert(called); +}); + +Deno.test(function onlyAbortsOnce() { + const controller = new AbortController(); + const { signal } = controller; + let called = 0; + signal.addEventListener("abort", () => called++); + signal.onabort = () => { + called++; + }; + controller.abort(); + assertEquals(called, 2); + controller.abort(); + assertEquals(called, 2); +}); + +Deno.test(function controllerHasProperToString() { + const actual = Object.prototype.toString.call(new AbortController()); + assertEquals(actual, "[object AbortController]"); +}); + +Deno.test(function abortReason() { + const signal = AbortSignal.abort("hey!"); + assertEquals(signal.aborted, true); + assertEquals(signal.reason, "hey!"); +}); diff --git a/test/js/deno/harness.ts b/test/js/deno/harness.ts new file mode 100644 index 000000000..167860d1f --- /dev/null +++ b/test/js/deno/harness.ts @@ -0,0 +1,226 @@ +// Deno's test utilities implemented using expect(). +// https://github.com/denoland/deno/blob/main/cli/tests/unit/test_util.ts + +import { concatArrayBuffers } from "bun"; +import { it, expect } from "bun:test"; + +export function test(fn: () => void): void { + it(fn.name, fn); +} + +export function assert(condition: unknown, message?: string): asserts condition is true { + if (message) { + it(message, () => assert(condition)); + } else { + expect(condition).toBeTruthy(); + } +} + +export function assertFalse(condition: unknown, message?: string): asserts condition is false { + if (message) { + it(message, () => assertFalse(condition)); + } else { + expect(condition).toBeFalsy(); + } +} + +export function assertEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertEquals(actual, expected)); + } else { + expect(actual).toEqual(expected); + } +} + +export function assertExists(value: unknown, message?: string): void { + if (message) { + it(message, () => assertExists(value)); + } else { + expect(value).toBeDefined(); + } +} + +export function assertNotEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertNotEquals(actual, expected)); + } else { + expect(actual).not.toEqual(expected); + } +} + +export function assertStrictEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertStrictEquals(actual, expected)); + } else { + expect(actual).toStrictEqual(expected); + } +} + +export function assertNotStrictEquals(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertNotStrictEquals(actual, expected)); + } else { + expect(actual).not.toStrictEqual(expected); + } +} + +export function assertAlmostEquals(actual: unknown, expected: number, epsilon: number = 1e-7, message?: string): void { + if (message) { + it(message, () => assertAlmostEquals(actual, expected)); + } else if (typeof actual === "number") { + // TODO: toBeCloseTo() + expect(Math.abs(actual - expected)).toBeLessThanOrEqual(epsilon); + } else { + expect(typeof actual).toBe("number"); + } +} + +export function assertInstanceOf(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertInstanceOf(actual, expected)); + } else if (typeof actual === "object") { + if (actual !== null) { + expect(actual).toHaveProperty("constructor", expected); + } else { + expect(actual).not.toBeNull(); + } + } else { + expect(typeof actual).toBe("object"); + } +} + +export function assertNotInstanceOf(actual: unknown, expected: unknown, message?: string): void { + if (message) { + it(message, () => assertNotInstanceOf(actual, expected)); + } else if (typeof actual === "object") { + if (actual !== null) { + expect(actual).not.toHaveProperty("constructor", expected); + } else { + expect(actual).not.toBeNull(); + } + } else { + expect(typeof actual).toBe("object"); + } +} + +export function assertStringIncludes(actual: unknown, expected: string, message?: string): void { + if (message) { + it(message, () => assertStringIncludes(actual, expected)); + } else if (typeof actual === "string") { + expect(actual).toContain(expected); + } else { + expect(typeof actual).toBe("string"); + } +} + +export function assertArrayIncludes(actual: unknown, expected: unknown[], message?: string): void { + if (message) { + it(message, () => assertArrayIncludes(actual, expected)); + } else if (Array.isArray(actual)) { + for (const value of expected) { + expect(actual).toContain(value); + } + } else { + expect(Array.isArray(actual)).toBe(true); + } +} + +export function assertMatch(actual: unknown, expected: RegExp, message?: string): void { + if (message) { + it(message, () => assertMatch(actual, expected)); + } else if (typeof actual === "string") { + expect(expected.test(actual)).toBe(true); + } else { + expect(typeof actual).toBe("string"); + } +} + +export function assertNotMatch(actual: unknown, expected: RegExp, message?: string): void { + if (message) { + it(message, () => assertNotMatch(actual, expected)); + } else if (typeof actual === "string") { + expect(expected.test(actual)).toBe(false); + } else { + expect(typeof actual).toBe("string"); + } +} + +export function assertObjectMatch(actual: unknown, expected: Record<PropertyKey, unknown>, message?: string): void { + if (message) { + it(message, () => assertObjectMatch(actual, expected)); + } else if (typeof actual === "object") { + // TODO: toMatchObject() + if (actual !== null) { + const expectedKeys = Object.keys(expected); + for (const key of Object.keys(actual)) { + if (!expectedKeys.includes(key)) { + // @ts-ignore + delete actual[key]; + } + } + expect(actual).toEqual(expected); + } else { + expect(actual).not.toBeNull(); + } + } else { + expect(typeof actual).toBe("object"); + } +} + +export function assertThrows(fn: () => void, message?: string): void { + if (message) { + it(message, () => assertThrows(fn)); + } else { + try { + fn(); + } catch (error) { + expect(error).toBeDefined(); + return; + } + throw new Error("Expected an error to be thrown"); + } +} + +export async function assertRejects(fn: () => Promise<unknown>, message?: string): Promise<void> { + if (message) { + it(message, () => assertRejects(fn)); + } else { + try { + await fn(); + } catch (error) { + expect(error).toBeDefined(); + return; + } + throw new Error("Expected an error to be thrown"); + } +} + +export function equal(a: unknown, b: unknown): boolean { + return Bun.deepEquals(a, b); +} + +export function fail(message: string): never { + throw new Error(message); +} + +export function unimplemented(message: string): never { + throw new Error(`Unimplemented: ${message}`); +} + +export function unreachable(): never { + throw new Error("Unreachable"); +} + +export function concat(...buffers: Uint8Array[]): Uint8Array { + return new Uint8Array(concatArrayBuffers(buffers)); +} + +export function inspect(...args: unknown[]): string { + return Bun.inspect(...args); +} + +// @ts-expect-error +globalThis["Deno"] = { + test, + inspect, +}; diff --git a/test/js/deno/html/blob.test.ts b/test/js/deno/html/blob.test.ts new file mode 100644 index 000000000..0b50c8452 --- /dev/null +++ b/test/js/deno/html/blob.test.ts @@ -0,0 +1,117 @@ +// Updated: Wed, 08 Mar 2023 00:55:15 GMT +// URL: https://raw.githubusercontent.com/denoland/deno/main/cli/tests/unit/blob_test.ts +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { assert, assertEquals, assertStringIncludes } from "deno:harness"; +import { concat } from "deno:harness"; + +Deno.test(function blobString() { + const b1 = new Blob(["Hello World"]); + const str = "Test"; + const b2 = new Blob([b1, str]); + assertEquals(b2.size, b1.size + str.length); +}); + +Deno.test(function blobBuffer() { + const buffer = new ArrayBuffer(12); + const u8 = new Uint8Array(buffer); + const f1 = new Float32Array(buffer); + const b1 = new Blob([buffer, u8]); + assertEquals(b1.size, 2 * u8.length); + const b2 = new Blob([b1, f1]); + assertEquals(b2.size, 3 * u8.length); +}); + +Deno.test(function blobSlice() { + const blob = new Blob(["Deno", "Foo"]); + const b1 = blob.slice(0, 3, "Text/HTML"); + assert(b1 instanceof Blob); + assertEquals(b1.size, 3); + assertEquals(b1.type, "text/html"); + const b2 = blob.slice(-1, 3); + assertEquals(b2.size, 0); + const b3 = blob.slice(100, 3); + assertEquals(b3.size, 0); + const b4 = blob.slice(0, 10); + assertEquals(b4.size, blob.size); +}); + +Deno.test(function blobInvalidType() { + const blob = new Blob(["foo"], { + type: "\u0521", + }); + + assertEquals(blob.type, ""); +}); + +Deno.test(function blobShouldNotThrowError() { + let hasThrown = false; + + try { + // deno-lint-ignore no-explicit-any + const options1: any = { + ending: "utf8", + hasOwnProperty: "hasOwnProperty", + }; + const options2 = Object.create(null); + new Blob(["Hello World"], options1); + new Blob(["Hello World"], options2); + } catch { + hasThrown = true; + } + + assertEquals(hasThrown, false); +}); + +/* TODO https://github.com/denoland/deno/issues/7540 +Deno.test(function nativeEndLine() { + const options = { + ending: "native", + } as const; + const blob = new Blob(["Hello\nWorld"], options); + + assertEquals(blob.size, Deno.build.os === "windows" ? 12 : 11); +}); +*/ + +Deno.test(async function blobText() { + const blob = new Blob(["Hello World"]); + assertEquals(await blob.text(), "Hello World"); +}); + +Deno.test(async function blobStream() { + const blob = new Blob(["Hello World"]); + const stream = blob.stream(); + assert(stream instanceof ReadableStream); + const reader = stream.getReader(); + let bytes = new Uint8Array(); + const read = async (): Promise<void> => { + const { done, value } = await reader.read(); + if (!done && value) { + bytes = concat(bytes, value); + return read(); + } + }; + await read(); + const decoder = new TextDecoder(); + assertEquals(decoder.decode(bytes), "Hello World"); +}); + +Deno.test(async function blobArrayBuffer() { + const uint = new Uint8Array([102, 111, 111]); + const blob = new Blob([uint]); + assertEquals(await blob.arrayBuffer(), uint.buffer); +}); + +Deno.test(function blobConstructorNameIsBlob() { + const blob = new Blob(); + assertEquals(blob.constructor.name, "Blob"); +}); + +Deno.test(function blobCustomInspectFunction() { + const blob = new Blob(); + assertEquals( + Deno.inspect(blob), + `Blob { size: 0, type: "" }`, + ); + assertStringIncludes(Deno.inspect(Blob.prototype), "Blob"); +}); diff --git a/test/js/deno/resources/imports.json b/test/js/deno/resources/imports.json new file mode 100644 index 000000000..0c75b5739 --- /dev/null +++ b/test/js/deno/resources/imports.json @@ -0,0 +1,4 @@ +[ + "test_util.ts", + "test_util/std/bytes/concat.ts" +] diff --git a/test/js/deno/resources/tests.json b/test/js/deno/resources/tests.json new file mode 100644 index 000000000..1d7e0f52a --- /dev/null +++ b/test/js/deno/resources/tests.json @@ -0,0 +1,10 @@ +[ + { + "path": "abort/abort-controller.test.ts", + "remotePath": "unit/abort_controller_test.ts" + }, + { + "path": "html/blob.test.ts", + "remotePath": "unit/blob_test.ts" + } +]
\ No newline at end of file diff --git a/test/js/deno/scripts/postinstall.ts b/test/js/deno/scripts/postinstall.ts new file mode 100644 index 000000000..4031b29df --- /dev/null +++ b/test/js/deno/scripts/postinstall.ts @@ -0,0 +1,31 @@ +import { mkdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import imports from "../resources/imports.json"; +import tests from "../resources/tests.json"; + +for (const test of tests) { + const path = join(import.meta.dir, "..", test.path); + const url = new URL( + test.remotePath, + "https://raw.githubusercontent.com/denoland/deno/main/cli/tests/" + ); + const response = await fetch(url); + console.log(response.status, url.toString(), "->", test.path); + if (!response.ok) { + throw new Error( + `Failed to download from GitHub: ${url} [status: ${response.status}]` + ); + } + let body = await response.text(); + for (const query of imports) { + const pattern = new RegExp(`"(.*${query})"`, "gmi"); + body = body.replace(pattern, "\"deno:harness\""); + } + const src = `// Updated: ${response.headers.get("Date")} +// URL: ${url} +${body}`; + try { + mkdirSync(dirname(path)); + } catch {} + await Bun.write(path, src); +} diff --git a/test/tsconfig.json b/test/tsconfig.json index e879cd5f5..12ad19dc7 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -12,12 +12,14 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "allowJs": true, + "resolveJsonModule": true, "types": ["bun-types"], "baseUrl": ".", "paths": { "harness": ["harness.ts"], "mkfifo": ["mkfifo.ts"], - "node-harness": ["js/node/harness.ts"] + "node-harness": ["js/node/harness.ts"], + "deno:harness": ["js/deno/harness.ts"] } } } |