diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 5e37833127e..c4deeef3c1f 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -66,15 +66,7 @@ import type { Localizable } from "#types/locales"; import type { Closed, Exact } from "#types/type-helpers"; import { coerceArray } from "#utils/array"; import type { Constructor } from "#utils/common"; -import { - BooleanHolder, - coerceArray, - NumberHolder, - randSeedFloat, - randSeedInt, - randSeedItem, - toDmgValue, -} from "#utils/common"; +import { BooleanHolder, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common"; import { toCamelCase } from "#utils/strings"; import i18next from "i18next"; diff --git a/src/data/pokemon/pokemon-data.ts b/src/data/pokemon/pokemon-data.ts index e213d55abfd..ed94a87f2af 100644 --- a/src/data/pokemon/pokemon-data.ts +++ b/src/data/pokemon/pokemon-data.ts @@ -16,7 +16,7 @@ import type { AttackMoveResult } from "#types/attack-move-result"; import type { IllusionData } from "#types/illusion-data"; import type { TurnMove } from "#types/turn-move"; import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers"; -import { setTypedArray } from "#utils/common"; +import { setTypedArray } from "#utils/array"; import { getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** diff --git a/test/utils/array.test.ts b/test/utils/array.test.ts index d0ed471edec..9265830ec86 100644 --- a/test/utils/array.test.ts +++ b/test/utils/array.test.ts @@ -1,122 +1,279 @@ -import { setTypedArray, subArray } from "#utils/array"; -import { describe, expect, it } from "vitest"; +import type { ReadonlyUint8Array } from "#types/typed-arrays"; +import { coerceArray, isTypedArray, setTypedArray, subArray } from "#utils/array"; +import { describe, expect, expectTypeOf, it } from "vitest"; /** - * Unit tests for the utility methods in `src/utils/array-utils.ts` + * Unit tests for the utility methods in `src/utils/array.ts` * @module */ -describe("subArray", () => { - it("returns the same array if length <= n (plain array)", () => { - const arr = [1, 2, 3]; - const result = subArray(arr, 5); - expect(result).toBe(arr); - expect(result).toEqual([1, 2, 3]); +describe("Utils - Array", () => { + describe("subArray", () => { + it("returns the same array if length <= n (plain array)", () => { + const arr = [1, 2, 3]; + const result = subArray(arr, 5); + expect(result).toBe(arr); + expect(result).toEqual([1, 2, 3]); + }); + + it("returns a sliced array if length > n (plain array)", () => { + const arr = [1, 2, 3, 4, 5]; + const result = subArray(arr, 3); + expect(result).not.toBe(arr); + expect(result).toEqual([1, 2, 3]); + }); + + it("returns the same typed array if length <= n", () => { + const arr = new Uint8Array([1, 2, 3]); + const result = subArray(arr, 5); + expect(result).toBe(arr); + expect(Array.from(result)).toEqual([1, 2, 3]); + }); + + it("returns a subarray if length > n (typed array)", () => { + const arr = new Uint8Array([1, 2, 3, 4, 5]); + const result = subArray(arr, 2); + expect(result).not.toBe(arr); + expect(Array.from(result)).toEqual([1, 2]); + }); + + it("returns empty array if n is 0 (plain array)", () => { + const arr = [1, 2, 3]; + const result = subArray(arr, 0); + expect(result).toEqual([]); + }); + + it("returns empty typed array if n is 0", () => { + const arr = new Uint8Array([1, 2, 3]); + const result = subArray(arr, 0); + expect(Array.from(result)).toEqual([]); + }); + + it("throws TypeError for non-array-like input", () => { + // @ts-expect-error + expect(() => subArray({ length: 4 }, 2)).toThrow(TypeError); + }); + + describe("output type inference", () => { + it("plain array input", () => { + const arr = [1, 2, 3, 4]; + const result = subArray(arr, 2); + expectTypeOf(result).toEqualTypeOf(); + }); + + it("typed array input", () => { + const arr = new Uint8Array([1, 2, 3, 4]); + const result = subArray(arr, 2); + // @ts-expect-error We get a questionable error about Uint8Array not being assignable to Uint8Array... + expectTypeOf(result).toEqualTypeOf(); + }); + + it("readonly array input", () => { + const arr: readonly number[] = [1, 2, 3, 4]; + const result = subArray(arr, 2); + expectTypeOf(result).toEqualTypeOf(); + }); + + it("readonly typed array input", () => { + const arr = new Uint8Array([1, 2, 3, 4]) as ReadonlyUint8Array; + const result = subArray(arr, 2); + expectTypeOf(result).toEqualTypeOf(); + }); + }); }); - it("returns a sliced array if length > n (plain array)", () => { - const arr = [1, 2, 3, 4, 5]; - const result = subArray(arr, 3); - expect(result).not.toBe(arr); - expect(result).toEqual([1, 2, 3]); - }); + describe("setTypedArray", () => { + it("sets values from source to target with no offset (fits exactly)", () => { + const target = new Uint8Array(3); + const source = [1, 2, 3]; + setTypedArray(target, source); + expect(Array.from(target)).toEqual([1, 2, 3]); + }); - it("returns the same typed array if length <= n", () => { - const arr = new Uint8Array([1, 2, 3]); - const result = subArray(arr, 5); - expect(result).toBe(arr); - expect(Array.from(result)).toEqual([1, 2, 3]); - }); + it("sets values from source to target with offset", () => { + const target = new Uint8Array([0, 0, 0, 0, 0]); + const source = [9, 8]; + setTypedArray(target, source, 2); + expect(Array.from(target)).toEqual([0, 0, 9, 8, 0]); + }); - it("returns a subarray if length > n (typed array)", () => { - const arr = new Uint8Array([1, 2, 3, 4, 5]); - const result = subArray(arr, 2); - expect(result).not.toBe(arr); - expect(Array.from(result)).toEqual([1, 2]); - }); + it("clamps source if it would overflow target", () => { + const target = new Uint8Array(4); + const source = [1, 2, 3, 4, 5, 6]; + setTypedArray(target, source, 2); + expect(Array.from(target)).toEqual([0, 0, 1, 2]); + }); - it("returns empty array if n is 0 (plain array)", () => { - const arr = [1, 2, 3]; - const result = subArray(arr, 0); - expect(result).toEqual([]); - }); + it("does nothing if offset < 0", () => { + const target = new Uint8Array([1, 2, 3]); + const source = [4, 5, 6]; + setTypedArray(target, source, -1); + expect(Array.from(target)).toEqual([1, 2, 3]); + }); - it("returns empty typed array if n is 0", () => { - const arr = new Uint8Array([1, 2, 3]); - const result = subArray(arr, 0); - expect(Array.from(result)).toEqual([]); - }); - - it("throws TypeError for non-array-like input", () => { - // @ts-expect-error - expect(() => subArray({ length: 4 }, 2)).toThrow(TypeError); - }); -}); - -describe("setTypedArray", () => { - it("sets values from source to target with no offset (fits exactly)", () => { - const target = new Uint8Array(3); - const source = [1, 2, 3]; - setTypedArray(target, source); - expect(Array.from(target)).toEqual([1, 2, 3]); - }); - - it("sets values from source to target with offset", () => { - const target = new Uint8Array([0, 0, 0, 0, 0]); - const source = [9, 8]; - setTypedArray(target, source, 2); - expect(Array.from(target)).toEqual([0, 0, 9, 8, 0]); - }); - - it("clamps source if it would overflow target", () => { - const target = new Uint8Array(4); - const source = [1, 2, 3, 4, 5, 6]; - setTypedArray(target, source, 2); - expect(Array.from(target)).toEqual([0, 0, 1, 2]); - }); - - it("does nothing if offset < 0", () => { - const target = new Uint8Array([1, 2, 3]); - const source = [4, 5, 6]; - setTypedArray(target, source, -1); - expect(Array.from(target)).toEqual([1, 2, 3]); - }); - - it("does nothing if offset >= target.length", () => { - const target = new Uint8Array([1, 2, 3]); - const source = [4, 5, 6]; - setTypedArray(target, source, 3); - expect(Array.from(target)).toEqual([1, 2, 3]); - }); - - it("does nothing if source is empty", () => { - const target = new Uint8Array([1, 2, 3]); - const source: number[] = []; - setTypedArray(target, source, 1); - expect(Array.from(target)).toEqual([1, 2, 3]); - }); - - it("works with typed array as source", () => { - const target = new Uint8Array(4); - const source = new Uint8Array([7, 8, 9]); - setTypedArray(target, source, 1); - expect(Array.from(target)).toEqual([0, 7, 8, 9]); - }); - - it("clamps source typed array if it would overflow target", () => { - const target = new Uint8Array(3); - const source = new Uint8Array([1, 2, 3, 4, 5]); - setTypedArray(target, source, 1); - expect(Array.from(target)).toEqual([0, 1, 2]); - }); - - it("works with BigUint64Array and bigint[]", () => { - if (typeof BigUint64Array !== "undefined") { - const target = new BigUint64Array(3); - const source = [1n, 2n, 3n, 4n]; + it("does nothing if offset >= target.length", () => { + const target = new Uint8Array([1, 2, 3]); + const source = [4, 5, 6]; + setTypedArray(target, source, 3); + expect(Array.from(target)).toEqual([1, 2, 3]); + }); + it("does nothing if source is empty", () => { + const target = new Uint8Array([1, 2, 3]); + const source: number[] = []; setTypedArray(target, source, 1); - expect(Array.from(target)).toEqual([0n, 1n, 2n]); - } + expect(Array.from(target)).toEqual([1, 2, 3]); + }); + + it("works with typed array as source", () => { + const target = new Uint8Array(4); + const source = new Uint8Array([7, 8, 9]); + setTypedArray(target, source, 1); + expect(Array.from(target)).toEqual([0, 7, 8, 9]); + }); + + it("clamps source typed array if it would overflow target", () => { + const target = new Uint8Array(3); + const source = new Uint8Array([1, 2, 3, 4, 5]); + setTypedArray(target, source, 1); + expect(Array.from(target)).toEqual([0, 1, 2]); + }); + + it("works with BigUint64Array and bigint[]", () => { + if (typeof BigUint64Array !== "undefined") { + const target = new BigUint64Array(3); + const source = [1n, 2n, 3n, 4n]; + + setTypedArray(target, source, 1); + expect(Array.from(target)).toEqual([0n, 1n, 2n]); + } + }); + }); + + describe("coerceArray", () => { + it("returns the same array if input is already an array", () => { + const arr = [1, 2, 3]; + const result = coerceArray(arr); + expect(result).toBe(arr); + expect(result).toEqual([1, 2, 3]); + }); + + it("wraps a non-array input in an array", () => { + const input = 42; + const result = coerceArray(input); + expect(result).toEqual([42]); + }); + + it("wraps an object in an array", () => { + const obj = { a: 1 }; + const result = coerceArray(obj); + expect(result).toEqual([{ a: 1 }]); + }); + + it("wraps a string in an array", () => { + const str = "hello"; + const result = coerceArray(str); + expect(result).toEqual(["hello"]); + }); + + it("wraps null in an array", () => { + const result = coerceArray(null); + expect(result).toEqual([null]); + }); + + it("wraps undefined in an array", () => { + const result = coerceArray(undefined); + expect(result).toEqual([undefined]); + }); + + it("returns the same array for empty array input", () => { + const arr: number[] = []; + const result = coerceArray(arr); + expect(result).toBe(arr); + expect(result).toEqual([]); + }); + + describe("typing", () => { + it("infers correct type for array input", () => { + const arr = [1, 2, 3]; + const result = coerceArray(arr); + expectTypeOf(result).toEqualTypeOf(); + }); + + it("infers correct type for non-array input", () => { + const input = "test"; + const result = coerceArray(input); + expectTypeOf(result).toEqualTypeOf<[string]>(); + }); + + it("infers correct type for object input", () => { + const obj = { key: "value" }; + const result = coerceArray(obj); + expectTypeOf(result).toEqualTypeOf<[{ key: string }]>(); + }); + + it("infers correct type for null input", () => { + const result = coerceArray(null); + expectTypeOf(result).toEqualTypeOf<[null]>(); + }); + + it("infers correct type for undefined input", () => { + const result = coerceArray(undefined); + expectTypeOf(result).toEqualTypeOf<[undefined]>(); + }); + + it("infers correct type for empty array input", () => { + const arr: number[] = []; + const result = coerceArray(arr); + expectTypeOf(result).toEqualTypeOf(); + }); + }); + }); + + describe("isTypedArray", () => { + it.each([ + ["Int8Array", Int8Array], + ["Uint8ClampedArray", Uint8ClampedArray], + ["Uint16Array", Uint16Array], + ["Uint32Array", Uint32Array], + ["Float64Array", Float64Array], + ["Float32Array", Float32Array], + ])("returns true for %s", (_, ArrayType) => { + if (typeof ArrayType !== "undefined") { + const arr = new ArrayType([1, 2, 3]); + expect(isTypedArray(arr)).toBe(true); + } + }); + + it.each([ + ["BigUint64Array", BigUint64Array], + ["BigInt64Array", BigInt64Array], + ])("returns true for %s", (_, ArrayType) => { + const arr = new ArrayType([1n, 2n, 3n]); + expect(isTypedArray(arr)).toBe(true); + }); + + it.each([ + ["strings", "hello"], + ["null", null], + ["undefined", undefined], + ["a number", 123], + ["an object", { a: 1 }], + ["a plain array", [1, 2, 3]], + ["a DataView", new DataView(new ArrayBuffer(8))], + ["an ArrayLike", { length: 2, 0: 1, 1: 2 }], + ])("returns false for %s", (_, input) => { + expect(isTypedArray(input)).toBe(false); + }); + + it("returns true for an object that extends a typed array", () => { + class MyUint8Array extends Uint8Array { + customMethod() { + return "custom"; + } + } + const arr = new MyUint8Array([1, 2, 3]); + expect(isTypedArray(arr)).toBe(true); + }); }); });