From ac9c21d58e8061dfaa89029964cae41b0327be7c Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 4 Oct 2025 18:00:25 +0200 Subject: [PATCH] Tests for extension methods --- src/util/util/extensions/Array.test.ts | 96 +++++++++++++++++++++++++ src/util/util/extensions/Global.test.ts | 16 +++++ src/util/util/extensions/Global.ts | 5 +- src/util/util/extensions/Math.test.ts | 19 +++++ src/util/util/extensions/Object.test.ts | 26 +++++++ src/util/util/extensions/Object.ts | 10 +-- src/util/util/extensions/Url.test.ts | 37 ++++++++++ 7 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 src/util/util/extensions/Array.test.ts create mode 100644 src/util/util/extensions/Global.test.ts create mode 100644 src/util/util/extensions/Math.test.ts create mode 100644 src/util/util/extensions/Object.test.ts create mode 100644 src/util/util/extensions/Url.test.ts diff --git a/src/util/util/extensions/Array.test.ts b/src/util/util/extensions/Array.test.ts new file mode 100644 index 00000000..6395ddcf --- /dev/null +++ b/src/util/util/extensions/Array.test.ts @@ -0,0 +1,96 @@ +import moduleAlias from "module-alias"; +moduleAlias(); +import './Array'; +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe("Array extensions", () => { + + it("containsAll", () => { + const arr = [1, 2, 3, 4, 5]; + assert(arr.containsAll([1, 2])); + assert(!arr.containsAll([1, 6])); + assert(arr.containsAll([])); + assert([].containsAll([])); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + assert(![].containsAll([1])); + }); + + it("partition", () => { + const arr = [1, 2, 3, 4, 5]; + const [even, odd] = arr.partition((n) => n % 2 === 0); + assert.deepEqual(even, [2, 4]); + assert.deepEqual(odd, [1, 3, 5]); + }); + + it("single", () => { + const arr = [1, 2, 3, 4, 5]; + assert.strictEqual(arr.single((n) => n === 3), 3); + assert.strictEqual(arr.single((n) => n === 6), null); + assert.throws(() => arr.single((n) => n > 2)); + }); + + it("forEachAsync", async () => { + const arr = [1, 2, 3]; + let sum = 0; + await arr.forEachAsync(async (n) => { + sum += n; + }); + assert.strictEqual(sum, 6); + }); + + it("remove", () => { + const arr = [1, 2, 3, 4, 5]; + arr.remove(3); + assert.deepEqual(arr, [1, 2, 4, 5]); + arr.remove(6); + assert.deepEqual(arr, [1, 2, 4, 5]); + }); + + it("first", () => { + const arr = [1, 2, 3]; + assert.strictEqual(arr.first(), 1); + assert.strictEqual([].first(), undefined); + }); + + it("last", () => { + const arr = [1, 2, 3]; + assert.strictEqual(arr.last(), 3); + assert.strictEqual([].last(), undefined); + }); + + it("distinct", () => { + const arr = [1, 2, 2, 3, 3, 3]; + assert.deepEqual(arr.distinct(), [1, 2, 3]); + assert.deepEqual([].distinct(), []); + }); + + it("distinctBy", () => { + const arr = [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }]; + assert.deepEqual(arr.distinctBy((x) => x.id), [{ id: 1 }, { id: 2 }, { id: 3 }]); + assert.deepEqual([].distinctBy((x) => x), []); + }); + + it("intersect", () => { + const arr1 = [1, 2, 3, 4]; + const arr2 = [3, 4, 5, 6]; + assert.deepEqual(arr1.intersect(arr2), [3, 4]); + assert.deepEqual(arr1.intersect([]), []); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + assert.deepEqual([].intersect(arr2), []); + }); + + it("except", () => { + const arr1 = [1, 2, 3, 4]; + const arr2 = [3, 4, 5, 6]; + assert.deepEqual(arr1.except(arr2), [1, 2]); + assert.deepEqual(arr1.except([]), [1, 2, 3, 4]); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + assert.deepEqual([].except(arr2), []); + }); + +}); \ No newline at end of file diff --git a/src/util/util/extensions/Global.test.ts b/src/util/util/extensions/Global.test.ts new file mode 100644 index 00000000..0c6a93dc --- /dev/null +++ b/src/util/util/extensions/Global.test.ts @@ -0,0 +1,16 @@ +import moduleAlias from "module-alias"; +moduleAlias(); +import './Global'; +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe("Global extensions", () => { + + it("sleep", async () => { + const start = Date.now(); + await sleep(100); + const duration = Date.now() - start; + assert(duration >= 100, `Sleep duration was less than expected: ${duration}ms`); + }); + +}); \ No newline at end of file diff --git a/src/util/util/extensions/Global.ts b/src/util/util/extensions/Global.ts index 0dc39b42..4634573d 100644 --- a/src/util/util/extensions/Global.ts +++ b/src/util/util/extensions/Global.ts @@ -6,10 +6,7 @@ export function globalSleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -if (!globalThis.sleep) { +if (!globalThis.sleep) globalThis.sleep = function (ms: number): Promise { return globalSleep(ms); }; -} - -export {}; diff --git a/src/util/util/extensions/Math.test.ts b/src/util/util/extensions/Math.test.ts new file mode 100644 index 00000000..5f112dc0 --- /dev/null +++ b/src/util/util/extensions/Math.test.ts @@ -0,0 +1,19 @@ +import moduleAlias from "module-alias"; +moduleAlias(); +import './Math'; +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe("Math extensions", () => { + + it("clamp", async () => { + assert.strictEqual(Math.clamp(5, 1, 10), 5); + assert.strictEqual(Math.clamp(0, 1, 10), 1); + assert.strictEqual(Math.clamp(15, 1, 10), 10); + assert.strictEqual(Math.clamp(-5, -10, -1), -5); + assert.strictEqual(Math.clamp(-15, -10, -1), -10); + assert.strictEqual(Math.clamp(-0.5, -1, 0), -0.5); + assert.strictEqual(Math.clamp(1.5, 1, 2), 1.5); + }); + +}); \ No newline at end of file diff --git a/src/util/util/extensions/Object.test.ts b/src/util/util/extensions/Object.test.ts new file mode 100644 index 00000000..45023bfd --- /dev/null +++ b/src/util/util/extensions/Object.test.ts @@ -0,0 +1,26 @@ +import moduleAlias from "module-alias"; +moduleAlias(); +import "./Object"; +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +describe("Object extensions", () => { + it("forEach", async () => { + const obj = { a: 1, b: 2, c: 3 }; + const keys: string[] = []; + const values: number[] = []; + obj.forEach((value, key) => { + keys.push(key); + values.push(value); + }); + console.log(keys, values); + assert.deepEqual(keys, ["a", "b", "c"]); + assert.deepEqual(values, [1, 2, 3]); + }); + + it("map", async () => { + const obj = { a: 1, b: 2, c: 3 }; + const result = obj.map((value, key) => `${key}:${value}`); + assert.deepEqual(result, { a: "a:1", b: "b:2", c: "c:3" }); + }); +}); diff --git a/src/util/util/extensions/Object.ts b/src/util/util/extensions/Object.ts index 46da5965..34bf81a0 100644 --- a/src/util/util/extensions/Object.ts +++ b/src/util/util/extensions/Object.ts @@ -1,17 +1,17 @@ declare global { interface Object { - forEach(callback: (value: any, key: string, object: any) => void): void; - map(callback: (value: any, key: string, object: any) => T): T[]; + forEach(callback: (value: unknown, key: string, object: unknown) => void): void; + map(callback: (value: unknown, key: string, object: unknown) => T): object; } } -export function objectForEach(obj: any, callback: (value: any, key: string, object: any) => void): void { +export function objectForEach(obj: never, callback: (value: T, key: string, object: unknown) => void): void { Object.keys(obj).forEach((key) => { callback(obj[key], key, obj); }); } -export function objectMap(obj: any, callback: (value: any, key: string, object: any) => T): T[] { +export function objectMap(obj: object, callback: (value: unknown, key: string, object: unknown) => T): T[] { return Object.keys(obj).map((key) => { return callback(obj[key], key, obj); }); @@ -21,9 +21,11 @@ if (!Object.prototype.forEach) Object.defineProperty(Object.prototype, "forEach", { value: objectForEach, enumerable: false, + writable: true }); if (!Object.prototype.map) Object.defineProperty(Object.prototype, "map", { value: objectMap, enumerable: false, + writable: true }); \ No newline at end of file diff --git a/src/util/util/extensions/Url.test.ts b/src/util/util/extensions/Url.test.ts new file mode 100644 index 00000000..37afd0a2 --- /dev/null +++ b/src/util/util/extensions/Url.test.ts @@ -0,0 +1,37 @@ +import moduleAlias from "module-alias"; +moduleAlias(); +import './Url'; +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe("URL extensions", () => { + + it("normalize", async () => { + const tests: [string, string][] = [ + ["http://example.com", "http://example.com/"], + ["http://example.com/", "http://example.com/"], + ["http://example.com/path/", "http://example.com/path"], + ["http://example.com/path//", "http://example.com/path/"], + ["http://example.com/path?b=2&a=1", "http://example.com/path?a=1&b=2"], + ["http://example.com/path?b=2&a=1&", "http://example.com/path?a=1&b=2"], + ["http://example.com/path?", "http://example.com/path"], + ["http://example.com/path#fragment", "http://example.com/path"], + ["http://example.com/path/?b=2&a=1#fragment", "http://example.com/path?a=1&b=2"], + ["ftp://example.com/resource/", "ftp://example.com/resource"], + ["https://example.com/resource?z=3&y=2&x=1", "https://example.com/resource?x=1&y=2&z=3"], + ["https://example.com/resource?z=3&y=2&x=1#", "https://example.com/resource?x=1&y=2&z=3"], + ["https://example.com/resource?z=3&y=2&x=1#section", "https://example.com/resource?x=1&y=2&z=3"], + ["https://example.com/resource/?z=3&y=2&x=1#section", "https://example.com/resource?x=1&y=2&z=3"], + ["https://example.com/resource//?z=3&y=2&x=1#section", "https://example.com/resource/?x=1&y=2&z=3"], + ["https://example.com/", "https://example.com/"], + ["https://example.com", "https://example.com/"], + ]; + for (const [input, expected] of tests) { + assert.doesNotThrow(() => new URL(input), `URL("${input}") should not throw`); + const url = new URL(input); + const normalized = url.normalize(); + assert.strictEqual(normalized, expected, `normalize("${input}") = "${normalized}", expected "${expected}"`); + } + }); + +}); \ No newline at end of file