From 7096552313b8e4c1c086feb788617bcc8050bc9e Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Tue, 17 Sep 2024 16:04:48 +0200 Subject: [PATCH 01/22] imp: Implemented tests for `dateDifference` function. --- src/utils/date.ts | 7 ++++- tests/utils/date.test.ts | 66 ++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 5 ++- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/utils/date.test.ts diff --git a/src/utils/date.ts b/src/utils/date.ts index 77f077f..17014a6 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -14,10 +14,15 @@ export enum TimeUnit export function dateDifference(start: string | Date, end: string | Date, unit = TimeUnit.Day): number { + let _round: (value: number) => number; + start = new Date(start); end = new Date(end); - return Math.floor((end.getTime() - start.getTime()) / unit); + if (start < end) { _round = Math.floor; } + else { _round = Math.ceil; } + + return _round((end.getTime() - start.getTime()) / unit); } export function dateRange(start: string | Date, end: string | Date, offset = TimeUnit.Day): SmartIterator diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts new file mode 100644 index 0000000..de9fda6 --- /dev/null +++ b/tests/utils/date.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, test } from "vitest"; + +import { dateDifference, dateRange, dateRound, TimeUnit } from "../../src/index.js"; + +describe("dateDifference", () => +{ + test("years", () => + { + expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Year)) + .toBe(-4); + }); + test("months", () => + { + expect(dateDifference("2020-02-28", "2024-03-01T12:23:34.456Z", TimeUnit.Month)) + .toBe(4 * 12); + }); + test("weeks", () => + { + expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Week)) + .toBe(-(4 * 52 + 1)); + }); + + test("default", () => + { + expect(dateDifference("2020-02-28", "2024-03-01T12:23:34.456Z")) + .toBe(365 * 4 + 3); + }); + test("days", () => + { + expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Day)) + .toBe(-(365 * 4 + 3)); + }); + test("hours", () => + { + expect(dateDifference(new Date("2020-02-28"), "2024-03-01T12:23:34.456Z", TimeUnit.Hour)) + .toBe((365 * 4 + 3) * 24 + 12); + }); + test("minutes", () => + { + expect(dateDifference("2024-03-01T12:23:34.456Z", new Date("2020-02-28"), TimeUnit.Minute)) + .toBe(-(((365 * 4 + 3) * 24 + 12) * 60 + 23)); + }); + test("seconds", () => + { + expect(dateDifference(new Date("2020-02-28"), new Date("2024-03-01T12:23:34.456Z"), TimeUnit.Second)) + .toBe((((365 * 4 + 3) * 24 + 12) * 60 + 23) * 60 + 34); + }); + test("milliseconds", () => + { + expect(dateDifference(new Date("2024-03-01T12:23:34.456Z"), new Date("2020-02-28"), TimeUnit.Millisecond)) + .toBe(-(((((365 * 4 + 3) * 24 + 12) * 60 + 23) * 60 + 34) * 1000 + 456)); + }); +}); + +describe("dateRange", () => +{ + test("default", () => + { + const range = dateRange("2020-02-28", "2020-03-02"); + + expect(range.next().value).toBeInstanceOf(Date); + expect(range.next().value).toBeInstanceOf(Date); + expect(range.next().value).toBeInstanceOf(Date); + expect(range.next().value).toBeUndefined(); + }); +} diff --git a/tsconfig.json b/tsconfig.json index cf44d83..f6588d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,10 @@ "forceConsistentCasingInFileNames": true, "baseUrl": "." }, - "include": ["src"], + "include": [ + "src", + "tests" + ], "exclude": [ "dist", "node_modules" From 52e6185df3d117d538a06943e535fcf1ce5698d2 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Wed, 8 Jan 2025 07:44:10 +0100 Subject: [PATCH 02/22] wip: Added some new tests... --- src/utils/date.ts | 22 ++++++++++++++-------- tests/utils/date.test.ts | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/utils/date.ts b/src/utils/date.ts index 772e5bd..5970411 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -1,8 +1,8 @@ -import { SmartIterator } from "../models/index.js"; +import { RangeException, SmartIterator } from "../models/index.js"; /** - * An enumeration that represents the time units and their conversion factors. + * An enumeration that represents the time units and their conversion factors. * It can be used as utility to express time values in a more * readable way or to convert time values between different units. * @@ -56,7 +56,7 @@ export enum TimeUnit } /** - * An enumeration that represents the days of the week. + * An enumeration that represents the days of the week. * It can be used as utility to identify the days of the week when working with dates. * * ```ts @@ -106,7 +106,7 @@ export enum WeekDay } /** - * An utility function that calculates the difference between two dates. + * An utility function that calculates the difference between two dates. * The difference can be expressed in different time units. * * ```ts @@ -138,7 +138,7 @@ export function dateDifference(start: string | Date, end: string | Date, unit = } /** - * An utility function that generates an iterator over a range of dates. + * An utility function that generates an iterator over a range of dates. * The step between the dates can be expressed in different time units. * * ```ts @@ -154,13 +154,19 @@ export function dateDifference(start: string | Date, end: string | Date, unit = * --- * * @param start The start date (included). - * @param end The end date (excluded). + * @param end + * The end date (excluded). + * + * Must be greater than the start date. If not, a {@link RangeException} will be thrown. + * * @param step The time unit to express the step between the dates. `TimeUnit.Day` by default. * * @returns A {@link SmartIterator} object that generates the dates in the range. */ export function dateRange(start: string | Date, end: string | Date, step = TimeUnit.Day): SmartIterator { + if (start >= end) { throw new RangeException("The end date must be greater than the start date."); } + return new SmartIterator(function* () { const endTime = new Date(end).getTime(); @@ -176,7 +182,7 @@ export function dateRange(start: string | Date, end: string | Date, step = TimeU } /** - * An utility function that rounds a date to the nearest time unit. + * An utility function that rounds a date to the nearest time unit. * The rounding can be expressed in different time units. * * ```ts @@ -200,7 +206,7 @@ export function dateRound(date: string | Date, unit = TimeUnit.Day): Date } /** - * An utility function that gets the week of a date. + * An utility function that gets the week of a date. * The first day of the week can be optionally specified. * * ```ts diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts index dd24f62..94d8004 100644 --- a/tests/utils/date.test.ts +++ b/tests/utils/date.test.ts @@ -25,6 +25,7 @@ describe("dateDifference", () => expect(dateDifference("2020-02-28", "2024-03-01T12:23:34.456Z")) .toBe(365 * 4 + 3); }); + test("days", () => { expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Day)) @@ -54,13 +55,38 @@ describe("dateDifference", () => describe("dateRange", () => { + test("years", () => + { + expect([...dateRange("2020-02-28", "2024-03-01T12:23:34.456Z", TimeUnit.Year)].map((date) => date.getTime())) + .toEqual([1582848000000, 1614384000000, 1645920000000, 1677456000000, 1708992000000]); + }); + test("months", () => + { + expect([...dateRange("2020-02-28T12:23:34.456Z", "2020-06-27", TimeUnit.Month)].map((date) => date.getTime())) + .toEqual([1582892614456, 1585484614456, 1588076614456, 1590668614456]); + }); + test("weeks", () => + { + expect([...dateRange("2020-02-28", "2020-03-31T12:23:34.456Z", TimeUnit.Week)].map((date) => date.getTime())) + .toEqual([1582848000000, 1583452800000, 1584057600000, 1584662400000, 1585267200000]); + }); + test("default", () => { - const range = dateRange("2020-02-28", "2020-03-02"); + expect([...dateRange("2020-02-28T12:23:34.456Z", "2020-03-02")].map((date) => date.getTime())) + .toEqual([1582892614456, 1582979014456, 1583065414456]); + }); - expect(range.next().value).toBeInstanceOf(Date); - expect(range.next().value).toBeInstanceOf(Date); - expect(range.next().value).toBeInstanceOf(Date); - expect(range.next().value).toBeUndefined(); + test("days", () => + { + expect([...dateRange("2020-02-28", "2020-03-02T12:23:34.456Z", TimeUnit.Day)].map((date) => date.getTime())) + .toEqual([1582848000000, 1582934400000, 1583020800000, 1583107200000]); + }); + test("hours", () => + { + expect([ + ...dateRange(new Date("2020-02-28T12:23:34.456Z"), "2020-02-28T18:00:00", TimeUnit.Hour) + ].map((date) => date.getTime())) + .toEqual([1582892614456, 1582896214456, 1582899814456, 1582903414456, 1582907014456]); }); }); From 9674ae32562bb5727143eaa0c25f986d514a78e1 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Wed, 8 Jan 2025 15:55:29 +0100 Subject: [PATCH 03/22] add: Completed tests for `utils/date` file. --- src/utils/date.ts | 24 +++++- tests/utils/date.test.ts | 169 ++++++++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 22 deletions(-) diff --git a/src/utils/date.ts b/src/utils/date.ts index 5970411..6401260 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -157,7 +157,7 @@ export function dateDifference(start: string | Date, end: string | Date, unit = * @param end * The end date (excluded). * - * Must be greater than the start date. If not, a {@link RangeException} will be thrown. + * Must be greater than the start date. Otherwise, a {@link RangeException} will be thrown. * * @param step The time unit to express the step between the dates. `TimeUnit.Day` by default. * @@ -194,14 +194,32 @@ export function dateRange(start: string | Date, end: string | Date, step = TimeU * --- * * @param date The date to round. - * @param unit The time unit to express the rounding. `TimeUnit.Day` by default. + * @param unit + * The time unit to express the rounding. `TimeUnit.Day` by default. + * + * Must be greater than a millisecond and less than or equal to a day. + * Otherwise, a {@link RangeException} will be thrown. * * @returns The rounded date. */ export function dateRound(date: string | Date, unit = TimeUnit.Day): Date { - date = new Date(date); + if (unit <= TimeUnit.Millisecond) + { + throw new RangeException( + "Rounding a timestamp by milliseconds or less makes no sense." + + "Use the timestamp value directly instead." + ); + } + if (unit > TimeUnit.Day) + { + throw new RangeException( + "Rounding by more than a day leads to unexpected results. " + + "Consider using other methods to round dates by weeks, months or years." + ); + } + date = new Date(date); return new Date(Math.floor(date.getTime() / unit) * unit); } diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts index 94d8004..1baf9cb 100644 --- a/tests/utils/date.test.ts +++ b/tests/utils/date.test.ts @@ -1,54 +1,55 @@ import { describe, expect, test } from "vitest"; -import { dateDifference, dateRange, TimeUnit } from "../../src/index.js"; +import { dateDifference, dateRange, dateRound, getWeek, WeekDay } from "../../src/index.js"; +import { RangeException, TimeUnit } from "../../src/index.js"; describe("dateDifference", () => { test("years", () => { - expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Year)) + expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28Z", TimeUnit.Year)) .toBe(-4); }); test("months", () => { - expect(dateDifference("2020-02-28", "2024-03-01T12:23:34.456Z", TimeUnit.Month)) + expect(dateDifference("2020-02-28Z", "2024-03-01T12:23:34.456Z", TimeUnit.Month)) .toBe(4 * 12); }); test("weeks", () => { - expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Week)) + expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28Z", TimeUnit.Week)) .toBe(-(4 * 52 + 1)); }); test("default", () => { - expect(dateDifference("2020-02-28", "2024-03-01T12:23:34.456Z")) + expect(dateDifference("2020-02-28Z", "2024-03-01T12:23:34.456Z")) .toBe(365 * 4 + 3); }); test("days", () => { - expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28", TimeUnit.Day)) + expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28Z", TimeUnit.Day)) .toBe(-(365 * 4 + 3)); }); test("hours", () => { - expect(dateDifference(new Date("2020-02-28"), "2024-03-01T12:23:34.456Z", TimeUnit.Hour)) + expect(dateDifference(new Date("2020-02-28Z"), "2024-03-01T12:23:34.456Z", TimeUnit.Hour)) .toBe((365 * 4 + 3) * 24 + 12); }); test("minutes", () => { - expect(dateDifference("2024-03-01T12:23:34.456Z", new Date("2020-02-28"), TimeUnit.Minute)) + expect(dateDifference("2024-03-01T12:23:34.456Z", new Date("2020-02-28Z"), TimeUnit.Minute)) .toBe(-(((365 * 4 + 3) * 24 + 12) * 60 + 23)); }); test("seconds", () => { - expect(dateDifference(new Date("2020-02-28"), new Date("2024-03-01T12:23:34.456Z"), TimeUnit.Second)) + expect(dateDifference(new Date("2020-02-28Z"), new Date("2024-03-01T12:23:34.456Z"), TimeUnit.Second)) .toBe((((365 * 4 + 3) * 24 + 12) * 60 + 23) * 60 + 34); }); test("milliseconds", () => { - expect(dateDifference(new Date("2024-03-01T12:23:34.456Z"), new Date("2020-02-28"), TimeUnit.Millisecond)) + expect(dateDifference(new Date("2024-03-01T12:23:34.456Z"), new Date("2020-02-28Z"), TimeUnit.Millisecond)) .toBe(-(((((365 * 4 + 3) * 24 + 12) * 60 + 23) * 60 + 34) * 1000 + 456)); }); }); @@ -57,36 +58,166 @@ describe("dateRange", () => { test("years", () => { - expect([...dateRange("2020-02-28", "2024-03-01T12:23:34.456Z", TimeUnit.Year)].map((date) => date.getTime())) - .toEqual([1582848000000, 1614384000000, 1645920000000, 1677456000000, 1708992000000]); + expect([ + ...dateRange("2020-02-28Z", "2027-03-01T12:23:34.456Z", TimeUnit.Year * 3) + ].map((date) => date.getTime())) + .toEqual([1582848000000, 1677456000000, 1772064000000]); }); test("months", () => { - expect([...dateRange("2020-02-28T12:23:34.456Z", "2020-06-27", TimeUnit.Month)].map((date) => date.getTime())) + expect([ + ...dateRange("2020-02-28T12:23:34.456Z", "2020-06-27Z", TimeUnit.Month) + ].map((date) => date.getTime())) .toEqual([1582892614456, 1585484614456, 1588076614456, 1590668614456]); }); test("weeks", () => { - expect([...dateRange("2020-02-28", "2020-03-31T12:23:34.456Z", TimeUnit.Week)].map((date) => date.getTime())) + expect([ + ...dateRange("2020-02-28Z", "2020-03-31T12:23:34.456Z", TimeUnit.Week) + ].map((date) => date.getTime())) .toEqual([1582848000000, 1583452800000, 1584057600000, 1584662400000, 1585267200000]); }); test("default", () => { - expect([...dateRange("2020-02-28T12:23:34.456Z", "2020-03-02")].map((date) => date.getTime())) + expect([ + ...dateRange("2020-02-28T12:23:34.456Z", "2020-03-02Z") + ].map((date) => date.getTime())) .toEqual([1582892614456, 1582979014456, 1583065414456]); }); + test("default (exception)", () => + { + expect(() => [...dateRange("2020-03-02Z", "2020-02-28T12:23:34.456Z")]) + .toThrowError(RangeException); + }); test("days", () => { - expect([...dateRange("2020-02-28", "2020-03-02T12:23:34.456Z", TimeUnit.Day)].map((date) => date.getTime())) - .toEqual([1582848000000, 1582934400000, 1583020800000, 1583107200000]); + expect([ + ...dateRange("2020-02-28Z", "2020-03-31T12:23:34.456Z", TimeUnit.Day * 7) + ].map((date) => date.getTime())) + .toEqual([1582848000000, 1583452800000, 1584057600000, 1584662400000, 1585267200000]); }); test("hours", () => { expect([ - ...dateRange(new Date("2020-02-28T12:23:34.456Z"), "2020-02-28T18:00:00", TimeUnit.Hour) + ...dateRange(new Date("2020-02-28T12:23:34.456Z"), "2020-02-28T18:00:00Z", TimeUnit.Hour * 2) + ].map((date) => date.getTime())) + .toEqual([1582892614456, 1582899814456, 1582907014456]); + }); + test("minutes", () => + { + expect([ + ...dateRange("2020-02-28T12:23:34.456Z", new Date("2020-02-28T12:28:33Z"), TimeUnit.Minute) + ].map((date) => date.getTime())) + .toEqual([1582892614456, 1582892674456, 1582892734456, 1582892794456, 1582892854456]); + }); + test("seconds", () => + { + expect([ + ...dateRange(new Date("2020-02-28T12:23:34.456Z"), new Date("2020-02-28T12:23:37Z"), TimeUnit.Second) + ].map((date) => date.getTime())) + .toEqual([1582892614456, 1582892615456, 1582892616456]); + }); + test("milliseconds", () => + { + expect([ + ...dateRange("2020-02-28T12:23:34.456Z", "2020-02-28T12:23:34.495Z", TimeUnit.Millisecond * 10) ].map((date) => date.getTime())) - .toEqual([1582892614456, 1582896214456, 1582899814456, 1582903414456, 1582907014456]); + .toEqual([1582892614456, 1582892614466, 1582892614476, 1582892614486]); + }); +}); + +describe("dateRound", () => +{ + test("years (exception)", () => + { + expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Year)) + .toThrowError(RangeException); + }); + test("months (exception)", () => + { + expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Month)) + .toThrowError(RangeException); + }); + test("weeks (exception)", () => + { + expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Week)) + .toThrowError(RangeException); + }); + + test("default", () => + { + expect(dateRound("2020-02-28T12:23:34.456Z").getTime()) + .toBe(1582848000000); + }); + test("days", () => + { + expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Day).getTime()) + .toBe(1582848000000); + }); + test("hours", () => + { + expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Hour).getTime()) + .toBe(1582891200000); + }); + test("minutes", () => + { + expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Minute).getTime()) + .toBe(1582892580000); + }); + test("seconds", () => + { + expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Second).getTime()) + .toBe(1582892614000); + }); + test("milliseconds (exception)", () => + { + expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Millisecond)) + .toThrowError(RangeException); + }); +}); + +describe("getWeek", () => +{ + test("default", () => + { + expect(getWeek("2020-02-28T12:23:34.456Z").getTime()) + .toBe(1582416000000); + }); + test("monday", () => + { + expect(getWeek("2020-02-28T12:23:34.456Z", WeekDay.Monday).getTime()) + .toBe(1582502400000); + }); + test("tuesday", () => + { + expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Tuesday).getTime()) + .toBe(1582588800000); + }); + test("wednesday", () => + { + expect(getWeek("2020-02-28T12:23:34.456Z", WeekDay.Wednesday).getTime()) + .toBe(1582675200000); + }); + test("thursday", () => + { + expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Thursday).getTime()) + .toBe(1582761600000); + }); + test("friday", () => + { + expect(getWeek("2020-02-28T12:23:34.456Z", WeekDay.Friday).getTime()) + .toBe(1582848000000); + }); + test("saturday", () => + { + expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Saturday).getTime()) + .toBe(1582329600000); + }); + test("sunday", () => + { + expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Sunday).getTime()) + .toBe(1582416000000); }); }); From c15edeb04b9f3f2b26c474c1a75eba2b0e0bb07d Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Wed, 8 Jan 2025 16:11:24 +0100 Subject: [PATCH 04/22] =?UTF-8?q?add:=20Added=20tests=20for=20`utils/curve?= =?UTF-8?q?`.=20+=20Trying=20tests=20generated=20by=20Copilot...=20?= =?UTF-8?q?=F0=9F=A4=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/utils/curve.test.ts | 53 ++++++++ tests/utils/date.test.ts | 254 ++++++++++++-------------------------- 2 files changed, 129 insertions(+), 178 deletions(-) create mode 100644 tests/utils/curve.test.ts diff --git a/tests/utils/curve.test.ts b/tests/utils/curve.test.ts new file mode 100644 index 0000000..a742486 --- /dev/null +++ b/tests/utils/curve.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect } from "vitest"; + +import { SmartIterator, ValueException } from "../../src/index.js"; +import { Curve } from "../../src/index.js"; + +describe("Curve", () => +{ + describe("Linear", () => + { + it("Should return an instance of `SmartIterator`", () => + { + const iterator = Curve.Linear(5); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should generate a linear sequence of values", () => + { + const values = Array.from(Curve.Linear(5)); + + expect(values).toEqual([0, 0.25, 0.5, 0.75, 1]); + }); + }); + + describe("Exponential", () => + { + it("Should return an instance of `SmartIterator`", () => + { + const iterator = Curve.Exponential(6); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + + it("Should generate an exponential sequence of values with default base", () => + { + const values = Array.from(Curve.Exponential(6)); + + expect(values).toEqual([0, 0.04000000000000001, 0.16000000000000003, 0.36, 0.6400000000000001, 1]); + }); + it("Should generate an exponential sequence of values with custom base", () => + { + const values = Array.from(Curve.Exponential(6, 3)); + + expect(values).toEqual( + [0, Math.pow(1 / 5, 3), Math.pow(2 / 5, 3), Math.pow(3 / 5, 3), Math.pow(4 / 5, 3), 1] + ); + }); + + it("Should throw a `ValueException` if base is negative", () => + { + expect(() => Curve.Exponential(6, -1)).toThrow(ValueException); + }); + }); +}); diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts index 1baf9cb..f33e7a0 100644 --- a/tests/utils/date.test.ts +++ b/tests/utils/date.test.ts @@ -1,223 +1,121 @@ -import { describe, expect, test } from "vitest"; +import { describe, it, expect } from "vitest"; -import { dateDifference, dateRange, dateRound, getWeek, WeekDay } from "../../src/index.js"; -import { RangeException, TimeUnit } from "../../src/index.js"; +import { RangeException } from "../../src/index.js"; +import { TimeUnit, WeekDay, dateDifference, dateRange, dateRound, getWeek } from "../../src/index.js"; -describe("dateDifference", () => +describe("TimeUnit", () => { - test("years", () => - { - expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28Z", TimeUnit.Year)) - .toBe(-4); - }); - test("months", () => - { - expect(dateDifference("2020-02-28Z", "2024-03-01T12:23:34.456Z", TimeUnit.Month)) - .toBe(4 * 12); - }); - test("weeks", () => + it("Should have correct conversion factors", () => { - expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28Z", TimeUnit.Week)) - .toBe(-(4 * 52 + 1)); + expect(TimeUnit.Millisecond).toBe(1); + expect(TimeUnit.Second).toBe(1000); + expect(TimeUnit.Minute).toBe(60 * TimeUnit.Second); + expect(TimeUnit.Hour).toBe(60 * TimeUnit.Minute); + expect(TimeUnit.Day).toBe(24 * TimeUnit.Hour); + expect(TimeUnit.Week).toBe(7 * TimeUnit.Day); + expect(TimeUnit.Month).toBe(30 * TimeUnit.Day); + expect(TimeUnit.Year).toBe(365 * TimeUnit.Day); }); +}); - test("default", () => +describe("WeekDay", () => +{ + it("Should have correct day values", () => { - expect(dateDifference("2020-02-28Z", "2024-03-01T12:23:34.456Z")) - .toBe(365 * 4 + 3); + expect(WeekDay.Sunday).toBe(0); + expect(WeekDay.Monday).toBe(1); + expect(WeekDay.Tuesday).toBe(2); + expect(WeekDay.Wednesday).toBe(3); + expect(WeekDay.Thursday).toBe(4); + expect(WeekDay.Friday).toBe(5); + expect(WeekDay.Saturday).toBe(6); }); +}); - test("days", () => - { - expect(dateDifference("2024-03-01T12:23:34.456Z", "2020-02-28Z", TimeUnit.Day)) - .toBe(-(365 * 4 + 3)); - }); - test("hours", () => - { - expect(dateDifference(new Date("2020-02-28Z"), "2024-03-01T12:23:34.456Z", TimeUnit.Hour)) - .toBe((365 * 4 + 3) * 24 + 12); - }); - test("minutes", () => +describe("dateDifference", () => +{ + it("Should calculate the difference in days by default", () => { - expect(dateDifference("2024-03-01T12:23:34.456Z", new Date("2020-02-28Z"), TimeUnit.Minute)) - .toBe(-(((365 * 4 + 3) * 24 + 12) * 60 + 23)); + const start = new Date("2025-01-01"); + const end = new Date("2025-01-31"); + + expect(dateDifference(start, end)).toBe(30); }); - test("seconds", () => + it("Should calculate the difference in specified `TimeUnit`", () => { - expect(dateDifference(new Date("2020-02-28Z"), new Date("2024-03-01T12:23:34.456Z"), TimeUnit.Second)) - .toBe((((365 * 4 + 3) * 24 + 12) * 60 + 23) * 60 + 34); + const start = new Date("2025-01-01"); + const end = new Date("2025-01-31"); + + expect(dateDifference(start, end, TimeUnit.Minute)).toBe(43200); }); - test("milliseconds", () => + + it("Should return negative difference if start date is after end date", () => { - expect(dateDifference(new Date("2024-03-01T12:23:34.456Z"), new Date("2020-02-28Z"), TimeUnit.Millisecond)) - .toBe(-(((((365 * 4 + 3) * 24 + 12) * 60 + 23) * 60 + 34) * 1000 + 456)); + const start = new Date("2025-01-31"); + const end = new Date("2025-01-01"); + + expect(dateDifference(start, end)).toBe(-30); }); }); describe("dateRange", () => { - test("years", () => - { - expect([ - ...dateRange("2020-02-28Z", "2027-03-01T12:23:34.456Z", TimeUnit.Year * 3) - ].map((date) => date.getTime())) - .toEqual([1582848000000, 1677456000000, 1772064000000]); - }); - test("months", () => + it("Should generate dates in the specified range", () => { - expect([ - ...dateRange("2020-02-28T12:23:34.456Z", "2020-06-27Z", TimeUnit.Month) - ].map((date) => date.getTime())) - .toEqual([1582892614456, 1585484614456, 1588076614456, 1590668614456]); - }); - test("weeks", () => - { - expect([ - ...dateRange("2020-02-28Z", "2020-03-31T12:23:34.456Z", TimeUnit.Week) - ].map((date) => date.getTime())) - .toEqual([1582848000000, 1583452800000, 1584057600000, 1584662400000, 1585267200000]); - }); + const start = new Date("2025-01-01"); + const end = new Date("2025-01-05"); + const iterator = dateRange(start, end); + const dates = Array.from(iterator); - test("default", () => - { - expect([ - ...dateRange("2020-02-28T12:23:34.456Z", "2020-03-02Z") - ].map((date) => date.getTime())) - .toEqual([1582892614456, 1582979014456, 1583065414456]); - }); - test("default (exception)", () => - { - expect(() => [...dateRange("2020-03-02Z", "2020-02-28T12:23:34.456Z")]) - .toThrowError(RangeException); - }); + expect(dates.length).toBe(4); - test("days", () => - { - expect([ - ...dateRange("2020-02-28Z", "2020-03-31T12:23:34.456Z", TimeUnit.Day * 7) - ].map((date) => date.getTime())) - .toEqual([1582848000000, 1583452800000, 1584057600000, 1584662400000, 1585267200000]); - }); - test("hours", () => - { - expect([ - ...dateRange(new Date("2020-02-28T12:23:34.456Z"), "2020-02-28T18:00:00Z", TimeUnit.Hour * 2) - ].map((date) => date.getTime())) - .toEqual([1582892614456, 1582899814456, 1582907014456]); - }); - test("minutes", () => - { - expect([ - ...dateRange("2020-02-28T12:23:34.456Z", new Date("2020-02-28T12:28:33Z"), TimeUnit.Minute) - ].map((date) => date.getTime())) - .toEqual([1582892614456, 1582892674456, 1582892734456, 1582892794456, 1582892854456]); - }); - test("seconds", () => - { - expect([ - ...dateRange(new Date("2020-02-28T12:23:34.456Z"), new Date("2020-02-28T12:23:37Z"), TimeUnit.Second) - ].map((date) => date.getTime())) - .toEqual([1582892614456, 1582892615456, 1582892616456]); + expect(dates[0].toISOString().slice(0, 10)).toBe("2025-01-01"); + expect(dates[3].toISOString().slice(0, 10)).toBe("2025-01-04"); }); - test("milliseconds", () => + + it("Should throw `RangeException` if start date is not less than end date", () => { - expect([ - ...dateRange("2020-02-28T12:23:34.456Z", "2020-02-28T12:23:34.495Z", TimeUnit.Millisecond * 10) - ].map((date) => date.getTime())) - .toEqual([1582892614456, 1582892614466, 1582892614476, 1582892614486]); + const start = new Date("2025-01-05"); + const end = new Date("2025-01-01"); + expect(() => dateRange(start, end)).toThrow(RangeException); }); }); describe("dateRound", () => { - test("years (exception)", () => - { - expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Year)) - .toThrowError(RangeException); - }); - test("months (exception)", () => - { - expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Month)) - .toThrowError(RangeException); - }); - test("weeks (exception)", () => + it("Should round date to the previous time unit", () => { - expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Week)) - .toThrowError(RangeException); - }); + const date = new Date("2025-01-01T12:34:56.789Z"); - test("default", () => - { - expect(dateRound("2020-02-28T12:23:34.456Z").getTime()) - .toBe(1582848000000); - }); - test("days", () => - { - expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Day).getTime()) - .toBe(1582848000000); - }); - test("hours", () => - { - expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Hour).getTime()) - .toBe(1582891200000); - }); - test("minutes", () => - { - expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Minute).getTime()) - .toBe(1582892580000); + expect(dateRound(date, TimeUnit.Hour).toISOString()).toBe("2025-01-01T12:00:00.000Z"); }); - test("seconds", () => + + it("Should throw `RangeException` if unit is less than or equal to a millisecond", () => { - expect(dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Second).getTime()) - .toBe(1582892614000); + const date = new Date("2025-01-01T12:34:56.789Z"); + + expect(() => dateRound(date, TimeUnit.Millisecond)).toThrow(RangeException); }); - test("milliseconds (exception)", () => + it("Should throw `RangeException` if unit is greater than a day", () => { - expect(() => dateRound("2020-02-28T12:23:34.456Z", TimeUnit.Millisecond)) - .toThrowError(RangeException); + const date = new Date("2025-01-01T12:34:56.789Z"); + + expect(() => dateRound(date, TimeUnit.Week)).toThrow(RangeException); }); }); describe("getWeek", () => { - test("default", () => - { - expect(getWeek("2020-02-28T12:23:34.456Z").getTime()) - .toBe(1582416000000); - }); - test("monday", () => - { - expect(getWeek("2020-02-28T12:23:34.456Z", WeekDay.Monday).getTime()) - .toBe(1582502400000); - }); - test("tuesday", () => - { - expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Tuesday).getTime()) - .toBe(1582588800000); - }); - test("wednesday", () => - { - expect(getWeek("2020-02-28T12:23:34.456Z", WeekDay.Wednesday).getTime()) - .toBe(1582675200000); - }); - test("thursday", () => - { - expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Thursday).getTime()) - .toBe(1582761600000); - }); - test("friday", () => - { - expect(getWeek("2020-02-28T12:23:34.456Z", WeekDay.Friday).getTime()) - .toBe(1582848000000); - }); - test("saturday", () => + it("Should get the first day of the week for the specified date", () => { - expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Saturday).getTime()) - .toBe(1582329600000); + const date = new Date("2025-01-01"); + expect(getWeek(date, WeekDay.Monday).toISOString() + .slice(0, 10)).toBe("2024-12-30"); }); - test("sunday", () => + it("Should get the first day of the week for the specified date with default first day", () => { - expect(getWeek(new Date("2020-02-28T12:23:34.456Z"), WeekDay.Sunday).getTime()) - .toBe(1582416000000); + const date = new Date("2025-01-01"); + expect(getWeek(date).toISOString() + .slice(0, 10)).toBe("2024-12-29"); }); }); From e01a6634ce851fd8ace53b0077a54bdaaeee1884 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Wed, 8 Jan 2025 17:18:18 +0100 Subject: [PATCH 05/22] wip: Added tests for files `utils/iterator`. + Minor improvements. --- .../aggregators/aggregated-async-iterator.ts | 13 -- src/models/aggregators/aggregated-iterator.ts | 34 ++--- src/models/aggregators/reduced-iterator.ts | 2 - src/models/iterators/smart-async-iterator.ts | 113 +++++++--------- src/models/iterators/smart-iterator.ts | 111 +++++++-------- src/utils/iterator.ts | 54 +++++--- tests/utils/date.test.ts | 15 ++- tests/utils/iterator.test.ts | 127 ++++++++++++++++++ 8 files changed, 288 insertions(+), 181 deletions(-) create mode 100644 tests/utils/iterator.test.ts diff --git a/src/models/aggregators/aggregated-async-iterator.ts b/src/models/aggregators/aggregated-async-iterator.ts index 51f460b..0197617 100644 --- a/src/models/aggregators/aggregated-async-iterator.ts +++ b/src/models/aggregators/aggregated-async-iterator.ts @@ -73,11 +73,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - if (await predicate(key, element, index)) { yield [key, element]; } indexes.set(key, index + 1); @@ -91,11 +89,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, V]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [key, await iteratee(key, element, index)]; indexes.set(key, index + 1); @@ -144,12 +140,10 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, V]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; const values = await iteratee(key, element, index); - for await (const value of values) { yield [key, value]; } indexes.set(key, index + 1); @@ -164,7 +158,6 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -186,7 +179,6 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -230,11 +222,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[K, T]> { const keys = new Map>(); - for await (const [key, element] of elements) { const values = keys.get(key) ?? new Set(); - if (values.has(element)) { continue; } values.add(element); @@ -283,11 +273,9 @@ export default class AggregatedAsyncIterator return new AggregatedAsyncIterator(async function* (): AsyncGenerator<[J, T]> { const indexes = new Map(); - for await (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [await iteratee(key, element, index), element]; indexes.set(key, index + 1); @@ -302,7 +290,6 @@ export default class AggregatedAsyncIterator return new SmartAsyncIterator(async function* () { const keys = new Set(); - for await (const [key] of elements) { if (keys.has(key)) { continue; } diff --git a/src/models/aggregators/aggregated-iterator.ts b/src/models/aggregators/aggregated-iterator.ts index a503ee9..f4f1c48 100644 --- a/src/models/aggregators/aggregated-iterator.ts +++ b/src/models/aggregators/aggregated-iterator.ts @@ -7,18 +7,18 @@ import type { KeyedIteratee, KeyedTypeGuardPredicate, KeyedReducer } from "./typ /** * A class representing an iterator that aggregates elements in a lazy and optimized way. * - * It's part of the {@link SmartIterator} implementation, providing a way to group elements of an iterable by key. + * It's part of the {@link SmartIterator} implementation, providing a way to group elements of an iterable by key. * For this reason, it isn't recommended to instantiate this class directly * (although it's still possible), but rather use the {@link SmartIterator.groupBy} method. * - * It isn't directly iterable like its parent class but rather needs to specify on what you want to iterate. + * It isn't directly iterable like its parent class but rather needs to specify on what you want to iterate. * See the {@link AggregatedIterator.keys}, {@link AggregatedIterator.items} - * & {@link AggregatedIterator.values} methods. + * & {@link AggregatedIterator.values} methods. * It does, however, provides the same set of methods to perform - * operations and transformation on the elements of the iterator, + * operations and transformation on the elements of the iterator, * having also the knowledge and context of the groups to which * they belong, allowing to handle them in a grouped manner. - * + * * * This is particularly useful when you need to group elements and * then perform specific operations on the groups themselves. @@ -123,15 +123,15 @@ export default class AggregatedIterator } /** - * Determines whether all elements of each group of the iterator satisfy a given condition. + * Determines whether all elements of each group of the iterator satisfy a given condition. * See also {@link AggregatedIterator.some}. * - * The method will iterate over all elements of the iterator checking if they satisfy the condition. + * The method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element of one group doesn't satisfy the condition, - * the result for the respective group will set to `false`. + * the result for the respective group will set to `false`. * * Eventually, it will return a new {@link ReducedIterator} - * object that will contain all the boolean results for each group. + * object that will contain all the boolean results for each group. * If the iterator is infinite, the function will never return. * * ```ts @@ -195,11 +195,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - if (predicate(key, element, index)) { yield [key, element]; } indexes.set(key, index + 1); @@ -213,11 +211,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [key, iteratee(key, element, index)]; indexes.set(key, index + 1); @@ -264,12 +260,10 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; const values = iteratee(key, element, index); - for (const value of values) { yield [key, value]; } indexes.set(key, index + 1); @@ -284,7 +278,6 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; @@ -306,12 +299,10 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; if (index >= limit) { continue; } - yield [key, element]; indexes.set(key, index + 1); @@ -352,11 +343,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const keys = new Map>(); - for (const [key, element] of elements) { const values = keys.get(key) ?? new Set(); - if (values.has(element)) { continue; } values.add(element); @@ -387,11 +376,9 @@ export default class AggregatedIterator public forEach(iteratee: KeyedIteratee): void { const indexes = new Map(); - for (const [key, element] of this._elements) { const index = indexes.get(key) ?? 0; - iteratee(key, element, index); indexes.set(key, index + 1); @@ -405,11 +392,9 @@ export default class AggregatedIterator return new AggregatedIterator(function* () { const indexes = new Map(); - for (const [key, element] of elements) { const index = indexes.get(key) ?? 0; - yield [iteratee(key, element, index), element]; indexes.set(key, index + 1); @@ -424,7 +409,6 @@ export default class AggregatedIterator return new SmartIterator(function* () { const keys = new Set(); - for (const [key] of elements) { if (keys.has(key)) { continue; } diff --git a/src/models/aggregators/reduced-iterator.ts b/src/models/aggregators/reduced-iterator.ts index 4d0d597..acbdb83 100644 --- a/src/models/aggregators/reduced-iterator.ts +++ b/src/models/aggregators/reduced-iterator.ts @@ -122,7 +122,6 @@ export default class ReducedIterator for (const [index, [key, element]] of elements) { if (index >= count) { break; } - yield [key, element]; } }); @@ -139,7 +138,6 @@ export default class ReducedIterator return new ReducedIterator(function* () { const values = new Set(); - for (const [key, element] of elements) { if (values.has(element)) { continue; } diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index 7315c7f..a8f2773 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -17,12 +17,12 @@ import type { * of the native {@link AsyncIterable} & {@link AsyncIterator} interfaces. * * It provides a set of utility methods to better manipulate and transform - * asynchronous iterators in a functional and highly performant way. + * asynchronous iterators in a functional and highly performant way. * It takes inspiration from the native {@link Array} methods like * {@link Array.map}, {@link Array.filter}, {@link Array.reduce}, etc... * * The class is lazy, meaning that the transformations are applied - * only when the resulting iterator is materialized, not before. + * only when the resulting iterator is materialized, not before. * This allows to chain multiple transformations without * the need to iterate over the elements multiple times. * @@ -233,13 +233,13 @@ export default class SmartAsyncIterator implements A * Determines whether all elements of the iterator satisfy a given condition. * See also {@link SmartAsyncIterator.some}. * - * The method will iterate over all elements of the iterator checking if they satisfy the condition. + * The method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element doesn't satisfy the condition, the method will return `false` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartAsyncIterator.find} instead. - * + * * If the iterator is infinite and every element satisfies the condition, the function will never return. * * ```ts @@ -274,13 +274,13 @@ export default class SmartAsyncIterator implements A * Determines whether any element of the iterator satisfies a given condition. * See also {@link SmartAsyncIterator.every}. * - * The method will iterate over all elements of the iterator checking if they satisfy the condition. + * The method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element satisfies the condition, the method will return `true` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartAsyncIterator.find} instead. - * + * * If the iterator is infinite and no element satisfies the condition, the function will never return. * * ```ts @@ -312,11 +312,11 @@ export default class SmartAsyncIterator implements A } /** - * Filters the elements of the iterator using a given condition. + * Filters the elements of the iterator using a given condition. * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -336,11 +336,11 @@ export default class SmartAsyncIterator implements A public filter(predicate: MaybeAsyncIteratee): SmartAsyncIterator; /** - * Filters the elements of the iterator using a given condition. + * Filters the elements of the iterator using a given condition. * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -354,9 +354,9 @@ export default class SmartAsyncIterator implements A * --- * * @template S - * The type of the elements that satisfy the condition. + * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. - * + * * It must be a subtype of the original type of the iterator. * * @param predicate The condition to check for each element of the iterator. @@ -371,11 +371,9 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (true) { const result = await iterator.next(); - if (result.done) { return result.value; } if (await predicate(result.value, index)) { yield result.value; } @@ -385,11 +383,11 @@ export default class SmartAsyncIterator implements A } /** - * Maps the elements of the iterator using a given transformation function. + * Maps the elements of the iterator using a given transformation function. * Since the iterator is lazy, the mapping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -415,7 +413,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (true) { const result = await iterator.next(); @@ -429,13 +426,13 @@ export default class SmartAsyncIterator implements A } /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * - * It will iterate over all elements of the iterator applying the reducer function. - * The result of each iteration will be passed as the accumulator to the next one. + * It will iterate over all elements of the iterator applying the reducer function. + * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the first element of the iterator. + * The first accumulator value will be the first element of the iterator. * The last accumulator value will be the final result of the reduction. * * Also note that: @@ -458,13 +455,13 @@ export default class SmartAsyncIterator implements A public async reduce(reducer: MaybeAsyncReducer): Promise; /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * - * It will iterate over all elements of the iterator applying the reducer function. - * The result of each iteration will be passed as the accumulator to the next one. + * It will iterate over all elements of the iterator applying the reducer function. + * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the initial value provided. + * The first accumulator value will be the initial value provided. * The last accumulator value will be the final result of the reduction. * * If the iterator is infinite, the function will never return. @@ -511,11 +508,11 @@ export default class SmartAsyncIterator implements A } /** - * Flattens the elements of the iterator using a given transformation function. + * Flattens the elements of the iterator using a given transformation function. * Since the iterator is lazy, the flattening process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -541,14 +538,12 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (true) { const result = await iterator.next(); if (result.done) { return result.value; } const elements = await iteratee(result.value, index); - for await (const element of elements) { yield element; @@ -560,17 +555,17 @@ export default class SmartAsyncIterator implements A } /** - * Drops a given number of elements at the beginning of the iterator. + * Drops a given number of elements at the beginning of the iterator. * The remaining elements will be returned in a new iterator. * * Since the iterator is lazy, the dropping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * - * Only the dropped elements will be consumed in the process. + * Only the dropped elements will be consumed in the process. * The rest of the iterator will be consumed only once the new one is. * * ```ts @@ -593,7 +588,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (index < count) { const result = await iterator.next(); @@ -613,17 +607,17 @@ export default class SmartAsyncIterator implements A } /** - * Takes a given number of elements at the beginning of the iterator. + * Takes a given number of elements at the beginning of the iterator. * These elements will be returned in a new iterator. * * Since the iterator is lazy, the taking process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * - * Only the taken elements will be consumed from the original iterator. + * Only the taken elements will be consumed from the original iterator. * The rest of the original iterator will be available for further consumption. * * ```ts @@ -647,7 +641,6 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { let index = 0; - while (index < limit) { const result = await iterator.next(); @@ -663,7 +656,7 @@ export default class SmartAsyncIterator implements A } /** - * Finds the first element of the iterator that satisfies a given condition. + * Finds the first element of the iterator that satisfies a given condition. * * The method will iterate over all elements of the iterator checking if they satisfy the condition. * The first element that satisfies the condition will be returned immediately. @@ -692,7 +685,7 @@ export default class SmartAsyncIterator implements A public async find(predicate: MaybeAsyncIteratee): Promise; /** - * Finds the first element of the iterator that satisfies a given condition. + * Finds the first element of the iterator that satisfies a given condition. * * The method will iterate over all elements of the iterator checking if they satisfy the condition. * The first element that satisfies the condition will be returned immediately. @@ -715,9 +708,9 @@ export default class SmartAsyncIterator implements A * --- * * @template S - * The type of the element that satisfies the condition. + * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. - * + * * It must be a subtype of the original type of the iterator. * * @param predicate The condition to check for each element of the iterator. @@ -741,13 +734,13 @@ export default class SmartAsyncIterator implements A } /** - * Enumerates the elements of the iterator. + * Enumerates the elements of the iterator. * Each element is be paired with its index in a new iterator. * * Since the iterator is lazy, the enumeration process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -771,13 +764,13 @@ export default class SmartAsyncIterator implements A } /** - * Removes all duplicate elements from the iterator. + * Removes all duplicate elements from the iterator. * The first occurrence of each element will be kept. * * Since the iterator is lazy, the deduplication process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -799,11 +792,9 @@ export default class SmartAsyncIterator implements A return new SmartAsyncIterator(async function* () { const values = new Set(); - while (true) { const result = await iterator.next(); - if (result.done) { return result.value; } if (values.has(result.value)) { continue; } @@ -815,7 +806,7 @@ export default class SmartAsyncIterator implements A } /** - * Counts the number of elements in the iterator. + * Counts the number of elements in the iterator. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the function will never return. @@ -845,7 +836,7 @@ export default class SmartAsyncIterator implements A } /** - * Iterates over all elements of the iterator applying a given function. + * Iterates over all elements of the iterator applying a given function. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the function will never return. @@ -880,7 +871,7 @@ export default class SmartAsyncIterator implements A } /** - * Advances the iterator to the next element and returns the result. + * Advances the iterator to the next element and returns the result. * If the iterator requires it, a value must be provided to be passed to the next element. * * Once the iterator is done, the method will return an object with the `done` property set to `true`. @@ -912,7 +903,7 @@ export default class SmartAsyncIterator implements A /** * An utility method that may be used to close the iterator gracefully, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal the end or to compute a specific final result of the iteration process. * * ```ts @@ -928,7 +919,7 @@ export default class SmartAsyncIterator implements A * for await (const value of iterator) * { * if (value > 5) { break; } // Closing the iterator... - * + * * console.log(value); // 1, 2, 3, 4, 5 * } * ``` @@ -948,7 +939,7 @@ export default class SmartAsyncIterator implements A /** * An utility method that may be used to close the iterator due to an error, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal that an error occurred during the iteration process or to handle it. * * ```ts @@ -992,14 +983,14 @@ export default class SmartAsyncIterator implements A } /** - * An utility method that aggregates the elements of the iterator using a given key function. + * An utility method that aggregates the elements of the iterator using a given key function. * The elements will be grouped by the resulting keys in a new specialized iterator. * See {@link AggregatedAsyncIterator}. * * Since the iterator is lazy, the grouping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * the new one is and that consuming one of them will consume also the other. * @@ -1029,7 +1020,7 @@ export default class SmartAsyncIterator implements A } /** - * Materializes the iterator into an array. + * Materializes the iterator into an array. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the function will never return. @@ -1043,7 +1034,7 @@ export default class SmartAsyncIterator implements A * * console.log(result); // [0, 1, 2, 3, 4] * ``` - * + * * @returns A promise that will resolve to an array containing all elements of the iterator. */ public toArray(): Promise diff --git a/src/models/iterators/smart-iterator.ts b/src/models/iterators/smart-iterator.ts index 05e9b57..0e44f4c 100644 --- a/src/models/iterators/smart-iterator.ts +++ b/src/models/iterators/smart-iterator.ts @@ -8,12 +8,12 @@ import type { GeneratorFunction, Iteratee, TypeGuardPredicate, Reducer, Iterator * of the native {@link Iterable} & {@link Iterator} interfaces. * * It provides a set of utility methods to better manipulate and - * transform iterators in a functional and highly performant way. + * transform iterators in a functional and highly performant way. * It takes inspiration from the native {@link Array} methods like * {@link Array.map}, {@link Array.filter}, {@link Array.reduce}, etc... * * The class is lazy, meaning that the transformations are applied - * only when the resulting iterator is materialized, not before. + * only when the resulting iterator is materialized, not before. * This allows to chain multiple transformations without * the need to iterate over the elements multiple times. * @@ -124,13 +124,13 @@ export default class SmartIterator implements Iterat /** * Determines whether all elements of the iterator satisfy a given condition. See also {@link SmartIterator.some}. * - * The method will iterate over all elements of the iterator checking if they satisfy the condition. + * The method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element doesn't satisfy the condition, the method will return `false` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartIterator.find} instead. - * + * * If the iterator is infinite and every element satisfies the condition, the function will never return. * * ```ts @@ -164,13 +164,13 @@ export default class SmartIterator implements Iterat /** * Determines whether any element of the iterator satisfies a given condition. See also {@link SmartIterator.every}. * - * The method will iterate over all elements of the iterator checking if they satisfy the condition. + * The method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element satisfies the condition, the method will return `true` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartIterator.find} instead. - * + * * If the iterator is infinite and no element satisfies the condition, the function will never return. * * ```ts @@ -202,11 +202,11 @@ export default class SmartIterator implements Iterat } /** - * Filters the elements of the iterator using a given condition. + * Filters the elements of the iterator using a given condition. * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -226,11 +226,11 @@ export default class SmartIterator implements Iterat public filter(predicate: Iteratee): SmartIterator; /** - * Filters the elements of the iterator using a given condition. + * Filters the elements of the iterator using a given condition. * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -244,9 +244,9 @@ export default class SmartIterator implements Iterat * --- * * @template S - * The type of the elements that satisfy the condition. + * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. - * + * * It must be a subtype of the original type of the iterator. * * @param predicate The condition to check for each element of the iterator. @@ -261,11 +261,9 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { let index = 0; - while (true) { const result = iterator.next(); - if (result.done) { return result.value; } if (predicate(result.value, index)) { yield result.value; } @@ -275,11 +273,11 @@ export default class SmartIterator implements Iterat } /** - * Maps the elements of the iterator using a given transformation function. + * Maps the elements of the iterator using a given transformation function. * Since the iterator is lazy, the mapping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -305,7 +303,6 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { let index = 0; - while (true) { const result = iterator.next(); @@ -319,13 +316,13 @@ export default class SmartIterator implements Iterat } /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * - * It will iterate over all elements of the iterator applying the reducer function. - * The result of each iteration will be passed as the accumulator to the next one. + * It will iterate over all elements of the iterator applying the reducer function. + * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the first element of the iterator. + * The first accumulator value will be the first element of the iterator. * The last accumulator value will be the final result of the reduction. * * Also note that: @@ -348,13 +345,13 @@ export default class SmartIterator implements Iterat public reduce(reducer: Reducer): T; /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * - * It will iterate over all elements of the iterator applying the reducer function. - * The result of each iteration will be passed as the accumulator to the next one. + * It will iterate over all elements of the iterator applying the reducer function. + * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the initial value provided. + * The first accumulator value will be the initial value provided. * The last accumulator value will be the final result of the reduction. * * If the iterator is infinite, the function will never return. @@ -401,11 +398,11 @@ export default class SmartIterator implements Iterat } /** - * Flattens the elements of the iterator using a given transformation function. + * Flattens the elements of the iterator using a given transformation function. * Since the iterator is lazy, the flattening process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -431,7 +428,6 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { let index = 0; - while (true) { const result = iterator.next(); @@ -449,17 +445,17 @@ export default class SmartIterator implements Iterat } /** - * Drops a given number of elements at the beginning of the iterator. + * Drops a given number of elements at the beginning of the iterator. * The remaining elements will be returned in a new iterator. * * Since the iterator is lazy, the dropping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * - * Only the dropped elements will be consumed in the process. + * Only the dropped elements will be consumed in the process. * The rest of the iterator will be consumed only once the new one is. * * ```ts @@ -501,17 +497,17 @@ export default class SmartIterator implements Iterat } /** - * Takes a given number of elements at the beginning of the iterator. + * Takes a given number of elements at the beginning of the iterator. * These elements will be returned in a new iterator. * * Since the iterator is lazy, the taking process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * - * Only the taken elements will be consumed from the original iterator. + * Only the taken elements will be consumed from the original iterator. * The rest of the original iterator will be available for further consumption. * * ```ts @@ -550,7 +546,7 @@ export default class SmartIterator implements Iterat } /** - * Finds the first element of the iterator that satisfies a given condition. + * Finds the first element of the iterator that satisfies a given condition. * * The method will iterate over all elements of the iterator checking if they satisfy the condition. * The first element that satisfies the condition will be returned immediately. @@ -579,7 +575,7 @@ export default class SmartIterator implements Iterat public find(predicate: Iteratee): T | undefined; /** - * Finds the first element of the iterator that satisfies a given condition. + * Finds the first element of the iterator that satisfies a given condition. * * The method will iterate over all elements of the iterator checking if they satisfy the condition. * The first element that satisfies the condition will be returned immediately. @@ -602,9 +598,9 @@ export default class SmartIterator implements Iterat * --- * * @template S - * The type of the element that satisfies the condition. + * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. - * + * * It must be a subtype of the original type of the iterator. * * @param predicate The condition to check for each element of the iterator. @@ -628,13 +624,13 @@ export default class SmartIterator implements Iterat } /** - * Enumerates the elements of the iterator. + * Enumerates the elements of the iterator. * Each element is be paired with its index in a new iterator. * * Since the iterator is lazy, the enumeration process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -658,13 +654,13 @@ export default class SmartIterator implements Iterat } /** - * Removes all duplicate elements from the iterator. + * Removes all duplicate elements from the iterator. * The first occurrence of each element will be kept. * * Since the iterator is lazy, the deduplication process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -686,14 +682,11 @@ export default class SmartIterator implements Iterat return new SmartIterator(function* () { const values = new Set(); - while (true) { const result = iterator.next(); - if (result.done) { return result.value; } if (values.has(result.value)) { continue; } - values.add(result.value); yield result.value; @@ -702,7 +695,7 @@ export default class SmartIterator implements Iterat } /** - * Counts the number of elements in the iterator. + * Counts the number of elements in the iterator. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the function will never return. @@ -732,7 +725,7 @@ export default class SmartIterator implements Iterat } /** - * Iterates over all elements of the iterator applying a given function. + * Iterates over all elements of the iterator applying a given function. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the function will never return. @@ -765,7 +758,7 @@ export default class SmartIterator implements Iterat } /** - * Advances the iterator to the next element and returns the result. + * Advances the iterator to the next element and returns the result. * If the iterator requires it, a value must be provided to be passed to the next element. * * Once the iterator is done, the method will return an object with the `done` property set to `true`. @@ -797,7 +790,7 @@ export default class SmartIterator implements Iterat /** * An utility method that may be used to close the iterator gracefully, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal the end or to compute a specific final result of the iteration process. * * ```ts @@ -813,7 +806,7 @@ export default class SmartIterator implements Iterat * for (const value of iterator) * { * if (value > 5) { break; } // Closing the iterator... - * + * * console.log(value); // 1, 2, 3, 4, 5 * } * ``` @@ -833,7 +826,7 @@ export default class SmartIterator implements Iterat /** * An utility method that may be used to close the iterator due to an error, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal that an error occurred during the iteration process or to handle it. * * ```ts @@ -877,13 +870,13 @@ export default class SmartIterator implements Iterat } /** - * An utility method that aggregates the elements of the iterator using a given key function. + * An utility method that aggregates the elements of the iterator using a given key function. * The elements will be grouped by the resulting keys in a new specialized iterator. See {@link AggregatedIterator}. * * Since the iterator is lazy, the grouping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * the new one is and that consuming one of them will consume also the other. * @@ -913,7 +906,7 @@ export default class SmartIterator implements Iterat } /** - * Materializes the iterator into an array. + * Materializes the iterator into an array. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the function will never return. @@ -927,7 +920,7 @@ export default class SmartIterator implements Iterat * * console.log(result); // [0, 1, 2, 3, 4] * ``` - * + * * @returns The array containing all elements of the iterator. */ public toArray(): T[] diff --git a/src/utils/iterator.ts b/src/utils/iterator.ts index c8f16cd..1397245 100644 --- a/src/utils/iterator.ts +++ b/src/utils/iterator.ts @@ -1,4 +1,4 @@ -import { SmartIterator } from "../models/index.js"; +import { RangeException, SmartIterator } from "../models/index.js"; /** * An utility function that chains multiple iterables into a single one. @@ -6,7 +6,7 @@ import { SmartIterator } from "../models/index.js"; * Since the iterator is lazy, the chaining process will be * executed only once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -66,13 +66,13 @@ export function count(elements: Iterable): number } /** - * An utility function that enumerates the elements of an iterable. + * An utility function that enumerates the elements of an iterable. * Each element is paired with its index in a new iterator. * * Since the iterator is lazy, the enumeration process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume also the other. * @@ -96,7 +96,6 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> return new SmartIterator<[number, T]>(function* () { let index = 0; - for (const element of elements) { yield [index, element]; @@ -107,7 +106,7 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> } /** - * An utility function that generates an iterator over a range of numbers. + * An utility function that generates an iterator over a range of numbers. * The values are included between `0` (included) and `end` (excluded). * * The default step between the numbers is `1`. @@ -131,7 +130,7 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> export function range(end: number): SmartIterator; /** - * An utility function that generates an iterator over a range of numbers. + * An utility function that generates an iterator over a range of numbers. * The values are included between `start` (included) and `end` (excluded). * * The step between the numbers can be specified with a custom value. Default is `1`. @@ -155,23 +154,39 @@ export function range(end: number): SmartIterator; * * If the `end` value is less than the `start` value, the iterator will generate the numbers in reverse order. * - * @param step The step between the numbers. Default is `1`. + * @param step + * The step between the numbers. Default is `1`. + * + * Must be a positive number. Otherwise, a {@link RangeError} will be thrown. * * @returns A {@link SmartIterator} object that generates the numbers in the range. */ export function range(start: number, end: number, step?: number): SmartIterator; export function range(start: number, end?: number, step = 1): SmartIterator { - return new SmartIterator(function* () + if (step <= 0) { - if (end === undefined) - { - end = start; - start = 0; - } + throw new RangeException( + "Step must be always a positive number, even when generating numbers in reverse order." + ); + } - if (start > end) { step = step ?? -1; } + if (end === undefined) + { + end = start; + start = 0; + } + if (start > end) + { + return new SmartIterator(function* () + { + for (let index = start; index > end; index -= step) { yield index; } + }); + } + + return new SmartIterator(function* () + { for (let index = start; index < end; index += step) { yield index; } }); } @@ -236,7 +251,6 @@ export function unique(elements: Iterable): SmartIterator return new SmartIterator(function* () { const values = new Set(); - for (const element of elements) { if (values.has(element)) { continue; } @@ -249,7 +263,7 @@ export function unique(elements: Iterable): SmartIterator } /** - * An utility function that zips two iterables into a single one. + * An utility function that zips two iterables into a single one. * The resulting iterable will contain the elements of the two iterables paired together. * * The function will stop when one of the two iterables is exhausted. @@ -273,11 +287,11 @@ export function unique(elements: Iterable): SmartIterator */ export function zip(first: Iterable, second: Iterable): SmartIterator<[T, U]> { + const firstIterator = first[Symbol.iterator](); + const secondIterator = second[Symbol.iterator](); + return new SmartIterator<[T, U]>(function* () { - const firstIterator = first[Symbol.iterator](); - const secondIterator = second[Symbol.iterator](); - while (true) { const firstResult = firstIterator.next(); diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts index f33e7a0..e7f6367 100644 --- a/tests/utils/date.test.ts +++ b/tests/utils/date.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; -import { RangeException } from "../../src/index.js"; +import { RangeException, SmartIterator } from "../../src/index.js"; import { TimeUnit, WeekDay, dateDifference, dateRange, dateRound, getWeek } from "../../src/index.js"; describe("TimeUnit", () => @@ -60,6 +60,16 @@ describe("dateDifference", () => describe("dateRange", () => { + it("Should return an instance of `SmartIterator`", () => + { + const start = new Date("2025-01-01"); + const end = new Date("2025-01-05"); + + const iterator = dateRange(start, end); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should generate dates in the specified range", () => { const start = new Date("2025-01-01"); @@ -77,6 +87,7 @@ describe("dateRange", () => { const start = new Date("2025-01-05"); const end = new Date("2025-01-01"); + expect(() => dateRange(start, end)).toThrow(RangeException); }); }); @@ -109,12 +120,14 @@ describe("getWeek", () => it("Should get the first day of the week for the specified date", () => { const date = new Date("2025-01-01"); + expect(getWeek(date, WeekDay.Monday).toISOString() .slice(0, 10)).toBe("2024-12-30"); }); it("Should get the first day of the week for the specified date with default first day", () => { const date = new Date("2025-01-01"); + expect(getWeek(date).toISOString() .slice(0, 10)).toBe("2024-12-29"); }); diff --git a/tests/utils/iterator.test.ts b/tests/utils/iterator.test.ts new file mode 100644 index 0000000..9ce2f79 --- /dev/null +++ b/tests/utils/iterator.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect } from "vitest"; + +import { RangeException, SmartIterator } from "../../src/index.js"; +import { chain, count, enumerate, range, shuffle, unique, zip } from "../../src/index.js"; + +describe("chain", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = chain([1, 2, 3], [4, 5, 6]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should chain multiple iterables into a single one", () => + { + const result = Array.from(chain([1, 2, 3], [4, 5, 6], [7, 8, 9])); + + expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); +}); + +describe("count", () => +{ + it("Should count the number of elements in an iterable", () => + { + expect(count([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])).toBe(10); + expect(count(new Set([1, 2, 3, 4, 5]))).toBe(5); + }); +}); + +describe("enumerate", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = enumerate(["A", "M", "N", "Z"]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should enumerate the elements of an iterable", () => + { + const result = Array.from(enumerate(["A", "M", "N", "Z"])); + + expect(result).toEqual([[0, "A"], [1, "M"], [2, "N"], [3, "Z"]]); + }); +}); + +describe("range", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = range(5); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + + it("Should throw `RangeException` if step is lower than or equal to 0", () => + { + expect(() => range(2, 7, 0)).toThrow(RangeException); + expect(() => range(5, 0, -1)).toThrow(RangeException); + }); + + it("Should generate an iterator over a range of numbers", () => + { + expect(Array.from(range(5))).toEqual([0, 1, 2, 3, 4]); + expect(Array.from(range(2, 7))).toEqual([2, 3, 4, 5, 6]); + }); + it("Should generate an iterator over a range of numbers with a specified step", () => + { + expect(Array.from(range(0, 10, 2))).toEqual([0, 2, 4, 6, 8]); + expect(Array.from(range(2, 13, 3))).toEqual([2, 5, 8, 11]); + }); + + it("Should generate an iterator over a range of numbers in reverse", () => + { + expect(Array.from(range(5, 0))).toEqual([5, 4, 3, 2, 1]); + expect(Array.from(range(7, 2))).toEqual([7, 6, 5, 4, 3]); + }); + it("Should generate an iterator over a range of numbers in reverse with a specified step", () => + { + expect(Array.from(range(10, 0, 2))).toEqual([10, 8, 6, 4, 2]); + expect(Array.from(range(13, 2, 3))).toEqual([13, 10, 7, 4]); + }); +}); + +describe("shuffle", () => +{ + it("Should shuffle the elements of an iterable", () => + { + const array = [1, 2, 3, 4, 5]; + const shuffled = shuffle(array); + + expect(shuffled).toHaveLength(array.length); + expect(new Set(shuffled)).toEqual(new Set(array)); + }); +}); + +describe("unique", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = unique([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should filter the elements of an iterable ensuring they are all unique", () => + { + const result = Array.from(unique([1, 1, 2, 3, 2, 3, 4, 5, 5, 4])); + + expect(result).toEqual([1, 2, 3, 4, 5]); + }); +}); + +describe("zip", () => +{ + it("Should return an instance of `SmartIterator`", () => + { + const iterator = zip([1, 2, 3, 4], ["A", "M", "N", "Z"]); + + expect(iterator).toBeInstanceOf(SmartIterator); + }); + it("Should zip two iterables into a single one", () => + { + const result = Array.from(zip([1, 2, 3, 4], ["A", "M", "N", "Z"])); + + expect(result).toEqual([[1, "A"], [2, "M"], [3, "N"], [4, "Z"]]); + }); +}); From e93595f918c73a676059d86f34a4ab4d4b0036be Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Wed, 8 Jan 2025 17:37:16 +0100 Subject: [PATCH 06/22] add: Implemented tests for `utils`. --- src/utils/math.ts | 10 ++-- tests/utils/math.test.ts | 47 +++++++++++++++ tests/utils/random.test.ts | 116 +++++++++++++++++++++++++++++++++++++ tests/utils/string.test.ts | 32 ++++++++++ 4 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 tests/utils/math.test.ts create mode 100644 tests/utils/random.test.ts create mode 100644 tests/utils/string.test.ts diff --git a/src/utils/math.ts b/src/utils/math.ts index 277f482..9dd2677 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -2,7 +2,7 @@ import { ValueException } from "../models/exceptions/index.js"; import { zip } from "./iterator.js"; /** - * Computes the average of a given list of values. + * Computes the average of a given list of values. * The values can be weighted using an additional list of weights. * * ```ts @@ -20,7 +20,7 @@ import { zip } from "./iterator.js"; * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown. * * @param weights - * The list of weights to apply to the values. + * The list of weights to apply to the values. * It should contain the same number of elements as the values list or * the smaller number of elements between the two lists will be considered. * @@ -63,7 +63,7 @@ export function average(values: Iterable, weights?: Iterabl } if (_index === 0) { throw new ValueException("You must provide at least one value and weight."); } - if (_count > 0) { throw new ValueException("The sum of weights must be greater than zero."); } + if (_count <= 0) { throw new ValueException("The sum of weights must be greater than zero."); } return _sum / _count; } @@ -72,7 +72,7 @@ export function average(values: Iterable, weights?: Iterabl * An utility function to compute the hash of a given string. * * The hash is computed using a simple variation of the - * {@link http://www.cse.yorku.ca/~oz/hash.html#djb2|djb2} algorithm. + * {@link http://www.cse.yorku.ca/~oz/hash.html#djb2|djb2} algorithm. * However, the hash is garanteed to be a 32-bit signed integer. * * ```ts @@ -109,7 +109,7 @@ export function hash(value: string): number * * --- * - * @template T The type of the values in the list. It must be or extend a `number` object. + * @template T The type of the values in the list. It must be or extend a `number` object. * * @param values The list of values to sum. * diff --git a/tests/utils/math.test.ts b/tests/utils/math.test.ts new file mode 100644 index 0000000..c70941c --- /dev/null +++ b/tests/utils/math.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest"; + +import { ValueException } from "../../src/index.js"; +import { average, hash, sum } from "../../src/index.js"; + +describe("average", () => +{ + it("Should compute the average of a list of values", () => + { + expect(average([1, 2, 3, 4, 5])).toBe(3); + }); + it("Should compute the weighted average of a list of values", () => + { + expect(average([6, 8.5, 4], [3, 2, 1])).toBe(6.5); + }); + + it("Should throw `ValueException` if no values are provided", () => + { + expect(() => average([])).toThrow(ValueException); + }); + it("Should throw `ValueException` if weights are provided and one of them is zero or negative", () => + { + expect(() => average([1, 2, 3], [1, 0, 1])).toThrow(ValueException); + expect(() => average([1, 2, 3], [1, -1, 1])).toThrow(ValueException); + }); + it("Should throw `ValueException` if the sum of weights is not greater than zero", () => + { + expect(() => average([1, 2, 3], [0, 0, 0])).toThrow(ValueException); + }); +}); + +describe("hash", () => +{ + it("Should compute the hash of a given string", () => + { + expect(hash("Hello, world!")).toBe(-1880044555); + expect(hash("How are you?")).toBe(1761539132); + }); +}); + +describe("sum", () => +{ + it("Should sum all the values of a given list", () => + { + expect(sum([1, 2, 3, 4, 5])).toBe(15); + }); +}); diff --git a/tests/utils/random.test.ts b/tests/utils/random.test.ts new file mode 100644 index 0000000..3126aa5 --- /dev/null +++ b/tests/utils/random.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect } from "vitest"; + +import { ValueException } from "../../src/index.js"; +import { Random } from "../../src/index.js"; + +describe("Random", () => +{ + describe("Boolean", () => + { + it("Should return a boolean value", () => + { + const result = Random.Boolean(); + + expect(typeof result).toBe("boolean"); + }); + + it("Should return true approximately 50% of the time with default ratio", () => + { + const results = Array.from({ length: 1000 }, () => Random.Boolean()); + const trueCount = results.filter(Boolean).length; + + expect(trueCount).toBeGreaterThan(400); + expect(trueCount).toBeLessThan(600); + }); + it("Should return true approximately 70% of the time with ratio 0.7", () => + { + const results = Array.from({ length: 1000 }, () => Random.Boolean(0.7)); + const trueCount = results.filter(Boolean).length; + + expect(trueCount).toBeGreaterThan(650); + expect(trueCount).toBeLessThan(750); + }); + }); + + describe("Integer", () => + { + it("Should return an integer between 0 and max (exclusive)", () => + { + const max = 5; + const result = Random.Integer(max); + + expect(result).toBeGreaterThanOrEqual(0); + expect(result).toBeLessThan(max); + }); + it("Should return an integer between min and max (exclusive)", () => + { + const min = 2; + const max = 7; + const result = Random.Integer(min, max); + + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + }); + }); + + describe("Decimal", () => + { + it("Should return a decimal between 0 and 1 (exclusive)", () => + { + const result = Random.Decimal(); + + expect(result).toBeGreaterThanOrEqual(0); + expect(result).toBeLessThan(1); + }); + it("Should return a decimal between 0 and max (exclusive)", () => + { + const max = 5; + const result = Random.Decimal(max); + + expect(result).toBeGreaterThanOrEqual(0); + expect(result).toBeLessThan(max); + }); + it("Should return a decimal between min and max (exclusive)", () => + { + const min = 2; + const max = 7; + const result = Random.Decimal(min, max); + + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThan(max); + }); + }); + + describe("Index", () => + { + it("Should return a valid index from the array", () => + { + const elements = [1, 2, 3, 4, 5]; + const index = Random.Index(elements); + + expect(index).toBeGreaterThanOrEqual(0); + expect(index).toBeLessThan(elements.length); + }); + + it("Should throw `ValueException` if the array is empty", () => + { + expect(() => Random.Index([])).toThrow(ValueException); + }); + }); + + describe("Choice", () => + { + it("Should return a random element from the array", () => + { + const elements = [1, 2, 3, 4, 5]; + const choice = Random.Choice(elements); + + expect(elements).toContain(choice); + }); + + it("Should throw `ValueException` if the array is empty", () => + { + expect(() => Random.Choice([])).toThrow(ValueException); + }); + }); +}); diff --git a/tests/utils/string.test.ts b/tests/utils/string.test.ts new file mode 100644 index 0000000..59c1365 --- /dev/null +++ b/tests/utils/string.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from "vitest"; + +import { capitalize } from "../../src/index.js"; + +describe("capitalize", () => +{ + it("Should capitalize the first letter of a string", () => + { + expect(capitalize("hello")).toBe("Hello"); + }); + it("Should handle strings that are already capitalized", () => + { + expect(capitalize("Hello")).toBe("Hello"); + }); + it("Should not change the case of other letters", () => + { + expect(capitalize("hELLo")).toBe("HELLo"); + }); + + it("Should return an empty string if input is an empty string", () => + { + expect(capitalize("")).toBe(""); + }); + it("Should handle single character strings", () => + { + expect(capitalize("a")).toBe("A"); + }); + it("Should handle strings with special characters", () => + { + expect(capitalize("!hello")).toBe("!hello"); + }); +}); From c0db4e6caf63422f48985ce152f4081c07145f0c Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Sat, 25 Jan 2025 17:05:01 +0100 Subject: [PATCH 07/22] add: Reintroduced some missing spaces. --- src/models/aggregators/aggregated-iterator.ts | 2 +- src/models/aggregators/reduced-iterator.ts | 4 +- src/models/iterators/smart-async-iterator.ts | 64 +++++++++---------- src/models/iterators/smart-iterator.ts | 63 +++++++++--------- src/models/iterators/types.ts | 2 +- src/utils/date.ts | 14 ++-- src/utils/iterator.ts | 12 ++-- src/utils/math.ts | 6 +- 8 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/models/aggregators/aggregated-iterator.ts b/src/models/aggregators/aggregated-iterator.ts index d1e4e24..5a62a0c 100644 --- a/src/models/aggregators/aggregated-iterator.ts +++ b/src/models/aggregators/aggregated-iterator.ts @@ -7,7 +7,7 @@ import type { KeyedIteratee, KeyedTypeGuardPredicate, KeyedReducer } from "./typ /** * A class representing an iterator that aggregates elements in a lazy and optimized way. * - * It's part of the {@link SmartIterator} implementation, providing a way to group elements of an iterable by key. + * It's part of the {@link SmartIterator} implementation, providing a way to group elements of an iterable by key. * For this reason, it isn't recommended to instantiate this class directly * (although it's still possible), but rather use the {@link SmartIterator.groupBy} method. * diff --git a/src/models/aggregators/reduced-iterator.ts b/src/models/aggregators/reduced-iterator.ts index b646111..c8b39a9 100644 --- a/src/models/aggregators/reduced-iterator.ts +++ b/src/models/aggregators/reduced-iterator.ts @@ -352,7 +352,7 @@ export default class ReducedIterator * It will iterate over all the elements of the iterator applying the reducer function. * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the provided initial value. + * The first accumulator value will be the provided initial value. * The last accumulator value will be the final result of the reduction. * * If the iterator is infinite, the method will never return. @@ -503,7 +503,7 @@ export default class ReducedIterator * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * - * Only the taken elements will be consumed from the original reduced iterator. + * Only the taken elements will be consumed from the original reduced iterator. * The rest of the original reduced iterator will be available for further consumption. * * ```ts diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index ab7721d..b143ee6 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -16,12 +16,12 @@ import type { * of the native {@link AsyncIterable} & {@link AsyncIterator} interfaces. * * It provides a set of utility methods to better manipulate and transform - * asynchronous iterators in a functional and highly performant way. + * asynchronous iterators in a functional and highly performant way. * It takes inspiration from the native {@link Array} methods like * {@link Array.map}, {@link Array.filter}, {@link Array.reduce}, etc... * * The class is lazy, meaning that the transformations are applied - * only when the resulting iterator is materialized, not before. + * only when the resulting iterator is materialized, not before. * This allows to chain multiple transformations without * the need to iterate over the elements multiple times. * @@ -235,8 +235,8 @@ export default class SmartAsyncIterator implements A * This method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element doesn't satisfy the condition, the method will return `false` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartAsyncIterator.find} instead. * * If the iterator is infinite and every element satisfies the condition, the method will never return. @@ -276,8 +276,8 @@ export default class SmartAsyncIterator implements A * This method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element satisfies the condition, the method will return `true` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartAsyncIterator.find} instead. * * If the iterator is infinite and no element satisfies the condition, the method will never return. @@ -319,7 +319,7 @@ export default class SmartAsyncIterator implements A * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -347,7 +347,7 @@ export default class SmartAsyncIterator implements A * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -361,7 +361,7 @@ export default class SmartAsyncIterator implements A * --- * * @template S - * The type of the elements that satisfy the condition. + * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. * * It must be a subtype of the original type of the elements. @@ -398,7 +398,7 @@ export default class SmartAsyncIterator implements A * Since the iterator is lazy, the mapping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -437,13 +437,13 @@ export default class SmartAsyncIterator implements A } /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * * It will iterate over all elements of the iterator applying the reducer function. * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the first element of the iterator. + * The first accumulator value will be the first element of the iterator. * The last accumulator value will be the final result of the reduction. * * Also note that: @@ -466,7 +466,7 @@ export default class SmartAsyncIterator implements A public async reduce(reducer: MaybeAsyncReducer): Promise; /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * * It will iterate over all elements of the iterator applying the reducer function. @@ -527,7 +527,7 @@ export default class SmartAsyncIterator implements A * Since the iterator is lazy, the flattening process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -578,11 +578,11 @@ export default class SmartAsyncIterator implements A * Since the iterator is lazy, the dropping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * - * Only the dropped elements will be consumed in the process. + * Only the dropped elements will be consumed in the process. * The rest of the iterator will be consumed only once the new one is. * * ```ts @@ -631,11 +631,11 @@ export default class SmartAsyncIterator implements A * Since the iterator is lazy, the taking process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * - * Only the taken elements will be consumed from the original iterator. + * Only the taken elements will be consumed from the original iterator. * The rest of the original iterator will be available for further consumption. * * ```ts @@ -705,11 +705,11 @@ export default class SmartAsyncIterator implements A /** * Finds the first element of the iterator that satisfies a given condition. * - * This method will iterate over all elements of the iterator checking if they satisfy the condition. + * This method will iterate over all elements of the iterator checking if they satisfy the condition. * The first element that satisfies the condition will be returned immediately. * * Only the elements that are necessary to find the first - * satisfying one will be consumed from the original iterator. + * satisfying one will be consumed from the original iterator. * The rest of the original iterator will be available for further consumption. * * Also note that: @@ -726,7 +726,7 @@ export default class SmartAsyncIterator implements A * --- * * @template S - * The type of the element that satisfies the condition. + * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. * * It must be a subtype of the original type of the elements. @@ -752,13 +752,13 @@ export default class SmartAsyncIterator implements A } /** - * Enumerates the elements of the iterator. + * Enumerates the elements of the iterator. * Each element is be paired with its index in a new iterator. * * Since the iterator is lazy, the enumeration process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -782,13 +782,13 @@ export default class SmartAsyncIterator implements A } /** - * Removes all duplicate elements from the iterator. + * Removes all duplicate elements from the iterator. * The first occurrence of each element will be kept. * * Since the iterator is lazy, the deduplication process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -824,7 +824,7 @@ export default class SmartAsyncIterator implements A } /** - * Counts the number of elements in the iterator. + * Counts the number of elements in the iterator. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the method will never return. @@ -890,7 +890,7 @@ export default class SmartAsyncIterator implements A } /** - * Advances the iterator to the next element and returns the result. + * Advances the iterator to the next element and returns the result. * If the iterator requires it, a value must be provided to be passed to the next element. * * Once the iterator is done, the method will return an object with the `done` property set to `true`. @@ -922,7 +922,7 @@ export default class SmartAsyncIterator implements A /** * An utility method that may be used to close the iterator gracefully, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal the end or to compute a specific final result of the iteration process. * * ```ts @@ -958,7 +958,7 @@ export default class SmartAsyncIterator implements A /** * An utility method that may be used to close the iterator due to an error, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal that an error occurred during the iteration process or to handle it. * * ```ts @@ -1002,14 +1002,14 @@ export default class SmartAsyncIterator implements A } /** - * An utility method that aggregates the elements of the iterator using a given key function. + * An utility method that aggregates the elements of the iterator using a given key function. * The elements will be grouped by the resulting keys in a new specialized iterator. * See {@link AggregatedAsyncIterator}. * * Since the iterator is lazy, the grouping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * the new one is and that consuming one of them will consume the other as well. * @@ -1039,7 +1039,7 @@ export default class SmartAsyncIterator implements A } /** - * Materializes the iterator into an array. + * Materializes the iterator into an array. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the method will never return. diff --git a/src/models/iterators/smart-iterator.ts b/src/models/iterators/smart-iterator.ts index 3f396d6..7a8d3a1 100644 --- a/src/models/iterators/smart-iterator.ts +++ b/src/models/iterators/smart-iterator.ts @@ -8,12 +8,12 @@ import type { GeneratorFunction, Iteratee, TypeGuardPredicate, Reducer, Iterator * of the native {@link Iterable} & {@link Iterator} interfaces. * * It provides a set of utility methods to better manipulate and - * transform iterators in a functional and highly performant way. + * transform iterators in a functional and highly performant way. * It takes inspiration from the native {@link Array} methods like * {@link Array.map}, {@link Array.filter}, {@link Array.reduce}, etc... * * The class is lazy, meaning that the transformations are applied - * only when the resulting iterator is materialized, not before. + * only when the resulting iterator is materialized, not before. * This allows to chain multiple transformations without * the need to iterate over the elements multiple times. * @@ -128,8 +128,8 @@ export default class SmartIterator implements Iterat * This method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element doesn't satisfy the condition, the method will return `false` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartIterator.find} instead. * * If the iterator is infinite and every element satisfies the condition, the method will never return. @@ -169,8 +169,8 @@ export default class SmartIterator implements Iterat * This method will iterate over all elements of the iterator checking if they satisfy the condition. * Once a single element satisfies the condition, the method will return `true` immediately. * - * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. - * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. + * This may lead to an unknown final state of the iterator, which may be entirely or partially consumed. + * For this reason, it's recommended to consider it as consumed in any case and to not use it anymore. * Consider using {@link SmartIterator.find} instead. * * If the iterator is infinite and no element satisfies the condition, the method will never return. @@ -212,7 +212,7 @@ export default class SmartIterator implements Iterat * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -240,7 +240,7 @@ export default class SmartIterator implements Iterat * Since the iterator is lazy, the filtering process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -254,7 +254,7 @@ export default class SmartIterator implements Iterat * --- * * @template S - * The type of the elements that satisfy the condition. + * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. * * It must be a subtype of the original type of the elements. @@ -291,7 +291,7 @@ export default class SmartIterator implements Iterat * Since the iterator is lazy, the mapping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -330,13 +330,13 @@ export default class SmartIterator implements Iterat } /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * * It will iterate over all elements of the iterator applying the reducer function. * The result of each iteration will be passed as the accumulator to the next one. * - * The first accumulator value will be the first element of the iterator. + * The first accumulator value will be the first element of the iterator. * The last accumulator value will be the final result of the reduction. * * Also note that: @@ -359,7 +359,7 @@ export default class SmartIterator implements Iterat public reduce(reducer: Reducer): T; /** - * Reduces the elements of the iterator using a given reducer function. + * Reduces the elements of the iterator using a given reducer function. * This method will consume the entire iterator in the process. * * It will iterate over all elements of the iterator applying the reducer function. @@ -420,7 +420,7 @@ export default class SmartIterator implements Iterat * Since the iterator is lazy, the flattening process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -471,11 +471,11 @@ export default class SmartIterator implements Iterat * Since the iterator is lazy, the dropping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * - * Only the dropped elements will be consumed in the process. + * Only the dropped elements will be consumed in the process. * The rest of the iterator will be consumed only once the new one is. * * ```ts @@ -524,11 +524,11 @@ export default class SmartIterator implements Iterat * Since the iterator is lazy, the taking process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * - * Only the taken elements will be consumed from the original iterator. + * Only the taken elements will be consumed from the original iterator. * The rest of the original iterator will be available for further consumption. * * ```ts @@ -619,7 +619,7 @@ export default class SmartIterator implements Iterat * --- * * @template S - * The type of the element that satisfies the condition. + * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. * * It must be a subtype of the original type of the elements. @@ -645,13 +645,13 @@ export default class SmartIterator implements Iterat } /** - * Enumerates the elements of the iterator. + * Enumerates the elements of the iterator. * Each element is be paired with its index in a new iterator. * * Since the iterator is lazy, the enumeration process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -672,13 +672,13 @@ export default class SmartIterator implements Iterat } /** - * Removes all duplicate elements from the iterator. + * Removes all duplicate elements from the iterator. * The first occurrence of each element will be kept. * * Since the iterator is lazy, the deduplication process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -713,7 +713,7 @@ export default class SmartIterator implements Iterat } /** - * Counts the number of elements in the iterator. + * Counts the number of elements in the iterator. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the method will never return. @@ -777,7 +777,7 @@ export default class SmartIterator implements Iterat } /** - * Advances the iterator to the next element and returns the result. + * Advances the iterator to the next element and returns the result. * If the iterator requires it, a value must be provided to be passed to the next element. * * Once the iterator is done, the method will return an object with the `done` property set to `true`. @@ -809,7 +809,7 @@ export default class SmartIterator implements Iterat /** * An utility method that may be used to close the iterator gracefully, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal the end or to compute a specific final result of the iteration process. * * ```ts @@ -845,7 +845,7 @@ export default class SmartIterator implements Iterat /** * An utility method that may be used to close the iterator due to an error, - * free the resources and perform any cleanup operation. + * free the resources and perform any cleanup operation. * It may also be used to signal that an error occurred during the iteration process or to handle it. * * ```ts @@ -889,13 +889,14 @@ export default class SmartIterator implements Iterat } /** - * An utility method that aggregates the elements of the iterator using a given key function. - * The elements will be grouped by the resulting keys in a new specialized iterator. See {@link AggregatedIterator}. + * An utility method that aggregates the elements of the iterator using a given key function. + * The elements will be grouped by the resulting keys in a new specialized iterator. + * See {@link AggregatedIterator}. * * Since the iterator is lazy, the grouping process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * the new one is and that consuming one of them will consume the other as well. * @@ -925,7 +926,7 @@ export default class SmartIterator implements Iterat } /** - * Materializes the iterator into an array. + * Materializes the iterator into an array. * This method will consume the entire iterator in the process. * * If the iterator is infinite, the method will never return. diff --git a/src/models/iterators/types.ts b/src/models/iterators/types.ts index f018351..7e7b384 100644 --- a/src/models/iterators/types.ts +++ b/src/models/iterators/types.ts @@ -276,7 +276,7 @@ export type MaybeAsyncReducer = (accumulator: A, value: T, index: number) export type IteratorLike = Iterable | Iterator; /** - * An union type that represents either an iterable or an iterator object that can be asynchronous. + * An union type that represents either an iterable or an iterator object that can be asynchronous. * More in general, it represents an object that can be looped over in one way or another. * * ```ts diff --git a/src/utils/date.ts b/src/utils/date.ts index 6401260..1d636af 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -2,7 +2,7 @@ import { RangeException, SmartIterator } from "../models/index.js"; /** - * An enumeration that represents the time units and their conversion factors. + * An enumeration that represents the time units and their conversion factors. * It can be used as utility to express time values in a more * readable way or to convert time values between different units. * @@ -56,7 +56,7 @@ export enum TimeUnit } /** - * An enumeration that represents the days of the week. + * An enumeration that represents the days of the week. * It can be used as utility to identify the days of the week when working with dates. * * ```ts @@ -106,7 +106,7 @@ export enum WeekDay } /** - * An utility function that calculates the difference between two dates. + * An utility function that calculates the difference between two dates. * The difference can be expressed in different time units. * * ```ts @@ -138,7 +138,7 @@ export function dateDifference(start: string | Date, end: string | Date, unit = } /** - * An utility function that generates an iterator over a range of dates. + * An utility function that generates an iterator over a range of dates. * The step between the dates can be expressed in different time units. * * ```ts @@ -182,7 +182,7 @@ export function dateRange(start: string | Date, end: string | Date, step = TimeU } /** - * An utility function that rounds a date to the nearest time unit. + * An utility function that rounds a date to the nearest time unit. * The rounding can be expressed in different time units. * * ```ts @@ -197,7 +197,7 @@ export function dateRange(start: string | Date, end: string | Date, step = TimeU * @param unit * The time unit to express the rounding. `TimeUnit.Day` by default. * - * Must be greater than a millisecond and less than or equal to a day. + * Must be greater than a millisecond and less than or equal to a day. * Otherwise, a {@link RangeException} will be thrown. * * @returns The rounded date. @@ -224,7 +224,7 @@ export function dateRound(date: string | Date, unit = TimeUnit.Day): Date } /** - * An utility function that gets the week of a date. + * An utility function that gets the week of a date. * The first day of the week can be optionally specified. * * ```ts diff --git a/src/utils/iterator.ts b/src/utils/iterator.ts index e6dabf6..8785c20 100644 --- a/src/utils/iterator.ts +++ b/src/utils/iterator.ts @@ -6,7 +6,7 @@ import { RangeException, SmartIterator } from "../models/index.js"; * Since the iterator is lazy, the chaining process will be * executed only once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -66,13 +66,13 @@ export function count(elements: Iterable): number } /** - * An utility function that enumerates the elements of an iterable. + * An utility function that enumerates the elements of an iterable. * Each element is paired with its index in a new iterator. * * Since the iterator is lazy, the enumeration process will * be executed once the resulting iterator is materialized. * - * A new iterator will be created, holding the reference to the original one. + * A new iterator will be created, holding the reference to the original one. * This means that the original iterator won't be consumed until the * new one is and that consuming one of them will consume the other as well. * @@ -106,7 +106,7 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> } /** - * An utility function that generates an iterator over a range of numbers. + * An utility function that generates an iterator over a range of numbers. * The values are included between `0` (included) and `end` (excluded). * * The default step between the numbers is `1`. @@ -130,7 +130,7 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> export function range(end: number): SmartIterator; /** - * An utility function that generates an iterator over a range of numbers. + * An utility function that generates an iterator over a range of numbers. * The values are included between `start` (included) and `end` (excluded). * * The step between the numbers can be specified with a custom value. Default is `1`. @@ -263,7 +263,7 @@ export function unique(elements: Iterable): SmartIterator } /** - * An utility function that zips two iterables into a single one. + * An utility function that zips two iterables into a single one. * The resulting iterable will contain the elements of the two iterables paired together. * * The function will stop when one of the two iterables is exhausted. diff --git a/src/utils/math.ts b/src/utils/math.ts index 9dd2677..7cc24ad 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -2,7 +2,7 @@ import { ValueException } from "../models/exceptions/index.js"; import { zip } from "./iterator.js"; /** - * Computes the average of a given list of values. + * Computes the average of a given list of values. * The values can be weighted using an additional list of weights. * * ```ts @@ -20,7 +20,7 @@ import { zip } from "./iterator.js"; * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown. * * @param weights - * The list of weights to apply to the values. + * The list of weights to apply to the values. * It should contain the same number of elements as the values list or * the smaller number of elements between the two lists will be considered. * @@ -72,7 +72,7 @@ export function average(values: Iterable, weights?: Iterabl * An utility function to compute the hash of a given string. * * The hash is computed using a simple variation of the - * {@link http://www.cse.yorku.ca/~oz/hash.html#djb2|djb2} algorithm. + * {@link http://www.cse.yorku.ca/~oz/hash.html#djb2|djb2} algorithm. * However, the hash is garanteed to be a 32-bit signed integer. * * ```ts From eba03c3c248644a80c90d7da10782fcce908fe00 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Mon, 27 Jan 2025 18:13:14 +0100 Subject: [PATCH 08/22] wip: Testing... --- package.json | 3 +- pnpm-lock.yaml | 356 +++++++++++++++++- .../aggregators/aggregated-async-iterator.ts | 64 ---- src/models/aggregators/aggregated-iterator.ts | 58 --- src/models/aggregators/reduced-iterator.ts | 50 --- src/models/aggregators/types.ts | 14 - src/models/callbacks/callable-object.ts | 2 - src/models/callbacks/publisher.ts | 8 - src/models/callbacks/switchable-callback.ts | 8 - src/models/callbacks/types.ts | 2 - src/models/exceptions/core.ts | 8 - src/models/exceptions/index.ts | 26 -- src/models/game-loop.ts | 8 - src/models/iterators/smart-async-iterator.ts | 56 --- src/models/iterators/smart-iterator.ts | 50 --- src/models/iterators/types.ts | 30 -- src/models/json/json-storage.ts | 48 --- src/models/promises/deferred-promise.ts | 6 - src/models/promises/smart-promise.ts | 18 - src/models/promises/timed-promise.ts | 4 - src/models/promises/types.ts | 12 - src/models/timers/clock.ts | 6 - src/models/timers/countdown.ts | 8 - src/utils/async.ts | 6 - src/utils/curve.ts | 4 - src/utils/date.ts | 8 - src/utils/dom.ts | 2 - src/utils/iterator.ts | 16 - src/utils/math.ts | 6 - src/utils/random.ts | 12 - src/utils/string.ts | 2 - tests/models/game-loop.test.ts | 87 +++++ tests/models/timers/clock.test.ts | 91 +++++ tests/models/timers/countdown.test.ts | 98 +++++ tests/utils/async.test.ts | 60 +++ tests/utils/curve.test.ts | 2 +- tests/utils/date.test.ts | 4 +- tests/utils/dom.test.ts | 82 ++++ tests/utils/iterator.test.ts | 2 +- tests/utils/math.test.ts | 4 +- tests/utils/random.test.ts | 2 +- tests/utils/string.test.ts | 2 +- vitest.config.ts | 5 + 43 files changed, 787 insertions(+), 553 deletions(-) create mode 100644 tests/models/game-loop.test.ts create mode 100644 tests/models/timers/clock.test.ts create mode 100644 tests/models/timers/countdown.test.ts create mode 100644 tests/utils/async.test.ts create mode 100644 tests/utils/dom.test.ts create mode 100644 vitest.config.ts diff --git a/package.json b/package.json index 1a89976..e2e9ec3 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "preview": "vite preview", "typecheck": "tsc", "lint": "eslint .", - "test": "vitest", + "test": "vitest run", "prepare": "husky", "ci": "pnpm install --frozen-lockfile" }, @@ -60,6 +60,7 @@ "@byloth/eslint-config-typescript": "^3.0.3", "@types/node": "^22.10.10", "husky": "^9.1.7", + "jsdom": "^26.0.0", "typescript": "^5.7.3", "vite": "^6.0.11", "vitest": "^3.0.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4f866d..52c1999 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: husky: specifier: ^9.1.7 version: 9.1.7 + jsdom: + specifier: ^26.0.0 + version: 26.0.0 typescript: specifier: ^5.7.3 version: 5.7.3 @@ -25,16 +28,47 @@ importers: version: 6.0.11(@types/node@22.10.10) vitest: specifier: ^3.0.4 - version: 3.0.4(@types/node@22.10.10) + version: 3.0.4(@types/node@22.10.10)(jsdom@26.0.0) packages: + '@asamuzakjp/css-color@2.8.3': + resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} + '@byloth/eslint-config-typescript@3.0.3': resolution: {integrity: sha512-97bJOula+nilxjPJDgRIuGlF8KyppcQ2HjxWyA+MVTNEOeWNF3+u3FG3R4+XqSxz2Nc4RNB4wRqhAjdVPW6gVw==} '@byloth/eslint-config@3.0.3': resolution: {integrity: sha512-fXpIxZByU2Ux+95jGcEEweKYb4bxS571xaMZDJ24wsasrDuczjld63EGAulnm9yRAJiLpScruvV0mWLow+16tg==} + '@csstools/color-helpers@5.0.1': + resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.1': + resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.7': + resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} @@ -453,6 +487,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -467,6 +505,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -507,6 +548,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -514,6 +559,14 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + cssstyle@4.2.1: + resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==} + engines: {node: '>=18'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -523,6 +576,9 @@ packages: supports-color: optional: true + decimal.js@10.5.0: + resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -530,6 +586,14 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} @@ -626,6 +690,10 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -654,11 +722,27 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} hasBin: true + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -683,6 +767,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -690,6 +777,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@26.0.0: + resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -716,6 +812,9 @@ packages: loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -727,6 +826,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -745,6 +852,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -761,6 +871,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -811,9 +924,19 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -848,6 +971,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -866,10 +992,25 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.75: + resolution: {integrity: sha512-AOvV5YYIAFFBfransBzSTyztkc3IMfz5Eq3YluaRiEu55nn43Fzaufx70UqEKYr8BoLCach4q8g/bg6e5+/aFw==} + + tldts@6.1.75: + resolution: {integrity: sha512-+lFzEXhpl7JXgWYaXcB6DqTYXbUArvrWAE/5ioq/X3CdWLbDjpPP4XTrQBmEJ91y3xbe4Fkw7Lxv4P3GWeJaNg==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@5.1.0: + resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + ts-api-utils@2.0.0: resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} engines: {node: '>=18.12'} @@ -964,6 +1105,26 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -978,12 +1139,39 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} snapshots: + '@asamuzakjp/css-color@2.8.3': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + lru-cache: 10.4.3 + '@byloth/eslint-config-typescript@3.0.3(eslint@9.19.0)(typescript@5.7.3)': dependencies: '@byloth/eslint-config': 3.0.3 @@ -1005,6 +1193,26 @@ snapshots: - jiti - supports-color + '@csstools/color-helpers@5.0.1': {} + + '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.1 + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -1341,6 +1549,8 @@ snapshots: acorn@8.14.0: {} + agent-base@7.1.3: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -1356,6 +1566,8 @@ snapshots: assertion-error@2.0.1: {} + asynckit@0.4.0: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -1396,6 +1608,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} cross-spawn@7.0.6: @@ -1404,14 +1620,30 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cssstyle@4.2.1: + dependencies: + '@asamuzakjp/css-color': 2.8.3 + rrweb-cssom: 0.8.0 + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + debug@4.4.0: dependencies: ms: 2.1.3 + decimal.js@10.5.0: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + + entities@4.5.0: {} + es-module-lexer@1.6.0: {} esbuild@0.24.2: @@ -1554,6 +1786,12 @@ snapshots: flatted@3.3.2: {} + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true @@ -1573,8 +1811,30 @@ snapshots: has-flag@4.0.0: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + husky@9.1.7: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.0: @@ -1592,12 +1852,42 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsdom@26.0.0: + dependencies: + cssstyle: 4.2.1 + data-urls: 5.0.0 + decimal.js: 10.5.0 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -1621,6 +1911,8 @@ snapshots: loupe@3.1.2: {} + lru-cache@10.4.3: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -1632,6 +1924,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -1646,6 +1944,8 @@ snapshots: natural-compare@1.4.0: {} + nwsapi@2.2.16: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -1667,6 +1967,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -1720,10 +2024,18 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.32.0 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + semver@7.6.3: {} shebang-command@2.0.0: @@ -1746,6 +2058,8 @@ snapshots: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -1756,10 +2070,24 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@6.1.75: {} + + tldts@6.1.75: + dependencies: + tldts-core: 6.1.75 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@5.1.0: + dependencies: + tldts: 6.1.75 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.0.0(typescript@5.7.3): dependencies: typescript: 5.7.3 @@ -1806,7 +2134,7 @@ snapshots: '@types/node': 22.10.10 fsevents: 2.3.3 - vitest@3.0.4(@types/node@22.10.10): + vitest@3.0.4(@types/node@22.10.10)(jsdom@26.0.0): dependencies: '@vitest/expect': 3.0.4 '@vitest/mocker': 3.0.4(vite@6.0.11(@types/node@22.10.10)) @@ -1830,6 +2158,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.10.10 + jsdom: 26.0.0 transitivePeerDependencies: - jiti - less @@ -1844,6 +2173,23 @@ snapshots: - tsx - yaml + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -1855,4 +2201,10 @@ snapshots: word-wrap@1.2.5: {} + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yocto-queue@0.1.0: {} diff --git a/src/models/aggregators/aggregated-async-iterator.ts b/src/models/aggregators/aggregated-async-iterator.ts index 4ec582b..347e7f7 100644 --- a/src/models/aggregators/aggregated-async-iterator.ts +++ b/src/models/aggregators/aggregated-async-iterator.ts @@ -39,8 +39,6 @@ import type { MaybeAsyncKeyedIteratee, MaybeAsyncKeyedReducer } from "./types.js * console.log(await results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * @template T The type of the elements to aggregate. */ @@ -58,8 +56,6 @@ export default class AggregatedAsyncIterator * const iterator = new AggregatedAsyncIterator([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]); * ``` * - * --- - * * @param iterable The iterable to aggregate. */ public constructor(iterable: Iterable<[K, T]>); @@ -72,8 +68,6 @@ export default class AggregatedAsyncIterator * const iterator = new AggregatedAsyncIterator(elements); * ``` * - * --- - * * @param iterable The iterable to aggregate. */ public constructor(iterable: AsyncIterable<[K, T]>); @@ -96,8 +90,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param iterator The iterator to aggregate. */ public constructor(iterator: Iterator<[K, T]>); @@ -120,8 +112,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param iterator The iterator to aggregate. */ public constructor(iterator: AsyncIterator<[K, T]>); @@ -141,8 +131,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param generatorFn The generator function to aggregate. */ public constructor(generatorFn: GeneratorFunction<[K, T]>); @@ -162,8 +150,6 @@ export default class AggregatedAsyncIterator * }); * ``` * - * --- - * * @param generatorFn The generator function to aggregate. */ public constructor(generatorFn: AsyncGeneratorFunction<[K, T]>); @@ -175,8 +161,6 @@ export default class AggregatedAsyncIterator * const iterator = new AggregatedAsyncIterator(asyncKeyedValues); * ``` * - * --- - * * @param argument The iterable, iterator or generator function to aggregate. */ public constructor(argument: MaybeAsyncIteratorLike<[K, T]> | MaybeAsyncGeneratorFunction<[K, T]>); @@ -206,8 +190,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the boolean results for each group. @@ -252,8 +234,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the boolean results for each group. @@ -295,8 +275,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link AggregatedAsyncIterator} containing the elements that satisfy the condition. @@ -321,8 +299,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, 5], even: [0, 6] } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -372,8 +348,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -419,8 +393,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * * @returns A new {@link ReducedIterator} containing the reduced results for each group. @@ -449,8 +421,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -483,8 +453,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: { value: 4 }, even: { value: 16 } } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -550,8 +518,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -601,8 +567,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [3, 5], even: [6, 8] } * ``` * - * --- - * * @param count The number of elements to drop from the beginning of each group. * * @returns A new {@link AggregatedAsyncIterator} containing the remaining elements. @@ -649,8 +613,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, -1], even: [0, 2] } * ``` * - * --- - * * @param count The number of elements to take from the beginning of each group. * * @returns A new {@link AggregatedAsyncIterator} containing the taken elements. @@ -694,8 +656,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 3, even: 2 } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. @@ -722,8 +682,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: -3, even: 0 } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -776,8 +734,6 @@ export default class AggregatedAsyncIterator * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] } * ``` * - * --- - * * @returns A new {@link AggregatedAsyncIterator} containing the enumerated elements. */ public enumerate(): AggregatedAsyncIterator @@ -804,8 +760,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns A new {@link AggregatedAsyncIterator} containing only the unique elements. */ public unique(): AggregatedAsyncIterator @@ -842,8 +796,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @returns A new {@link ReducedIterator} containing the number of elements for each group. */ public async count(): Promise> @@ -880,8 +832,6 @@ export default class AggregatedAsyncIterator * }; * ``` * - * --- - * * @param iteratee The function to execute for each element of the iterator. */ public async forEach(iteratee: MaybeAsyncKeyedIteratee): Promise @@ -918,8 +868,6 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] } * ``` * - * --- - * * @template J The type of the new key. * * @param iteratee The function to determine the new key for each element of the iterator. @@ -963,8 +911,6 @@ export default class AggregatedAsyncIterator * console.log(await keys.toArray()); // ["number", "symbol", "string", "object", "boolean"] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing all the keys of the iterator. */ public keys(): SmartAsyncIterator @@ -1004,8 +950,6 @@ export default class AggregatedAsyncIterator * console.log(await entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing all the entries of the iterator. */ public entries(): SmartAsyncIterator<[K, T]> @@ -1032,8 +976,6 @@ export default class AggregatedAsyncIterator * console.log(await values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing all the values of the iterator. */ public values(): SmartAsyncIterator @@ -1059,8 +1001,6 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]] * ``` * - * --- - * * @returns An {@link Array} of arrays containing the elements of the iterator. */ public async toArray(): Promise @@ -1083,8 +1023,6 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] } * ``` * - * --- - * * @returns A {@link Map} containing the elements of the iterator. */ public async toMap(): Promise> @@ -1115,8 +1053,6 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns An {@link Object} containing the elements of the iterator. */ public async toObject(): Promise> diff --git a/src/models/aggregators/aggregated-iterator.ts b/src/models/aggregators/aggregated-iterator.ts index 5a62a0c..ee1032f 100644 --- a/src/models/aggregators/aggregated-iterator.ts +++ b/src/models/aggregators/aggregated-iterator.ts @@ -30,8 +30,6 @@ import type { KeyedIteratee, KeyedTypeGuardPredicate, KeyedReducer } from "./typ * console.log(results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * @template T The type of the elements to aggregate. */ @@ -49,8 +47,6 @@ export default class AggregatedIterator * const iterator = new AggregatedIterator([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]); * ``` * - * --- - * * @param iterable The iterable to aggregate. */ public constructor(iterable: Iterable<[K, T]>); @@ -73,8 +69,6 @@ export default class AggregatedIterator * }); * ``` * - * --- - * * @param iterator The iterator to aggregate. */ public constructor(iterator: Iterator<[K, T]>); @@ -94,8 +88,6 @@ export default class AggregatedIterator * }); * ``` * - * --- - * * @param generatorFn The generator function to aggregate. */ public constructor(generatorFn: GeneratorFunction<[K, T]>); @@ -107,8 +99,6 @@ export default class AggregatedIterator * const iterator = new AggregatedIterator(keyedValues); * ``` * - * --- - * * @param argument The iterable, iterator or generator function to aggregate. */ public constructor(argument: IteratorLike<[K, T]> | GeneratorFunction<[K, T]>); @@ -138,8 +128,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the boolean results for each group. @@ -184,8 +172,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: false, even: true } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A {@link ReducedIterator} containing the boolean results for each group. @@ -230,8 +216,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link AggregatedIterator} containing only the elements that satisfy the condition. @@ -259,8 +243,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, 5], even: [0, 6] } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -310,8 +292,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -357,8 +337,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * * @returns A new {@link ReducedIterator} containing the reduced results for each group. @@ -387,8 +365,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -420,8 +396,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: { value: 4 }, even: { value: 16 } } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -484,8 +458,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -535,8 +507,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [3, 5], even: [6, 8] } * ``` * - * --- - * * @param count The number of elements to drop from the beginning of each group. * * @returns A new {@link AggregatedIterator} containing the remaining elements. @@ -583,8 +553,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, -1], even: [0, 2] } * ``` * - * --- - * * @param count The number of elements to take from the beginning of each group. * * @returns A new {@link AggregatedIterator} containing the taken elements. @@ -627,8 +595,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 3, even: 2 } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. @@ -655,8 +621,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: -3, even: 0 } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -707,8 +671,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] } * ``` * - * --- - * * @returns A new {@link AggregatedIterator} containing the enumerated elements. */ public enumerate(): AggregatedIterator @@ -735,8 +697,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns A new {@link AggregatedIterator} containing only the unique elements. */ public unique(): AggregatedIterator @@ -773,8 +733,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @returns A new {@link ReducedIterator} containing the number of elements for each group. */ public count(): ReducedIterator @@ -811,8 +769,6 @@ export default class AggregatedIterator * }; * ``` * - * --- - * * @param iteratee The function to execute for each element of the iterator. */ public forEach(iteratee: KeyedIteratee): void @@ -847,8 +803,6 @@ export default class AggregatedIterator * console.log(results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] } * ``` * - * --- - * * @template J The type of the new key. * * @param iteratee The function to determine the new key for each element of the iterator. @@ -891,8 +845,6 @@ export default class AggregatedIterator * console.log(keys.toArray()); // ["number", "symbol", "string", "object", "boolean"] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the keys of the iterator. */ public keys(): SmartIterator @@ -932,8 +884,6 @@ export default class AggregatedIterator * console.log(entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the entries of the iterator. */ public entries(): SmartIterator<[K, T]> @@ -960,8 +910,6 @@ export default class AggregatedIterator * console.log(values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the values of the iterator. */ public values(): SmartIterator @@ -987,8 +935,6 @@ export default class AggregatedIterator * console.log(aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]] * ``` * - * --- - * * @returns An {@link Array} of arrays containing the elements of the iterator. */ public toArray(): T[][] @@ -1011,8 +957,6 @@ export default class AggregatedIterator * console.log(aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] } * ``` * - * --- - * * @returns A {@link Map} containing the elements of the iterator. */ public toMap(): Map @@ -1043,8 +987,6 @@ export default class AggregatedIterator * console.log(aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @returns An {@link Object} containing the elements of the iterator. */ public toObject(): Record diff --git a/src/models/aggregators/reduced-iterator.ts b/src/models/aggregators/reduced-iterator.ts index c8b39a9..04d588d 100644 --- a/src/models/aggregators/reduced-iterator.ts +++ b/src/models/aggregators/reduced-iterator.ts @@ -32,8 +32,6 @@ import type { KeyedIteratee, KeyedReducer, KeyedTypeGuardPredicate } from "./typ * console.log(results.toObject()); // { odd: 4, even: 4 } * ``` * - * --- - * * @template K The type of the key used to group the elements. * @template T The type of the elements in the iterator. */ @@ -51,8 +49,6 @@ export default class ReducedIterator * const results = new ReducedIterator([["A", 1], ["B", 2], ["C", 4]]); * ``` * - * --- - * * @param iterable A reduced iterable object. */ public constructor(iterable: Iterable<[K, T]>); @@ -73,8 +69,6 @@ export default class ReducedIterator * }); * ``` * - * --- - * * @param iterator An reduced iterator object. */ public constructor(iterator: Iterator<[K, T]>); @@ -94,8 +88,6 @@ export default class ReducedIterator * }); * ``` * - * --- - * * @param generatorFn A generator function that produces the reduced elements. */ public constructor(generatorFn: GeneratorFunction<[K, T]>); @@ -107,8 +99,6 @@ export default class ReducedIterator * const results = new ReducedIterator(reducedValues); * ``` * - * --- - * * @param argument An iterable, iterator or generator function that produces the reduced elements. */ public constructor(argument: Iterable<[K, T]> | Iterator<[K, T]> | GeneratorFunction<[K, T]>); @@ -139,8 +129,6 @@ export default class ReducedIterator * console.log(results); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if all elements satisfy the condition, `false` otherwise. @@ -177,8 +165,6 @@ export default class ReducedIterator * console.log(results); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if any element satisfies the condition, `false` otherwise. @@ -215,8 +201,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link ReducedIterator} containing only the elements that satisfy the condition. @@ -245,8 +229,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: 4 } * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the iterator. @@ -293,8 +275,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: 8, even: 32 } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -337,8 +317,6 @@ export default class ReducedIterator * console.log(result); // 20 * ``` * - * --- - * * @param reducer The reducer function to apply to the elements of the iterator. * * @returns The final value after reducing all the elements of the iterator. @@ -366,8 +344,6 @@ export default class ReducedIterator * console.log(result); // { value: 20 } * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to the elements of the iterator. @@ -421,8 +397,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -471,8 +445,6 @@ export default class ReducedIterator * * console.log(results.toObject()); // { even: [0, 2, 6, 8] } * ``` - * - * --- * * @param count The number of elements to drop. * @@ -517,8 +489,6 @@ export default class ReducedIterator * console.log(reduced.toObject()); // { even: [0, 2, 6, 8] } * ``` * - * --- - * * @param count The number of elements to take. * * @returns A new {@link ReducedIterator} containing the taken elements. @@ -562,8 +532,6 @@ export default class ReducedIterator * console.log(results.toObject()); // [[0, 4], [1, 16]] * ``` * - * --- - * * @returns A new {@link ReducedIterator} object containing the enumerated elements. */ public enumerate(): ReducedIterator @@ -625,8 +593,6 @@ export default class ReducedIterator * console.log(results); // 2 * ``` * - * --- - * * @returns The number of elements in the iterator. */ public count(): number @@ -656,8 +622,6 @@ export default class ReducedIterator * }); * ``` * - * --- - * * @param iteratee The function to apply to each element of the reduced iterator. */ public forEach(iteratee: KeyedIteratee): void @@ -688,8 +652,6 @@ export default class ReducedIterator * console.log(results.toObject()); // { positive: 4, negative: -12 } * ``` * - * --- - * * @template J The type of the new keys used to group the elements. * * @param iteratee The function to determine the new key of each element of the iterator. @@ -729,8 +691,6 @@ export default class ReducedIterator * console.log(keys.toArray()); // ["odd", "even"] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the keys of the iterator. */ public keys(): SmartIterator @@ -767,8 +727,6 @@ export default class ReducedIterator * console.log(entries.toArray()); // [["odd", 4], ["even", 16]] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the entries of the iterator. */ public entries(): SmartIterator<[K, T]> @@ -796,8 +754,6 @@ export default class ReducedIterator * console.log(values.toArray()); // [4, 16] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing all the values of the iterator. */ public values(): SmartIterator @@ -827,8 +783,6 @@ export default class ReducedIterator * console.log(reduced.toArray()); // [4, 16] * ``` * - * --- - * * @returns The {@link Array} containing all elements of the iterator. */ public toArray(): T[] @@ -850,8 +804,6 @@ export default class ReducedIterator * console.log(reduced.toMap()); // Map(2) { "odd" => 4, "even" => 16 } * ``` * - * --- - * * @returns The {@link Map} containing all elements of the iterator. */ public toMap(): Map @@ -873,8 +825,6 @@ export default class ReducedIterator * console.log(reduced.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @returns The {@link Object} containing all elements of the iterator. */ public toObject(): Record diff --git a/src/models/aggregators/types.ts b/src/models/aggregators/types.ts index 7169ca0..9178b51 100644 --- a/src/models/aggregators/types.ts +++ b/src/models/aggregators/types.ts @@ -14,8 +14,6 @@ import type { MaybePromise } from "../promises/types.js"; * console.log(results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. @@ -36,8 +34,6 @@ export type KeyedIteratee = (key: K, value: * console.log(await results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. @@ -58,8 +54,6 @@ export type AsyncKeyedIteratee = (key: K, va * console.log(await results.toObject()); // { odd: ["-3", "-1", "3", "5"], even: ["0", "2", "6", "8"] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. @@ -86,8 +80,6 @@ export type MaybeAsyncKeyedIteratee = * console.log(results.toObject()); // { odd: ["0", "5", "8"], even: [] } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template R @@ -119,8 +111,6 @@ export type KeyedTypeGuardPredicate = * console.log(results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template A The type of the accumulator. @@ -142,8 +132,6 @@ export type KeyedReducer = (key: K, accumulator: A, * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template A The type of the accumulator. @@ -166,8 +154,6 @@ export type AsyncKeyedReducer = * console.log(await results.toObject()); // { odd: 4, even: 16 } * ``` * - * --- - * * @template K The type of the key used to aggregate elements in the iterable. * @template T The type of the elements in the iterable. * @template A The type of the accumulator. diff --git a/src/models/callbacks/callable-object.ts b/src/models/callbacks/callable-object.ts index 41e7443..c13ea08 100644 --- a/src/models/callbacks/callable-object.ts +++ b/src/models/callbacks/callable-object.ts @@ -25,8 +25,6 @@ const SmartFunction = (Function as unknown) as new { callback.enabled = false; }); * ``` * - * --- - * * @template T * The type signature of the callback function. * It must be a function. Default is `(...args: any[]) => any`. diff --git a/src/models/callbacks/publisher.ts b/src/models/callbacks/publisher.ts index f02ff9e..e27d066 100644 --- a/src/models/callbacks/publisher.ts +++ b/src/models/callbacks/publisher.ts @@ -30,8 +30,6 @@ import type { Callback } from "./types.js"; * }); * ``` * - * --- - * * @template T * A map containing the names of the emittable events and the * related callback signatures that can be subscribed to them. @@ -93,8 +91,6 @@ export default class Publisher * publisher.publish("player:move", { x: 10, y: 20 }); * ``` * - * --- - * * @template K The key of the map containing the callback signature to publish. * * @param event The name of the event to publish. @@ -123,8 +119,6 @@ export default class Publisher * }); * ``` * - * --- - * * @template K The key of the map containing the callback signature to subscribe. * * @param event The name of the event to subscribe to. @@ -162,8 +156,6 @@ export default class Publisher * publisher.subscribe("player:death", () => publisher.unsubscribe("player:move", onPlayerMove)); * ``` * - * --- - * * @template K The key of the map containing the callback signature to unsubscribe. * * @param event The name of the event to unsubscribe from. diff --git a/src/models/callbacks/switchable-callback.ts b/src/models/callbacks/switchable-callback.ts index 592ce6e..4372d21 100644 --- a/src/models/callbacks/switchable-callback.ts +++ b/src/models/callbacks/switchable-callback.ts @@ -20,8 +20,6 @@ import type { Callback } from "./types.js"; * window.addEventListener("pointerup", () => { onPointerMove.switch("released"); }); * ``` * - * --- - * * @template T The type signature of the callback. Default is `(...args: any[]) => any`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -167,8 +165,6 @@ export default class SwitchableCallback = Callbac * onPointerMove.register("released", () => { [...] }); * ``` * - * --- - * * @param key The key that will be associated with the implementation. * @param callback The implementation to register. */ @@ -198,8 +194,6 @@ export default class SwitchableCallback = Callbac * onPointerMove.unregister("released"); * ``` * - * --- - * * @param key The key that is associated with the implementation to unregister. */ public unregister(key: string): void @@ -227,8 +221,6 @@ export default class SwitchableCallback = Callbac * window.addEventListener("pointerup", () => { onPointerMove.switch("released"); }); * ``` * - * --- - * * @param key The key that is associated with the implementation to switch to. */ public switch(key: string): void diff --git a/src/models/callbacks/types.ts b/src/models/callbacks/types.ts index 06ddc71..6fa4cc4 100644 --- a/src/models/callbacks/types.ts +++ b/src/models/callbacks/types.ts @@ -8,8 +8,6 @@ * const callback: Callback<[PointerEvent]> = (evt: PointerEvent): void => { [...] }; * ``` * - * --- - * * @template A * The type of the arguments that the function accepts. * It must be an array of types, even if it's empty. Default is `[]`. diff --git a/src/models/exceptions/core.ts b/src/models/exceptions/core.ts index 13319c3..b3559c6 100644 --- a/src/models/exceptions/core.ts +++ b/src/models/exceptions/core.ts @@ -36,8 +36,6 @@ export default class Exception extends Error * } * ``` * - * --- - * * @param error The caught error to convert. * * @returns An instance of the {@link Exception} class. @@ -68,8 +66,6 @@ export default class Exception extends Error * throw new Exception("An error occurred while processing the request."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"Exception"`. @@ -125,8 +121,6 @@ export class FatalErrorException extends Exception * throw new FatalErrorException("This error should never happen. Please, contact the support team."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FatalErrorException"`. @@ -170,8 +164,6 @@ export class NotImplementedException extends FatalErrorException * throw new NotImplementedException("This method hasn't been implemented yet. Check back later."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"NotImplementedException"`. diff --git a/src/models/exceptions/index.ts b/src/models/exceptions/index.ts index bd0e59b..3050ce6 100644 --- a/src/models/exceptions/index.ts +++ b/src/models/exceptions/index.ts @@ -26,8 +26,6 @@ export class FileException extends Exception * throw new FileException("An error occurred while trying to read the file."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FileException"`. @@ -61,8 +59,6 @@ export class FileExistsException extends FileException * throw new FileExistsException("The file named 'data.json' already exists on the server."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FileExistsException"`. @@ -96,8 +92,6 @@ export class FileNotFoundException extends FileException * throw new FileNotFoundException("The file named 'data.json' wasn't found on the server."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"FileNotFoundException"`. @@ -131,8 +125,6 @@ export class KeyException extends Exception * throw new KeyException("The 'id' key wasn't found in the dictionary."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"KeyException"`. @@ -174,8 +166,6 @@ export class NetworkException extends Exception * throw new NetworkException("Couldn't connect to the server. Please, try again later."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"NetworkException"`. @@ -209,8 +199,6 @@ export class PermissionException extends Exception * throw new PermissionException("You don't have permission to access this resource."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"PermissionException"`. @@ -244,8 +232,6 @@ export class ReferenceException extends Exception * throw new ReferenceException("The 'canvas' element wasn't found in the document."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"ReferenceException"`. @@ -281,8 +267,6 @@ export class RuntimeException extends Exception * throw new RuntimeException("The received input seems to be malformed or corrupted."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"RuntimeException"`. @@ -316,8 +300,6 @@ export class EnvironmentException extends RuntimeException * throw new EnvironmentException("The required environment variable 'API_KEY' isn't set."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"EnvironmentException"`. @@ -350,8 +332,6 @@ export class TimeoutException extends Exception * throw new TimeoutException("The task took too long to complete."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"TimeoutException"`. @@ -387,8 +367,6 @@ export class TypeException extends Exception * throw new TypeException("The 'username' argument must be a valid string."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"TypeException"`. @@ -424,8 +402,6 @@ export class ValueException extends Exception * throw new ValueException("The 'grade' argument cannot be negative."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"ValueException"`. @@ -461,8 +437,6 @@ export class RangeException extends ValueException * throw new RangeException("The 'percentage' argument must be between 0 and 100."); * ``` * - * --- - * * @param message The message that describes the error. * @param cause The previous caught error that caused this one, if any. * @param name The name of the exception. Default is `"RangeException"`. diff --git a/src/models/game-loop.ts b/src/models/game-loop.ts index 87038c3..9fa3fbc 100644 --- a/src/models/game-loop.ts +++ b/src/models/game-loop.ts @@ -120,8 +120,6 @@ export default class GameLoop * const loop = new GameLoop((elapsedTime: number) => { [...] }); * ``` * - * --- - * * @param callback The function that will be executed at each iteration of the game loop. * @param msIfNotBrowser * The interval in milliseconds that will be used if the current environment isn't a browser. Default is `40`. @@ -171,8 +169,6 @@ export default class GameLoop * loop.start(); * ``` * - * --- - * * @param elapsedTime The elapsed time to set as default when the game loop starts. Default is `0`. */ public start(elapsedTime = 0): void @@ -218,8 +214,6 @@ export default class GameLoop * loop.onStart(() => { console.log("The game loop has started."); }); * ``` * - * --- - * * @param callback The function that will be executed when the game loop starts. * * @returns A function that can be used to unsubscribe from the event. @@ -236,8 +230,6 @@ export default class GameLoop * loop.onStop(() => { console.log("The game loop has stopped."); }); * ``` * - * --- - * * @param callback The function that will be executed when the game loop stops. * * @returns A function that can be used to unsubscribe from the event. diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index b143ee6..e6a4dbd 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -36,8 +36,6 @@ import type { * console.log(await result); // 31 * ``` * - * --- - * * @template T The type of elements in the iterator. * @template R The type of the final result of the iterator. Default is `void`. * @template N The type of the argument passed to the `next` method. Default is `undefined`. @@ -56,8 +54,6 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator(["A", "B", "C"]); * ``` * - * --- - * * @param iterable The iterable object to wrap. */ public constructor(iterable: Iterable); @@ -69,8 +65,6 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); * ``` * - * --- - * * @param iterable The asynchronous iterable object to wrap. */ public constructor(iterable: AsyncIterable); @@ -92,8 +86,6 @@ export default class SmartAsyncIterator implements A * }) * ``` * - * --- - * * @param iterator The iterator object to wrap. */ public constructor(iterator: Iterator); @@ -115,8 +107,6 @@ export default class SmartAsyncIterator implements A * }) * ``` * - * --- - * * @param iterator The asynchronous iterator object to wrap. */ public constructor(iterator: AsyncIterator); @@ -131,8 +121,6 @@ export default class SmartAsyncIterator implements A * }); * ``` * - * --- - * * @param generatorFn The generator function to wrap. */ public constructor(generatorFn: GeneratorFunction); @@ -147,8 +135,6 @@ export default class SmartAsyncIterator implements A * }); * ``` * - * --- - * * @param generatorFn The asynchronous generator function to wrap. */ public constructor(generatorFn: AsyncGeneratorFunction); @@ -160,8 +146,6 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator(values); * ``` * - * --- - * * @param argument The synchronous or asynchronous iterable, iterator or generator function to wrap. */ public constructor(argument: MaybeAsyncIteratorLike | MaybeAsyncGeneratorFunction); @@ -248,8 +232,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // false * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A promise that will resolve to `true` if all elements satisfy the condition, `false` otherwise. @@ -289,8 +271,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A promise that will resolve to `true` if any element satisfies the condition, `false` otherwise. @@ -330,8 +310,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [-2, -1] * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link SmartAsyncIterator} containing only the elements that satisfy the condition. @@ -358,8 +336,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [-2, 1] * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -409,8 +385,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [2, 1, 0, 1, 2] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -457,8 +431,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // 15 * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * * @returns A promise that will resolve to the final result of the reduction. @@ -484,8 +456,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // 25 * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -538,8 +508,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -592,8 +560,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [1, 2] * ``` * - * --- - * * @param count The number of elements to drop. * * @returns A new {@link SmartAsyncIterator} containing the remaining elements. @@ -646,8 +612,6 @@ export default class SmartAsyncIterator implements A * console.log(await iterator.toArray()); // [1, 2] * ``` * - * --- - * * @param limit The number of elements to take. * * @returns A new {@link SmartAsyncIterator} containing the taken elements. @@ -694,8 +658,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // 1 * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A promise that will resolve to the first element that satisfies the condition, `undefined` otherwise. @@ -723,8 +685,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // -2 * ``` * - * --- - * * @template S * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. @@ -772,8 +732,6 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing the enumerated elements. */ public enumerate(): SmartAsyncIterator<[number, T], R> @@ -799,8 +757,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toArray()); // [1, 2, 3, 4, 5] * ``` * - * --- - * * @returns A new {@link SmartAsyncIterator} containing only the unique elements. */ public unique(): SmartAsyncIterator @@ -836,8 +792,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // 5 * ``` * - * --- - * * @returns A promise that will resolve to the number of elements in the iterator. */ public async count(): Promise @@ -868,8 +822,6 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @param iteratee The function to apply to each element of the iterator. * * @returns A promise that will resolve once the iteration is complete. @@ -909,8 +861,6 @@ export default class SmartAsyncIterator implements A * console.log(result); // { done: true, value: undefined } * ``` * - * --- - * * @param values The value to pass to the next element, if required. * * @returns A promise that will resolve to the result of the iteration, containing the value of the operation. @@ -943,8 +893,6 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @param value The final value of the iterator. * * @returns A promise that will resolve to the final result of the iterator. @@ -988,8 +936,6 @@ export default class SmartAsyncIterator implements A * } * ``` * - * --- - * * @param error The error to throw into the iterator. * * @returns A promise that will resolve to the final result of the iterator. @@ -1020,8 +966,6 @@ export default class SmartAsyncIterator implements A * console.log(await result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * * @param iteratee The key function to apply to each element of the iterator. diff --git a/src/models/iterators/smart-iterator.ts b/src/models/iterators/smart-iterator.ts index 7a8d3a1..280b742 100644 --- a/src/models/iterators/smart-iterator.ts +++ b/src/models/iterators/smart-iterator.ts @@ -28,8 +28,6 @@ import type { GeneratorFunction, Iteratee, TypeGuardPredicate, Reducer, Iterator * console.log(result); // 31 * ``` * - * --- - * * @template T The type of elements in the iterator. * @template R The type of the final result of the iterator. Default is `void`. * @template N The type of the argument required by the `next` method. Default is `undefined`. @@ -48,8 +46,6 @@ export default class SmartIterator implements Iterat * const iterator = new SmartIterator(["A", "B", "C"]); * ``` * - * --- - * * @param iterable The iterable object to wrap. */ public constructor(iterable: Iterable); @@ -71,8 +67,6 @@ export default class SmartIterator implements Iterat * }) * ``` * - * --- - * * @param iterator The iterator object to wrap. */ public constructor(iterator: Iterator); @@ -87,8 +81,6 @@ export default class SmartIterator implements Iterat * }); * ``` * - * --- - * * @param generatorFn The generator function to wrap. */ public constructor(generatorFn: GeneratorFunction); @@ -100,8 +92,6 @@ export default class SmartIterator implements Iterat * const iterator = new SmartIterator(values); * ``` * - * --- - * * @param argument The iterable, iterator or generator function to wrap. */ public constructor(argument: IteratorLike | GeneratorFunction); @@ -141,8 +131,6 @@ export default class SmartIterator implements Iterat * console.log(result); // false * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if all elements satisfy the condition, `false` otherwise. @@ -182,8 +170,6 @@ export default class SmartIterator implements Iterat * console.log(result); // true * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns `true` if any element satisfies the condition, `false` otherwise. @@ -223,8 +209,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [-2, -1] * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns A new {@link SmartIterator} containing only the elements that satisfy the condition. @@ -251,8 +235,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [-2, 1] * ``` * - * --- - * * @template S * The type of the elements that satisfy the condition. * This allows the type-system to infer the correct type of the new iterator. @@ -302,8 +284,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [2, 1, 0, 1, 2] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -350,8 +330,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 15 * ``` * - * --- - * * @param reducer The reducer function to apply to each element of the iterator. * * @returns The final result of the reduction. @@ -377,8 +355,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 25 * ``` * - * --- - * * @template A The type of the accumulator value which will also be the type of the final result of the reduction. * * @param reducer The reducer function to apply to each element of the iterator. @@ -431,8 +407,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [-2, -1, 0, 1, 2, 3, 4, 5] * ``` * - * --- - * * @template V The type of the elements after the transformation. * * @param iteratee The transformation function to apply to each element of the iterator. @@ -485,8 +459,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [1, 2] * ``` * - * --- - * * @param count The number of elements to drop. * * @returns A new {@link SmartIterator} containing the remaining elements. @@ -539,8 +511,6 @@ export default class SmartIterator implements Iterat * console.log(iterator.toArray()); // [1, 2] * ``` * - * --- - * * @param limit The number of elements to take. * * @returns A new {@link SmartIterator} containing the taken elements. @@ -587,8 +557,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 1 * ``` * - * --- - * * @param predicate The condition to check for each element of the iterator. * * @returns The first element that satisfies the condition, `undefined` otherwise. @@ -616,8 +584,6 @@ export default class SmartIterator implements Iterat * console.log(result); // -2 * ``` * - * --- - * * @template S * The type of the element that satisfies the condition. * This allows the type-system to infer the correct type of the result. @@ -662,8 +628,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [[0, "A"], [1, "M"], [2, "N"], [3, "Z"]] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing the enumerated elements. */ public enumerate(): SmartIterator<[number, T], R> @@ -689,8 +653,6 @@ export default class SmartIterator implements Iterat * console.log(result.toArray()); // [1, 2, 3, 4, 5] * ``` * - * --- - * * @returns A new {@link SmartIterator} containing only the unique elements. */ public unique(): SmartIterator @@ -725,8 +687,6 @@ export default class SmartIterator implements Iterat * console.log(result); // 5 * ``` * - * --- - * * @returns The number of elements in the iterator. */ public count(): number @@ -757,8 +717,6 @@ export default class SmartIterator implements Iterat * } * ``` * - * --- - * * @param iteratee The function to apply to each element of the iterator. */ public forEach(iteratee: Iteratee): void @@ -796,8 +754,6 @@ export default class SmartIterator implements Iterat * console.log(result); // { done: true, value: undefined } * ``` * - * --- - * * @param values The value to pass to the next element, if required. * * @returns The result of the iteration, containing the value of the operation. @@ -830,8 +786,6 @@ export default class SmartIterator implements Iterat * } * ``` * - * --- - * * @param value The final value of the iterator. * * @returns The result of the iterator. @@ -875,8 +829,6 @@ export default class SmartIterator implements Iterat * } * ``` * - * --- - * * @param error The error to throw into the iterator. * * @returns The final result of the iterator. @@ -907,8 +859,6 @@ export default class SmartIterator implements Iterat * console.log(result.toObject()); // { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8, 10] } * ``` * - * --- - * * @template K The type of the keys used to group the elements. * * @param iteratee The key function to apply to each element of the iterator. diff --git a/src/models/iterators/types.ts b/src/models/iterators/types.ts index 7e7b384..ede99d4 100644 --- a/src/models/iterators/types.ts +++ b/src/models/iterators/types.ts @@ -11,8 +11,6 @@ import type { MaybePromise } from "../promises/types.js"; * } * ``` * - * --- - * * @template T The type of the elements in the iterable. */ export type MaybeAsyncIterable = Iterable | AsyncIterable; @@ -28,8 +26,6 @@ export type MaybeAsyncIterable = Iterable | * } * ``` * - * --- - * * @template T The type of the elements in the iterator. */ export type MaybeAsyncIterator = Iterator | AsyncIterator; @@ -60,8 +56,6 @@ export type MaybeAsyncGenerator = Generator * } * ``` * - * --- - * * @template T The type of the elements generated by the generator. * @template R The type of the return value of the generator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -81,8 +75,6 @@ export type GeneratorFunction = () => Generator = () => AsyncGene * } * ``` * - * --- - * * @template T The type of the elements generated by the generator. * @template R The type of the return value of the generator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -122,8 +112,6 @@ export type MaybeAsyncGeneratorFunction = () => Mayb * console.log(values); // ["1", "2", "3", "4", "5"] * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. */ @@ -142,8 +130,6 @@ export type Iteratee = (value: T, index: number) => R; * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. */ @@ -163,8 +149,6 @@ export type AsyncIteratee = (value: T, index: number) => Promise * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iteratee. Default is `void`. */ @@ -186,8 +170,6 @@ export type MaybeAsyncIteratee = (value: T, index: number) => Maybe * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R * The type of the elements that pass the type guard. @@ -211,8 +193,6 @@ export type TypeGuardPredicate = (value: T, index: number) => va * console.log(total); // 15 * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template A The type of the accumulator. */ @@ -229,8 +209,6 @@ export type Reducer = (accumulator: A, value: T, index: number) => A; * console.log(result); // 15 * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template A The type of the accumulator. */ @@ -247,8 +225,6 @@ export type AsyncReducer = (accumulator: A, value: T, index: number) => Pr * console.log(result); // 15 * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template A The type of the accumulator. */ @@ -267,8 +243,6 @@ export type MaybeAsyncReducer = (accumulator: A, value: T, index: number) * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iterator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -288,8 +262,6 @@ export type IteratorLike = Iterable | Itera * } * ``` * - * --- - * * @template T The type of the elements in the iterable. * @template R The type of the return value of the iterator. Default is `void`. * @template N The type of the `next` method argument. Default is `undefined`. @@ -310,8 +282,6 @@ export type AsyncIteratorLike = AsyncIterable("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -129,8 +125,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.get("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -150,8 +144,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.get("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -178,8 +170,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.recall("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -195,8 +185,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.recall("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -213,8 +201,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.recall("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -236,8 +222,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.retrieve("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -254,8 +238,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.retrieve("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -273,8 +255,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.retrieve("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -295,8 +275,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.read("key"); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -312,8 +290,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.read("key", defaultValue); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -330,8 +306,6 @@ export default class JSONStorage * const value: TValue = jsonStorage.read("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to retrieve. * * @param key The key of the value to retrieve. @@ -355,8 +329,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * @param persistent * Whether to prefer the persistent {@link localStorage} over the volatile {@link sessionStorage}. @@ -381,8 +353,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * * @returns `true` if the key exists, `false` otherwise. @@ -403,8 +373,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * * @returns `true` if the key exists, `false` otherwise. @@ -424,8 +392,6 @@ export default class JSONStorage * } * ``` * - * --- - * * @param key The key of the value to check. * * @returns `true` if the key exists, `false` otherwise. @@ -445,8 +411,6 @@ export default class JSONStorage * jsonStorage.set("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to set. * * @param key The key of the value to set. @@ -472,8 +436,6 @@ export default class JSONStorage * jsonStorage.remember("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to set. * * @param key The key of the value to set. @@ -494,8 +456,6 @@ export default class JSONStorage * jsonStorage.write("key", obj?.value); * ``` * - * --- - * * @template T The type of the value to set. * * @param key The key of the value to set. @@ -513,8 +473,6 @@ export default class JSONStorage * jsonStorage.delete("key"); * ``` * - * --- - * * @param key The key of the value to remove. * @param persistent * Whether to prefer the persistent {@link localStorage} over the volatile {@link sessionStorage}. @@ -534,8 +492,6 @@ export default class JSONStorage * jsonStorage.forget("key"); * ``` * - * --- - * * @param key The key of the value to remove. */ public forget(key: string): void @@ -550,8 +506,6 @@ export default class JSONStorage * jsonStorage.erase("key"); * ``` * - * --- - * * @param key The key of the value to remove. */ public erase(key: string): void @@ -567,8 +521,6 @@ export default class JSONStorage * jsonStorage.clear("key"); * ``` * - * --- - * * @param key The key of the value to remove. */ public clear(key: string): void diff --git a/src/models/promises/deferred-promise.ts b/src/models/promises/deferred-promise.ts index 8ea0e94..47046ae 100644 --- a/src/models/promises/deferred-promise.ts +++ b/src/models/promises/deferred-promise.ts @@ -19,8 +19,6 @@ import SmartPromise from "./smart-promise.js"; * promise.resolve("Hello, World!"); * ``` * - * --- - * * @template T The type of value the promise expects to initially be resolved with. Default is `void`. * @template F * The type of value returned by the `onFulfilled` callback. @@ -64,8 +62,6 @@ export default class DeferredPromise extends SmartPr * const promise = new DeferredPromise((value: string) => value.split(" ")); * ``` * - * --- - * * @param onFulfilled The callback to execute once the promise is fulfilled. * @param onRejected The callback to execute once the promise is rejected. */ @@ -100,8 +96,6 @@ export default class DeferredPromise extends SmartPr * deferred.watch(promise); * ``` * - * --- - * * @param otherPromise The promise to watch. * * @returns The current instance of the {@link DeferredPromise} class. diff --git a/src/models/promises/smart-promise.ts b/src/models/promises/smart-promise.ts index c81420e..87cb0d8 100644 --- a/src/models/promises/smart-promise.ts +++ b/src/models/promises/smart-promise.ts @@ -22,8 +22,6 @@ import type { FulfilledHandler, PromiseExecutor, RejectedHandler } from "./types * console.log(promise.isFulfilled); // true * ``` * - * --- - * * @template T The type of value the promise will eventually resolve to. Default is `void`. */ export default class SmartPromise implements Promise @@ -42,8 +40,6 @@ export default class SmartPromise implements Promise * console.log(smartRequest.isFulfilled); // true * ``` * - * --- - * * @param promise The promise to wrap. * * @returns A new {@link SmartPromise} object that wraps the provided promise. @@ -116,8 +112,6 @@ export default class SmartPromise implements Promise * }); * ``` * - * --- - * * @param executor * The function responsible for eventually resolving or rejecting the promise. * Similarly to the native {@link Promise} object, it's immediately executed after the promise is created. @@ -159,8 +153,6 @@ export default class SmartPromise implements Promise * console.log(await promise.then()); // "Hello, World!" * ``` * - * --- - * * @returns A new {@link Promise} identical to the original one. */ public then(onFulfilled?: null): Promise; @@ -180,8 +172,6 @@ export default class SmartPromise implements Promise * promise.then((result) => console.log(result)); // "Hello, World!" * ``` * - * --- - * * @template F The type of value the new promise will eventually resolve to. Default is `T`. * * @param onFulfilled The callback to execute once the promise is fulfilled. @@ -213,8 +203,6 @@ export default class SmartPromise implements Promise * promise.then(() => console.log("OK!"), () => console.log("KO!")); // "OK!" or "KO!" * ``` * - * --- - * * @template F The type of value the new promise will eventually resolve to. Default is `T`. * @template R The type of value the new promise will eventually resolve to. Default is `never`. * @@ -244,8 +232,6 @@ export default class SmartPromise implements Promise * promise.catch(); // Uncaught Error: An unknown error occurred. * ``` * - * --- - * * @returns A new {@link Promise} identical to the original one. */ public catch(onRejected?: null): Promise; @@ -268,8 +254,6 @@ export default class SmartPromise implements Promise * promise.catch((reason) => console.error(reason)); // "Error: An unknown error occurred." * ``` * - * --- - * * @template R The type of value the new promise will eventually resolve to. Default is `T`. * * @param onRejected The callback to execute once the promise is rejected. @@ -299,8 +283,6 @@ export default class SmartPromise implements Promise * .finally(() => console.log("Done!")); // Always logs "Done!". * ``` * - * --- - * * @param onFinally The callback to execute when once promise is settled. * * @returns A new {@link Promise} that executes the callback once the promise is settled. diff --git a/src/models/promises/timed-promise.ts b/src/models/promises/timed-promise.ts index 9a1a2fa..86b2695 100644 --- a/src/models/promises/timed-promise.ts +++ b/src/models/promises/timed-promise.ts @@ -21,8 +21,6 @@ import type { MaybePromise, PromiseExecutor } from "./types.js"; * .catch((error) => console.error(error)); // TimeoutException: The operation has timed out. * ``` * - * --- - * * @template T The type of value the promise will eventually resolve to. Default is `void`. */ export default class TimedPromise extends SmartPromise @@ -38,8 +36,6 @@ export default class TimedPromise extends SmartPromise * }, 5_000); * ``` * - * --- - * * @param executor * The function responsible for eventually resolving or rejecting the promise. * Similarly to the native {@link Promise} object, it's immediately executed after the promise is created. diff --git a/src/models/promises/types.ts b/src/models/promises/types.ts index 92c5c08..46a932f 100644 --- a/src/models/promises/types.ts +++ b/src/models/promises/types.ts @@ -9,8 +9,6 @@ * } * ``` * - * --- - * * @template T The type of the value. */ export type MaybePromise = T | PromiseLike; @@ -26,8 +24,6 @@ export type MaybePromise = T | PromiseLike; * .then(onFulfilled); * ``` * - * --- - * * @template T The type of value accepted by the function. Default is `void`. * @template R The type of value returned by the function. Default is `T`. */ @@ -44,8 +40,6 @@ export type FulfilledHandler = (value: T) => MaybePromise; * .catch(onRejected); * ``` * - * --- - * * @template E The type of value accepted by the function. Default is `unknown`. * @template R The type of value returned by the function. Default is `never`. */ @@ -61,8 +55,6 @@ export type RejectedHandler = (reason: E) => MaybePromis * await new Promise((resolve) => { _resolve = resolve; }); * ``` * - * --- - * * @template T The type of the value accepted by the function. Default is `void`. */ export type PromiseResolver = (result: MaybePromise) => void; @@ -77,8 +69,6 @@ export type PromiseResolver = (result: MaybePromise) => void; * await new Promise((_, reject) => { _reject = reject; }); * ``` * - * --- - * * @template E The type of the value accepted by the function. Default is `unknown`. */ export type PromiseRejecter = (reason?: MaybePromise) => void; @@ -96,8 +86,6 @@ export type PromiseRejecter = (reason?: MaybePromise) => void; * await new Promise(executor); * ``` * - * --- - * * @template T The type of value accepted by the `resolve` function. Default is `void`. * @template E The type of value accepted by the `reject` function. Default is `unknown`. */ diff --git a/src/models/timers/clock.ts b/src/models/timers/clock.ts index 6d2c03f..d61579b 100644 --- a/src/models/timers/clock.ts +++ b/src/models/timers/clock.ts @@ -45,8 +45,6 @@ export default class Clock extends GameLoop * const clock = new Clock(); * ``` * - * --- - * * @param msIfNotBrowser * The interval in milliseconds at which the clock will tick if the environment is not a browser. * `TimeUnit.Second` by default. @@ -68,8 +66,6 @@ export default class Clock extends GameLoop * clock.start(); * ``` * - * --- - * * @param elapsedTime The elapsed time to set as default when the clock starts. Default is `0`. */ public override start(elapsedTime = 0): void @@ -113,8 +109,6 @@ export default class Clock extends GameLoop * clock.start(); * ``` * - * --- - * * @param callback The callback that will be executed when the clock ticks. * @param tickStep * The minimum time in milliseconds that must pass from the previous execution of the callback to the next one. diff --git a/src/models/timers/countdown.ts b/src/models/timers/countdown.ts index 2640faa..b6ad044 100644 --- a/src/models/timers/countdown.ts +++ b/src/models/timers/countdown.ts @@ -78,8 +78,6 @@ export default class Countdown extends GameLoop * const countdown = new Countdown(10_000); * ``` * - * --- - * * @param duration * The total duration of the countdown in milliseconds. * @@ -146,8 +144,6 @@ export default class Countdown extends GameLoop * countdown.start(); * ``` * - * --- - * * @param remainingTime * The remaining time to set as default when the countdown starts. * Default is the {@link Countdown.duration} itself. @@ -177,8 +173,6 @@ export default class Countdown extends GameLoop * countdown.stop(); * ``` * - * --- - * * @param reason * The reason why the countdown has stopped. * @@ -221,8 +215,6 @@ export default class Countdown extends GameLoop * countdown.start(); * ``` * - * --- - * * @param callback The callback that will be executed when the countdown ticks. * @param tickStep * The minimum time in milliseconds that must pass from the previous execution of the callback to the next one. diff --git a/src/utils/async.ts b/src/utils/async.ts index a655a8c..493e88b 100644 --- a/src/utils/async.ts +++ b/src/utils/async.ts @@ -8,8 +8,6 @@ * doSomethingElse(); * ``` * - * --- - * * @param milliseconds The number of milliseconds to wait before resolving the promise. * * @returns A promise that resolves after the specified number of milliseconds. @@ -31,8 +29,6 @@ export function delay(milliseconds: number): Promise * $el.style.opacity = "1"; * ``` * - * --- - * * @returns A promise that resolves on the next animation frame. */ export function nextAnimationFrame(): Promise @@ -53,8 +49,6 @@ export function nextAnimationFrame(): Promise * } * ``` * - * --- - * * @returns A promise that resolves on the next microtask. */ export function yieldToEventLoop(): Promise diff --git a/src/utils/curve.ts b/src/utils/curve.ts index ee3c3b1..460b36a 100644 --- a/src/utils/curve.ts +++ b/src/utils/curve.ts @@ -20,8 +20,6 @@ export default class Curve * } * ``` * - * --- - * * @param values The number of values to generate. * * @returns A {@link SmartIterator} object that generates the values following a linear curve. @@ -47,8 +45,6 @@ export default class Curve * } * ``` * - * --- - * * @param values The number of values to generate. * @param base * The base of the exponential curve. Default is `2`. diff --git a/src/utils/date.ts b/src/utils/date.ts index 1d636af..21c1c18 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -116,8 +116,6 @@ export enum WeekDay * dateDifference(start, end, TimeUnit.Minute); // 43200 * ``` * - * --- - * * @param start The start date. * @param end The end date. * @param unit The time unit to express the difference. `TimeUnit.Day` by default. @@ -151,8 +149,6 @@ export function dateDifference(start: string | Date, end: string | Date, unit = * } * ``` * - * --- - * * @param start The start date (included). * @param end * The end date (excluded). @@ -191,8 +187,6 @@ export function dateRange(start: string | Date, end: string | Date, step = TimeU * dateRound(date, TimeUnit.Hour); // 2025-01-01T12:00:00.000Z * ``` * - * --- - * * @param date The date to round. * @param unit * The time unit to express the rounding. `TimeUnit.Day` by default. @@ -233,8 +227,6 @@ export function dateRound(date: string | Date, unit = TimeUnit.Day): Date * getWeek(date, WeekDay.Monday); // 2024-12-30 * ``` * - * --- - * * @param date The date to get the week of. * @param firstDay The first day of the week. `WeekDay.Sunday` by default. * diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 2bbf6a9..372ae78 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -6,8 +6,6 @@ * await loadScript("https://analytics.service/script.js?id=0123456789"); * ``` * - * --- - * * @param scriptUrl The URL of the script to load. * @param scriptType The type of the script to load. Default is `"text/javascript"`. * diff --git a/src/utils/iterator.ts b/src/utils/iterator.ts index 8785c20..60edf99 100644 --- a/src/utils/iterator.ts +++ b/src/utils/iterator.ts @@ -17,8 +17,6 @@ import { RangeException, SmartIterator } from "../models/index.js"; * } * ``` * - * --- - * * @template T The type of elements in the iterables. * * @param iterables The list of iterables to chain. @@ -47,8 +45,6 @@ export function chain(...iterables: readonly Iterable[]): SmartIterator * count([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // 10 * ``` * - * --- - * * @template T The type of elements in the iterable. * * @param elements The iterable to count. @@ -83,8 +79,6 @@ export function count(elements: Iterable): number * } * ``` * - * --- - * * @template T The type of elements in the iterable. * * @param elements The iterable to enumerate. @@ -118,8 +112,6 @@ export function enumerate(elements: Iterable): SmartIterator<[number, T]> * } * ``` * - * --- - * * @param end * The end value (excluded). * @@ -142,8 +134,6 @@ export function range(end: number): SmartIterator; * } * ``` * - * --- - * * @param start * The start value (included). * @@ -206,8 +196,6 @@ export function range(start: number, end?: number, step = 1): SmartIterator(iterable: Iterable): T[] * } * ``` * - * --- - * * @template T The type of elements in the iterable. * * @param elements The iterable to filter. @@ -275,8 +261,6 @@ export function unique(elements: Iterable): SmartIterator * } * ``` * - * --- - * * @template T The type of elements in the first iterable. * @template U The type of elements in the second iterable. * diff --git a/src/utils/math.ts b/src/utils/math.ts index 7cc24ad..3b24b2a 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -10,8 +10,6 @@ import { zip } from "./iterator.js"; * average([6, 8.5, 4], [3, 2, 1]); // 6.5 * ``` * - * --- - * * @template T The type of the values in the list. It must be or extend a `number` object. * * @param values @@ -80,8 +78,6 @@ export function average(values: Iterable, weights?: Iterabl * hash("How are you?"); // 1761539132 * ``` * - * --- - * * @param value The string to hash. * * @returns The hash of the specified string. @@ -107,8 +103,6 @@ export function hash(value: string): number * sum([1, 2, 3, 4, 5]); // 15 * ``` * - * --- - * * @template T The type of the values in the list. It must be or extend a `number` object. * * @param values The list of values to sum. diff --git a/src/utils/random.ts b/src/utils/random.ts index 6252b05..b0ac914 100644 --- a/src/utils/random.ts +++ b/src/utils/random.ts @@ -19,8 +19,6 @@ export default class Random * } * ``` * - * --- - * * @param ratio * The probability of generating `true`. * @@ -40,8 +38,6 @@ export default class Random * Random.Integer(5); // 0, 1, 2, 3, 4 * ``` * - * --- - * * @param max The maximum value (excluded). * * @returns A random integer value. @@ -55,8 +51,6 @@ export default class Random * Random.Integer(2, 7); // 2, 3, 4, 5, 6 * ``` * - * --- - * * @param min The minimum value (included). * @param max The maximum value (excluded). * @@ -77,8 +71,6 @@ export default class Random * Random.Decimal(); // 0.123456789 * ``` * - * --- - * * @returns A random decimal value. */ public static Decimal(): number; @@ -90,8 +82,6 @@ export default class Random * Random.Decimal(5); // 2.3456789 * ``` * - * --- - * * @param max The maximum value (excluded). * * @returns A random decimal value. @@ -105,8 +95,6 @@ export default class Random * Random.Decimal(2, 7); // 4.56789 * ``` * - * --- - * * @param min The minimum value (included). * @param max The maximum value (excluded). * diff --git a/src/utils/string.ts b/src/utils/string.ts index 424cb47..b348248 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -5,8 +5,6 @@ * capitalize('hello'); // 'Hello' * ``` * - * --- - * * @param value The string to capitalize. * * @returns The capitalized string. diff --git a/tests/models/game-loop.test.ts b/tests/models/game-loop.test.ts new file mode 100644 index 0000000..ad6864d --- /dev/null +++ b/tests/models/game-loop.test.ts @@ -0,0 +1,87 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { FatalErrorException, RuntimeException } from "../../src/index.js"; +import { GameLoop } from "../../src/index.js"; + +describe("GameLoop", () => +{ + let callback: FrameRequestCallback; + let gameLoop: GameLoop; + + beforeEach(() => + { + callback = vi.fn(); + gameLoop = new GameLoop(callback); + + vi.useFakeTimers(); + }); + + afterEach(() => vi.clearAllMocks()); + + it("Should initialize with default values", () => + { + expect(gameLoop.startTime).toBe(0); + expect(gameLoop.isRunning).toBe(false); + }); + + it("Should start the game loop", () => + { + gameLoop.start(); + + expect(gameLoop.isRunning).toBe(true); + expect(callback).toHaveBeenCalled(); + }); + it("Should throw `RuntimeException` if start is called while already running", () => + { + gameLoop.start(); + + expect(() => gameLoop.start()).toThrow(RuntimeException); + }); + + it("Should calculate elapsed time correctly", () => + { + gameLoop.start(); + + expect(gameLoop.elapsedTime).toBeGreaterThanOrEqual(0); + }); + + it("Should stop the game loop", () => + { + gameLoop.start(); + gameLoop.stop(); + + expect(gameLoop.isRunning).toBe(false); + }); + it("Should throw `RuntimeException` if stop is called while not running", () => + { + expect(() => gameLoop.stop()).toThrow(RuntimeException); + }); + + it("Should throw `FatalErrorException` if stop is called without a handle", () => + { + gameLoop.start(); + gameLoop["_handle"] = undefined; + + expect(() => gameLoop.stop()).toThrow(FatalErrorException); + }); + + it("Should subscribe to start event", () => + { + const _callback = vi.fn(); + + gameLoop.onStart(_callback); + gameLoop.start(); + + expect(_callback).toHaveBeenCalled(); + }); + it("Should subscribe to stop event", () => + { + const _callback = vi.fn(); + + gameLoop.onStop(_callback); + gameLoop.start(); + gameLoop.stop(); + + expect(_callback).toHaveBeenCalled(); + }); +}); diff --git a/tests/models/timers/clock.test.ts b/tests/models/timers/clock.test.ts new file mode 100644 index 0000000..7e83857 --- /dev/null +++ b/tests/models/timers/clock.test.ts @@ -0,0 +1,91 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { Clock } from "../../../src/index.js"; +import { FatalErrorException, RangeException, RuntimeException } from "../../../src/index.js"; + +describe("Clock", () => +{ + let clock: Clock; + + beforeEach(() => + { + clock = new Clock(); + + vi.useFakeTimers(); + }); + afterEach(() => vi.clearAllMocks()); + + it("Should start the clock and publish start event", () => + { + const _callback = vi.fn(); + + clock.onStart(_callback); + clock.start(); + + expect(_callback).toHaveBeenCalled(); + expect(clock.isRunning).toBe(true); + }); + it("Should throw `RuntimeException` if start is called when clock is already running", () => + { + clock.start(); + + expect(() => clock.start()).toThrow(RuntimeException); + }); + + it("Should stop the clock and publish stop event", () => + { + const _callback = vi.fn(); + + clock.onStop(_callback); + clock.start(); + clock.stop(); + + expect(_callback).toHaveBeenCalled(); + expect(clock.isRunning).toBe(false); + }); + it("Should throw `RuntimeException` if stop is called when clock isn't running", () => + { + expect(() => clock.stop()).toThrow(RuntimeException); + }); + + it("Should throw `FatalErrorException` if stop is called without a handle", () => + { + clock.start(); + clock["_handle"] = undefined; + + expect(() => clock.stop()).toThrow(FatalErrorException); + }); + + it("Should publish tick event at each tick", () => + { + const _callback = vi.fn(); + + clock.onTick(_callback); + clock.start(); + + vi.advanceTimersByTime(304); + + expect(_callback).toHaveBeenCalledTimes(20); + }); + + it("Should execute tick callback only if elapsed time is greater than tickStep", () => + { + const _callback = vi.fn(); + + clock.onTick(_callback, 600); + clock.start(); + + vi.advanceTimersByTime(200); + expect(_callback).toHaveBeenCalledTimes(0); + + vi.advanceTimersByTime(1400); + expect(_callback).toHaveBeenCalledTimes(2); + + vi.advanceTimersByTime(1000); + expect(_callback).toHaveBeenCalledTimes(4); + }); + it("Should throw `RangeException` if tickStep is negative", () => + { + expect(() => clock.onTick(() => { /* ... */ }, -1)).toThrow(RangeException); + }); +}); diff --git a/tests/models/timers/countdown.test.ts b/tests/models/timers/countdown.test.ts new file mode 100644 index 0000000..6e5f2b1 --- /dev/null +++ b/tests/models/timers/countdown.test.ts @@ -0,0 +1,98 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { RuntimeException, RangeException } from "../../../src/index.js"; +import { Countdown } from "../../../src/index.js"; + +describe("Countdown", () => +{ + const duration = 10_000; + let countdown: Countdown; + + beforeEach(() => + { + countdown = new Countdown(duration); + + vi.useFakeTimers(); + }); + afterEach(() => vi.clearAllMocks()); + + it("Should initialize with the correct duration", () => + { + expect(countdown.duration).toBe(duration); + }); + it("Should calculate remaining time correctly", () => + { + expect(countdown.remainingTime).toBe(duration); + }); + + it("Should start the countdown and publish start event", () => + { + const _callback = vi.fn(); + + countdown.onStart(_callback); + countdown.start(); + + expect(_callback).toHaveBeenCalled(); + }); + it("Should throw `RuntimeException` if start is called while running", () => + { + countdown.start(); + + expect(() => countdown.start()).toThrow(RuntimeException); + }); + + it("Should stop the countdown and publish stop event", () => + { + const _callback = vi.fn(); + + countdown.onStop(_callback); + countdown.start(); + countdown.stop("test reason"); + + expect(_callback).toHaveBeenCalledWith("test reason"); + }); + it("Should throw `RuntimeException` if stop is called before start", () => + { + expect(() => countdown.stop()).toThrow(RuntimeException); + }); + + it("Should publish tick events", () => + { + const _callback = vi.fn(); + + countdown.onTick(_callback); + countdown.start(); + + vi.advanceTimersByTime(304); + + expect(_callback).toHaveBeenCalledTimes(20); + }); + it("Should execute tick callback only if elapsed time is greater than tickStep", () => + { + const _callback = vi.fn(); + + countdown.onTick(_callback, 250); + countdown.start(); + + vi.advanceTimersByTime(1_000); + + expect(_callback).toHaveBeenCalledTimes(4); + }); + + it("Should publish expire event when time is up", () => + { + const _callback = vi.fn(); + + countdown.onExpire(_callback); + countdown.start(); + + vi.advanceTimersByTime(1_000); + + expect(_callback).toHaveBeenCalled(); + }); + + it("Should throw `RangeException` if tickStep is negative", () => + { + expect(() => countdown.onTick(() => { /* ... */ }, -1)).toThrow(RangeException); + }); +}); diff --git a/tests/utils/async.test.ts b/tests/utils/async.test.ts new file mode 100644 index 0000000..fcd7ae6 --- /dev/null +++ b/tests/utils/async.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it, vi } from "vitest"; + +import { delay, nextAnimationFrame, yieldToEventLoop } from "../../src/index.js"; +import type { Timeout } from "../../src/index.js"; + +describe("delay", () => +{ + it("Should resolve after the specified number of milliseconds", async () => + { + let milliseconds = 100; + + const start = Date.now(); + await delay(milliseconds); + const difference = Date.now() - start; + + milliseconds -= 1; + expect(difference).toBeGreaterThanOrEqual(milliseconds); + + milliseconds += (milliseconds / 10); + expect(difference).toBeLessThan(milliseconds); + }); +}); + +describe("nextAnimationFrame", () => +{ + it("Should resolve on the next animation frame", async () => + { + const _requestAnimationFrame = vi.spyOn(window, "requestAnimationFrame") + .mockImplementation((callback) => + { + callback(0); + + return -1; + }); + + await nextAnimationFrame(); + expect(_requestAnimationFrame).toHaveBeenCalled(); + + _requestAnimationFrame.mockRestore(); + }); +}); + +describe("yieldToEventLoop", () => +{ + it("Should resolve on the next microtask", async () => + { + const setTimeoutSpy = vi.spyOn(window, "setTimeout") + .mockImplementation((callback): Timeout => + { + callback(); + + return (-1 as unknown) as Timeout; + }); + + await yieldToEventLoop(); + expect(setTimeoutSpy).toHaveBeenCalled(); + + setTimeoutSpy.mockRestore(); + }); +}); diff --git a/tests/utils/curve.test.ts b/tests/utils/curve.test.ts index a742486..2397ce6 100644 --- a/tests/utils/curve.test.ts +++ b/tests/utils/curve.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { SmartIterator, ValueException } from "../../src/index.js"; import { Curve } from "../../src/index.js"; diff --git a/tests/utils/date.test.ts b/tests/utils/date.test.ts index e7f6367..c6a17a1 100644 --- a/tests/utils/date.test.ts +++ b/tests/utils/date.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { RangeException, SmartIterator } from "../../src/index.js"; import { TimeUnit, WeekDay, dateDifference, dateRange, dateRound, getWeek } from "../../src/index.js"; @@ -83,7 +83,7 @@ describe("dateRange", () => expect(dates[3].toISOString().slice(0, 10)).toBe("2025-01-04"); }); - it("Should throw `RangeException` if start date is not less than end date", () => + it("Should throw `RangeException` if start date isn't less than end date", () => { const start = new Date("2025-01-05"); const end = new Date("2025-01-01"); diff --git a/tests/utils/dom.test.ts b/tests/utils/dom.test.ts new file mode 100644 index 0000000..8e69a16 --- /dev/null +++ b/tests/utils/dom.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it, vi } from "vitest"; + +import { loadScript } from "../../src/index.js"; + +describe("loadScript", () => +{ + it("Should load a script successfully", async () => + { + const scriptUrl = "https://example.com/script.js"; + + const _appendChild = vi.spyOn(document.body, "appendChild") + .mockImplementation((element) => + { + const _script = element as HTMLScriptElement; + _script.onload!(new Event("load")); + + return _script; + }); + + const { resolves } = expect(loadScript(scriptUrl)); + await resolves.toBeUndefined(); + + expect(_appendChild).toHaveBeenCalled(); + + const script = _appendChild.mock.calls[0][0] as HTMLScriptElement; + expect(script).toBeInstanceOf(HTMLScriptElement); + expect(script.src).toBe(scriptUrl); + + _appendChild.mockRestore(); + }); + it("Should fail to load a script", async () => + { + const scriptUrl = "https://example.com/script.js"; + + const _appendChild = vi.spyOn(document.body, "appendChild") + .mockImplementation((element) => + { + const _script = element as HTMLScriptElement; + _script.onerror!(new Event("error")); + + return _script; + }); + + const { rejects } = expect(loadScript(scriptUrl)); + await rejects.toBeInstanceOf(Event); + + expect(_appendChild).toHaveBeenCalled(); + + const script = _appendChild.mock.calls[0][0] as HTMLScriptElement; + expect(script).toBeInstanceOf(HTMLScriptElement); + expect(script.src).toBe(scriptUrl); + + _appendChild.mockRestore(); + }); + + it("Should set the correct script type", async () => + { + const scriptUrl = "https://example.com/script.js"; + + const scriptType = "module"; + const _appendChild = vi.spyOn(document.body, "appendChild") + .mockImplementation((element) => + { + const _script = element as HTMLScriptElement; + _script.onload!(new Event("load")); + + return _script; + }); + + const { resolves } = expect(loadScript(scriptUrl, scriptType)); + await resolves.toBeUndefined(); + + expect(_appendChild).toHaveBeenCalled(); + + const script = _appendChild.mock.calls[0][0] as HTMLScriptElement; + expect(script).toBeInstanceOf(HTMLScriptElement); + expect(script.src).toBe(scriptUrl); + expect(script.type).toBe(scriptType); + + _appendChild.mockRestore(); + }); +}); diff --git a/tests/utils/iterator.test.ts b/tests/utils/iterator.test.ts index 9ce2f79..a48b64a 100644 --- a/tests/utils/iterator.test.ts +++ b/tests/utils/iterator.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { RangeException, SmartIterator } from "../../src/index.js"; import { chain, count, enumerate, range, shuffle, unique, zip } from "../../src/index.js"; diff --git a/tests/utils/math.test.ts b/tests/utils/math.test.ts index c70941c..0143d08 100644 --- a/tests/utils/math.test.ts +++ b/tests/utils/math.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { ValueException } from "../../src/index.js"; import { average, hash, sum } from "../../src/index.js"; @@ -23,7 +23,7 @@ describe("average", () => expect(() => average([1, 2, 3], [1, 0, 1])).toThrow(ValueException); expect(() => average([1, 2, 3], [1, -1, 1])).toThrow(ValueException); }); - it("Should throw `ValueException` if the sum of weights is not greater than zero", () => + it("Should throw `ValueException` if the sum of weights isn't greater than zero", () => { expect(() => average([1, 2, 3], [0, 0, 0])).toThrow(ValueException); }); diff --git a/tests/utils/random.test.ts b/tests/utils/random.test.ts index 3126aa5..f486160 100644 --- a/tests/utils/random.test.ts +++ b/tests/utils/random.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { ValueException } from "../../src/index.js"; import { Random } from "../../src/index.js"; diff --git a/tests/utils/string.test.ts b/tests/utils/string.test.ts index 59c1365..575c440 100644 --- a/tests/utils/string.test.ts +++ b/tests/utils/string.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { capitalize } from "../../src/index.js"; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..69100be --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { environment: "jsdom" } +}); From fdca9d17078a55488c19d131950b00e0a9c1db3f Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Mon, 27 Jan 2025 22:59:40 +0100 Subject: [PATCH 09/22] test: Implemented tests for `models/timers`. Closes #10. --- pnpm-lock.yaml | 90 ++++++++++++------------- src/models/promises/deferred-promise.ts | 2 +- src/models/timers/countdown.ts | 2 +- tests/models/game-loop.test.ts | 2 +- tests/models/timers/clock.test.ts | 8 +-- tests/models/timers/countdown.test.ts | 17 +++-- 6 files changed, 62 insertions(+), 59 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52c1999..f7ce378 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -401,51 +401,51 @@ packages: '@types/node@22.10.10': resolution: {integrity: sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==} - '@typescript-eslint/eslint-plugin@8.21.0': - resolution: {integrity: sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==} + '@typescript-eslint/eslint-plugin@8.22.0': + resolution: {integrity: sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@8.21.0': - resolution: {integrity: sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==} + '@typescript-eslint/parser@8.22.0': + resolution: {integrity: sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@8.21.0': - resolution: {integrity: sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==} + '@typescript-eslint/scope-manager@8.22.0': + resolution: {integrity: sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.21.0': - resolution: {integrity: sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==} + '@typescript-eslint/type-utils@8.22.0': + resolution: {integrity: sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@8.21.0': - resolution: {integrity: sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==} + '@typescript-eslint/types@8.22.0': + resolution: {integrity: sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.21.0': - resolution: {integrity: sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==} + '@typescript-eslint/typescript-estree@8.22.0': + resolution: {integrity: sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@8.21.0': - resolution: {integrity: sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==} + '@typescript-eslint/utils@8.22.0': + resolution: {integrity: sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@8.21.0': - resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==} + '@typescript-eslint/visitor-keys@8.22.0': + resolution: {integrity: sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/expect@3.0.4': @@ -1175,8 +1175,8 @@ snapshots: '@byloth/eslint-config-typescript@3.0.3(eslint@9.19.0)(typescript@5.7.3)': dependencies: '@byloth/eslint-config': 3.0.3 - '@typescript-eslint/eslint-plugin': 8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3) - '@typescript-eslint/parser': 8.21.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/parser': 8.22.0(eslint@9.19.0)(typescript@5.7.3) transitivePeerDependencies: - eslint - jiti @@ -1426,14 +1426,14 @@ snapshots: dependencies: undici-types: 6.20.0 - '@typescript-eslint/eslint-plugin@8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.21.0(eslint@9.19.0)(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/type-utils': 8.21.0(eslint@9.19.0)(typescript@5.7.3) - '@typescript-eslint/utils': 8.21.0(eslint@9.19.0)(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/parser': 8.22.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/type-utils': 8.22.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/utils': 8.22.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.22.0 eslint: 9.19.0 graphemer: 1.4.0 ignore: 5.3.2 @@ -1443,27 +1443,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.21.0(eslint@9.19.0)(typescript@5.7.3)': + '@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.22.0 debug: 4.4.0 eslint: 9.19.0 typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.21.0': + '@typescript-eslint/scope-manager@8.22.0': dependencies: - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/visitor-keys': 8.22.0 - '@typescript-eslint/type-utils@8.21.0(eslint@9.19.0)(typescript@5.7.3)': + '@typescript-eslint/type-utils@8.22.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) - '@typescript-eslint/utils': 8.21.0(eslint@9.19.0)(typescript@5.7.3) + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.22.0(eslint@9.19.0)(typescript@5.7.3) debug: 4.4.0 eslint: 9.19.0 ts-api-utils: 2.0.0(typescript@5.7.3) @@ -1471,12 +1471,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.21.0': {} + '@typescript-eslint/types@8.22.0': {} - '@typescript-eslint/typescript-estree@8.21.0(typescript@5.7.3)': + '@typescript-eslint/typescript-estree@8.22.0(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/visitor-keys': 8.21.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/visitor-keys': 8.22.0 debug: 4.4.0 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -1487,20 +1487,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.21.0(eslint@9.19.0)(typescript@5.7.3)': + '@typescript-eslint/utils@8.22.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0) - '@typescript-eslint/scope-manager': 8.21.0 - '@typescript-eslint/types': 8.21.0 - '@typescript-eslint/typescript-estree': 8.21.0(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.22.0 + '@typescript-eslint/types': 8.22.0 + '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) eslint: 9.19.0 typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.21.0': + '@typescript-eslint/visitor-keys@8.22.0': dependencies: - '@typescript-eslint/types': 8.21.0 + '@typescript-eslint/types': 8.22.0 eslint-visitor-keys: 4.2.0 '@vitest/expect@3.0.4': diff --git a/src/models/promises/deferred-promise.ts b/src/models/promises/deferred-promise.ts index 47046ae..4e73875 100644 --- a/src/models/promises/deferred-promise.ts +++ b/src/models/promises/deferred-promise.ts @@ -79,7 +79,7 @@ export default class DeferredPromise extends SmartPr _reject = reject; }); - this._promise.then(onFulfilled as FulfilledHandler, onRejected); + this._promise = this._promise.then(onFulfilled as FulfilledHandler, onRejected); this._resolve = _resolve!; this._reject = _reject!; diff --git a/src/models/timers/countdown.ts b/src/models/timers/countdown.ts index b6ad044..b5df491 100644 --- a/src/models/timers/countdown.ts +++ b/src/models/timers/countdown.ts @@ -231,7 +231,7 @@ export default class Countdown extends GameLoop if (tickStep < 0) { throw new RangeException("The tick step must be a non-negative number."); } if (tickStep === 0) { return this._publisher.subscribe("tick", callback); } - let lastTick = 0; + let lastTick = this.remainingTime; return this._publisher.subscribe("tick", (remainingTime: number) => { diff --git a/tests/models/game-loop.test.ts b/tests/models/game-loop.test.ts index ad6864d..a2ad9f8 100644 --- a/tests/models/game-loop.test.ts +++ b/tests/models/game-loop.test.ts @@ -16,7 +16,7 @@ describe("GameLoop", () => vi.useFakeTimers(); }); - afterEach(() => vi.clearAllMocks()); + afterEach(() => vi.clearAllTimers()); it("Should initialize with default values", () => { diff --git a/tests/models/timers/clock.test.ts b/tests/models/timers/clock.test.ts index 7e83857..d47f606 100644 --- a/tests/models/timers/clock.test.ts +++ b/tests/models/timers/clock.test.ts @@ -13,7 +13,7 @@ describe("Clock", () => vi.useFakeTimers(); }); - afterEach(() => vi.clearAllMocks()); + afterEach(() => vi.clearAllTimers()); it("Should start the clock and publish start event", () => { @@ -75,13 +75,13 @@ describe("Clock", () => clock.onTick(_callback, 600); clock.start(); - vi.advanceTimersByTime(200); + vi.advanceTimersByTime(216); expect(_callback).toHaveBeenCalledTimes(0); - vi.advanceTimersByTime(1400); + vi.advanceTimersByTime(1_000); expect(_callback).toHaveBeenCalledTimes(2); - vi.advanceTimersByTime(1000); + vi.advanceTimersByTime(1_216); expect(_callback).toHaveBeenCalledTimes(4); }); it("Should throw `RangeException` if tickStep is negative", () => diff --git a/tests/models/timers/countdown.test.ts b/tests/models/timers/countdown.test.ts index 6e5f2b1..0addf5b 100644 --- a/tests/models/timers/countdown.test.ts +++ b/tests/models/timers/countdown.test.ts @@ -14,7 +14,7 @@ describe("Countdown", () => vi.useFakeTimers(); }); - afterEach(() => vi.clearAllMocks()); + afterEach(() => vi.clearAllTimers()); it("Should initialize with the correct duration", () => { @@ -41,15 +41,18 @@ describe("Countdown", () => expect(() => countdown.start()).toThrow(RuntimeException); }); - it("Should stop the countdown and publish stop event", () => + it("Should stop the countdown and publish stop event", async () => { const _callback = vi.fn(); countdown.onStop(_callback); - countdown.start(); - countdown.stop("test reason"); - expect(_callback).toHaveBeenCalledWith("test reason"); + const { rejects } = expect(countdown.start()); + countdown.stop("This is a test!"); + + await rejects.toBe("This is a test!"); + + expect(_callback).toHaveBeenCalledWith("This is a test!"); }); it("Should throw `RuntimeException` if stop is called before start", () => { @@ -74,7 +77,7 @@ describe("Countdown", () => countdown.onTick(_callback, 250); countdown.start(); - vi.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_024); expect(_callback).toHaveBeenCalledTimes(4); }); @@ -86,7 +89,7 @@ describe("Countdown", () => countdown.onExpire(_callback); countdown.start(); - vi.advanceTimersByTime(1_000); + vi.advanceTimersByTime(10_000); expect(_callback).toHaveBeenCalled(); }); From 451f4c4eebae3ccd390d20bb8010644a6bc89ae4 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Wed, 29 Jan 2025 07:38:08 +0100 Subject: [PATCH 10/22] add: Added some other tests... --- package.json | 2 +- .../models/callbacks/callable-object.test.ts | 38 +++++ tests/models/callbacks/publisher.test.ts | 85 +++++++++++ .../callbacks/switchable-callback.test.ts | 143 ++++++++++++++++++ tests/utils/curve.test.ts | 2 +- 5 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 tests/models/callbacks/callable-object.test.ts create mode 100644 tests/models/callbacks/publisher.test.ts create mode 100644 tests/models/callbacks/switchable-callback.test.ts diff --git a/package.json b/package.json index e2e9ec3..fff4f94 100644 --- a/package.json +++ b/package.json @@ -65,5 +65,5 @@ "vite": "^6.0.11", "vitest": "^3.0.4" }, - "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a" + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" } diff --git a/tests/models/callbacks/callable-object.test.ts b/tests/models/callbacks/callable-object.test.ts new file mode 100644 index 0000000..2e58bd9 --- /dev/null +++ b/tests/models/callbacks/callable-object.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; + +import { CallableObject } from "../../../src/index.js"; + +class Multiply extends CallableObject<(arg: number) => number> +{ + public multiplier; + + public constructor(multiplier = 2) + { + super(); + + this.multiplier = multiplier; + } + + protected _invoke(value: number): number + { + return value * this.multiplier; + } +} + +describe("CallableObject", () => +{ + it("Should be callable and return the correct result", () => + { + const multiply = new Multiply(); + + const result = multiply(5); + expect(result).toBe(10); + }); + it("Should bind the correct context", () => + { + const multiply = new Multiply(3); + + const result = multiply(6); + expect(result).toBe(18); + }); +}); diff --git a/tests/models/callbacks/publisher.test.ts b/tests/models/callbacks/publisher.test.ts new file mode 100644 index 0000000..1107242 --- /dev/null +++ b/tests/models/callbacks/publisher.test.ts @@ -0,0 +1,85 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { ReferenceException } from "../../../src/index.js"; +import { Publisher } from "../../../src/index.js"; + +interface EventsMap { + "player:spawn": (evt: { x: number, y: number }) => void; + "player:move": (coords: { x: number, y: number }) => void; + "player:death": () => void; +} + +describe("Publisher", () => +{ + let publisher: Publisher; + + beforeEach(() => { publisher = new Publisher(); }); + + it("Should subscribe and publish events", () => + { + const _spawnHandler = vi.fn(); + const _moveHandler = vi.fn(); + + publisher.subscribe("player:spawn", _spawnHandler); + publisher.subscribe("player:move", _moveHandler); + + publisher.publish("player:spawn", { x: 10, y: 20 }); + publisher.publish("player:move", { x: 30, y: 40 }); + + expect(_spawnHandler).toHaveBeenCalledWith({ x: 10, y: 20 }); + expect(_moveHandler).toHaveBeenCalledWith({ x: 30, y: 40 }); + }); + + it("Should unsubscribe from events", () => + { + const _moveHandler = vi.fn(); + const unsubscribe = publisher.subscribe("player:move", _moveHandler); + + unsubscribe(); + expect(() => unsubscribe()).toThrow(ReferenceException); + + publisher.publish("player:move", { x: 30, y: 40 }); + + expect(_moveHandler).not.toHaveBeenCalled(); + }); + + it("Should clear all subscribers", () => + { + const _spawnHandler = vi.fn(); + const _moveHandler = vi.fn(); + const _deathHandler = vi.fn(); + + publisher.subscribe("player:spawn", _spawnHandler); + publisher.subscribe("player:move", _moveHandler); + publisher.subscribe("player:death", _deathHandler); + + publisher.clear(); + + publisher.publish("player:spawn", { x: 10, y: 20 }); + publisher.publish("player:move", { x: 30, y: 40 }); + publisher.publish("player:death"); + + expect(_spawnHandler).not.toHaveBeenCalled(); + expect(_moveHandler).not.toHaveBeenCalled(); + expect(_deathHandler).not.toHaveBeenCalled(); + }); + + it("Should not throw `ReferenceException` when unsubscribing a non-existent subscriber", () => + { + const _moveHandler = vi.fn(); + + expect(() => publisher.unsubscribe("player:move", _moveHandler)).not.toThrow(ReferenceException); + }); + + it("Should return an array of return values from subscribers", () => + { + const _moveHandler1 = vi.fn(() => "handler1"); + const _moveHandler2 = vi.fn(() => "handler2"); + + publisher.subscribe("player:move", _moveHandler1); + publisher.subscribe("player:move", _moveHandler2); + + const results = publisher.publish("player:move", { x: 30, y: 40 }); + expect(results).toEqual(["handler1", "handler2"]); + }); +}); diff --git a/tests/models/callbacks/switchable-callback.test.ts b/tests/models/callbacks/switchable-callback.test.ts new file mode 100644 index 0000000..4a520ce --- /dev/null +++ b/tests/models/callbacks/switchable-callback.test.ts @@ -0,0 +1,143 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { KeyException, NotImplementedException, RuntimeException } from "../../../src/index.js"; +import { SwitchableCallback } from "../../../src/index.js"; + +interface Point +{ + x: number; + y: number; +} + +describe("SwitchableCallback", () => +{ + let callback: SwitchableCallback<(point: Point) => void>; + + const _newPoint = () => + { + return { + x: Math.floor(Math.random() * 1920), + y: Math.floor(Math.random() * 1080) + }; + }; + + beforeEach(() => { callback = new SwitchableCallback(); }); + + it("Should throw `NotImplementedException` if no callback is registered", () => + { + expect(() => callback(_newPoint())).toThrow(NotImplementedException); + }); + + it("Should register a new callback and set it as default", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + const result = _newPoint(); + + callback.register("default", _callback); + + callback(result); + + expect(callback.key).toBe("default"); + expect(_callback).toHaveBeenCalledWith(result); + }); + + it("Should throw `KeyException` if trying to register a callback with an existing key", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + callback.register("default", _callback); + + expect(() => callback.register("default", _callback)).toThrow(KeyException); + }); + + it("Should enable & disable the callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + + callback.register("default", _callback); + callback(_newPoint()); + + expect(callback.isEnabled).toBe(true); + + callback.disable(); + callback(_newPoint()); + + expect(callback.isEnabled).toBe(false); + + callback.enable(); + callback(_newPoint()); + + expect(callback.isEnabled).toBe(true); + expect(_callback).toHaveBeenCalledTimes(2); + }); + + it("Should throw `RuntimeException` if enabling an already enabled callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + callback.register("default", _callback); + + expect(() => callback.enable()).toThrow(RuntimeException); + }); + it("Should throw `RuntimeException` if disabling an already disabled callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + callback.register("default", _callback); + callback.disable(); + + expect(() => callback.disable()).toThrow(RuntimeException); + }); + + it("Should switch to a different registered callback", () => + { + const _callback1 = vi.fn((point: Point) => { /* ... */ }); + const _callback2 = vi.fn((point: Point) => { /* ... */ }); + + callback.register("first", _callback1); + callback(_newPoint()); + + expect(callback.key).toBe("first"); + + callback.register("second", _callback2); + callback(_newPoint()); + + expect(callback.key).toBe("first"); + + callback.switch("second"); + callback(_newPoint()); + + expect(callback.key).toBe("second"); + expect(_callback1).toHaveBeenCalledTimes(2); + expect(_callback2).toHaveBeenCalledTimes(1); + }); + + it("Should throw `KeyException` if switching to a non-existent callback", () => + { + expect(() => callback.switch("nonexistent")).toThrow(KeyException); + }); + + it("Should unregister a callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + + callback.register("default", _callback); + callback.register("second", _callback); + + callback.switch("second"); + callback.unregister("default"); + + expect(() => callback.switch("default")).toThrow(KeyException); + }); + + it("Should throw `KeyException` if unregistering the currently selected callback", () => + { + const _callback = vi.fn((point: Point) => { /* ... */ }); + + callback.register("default", _callback); + + expect(() => callback.unregister("default")).toThrow(KeyException); + }); + + it("Should throw `KeyException` if unregistering a non-existent callback", () => + { + expect(() => callback.unregister("nonexistent")).toThrow(KeyException); + }); +}); diff --git a/tests/utils/curve.test.ts b/tests/utils/curve.test.ts index 2397ce6..c2be9a6 100644 --- a/tests/utils/curve.test.ts +++ b/tests/utils/curve.test.ts @@ -45,7 +45,7 @@ describe("Curve", () => ); }); - it("Should throw a `ValueException` if base is negative", () => + it("Should throw `ValueException` if base is negative", () => { expect(() => Curve.Exponential(6, -1)).toThrow(ValueException); }); From 1d3c524ce8607b8dee71565004db4ca871a5b39c Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Thu, 30 Jan 2025 15:34:23 +0100 Subject: [PATCH 11/22] wip: Implemented some tests for `models/iterators`... --- src/models/iterators/smart-async-iterator.ts | 4 +- src/models/iterators/smart-iterator.ts | 2 +- tests/iterators/smart-async-iterator.test.ts | 194 +++++++++++++++++++ tests/iterators/smart-iterator.test.ts | 192 ++++++++++++++++++ tests/models/timers/clock.test.ts | 12 +- tests/models/timers/countdown.test.ts | 12 +- tests/utils/async.test.ts | 20 +- tests/utils/iterator.test.ts | 16 +- 8 files changed, 421 insertions(+), 31 deletions(-) create mode 100644 tests/iterators/smart-async-iterator.test.ts create mode 100644 tests/iterators/smart-iterator.test.ts diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index e6a4dbd..d104005 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -76,7 +76,7 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator({ * _sum: 0, _count: 0, * - * next: function (value: number) + * next: function(value: number) * { * this._sum += value; * this._count += 1; @@ -97,7 +97,7 @@ export default class SmartAsyncIterator implements A * const iterator = new SmartAsyncIterator({ * _sum: 0, _count: 0, * - * next: async function (value: number) + * next: async function(value: number) * { * this._sum += value; * this._count += 1; diff --git a/src/models/iterators/smart-iterator.ts b/src/models/iterators/smart-iterator.ts index 280b742..167c8d7 100644 --- a/src/models/iterators/smart-iterator.ts +++ b/src/models/iterators/smart-iterator.ts @@ -57,7 +57,7 @@ export default class SmartIterator implements Iterat * const iterator = new SmartIterator({ * _sum: 0, _count: 0, * - * next: function (value: number) + * next: function(value: number) * { * this._sum += value; * this._count += 1; diff --git a/tests/iterators/smart-async-iterator.test.ts b/tests/iterators/smart-async-iterator.test.ts new file mode 100644 index 0000000..e462336 --- /dev/null +++ b/tests/iterators/smart-async-iterator.test.ts @@ -0,0 +1,194 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { delay } from "../../src/index.js"; +import { SmartAsyncIterator } from "../../src/index.js"; + +describe("SmartAsyncIterator", () => +{ + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should initialize with an iterable", async () => + { + const _iterable = [1, 2, 3]; + + const iterator = new SmartAsyncIterator(_iterable); + expect(await iterator.toArray()).toEqual([1, 2, 3]); + }); + it("Should initialize with an iterator", async () => + { + const _iterator = { + _index: 0, + + next: async function() + { + await delay(100); + + if (this._index < 3) + { + this._index += 1; + + return { done: false, value: this._index }; + } + + return { done: true, value: undefined }; + } + }; + + const iterator = new SmartAsyncIterator(_iterator); + + let resolved = false; + iterator.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([1, 2, 3]); + }); + + await vi.advanceTimersByTimeAsync(300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should initialize with a generator function", async () => + { + const _generatorFn = async function* () + { + await delay(100); + yield 1; + + await delay(100); + yield 2; + + await delay(100); + yield 3; + + await delay(100); + }; + + const iterator = new SmartAsyncIterator(_generatorFn); + + let resolved = false; + iterator.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([1, 2, 3]); + }); + + await vi.advanceTimersByTimeAsync(300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should map values correctly", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = iterator.map(async (value) => value * 2); + expect(await results.toArray()).toEqual([2, 4, 6, 8, 10]); + }); + + it("Should filter values correctly", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = iterator.filter(async (value) => value % 2 === 0); + expect(await results.toArray()).toEqual([2, 4]); + }); + + it("Should reduce values correctly", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = await iterator.reduce(async (acc, value) => acc + value, 0); + expect(results).toBe(15); + }); + + it("Should find the first matching value", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = await iterator.find(async (value) => value > 3); + expect(results).toBe(4); + }); + + it("Should return true if every value matches the predicate", async () => + { + const iterator = new SmartAsyncIterator([2, 4, 6, 8, 10]); + const results = await iterator.every(async (value) => value % 2 === 0); + expect(results).toBe(true); + }); + + it("Should return true if some values match the predicate", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = await iterator.some(async (value) => value > 3); + expect(results).toBe(true); + }); + + it("Should drop the specified number of elements", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = iterator.drop(3); + expect(await results.toArray()).toEqual([4, 5]); + }); + + it("Should take the specified number of elements", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = iterator.take(3); + expect(await results.toArray()).toEqual([1, 2, 3]); + }); + + it("Should enumerate elements correctly", async () => + { + const iterator = new SmartAsyncIterator(["A", "B", "C"]); + const results = iterator.enumerate(); + expect(await results.toArray()).toEqual([[0, "A"], [1, "B"], [2, "C"]]); + }); + + it("Should remove duplicate elements", async () => + { + const iterator = new SmartAsyncIterator([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]); + const results = iterator.unique(); + expect(await results.toArray()).toEqual([1, 2, 3, 4, 5]); + }); + + it("Should count the number of elements", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const results = await iterator.count(); + expect(results).toBe(5); + }); + + it("Should iterate over elements with forEach", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const values: number[] = []; + await iterator.forEach(async (value) => + { + values.push(value); + }); + expect(values).toEqual([1, 2, 3, 4, 5]); + }); + + it("Should flatten elements with flatMap", async () => + { + const iterator = new SmartAsyncIterator([[1, 2], [3, 4], [5]]); + const results = iterator.flatMap(async (value) => value); + expect(await results.toArray()).toEqual([1, 2, 3, 4, 5]); + }); + + it("Should group elements by key", async () => + { + const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5, 6]); + const results = iterator.groupBy(async (value) => (value % 2 === 0 ? "even" : "odd")); + const grouped = await results.toObject(); + expect(grouped).toEqual({ + odd: [1, 3, 5], + even: [2, 4, 6] + }); + }); +}); diff --git a/tests/iterators/smart-iterator.test.ts b/tests/iterators/smart-iterator.test.ts new file mode 100644 index 0000000..48a4ec2 --- /dev/null +++ b/tests/iterators/smart-iterator.test.ts @@ -0,0 +1,192 @@ +import { describe, it, expect, vi } from "vitest"; + +import { ValueException } from "../../src/index.js"; +import { SmartIterator } from "../../src/index.js"; + +describe("SmartIterator", () => +{ + it("Should initialize with an iterable", () => + { + const _iterable = [1, 2, 3]; + + const iterator = new SmartIterator(_iterable); + expect(iterator.toArray()).toEqual([1, 2, 3]); + }); + it("Should initialize with an iterator", () => + { + const _iterator = { + _index: 0, + + next: function() + { + if (this._index < 3) + { + this._index += 1; + + return { done: false, value: this._index }; + } + + return { done: true, value: undefined }; + } + }; + + const iterator = new SmartIterator(_iterator); + expect(iterator.toArray()).toEqual([1, 2, 3]); + }); + it("Should initialize with a generator function", () => + { + const _generatorFn = function* () + { + yield 1; + yield 2; + yield 3; + }; + + const iterator = new SmartIterator(_generatorFn); + expect(iterator.toArray()).toEqual([1, 2, 3]); + }); + + it("Should map values correctly", () => + { + const iterator = new SmartIterator([1, 2, 3]); + + const results = iterator.map((x) => x * 2); + expect(results.toArray()).toEqual([2, 4, 6]); + }); + + it("Should filter values correctly", () => + { + const iterator = new SmartIterator([1, 2, 3, 4]); + + const results = iterator.filter((x) => x % 2 === 0); + expect(results.toArray()).toEqual([2, 4]); + }); + + it("Should reduce values correctly", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.reduce((acc, value) => acc + value); + expect(results).toBe(15); + }); + it("Should reduce values with initial value correctly", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.reduce((acc, value) => acc + value, 10); + expect(results).toBe(25); + }); + + it("Should throw `ValueException` when reducing an empty iterator without initial value", () => + { + const iterator = new SmartIterator([]); + + expect(() => iterator.reduce((acc, value) => acc + value)).toThrow(ValueException); + }); + + it("Should find the first matching value", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.find((x) => x > 3); + expect(results).toBe(4); + }); + it("Should return undefined when no matching value is found", () => + { + const iterator = new SmartIterator([1, 2, 3]); + + const results = iterator.find((x) => x > 3); + expect(results).toBeUndefined(); + }); + + it("Should drop the specified number of elements", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.drop(3); + expect(results.toArray()).toEqual([4, 5]); + }); + it("Should take the specified number of elements", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.take(3); + expect(results.toArray()).toEqual([1, 2, 3]); + }); + + it("Should count the number of elements", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.count(); + expect(results).toBe(5); + }); + it("Should enumerate elements with their indices", () => + { + const iterator = new SmartIterator(["A", "B", "C"]); + + const results = iterator.enumerate(); + expect(results.toArray()).toEqual([[0, "A"], [1, "B"], [2, "C"]]); + }); + it("Should remove duplicate elements", () => + { + const iterator = new SmartIterator([1, 2, 2, 1, 3, 1, 4, 3, 4, 5, 5]); + + const results = iterator.unique(); + expect(results.toArray()).toEqual([1, 2, 3, 4, 5]); + }); + + it("Should `flatMap` elements correctly", () => + { + const iterator = new SmartIterator([1, [2, 3], 4, 5, [6, 7, 8]]); + + const results = iterator.flatMap((x) => x); + expect(results.toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + }); + + it("Should execute `forEach` correctly", () => + { + const _iteratee = vi.fn((x: number) => results.push(x)); + + const iterator = new SmartIterator([1, 2, 3]); + const results: number[] = []; + + iterator.forEach(_iteratee); + + expect(results).toEqual([1, 2, 3]); + expect(_iteratee).toBeCalledTimes(3); + }); + + it("Should handle return method correctly", () => + { + const _iterator: Iterator = { + next: () => ({ done: false, value: 1 }), + return: () => ({ done: true, value: "done" }) + }; + + const iterator = new SmartIterator(_iterator); + const results = iterator.return(); + + expect(results).toEqual({ done: true, value: "done" }); + }); + it("Should handle throw method correctly", () => + { + const _iterator = { + next: () => ({ done: false, value: 1 }), + throw: (error: unknown) => { throw error; } + }; + + const iterator = new SmartIterator(_iterator); + const reason = new Error("Something went wrong!"); + + expect(() => iterator.throw(reason)).toThrow(reason); + }); + + it("Should group elements by key", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5, 6]); + + const results = iterator.groupBy((x) => x % 2 === 0 ? "even" : "odd"); + expect(results.toObject()).toEqual({ odd: [1, 3, 5], even: [2, 4, 6] }); + }); +}); diff --git a/tests/models/timers/clock.test.ts b/tests/models/timers/clock.test.ts index d47f606..6382ddb 100644 --- a/tests/models/timers/clock.test.ts +++ b/tests/models/timers/clock.test.ts @@ -56,32 +56,32 @@ describe("Clock", () => expect(() => clock.stop()).toThrow(FatalErrorException); }); - it("Should publish tick event at each tick", () => + it("Should publish tick event at each tick", async () => { const _callback = vi.fn(); clock.onTick(_callback); clock.start(); - vi.advanceTimersByTime(304); + await vi.advanceTimersByTimeAsync(304); expect(_callback).toHaveBeenCalledTimes(20); }); - it("Should execute tick callback only if elapsed time is greater than tickStep", () => + it("Should execute tick callback only if elapsed time is greater than tickStep", async () => { const _callback = vi.fn(); clock.onTick(_callback, 600); clock.start(); - vi.advanceTimersByTime(216); + await vi.advanceTimersByTimeAsync(216); expect(_callback).toHaveBeenCalledTimes(0); - vi.advanceTimersByTime(1_000); + await vi.advanceTimersByTimeAsync(1_000); expect(_callback).toHaveBeenCalledTimes(2); - vi.advanceTimersByTime(1_216); + await vi.advanceTimersByTimeAsync(1_216); expect(_callback).toHaveBeenCalledTimes(4); }); it("Should throw `RangeException` if tickStep is negative", () => diff --git a/tests/models/timers/countdown.test.ts b/tests/models/timers/countdown.test.ts index 0addf5b..d0f8768 100644 --- a/tests/models/timers/countdown.test.ts +++ b/tests/models/timers/countdown.test.ts @@ -59,37 +59,37 @@ describe("Countdown", () => expect(() => countdown.stop()).toThrow(RuntimeException); }); - it("Should publish tick events", () => + it("Should publish tick events", async () => { const _callback = vi.fn(); countdown.onTick(_callback); countdown.start(); - vi.advanceTimersByTime(304); + await vi.advanceTimersByTimeAsync(304); expect(_callback).toHaveBeenCalledTimes(20); }); - it("Should execute tick callback only if elapsed time is greater than tickStep", () => + it("Should execute tick callback only if elapsed time is greater than tickStep", async () => { const _callback = vi.fn(); countdown.onTick(_callback, 250); countdown.start(); - vi.advanceTimersByTime(1_024); + await vi.advanceTimersByTimeAsync(1_024); expect(_callback).toHaveBeenCalledTimes(4); }); - it("Should publish expire event when time is up", () => + it("Should publish expire event when time is up", async () => { const _callback = vi.fn(); countdown.onExpire(_callback); countdown.start(); - vi.advanceTimersByTime(10_000); + await vi.advanceTimersByTimeAsync(10_000); expect(_callback).toHaveBeenCalled(); }); diff --git a/tests/utils/async.test.ts b/tests/utils/async.test.ts index fcd7ae6..d83066f 100644 --- a/tests/utils/async.test.ts +++ b/tests/utils/async.test.ts @@ -7,17 +7,21 @@ describe("delay", () => { it("Should resolve after the specified number of milliseconds", async () => { - let milliseconds = 100; + vi.useFakeTimers(); - const start = Date.now(); - await delay(milliseconds); - const difference = Date.now() - start; + const milliseconds = 100; - milliseconds -= 1; - expect(difference).toBeGreaterThanOrEqual(milliseconds); + let resolved = false; + delay(milliseconds) + .then(() => { resolved = true; }); - milliseconds += (milliseconds / 10); - expect(difference).toBeLessThan(milliseconds); + await vi.advanceTimersByTimeAsync(milliseconds - 1); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(1); + expect(resolved).toBe(true); + + vi.clearAllTimers(); }); }); diff --git a/tests/utils/iterator.test.ts b/tests/utils/iterator.test.ts index a48b64a..f25fd84 100644 --- a/tests/utils/iterator.test.ts +++ b/tests/utils/iterator.test.ts @@ -13,9 +13,9 @@ describe("chain", () => }); it("Should chain multiple iterables into a single one", () => { - const result = Array.from(chain([1, 2, 3], [4, 5, 6], [7, 8, 9])); + const results = Array.from(chain([1, 2, 3], [4, 5, 6], [7, 8, 9])); - expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(results).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); }); }); @@ -38,9 +38,9 @@ describe("enumerate", () => }); it("Should enumerate the elements of an iterable", () => { - const result = Array.from(enumerate(["A", "M", "N", "Z"])); + const results = Array.from(enumerate(["A", "M", "N", "Z"])); - expect(result).toEqual([[0, "A"], [1, "M"], [2, "N"], [3, "Z"]]); + expect(results).toEqual([[0, "A"], [1, "M"], [2, "N"], [3, "Z"]]); }); }); @@ -104,9 +104,9 @@ describe("unique", () => }); it("Should filter the elements of an iterable ensuring they are all unique", () => { - const result = Array.from(unique([1, 1, 2, 3, 2, 3, 4, 5, 5, 4])); + const results = Array.from(unique([1, 1, 2, 3, 2, 3, 4, 5, 5, 4])); - expect(result).toEqual([1, 2, 3, 4, 5]); + expect(results).toEqual([1, 2, 3, 4, 5]); }); }); @@ -120,8 +120,8 @@ describe("zip", () => }); it("Should zip two iterables into a single one", () => { - const result = Array.from(zip([1, 2, 3, 4], ["A", "M", "N", "Z"])); + const results = Array.from(zip([1, 2, 3, 4], ["A", "M", "N", "Z"])); - expect(result).toEqual([[1, "A"], [2, "M"], [3, "N"], [4, "Z"]]); + expect(results).toEqual([[1, "A"], [2, "M"], [3, "N"], [4, "Z"]]); }); }); From eed5ab51389483398c56e97ed109d30af2267a8d Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Thu, 30 Jan 2025 17:42:48 +0100 Subject: [PATCH 12/22] imp: Implemented tests for `models/iterators`. --- src/models/iterators/smart-async-iterator.ts | 9 +- tests/iterators/smart-async-iterator.test.ts | 460 ++++++++++++++++--- tests/iterators/smart-iterator.test.ts | 97 ++-- 3 files changed, 465 insertions(+), 101 deletions(-) diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index d104005..9d72fec 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -1,5 +1,6 @@ import AggregatedAsyncIterator from "../aggregators/aggregated-async-iterator.js"; import { ValueException } from "../exceptions/index.js"; +import type { MaybePromise } from "../types.js"; import type { GeneratorFunction, @@ -897,11 +898,13 @@ export default class SmartAsyncIterator implements A * * @returns A promise that will resolve to the final result of the iterator. */ - public async return(value?: R): Promise> + public async return(value?: MaybePromise): Promise> { - if (this._iterator.return) { return this._iterator.return(value); } + const _value = (await value) as R; - return { done: true, value: value as R }; + if (this._iterator.return) { return await this._iterator.return(_value); } + + return { done: true, value: _value }; } /** diff --git a/tests/iterators/smart-async-iterator.test.ts b/tests/iterators/smart-async-iterator.test.ts index e462336..1dbf6cd 100644 --- a/tests/iterators/smart-async-iterator.test.ts +++ b/tests/iterators/smart-async-iterator.test.ts @@ -1,10 +1,23 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { delay } from "../../src/index.js"; +import { delay, ValueException, type MaybePromise } from "../../src/index.js"; import { SmartAsyncIterator } from "../../src/index.js"; describe("SmartAsyncIterator", () => { + const _toAsync = (elements: Iterable) => + { + return async function* () + { + for (const element of elements) + { + await delay(100); + + yield element; + } + }; + }; + beforeEach(() => vi.useFakeTimers()); afterEach(() => vi.clearAllTimers()); @@ -39,11 +52,11 @@ describe("SmartAsyncIterator", () => let resolved = false; iterator.toArray() - .then((result) => + .then((results) => { resolved = true; - expect(result).toEqual([1, 2, 3]); + expect(results).toEqual([1, 2, 3]); }); await vi.advanceTimersByTimeAsync(300); @@ -56,14 +69,12 @@ describe("SmartAsyncIterator", () => { const _generatorFn = async function* () { - await delay(100); - yield 1; - - await delay(100); - yield 2; + for (let i = 1; i < 4; i += 1) + { + await delay(100); - await delay(100); - yield 3; + yield i; + } await delay(100); }; @@ -72,11 +83,11 @@ describe("SmartAsyncIterator", () => let resolved = false; iterator.toArray() - .then((result) => + .then((results) => { resolved = true; - expect(result).toEqual([1, 2, 3]); + expect(results).toEqual([1, 2, 3]); }); await vi.advanceTimersByTimeAsync(300); @@ -86,109 +97,426 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should map values correctly", async () => + it("Should return `true` if every value matches the predicate", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); - const results = iterator.map(async (value) => value * 2); - expect(await results.toArray()).toEqual([2, 4, 6, 8, 10]); + const iterator = new SmartAsyncIterator(_toAsync([2, 4, 6, 8, 10])); + + let resolved = false; + iterator.every(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(true); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return `false` if not every value matches the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([2, 4, 5, 6, 7, 8, 10])); + + let resolved = false; + iterator.every(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(false); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should return `true` if some values match the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.some(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(true); + }); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return `false` if no values match the predicate", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 3, 5, 7, 9])); + + let resolved = false; + iterator.some(async (value) => value % 2 === 0) + .then((results) => + { + resolved = true; + + expect(results).toBe(false); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); it("Should filter values correctly", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.filter(async (value) => value % 2 === 0); - expect(await results.toArray()).toEqual([2, 4]); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([2, 4]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should map values correctly", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.map(async (value) => value * 2); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([2, 4, 6, 8, 10]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); it("Should reduce values correctly", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); - const results = await iterator.reduce(async (acc, value) => acc + value, 0); - expect(results).toBe(15); - }); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); - it("Should find the first matching value", async () => + let resolved = false; + iterator.reduce(async (acc, value) => acc + value) + .then((results) => + { + resolved = true; + + expect(results).toBe(15); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should reduce values with initial value correctly", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); - const results = await iterator.find(async (value) => value > 3); - expect(results).toBe(4); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.reduce(async (acc, value) => acc + value, 10) + .then((results) => + { + resolved = true; + + expect(results).toBe(25); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); - it("Should return true if every value matches the predicate", async () => + it("Should throw `ValueException` when reducing an empty iterator without initial value", async () => { - const iterator = new SmartAsyncIterator([2, 4, 6, 8, 10]); - const results = await iterator.every(async (value) => value % 2 === 0); - expect(results).toBe(true); + const iterator = new SmartAsyncIterator(_toAsync([])); + + try + { + await iterator.reduce((acc, value) => acc + value); + } + catch (error) + { + expect(error).toBeInstanceOf(ValueException); + } }); - it("Should return true if some values match the predicate", async () => + it("Should flatten elements with `flatMap`", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); - const results = await iterator.some(async (value) => value > 3); - expect(results).toBe(true); + const iterator = new SmartAsyncIterator(_toAsync([1, [2, 3], 4, 5, [6, 7, 8]])); + const results = iterator.flatMap(async (value) => value); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); it("Should drop the specified number of elements", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.drop(3); - expect(await results.toArray()).toEqual([4, 5]); - }); + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([4, 5]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); it("Should take the specified number of elements", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.take(3); - expect(await results.toArray()).toEqual([1, 2, 3]); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([1, 2, 3]); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); - it("Should enumerate elements correctly", async () => + it("Should find the first matching value", async () => { - const iterator = new SmartAsyncIterator(["A", "B", "C"]); - const results = iterator.enumerate(); - expect(await results.toArray()).toEqual([[0, "A"], [1, "B"], [2, "C"]]); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.find(async (value) => value > 3) + .then((results) => + { + resolved = true; + + expect(results).toBe(4); + }); + + await vi.advanceTimersByTimeAsync(300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); + it("Should return `undefined` when no matching value is found", async () => + { + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3])); + let resolved = false; + iterator.find(async (value) => value > 3) + .then((results) => + { + resolved = true; + + expect(results).toBeUndefined(); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should enumerate elements with their indices", async () => + { + const iterator = new SmartAsyncIterator(_toAsync(["A", "B", "C"])); + const results = iterator.enumerate(); + + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([[0, "A"], [1, "B"], [2, "C"]]); + }); + + await vi.advanceTimersByTimeAsync(200); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); it("Should remove duplicate elements", async () => { - const iterator = new SmartAsyncIterator([1, 1, 2, 3, 2, 3, 4, 5, 5, 4]); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 2, 1, 3, 1, 4, 3, 4, 5, 5])); const results = iterator.unique(); - expect(await results.toArray()).toEqual([1, 2, 3, 4, 5]); - }); + let resolved = false; + results.toArray() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual([1, 2, 3, 4, 5]); + }); + + await vi.advanceTimersByTimeAsync(1_000); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); it("Should count the number of elements", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); - const results = await iterator.count(); - expect(results).toBe(5); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + const results = iterator.count(); + + let resolved = false; + results.then((_results) => + { + resolved = true; + + expect(_results).toBe(5); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should iterate over elements with `forEach`", async () => + { + const results: number[] = []; + const _iteratee = vi.fn(async (x: MaybePromise) => { results.push(await x); }); + + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); + + let resolved = false; + iterator.forEach(_iteratee) + .then(() => + { + resolved = true; + + expect(results).toEqual([1, 2, 3, 4, 5]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); - it("Should iterate over elements with forEach", async () => + it("Should handle return method correctly", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5]); - const values: number[] = []; - await iterator.forEach(async (value) => + const _iterator: AsyncIterableIterator = { + next: async () => ({ done: false, value: 1 }), + return: async (value?: string) => + { + return { done: true, value: value ?? "Naturally done!" }; + }, + + [Symbol.asyncIterator]: () => _iterator + }; + + const iterator = new SmartAsyncIterator(_iterator as AsyncIterator); + const results = iterator.return("Prematurely done!"); + + let resolved = false; + results.then((_results) => { - values.push(value); + resolved = true; + + expect(_results).toEqual({ done: true, value: "Prematurely done!" }); }); - expect(values).toEqual([1, 2, 3, 4, 5]); - }); - it("Should flatten elements with flatMap", async () => + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should handle throw method correctly", async () => { - const iterator = new SmartAsyncIterator([[1, 2], [3, 4], [5]]); - const results = iterator.flatMap(async (value) => value); - expect(await results.toArray()).toEqual([1, 2, 3, 4, 5]); + const _iterator: AsyncIterator = { + next: async () => ({ done: false, value: 1 }), + throw: async (error: unknown) => { throw error; } + }; + + const iterator = new SmartAsyncIterator(_iterator); + const reason = new Error("Something went wrong!"); + + try + { + await iterator.throw(reason); + } + catch (error) + { + expect(error).toBe(reason); + } }); it("Should group elements by key", async () => { - const iterator = new SmartAsyncIterator([1, 2, 3, 4, 5, 6]); + const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5, 6])); const results = iterator.groupBy(async (value) => (value % 2 === 0 ? "even" : "odd")); - const grouped = await results.toObject(); - expect(grouped).toEqual({ - odd: [1, 3, 5], - even: [2, 4, 6] - }); + + let resolved = false; + results.toObject() + .then((_results) => + { + resolved = true; + + expect(_results).toEqual({ odd: [1, 3, 5], even: [2, 4, 6] }); + }); + + await vi.advanceTimersByTimeAsync(500); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); }); }); diff --git a/tests/iterators/smart-iterator.test.ts b/tests/iterators/smart-iterator.test.ts index 48a4ec2..8fe348f 100644 --- a/tests/iterators/smart-iterator.test.ts +++ b/tests/iterators/smart-iterator.test.ts @@ -46,12 +46,34 @@ describe("SmartIterator", () => expect(iterator.toArray()).toEqual([1, 2, 3]); }); - it("Should map values correctly", () => + it("Should return `true` if every value matches the predicate", () => { - const iterator = new SmartIterator([1, 2, 3]); + const iterator = new SmartIterator([2, 4, 6, 8, 10]); - const results = iterator.map((x) => x * 2); - expect(results.toArray()).toEqual([2, 4, 6]); + const results = iterator.every((value) => value % 2 === 0); + expect(results).toBe(true); + }); + it("Should return `false` if not every value matches the predicate", () => + { + const iterator = new SmartIterator([2, 4, 5, 6, 7, 8, 10]); + + const results = iterator.every((value) => value % 2 === 0); + expect(results).toBe(false); + }); + + it("Should return `true` if some values match the predicate", () => + { + const iterator = new SmartIterator([1, 2, 3, 4, 5]); + + const results = iterator.some((value) => value % 2 === 0); + expect(results).toBe(true); + }); + it("Should return `false` if no values match the predicate", () => + { + const iterator = new SmartIterator([1, 3, 5, 7, 9]); + + const results = iterator.some((value) => value % 2 === 0); + expect(results).toBe(false); }); it("Should filter values correctly", () => @@ -61,6 +83,13 @@ describe("SmartIterator", () => const results = iterator.filter((x) => x % 2 === 0); expect(results.toArray()).toEqual([2, 4]); }); + it("Should map values correctly", () => + { + const iterator = new SmartIterator([1, 2, 3]); + + const results = iterator.map((x) => x * 2); + expect(results.toArray()).toEqual([2, 4, 6]); + }); it("Should reduce values correctly", () => { @@ -84,19 +113,12 @@ describe("SmartIterator", () => expect(() => iterator.reduce((acc, value) => acc + value)).toThrow(ValueException); }); - it("Should find the first matching value", () => + it("Should flatten elements with `flatMap`", () => { - const iterator = new SmartIterator([1, 2, 3, 4, 5]); - - const results = iterator.find((x) => x > 3); - expect(results).toBe(4); - }); - it("Should return undefined when no matching value is found", () => - { - const iterator = new SmartIterator([1, 2, 3]); + const iterator = new SmartIterator([1, [2, 3], 4, 5, [6, 7, 8]]); - const results = iterator.find((x) => x > 3); - expect(results).toBeUndefined(); + const results = iterator.flatMap((x) => x); + expect(results.toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); }); it("Should drop the specified number of elements", () => @@ -114,13 +136,21 @@ describe("SmartIterator", () => expect(results.toArray()).toEqual([1, 2, 3]); }); - it("Should count the number of elements", () => + it("Should find the first matching value", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); - const results = iterator.count(); - expect(results).toBe(5); + const results = iterator.find((x) => x > 3); + expect(results).toBe(4); + }); + it("Should return `undefined` when no matching value is found", () => + { + const iterator = new SmartIterator([1, 2, 3]); + + const results = iterator.find((x) => x > 3); + expect(results).toBeUndefined(); }); + it("Should enumerate elements with their indices", () => { const iterator = new SmartIterator(["A", "B", "C"]); @@ -135,22 +165,20 @@ describe("SmartIterator", () => const results = iterator.unique(); expect(results.toArray()).toEqual([1, 2, 3, 4, 5]); }); - - it("Should `flatMap` elements correctly", () => + it("Should count the number of elements", () => { - const iterator = new SmartIterator([1, [2, 3], 4, 5, [6, 7, 8]]); + const iterator = new SmartIterator([1, 2, 3, 4, 5]); - const results = iterator.flatMap((x) => x); - expect(results.toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + const results = iterator.count(); + expect(results).toBe(5); }); - it("Should execute `forEach` correctly", () => + it("Should iterate over elements with `forEach`", () => { - const _iteratee = vi.fn((x: number) => results.push(x)); - - const iterator = new SmartIterator([1, 2, 3]); const results: number[] = []; + const _iteratee = vi.fn((x: number) => { results.push(x); }); + const iterator = new SmartIterator([1, 2, 3]); iterator.forEach(_iteratee); expect(results).toEqual([1, 2, 3]); @@ -159,15 +187,20 @@ describe("SmartIterator", () => it("Should handle return method correctly", () => { - const _iterator: Iterator = { + const _iterator: IterableIterator = { next: () => ({ done: false, value: 1 }), - return: () => ({ done: true, value: "done" }) + return: (value?: string) => + { + return { done: true, value: value ?? "Naturally done!" }; + }, + + [Symbol.iterator]: () => _iterator }; - const iterator = new SmartIterator(_iterator); - const results = iterator.return(); + const iterator = new SmartIterator(_iterator as Iterator); + const results = iterator.return("Prematurely done!"); - expect(results).toEqual({ done: true, value: "done" }); + expect(results).toEqual({ done: true, value: "Prematurely done!" }); }); it("Should handle throw method correctly", () => { From b9287f21a3a503399c030df64f0d0b42662b5791 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Thu, 30 Jan 2025 17:43:58 +0100 Subject: [PATCH 13/22] fix: Changed directory. --- tests/{ => models}/iterators/smart-async-iterator.test.ts | 4 ++-- tests/{ => models}/iterators/smart-iterator.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename tests/{ => models}/iterators/smart-async-iterator.test.ts (99%) rename tests/{ => models}/iterators/smart-iterator.test.ts (98%) diff --git a/tests/iterators/smart-async-iterator.test.ts b/tests/models/iterators/smart-async-iterator.test.ts similarity index 99% rename from tests/iterators/smart-async-iterator.test.ts rename to tests/models/iterators/smart-async-iterator.test.ts index 1dbf6cd..e26167a 100644 --- a/tests/iterators/smart-async-iterator.test.ts +++ b/tests/models/iterators/smart-async-iterator.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { delay, ValueException, type MaybePromise } from "../../src/index.js"; -import { SmartAsyncIterator } from "../../src/index.js"; +import { delay, ValueException, type MaybePromise } from "../../../src/index.js"; +import { SmartAsyncIterator } from "../../../src/index.js"; describe("SmartAsyncIterator", () => { diff --git a/tests/iterators/smart-iterator.test.ts b/tests/models/iterators/smart-iterator.test.ts similarity index 98% rename from tests/iterators/smart-iterator.test.ts rename to tests/models/iterators/smart-iterator.test.ts index 8fe348f..e24eaee 100644 --- a/tests/iterators/smart-iterator.test.ts +++ b/tests/models/iterators/smart-iterator.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from "vitest"; -import { ValueException } from "../../src/index.js"; -import { SmartIterator } from "../../src/index.js"; +import { ValueException } from "../../../src/index.js"; +import { SmartIterator } from "../../../src/index.js"; describe("SmartIterator", () => { From a1b77c1b64cb85cac5a5dcffcee632151f27d6b6 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Tue, 28 Jan 2025 17:46:14 +0100 Subject: [PATCH 14/22] imp: Implemented some other tests. --- .husky/pre-commit | 1 + src/index.ts | 1 + src/models/index.ts | 8 +- src/models/timers/clock.ts | 3 +- src/models/timers/countdown.ts | 3 +- src/models/{ => timers}/game-loop.ts | 10 +- src/models/timers/index.ts | 3 +- tests/models/json/json-storage.test.ts | 138 ++++++++++++++++++ .../models/promises/deferred-promise.test.ts | 73 +++++++++ tests/models/promises/smart-promise.test.ts | 133 +++++++++++++++++ tests/models/promises/timed-promise.test.ts | 93 ++++++++++++ tests/models/timers/countdown.test.ts | 7 +- tests/models/{ => timers}/game-loop.test.ts | 4 +- 13 files changed, 458 insertions(+), 19 deletions(-) rename src/models/{ => timers}/game-loop.ts (96%) create mode 100644 tests/models/json/json-storage.test.ts create mode 100644 tests/models/promises/deferred-promise.test.ts create mode 100644 tests/models/promises/smart-promise.test.ts create mode 100644 tests/models/promises/timed-promise.test.ts rename tests/models/{ => timers}/game-loop.test.ts (94%) diff --git a/.husky/pre-commit b/.husky/pre-commit index 84c031a..0fc24b2 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,3 @@ NODE_ENV="production" pnpm run lint pnpm run typecheck +pnpm run test diff --git a/src/index.ts b/src/index.ts index 2739fea..f66cd4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ export { Clock, Countdown, DeferredPromise, + EnvironmentException, Exception, FatalErrorException, FileException, diff --git a/src/models/index.ts b/src/models/index.ts index 373071f..9a1fd97 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -10,6 +10,7 @@ export { Exception, FatalErrorException, NotImplementedException, + EnvironmentException, FileException, FileExistsException, FileNotFoundException, @@ -25,12 +26,7 @@ export { } from "./exceptions/index.js"; -import GameLoop from "./game-loop.js"; - export { SmartIterator, SmartAsyncIterator } from "./iterators/index.js"; export { JSONStorage } from "./json/index.js"; export { DeferredPromise, SmartPromise, TimedPromise } from "./promises/index.js"; - -export { Clock, Countdown } from "./timers/index.js"; - -export { GameLoop }; +export { Clock, Countdown, GameLoop } from "./timers/index.js"; diff --git a/src/models/timers/clock.ts b/src/models/timers/clock.ts index d61579b..cc261bb 100644 --- a/src/models/timers/clock.ts +++ b/src/models/timers/clock.ts @@ -2,9 +2,10 @@ import { TimeUnit } from "../../utils/date.js"; import Publisher from "../callbacks/publisher.js"; import { FatalErrorException, RangeException, RuntimeException } from "../exceptions/index.js"; -import GameLoop from "../game-loop.js"; import type { Callback } from "../types.js"; +import GameLoop from "./game-loop.js"; + interface ClockEventMap { start: () => void; diff --git a/src/models/timers/countdown.ts b/src/models/timers/countdown.ts index b5df491..7b76fe6 100644 --- a/src/models/timers/countdown.ts +++ b/src/models/timers/countdown.ts @@ -2,10 +2,11 @@ import { TimeUnit } from "../../utils/date.js"; import Publisher from "../callbacks/publisher.js"; import { FatalErrorException, RangeException, RuntimeException } from "../exceptions/index.js"; -import GameLoop from "../game-loop.js"; import { DeferredPromise, SmartPromise } from "../promises/index.js"; import type { Callback } from "../types.js"; +import GameLoop from "./game-loop.js"; + interface CountdownEventMap { start: () => void; diff --git a/src/models/game-loop.ts b/src/models/timers/game-loop.ts similarity index 96% rename from src/models/game-loop.ts rename to src/models/timers/game-loop.ts index 9fa3fbc..5e36ff7 100644 --- a/src/models/game-loop.ts +++ b/src/models/timers/game-loop.ts @@ -1,9 +1,9 @@ -import type { Interval } from "../core/types.js"; -import { isBrowser } from "../helpers.js"; +import type { Interval } from "../../core/types.js"; +import { isBrowser } from "../../helpers.js"; -import Publisher from "./callbacks/publisher.js"; -import { FatalErrorException, RuntimeException } from "./exceptions/index.js"; -import type { Callback } from "./types.js"; +import Publisher from "../callbacks/publisher.js"; +import { FatalErrorException, RuntimeException } from "../exceptions/index.js"; +import type { Callback } from "../types.js"; interface GameLoopEventMap { diff --git a/src/models/timers/index.ts b/src/models/timers/index.ts index ab1d3b5..c98a2f1 100644 --- a/src/models/timers/index.ts +++ b/src/models/timers/index.ts @@ -1,4 +1,5 @@ import Clock from "./clock.js"; import Countdown from "./countdown.js"; +import GameLoop from "./game-loop.js"; -export { Clock, Countdown }; +export { Clock, Countdown, GameLoop }; diff --git a/tests/models/json/json-storage.test.ts b/tests/models/json/json-storage.test.ts new file mode 100644 index 0000000..a12acde --- /dev/null +++ b/tests/models/json/json-storage.test.ts @@ -0,0 +1,138 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import * as helpers from "../../../src/helpers.js"; + +import { EnvironmentException } from "../../../src/index.js"; +import { JSONStorage } from "../../../src/index.js"; + +describe("JSONStorage", () => +{ + let jsonStorage: JSONStorage; + + beforeEach(() => + { + vi.spyOn(helpers, "isBrowser", "get") + .mockReturnValue(true); + + const mockStorage = () => + { + let store: Record = { }; + + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { store[key] = value; }, + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + removeItem: (key: string) => delete store[key], + clear: () => { store = { }; } + }; + }; + + Object.defineProperty(window, "localStorage", { value: mockStorage() }); + Object.defineProperty(window, "sessionStorage", { value: mockStorage() }); + + jsonStorage = new JSONStorage(); + }); + + it("Should throw an EnvironmentException if not in a browser environment", () => + { + vi.spyOn(helpers, "isBrowser", "get") + .mockReturnValue(false); + + expect(() => new JSONStorage()).toThrow(EnvironmentException); + }); + + it("Should store and retrieve a value in `localStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + + const retrievedValue = jsonStorage.read(key); + expect(retrievedValue).toEqual(value); + }); + it("Should store and retrieve a value in `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.remember(key, value); + + const retrievedValue = jsonStorage.recall(key); + expect(retrievedValue).toEqual(value); + }); + + it("Should remove a value from `localStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + jsonStorage.erase(key); + + const retrievedValue = jsonStorage.read(key); + expect(retrievedValue).toBeUndefined(); + }); + it("Should remove a value from `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.remember(key, value); + jsonStorage.forget(key); + + const retrievedValue = jsonStorage.recall(key); + expect(retrievedValue).toBeUndefined(); + }); + + it("Should prefer `localStorage` over `sessionStorage` when retrieving a value", () => + { + const key = "testKey"; + const localStorageValue = { test: "localStorageValue" }; + const sessionStorageValue = { test: "sessionStorageValue" }; + + jsonStorage.write(key, localStorageValue); + jsonStorage.remember(key, sessionStorageValue); + + const retrievedValue = jsonStorage.get(key); + expect(retrievedValue).toEqual(localStorageValue); + }); + + it("Should check if a key exists in `localStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + + const exists = jsonStorage.exists(key); + expect(exists).toBe(true); + }); + it("Should check if a key exists in `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.remember(key, value); + const exists = jsonStorage.knows(key); + + expect(exists).toBe(true); + }); + + it("Should clear a key from both `localStorage` and `sessionStorage`", () => + { + const key = "testKey"; + const value = { test: "value" }; + + jsonStorage.write(key, value); + jsonStorage.remember(key, value); + jsonStorage.clear(key); + + const localStorageValue = jsonStorage.read(key); + const sessionStorageValue = jsonStorage.recall(key); + + expect(localStorageValue).toBeUndefined(); + expect(sessionStorageValue).toBeUndefined(); + }); +}); diff --git a/tests/models/promises/deferred-promise.test.ts b/tests/models/promises/deferred-promise.test.ts new file mode 100644 index 0000000..821d3d7 --- /dev/null +++ b/tests/models/promises/deferred-promise.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it, vi } from "vitest"; + +import { DeferredPromise } from "../../../src/index.js"; + +describe("DeferredPromise", () => +{ + it("Should resolve with the correct value", async () => + { + const deferred = new DeferredPromise((value: string) => value.split(" ")); + deferred.resolve("Hello, world!"); + + const result = await deferred; + expect(result).toEqual(["Hello,", "world!"]); + }); + + it("Should reject with the correct reason", async () => + { + const deferred = new DeferredPromise((value: string) => value.split(" ")); + const reason = new Error("Something went wrong"); + + deferred.reject(reason); + + try + { + await deferred; + } + catch (error) + { + expect(error).toBe(reason); + } + }); + + it("Should watch another promise and resolve when the other promise resolves", async () => + { + vi.useFakeTimers(); + + const otherPromise = new Promise((resolve) => setTimeout(() => resolve("Hello, world!"), 100)); + const deferred = new DeferredPromise((value: string) => value.split(" ")); + + deferred.watch(otherPromise); + + vi.advanceTimersByTime(100); + + const result = await deferred; + expect(result).toEqual(["Hello,", "world!"]); + + vi.clearAllTimers(); + }); + + it("Should watch another promise and reject when the other promise rejects", async () => + { + vi.useFakeTimers(); + + const reason = new Error("Something went wrong"); + const otherPromise = new Promise((_, reject) => setTimeout(() => reject(reason), 100)); + const deferred = new DeferredPromise((value: string) => value.split(" ")); + + deferred.watch(otherPromise); + + vi.advanceTimersByTime(100); + + try + { + await deferred; + } + catch (error) + { + expect(error).toBe(reason); + } + + vi.clearAllTimers(); + }); +}); diff --git a/tests/models/promises/smart-promise.test.ts b/tests/models/promises/smart-promise.test.ts new file mode 100644 index 0000000..17be7ad --- /dev/null +++ b/tests/models/promises/smart-promise.test.ts @@ -0,0 +1,133 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { SmartPromise } from "../../../src/index.js"; + +describe("SmartPromise", () => +{ + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should be pending initially", () => + { + const _callback = vi.fn(); + const promise = new SmartPromise(_callback); + + expect(promise.isPending).toBe(true); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(false); + + expect(_callback).toHaveBeenCalled(); + }); + + it("Should be fulfilled after resolving", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + vi.advanceTimersByTime(100); + + const result = await promise; + expect(result).toBe("Hello, world!"); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); + it("Should be rejected after rejecting", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => reject(new Error("An error occurred")), 100); + }); + + vi.advanceTimersByTime(100); + + try + { + await promise; + } + catch (error) + { + expect(error).toEqual(new Error("An error occurred")); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(true); + } + }); + + it("Should wrap an existing promise", async () => + { + const nativePromise = new Promise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + const smartPromise = SmartPromise.FromPromise(nativePromise); + expect(smartPromise.isPending).toBe(true); + expect(smartPromise.isFulfilled).toBe(false); + expect(smartPromise.isRejected).toBe(false); + + vi.advanceTimersByTime(100); + + const result = await smartPromise; + expect(result).toBe("Hello, world!"); + + expect(smartPromise.isPending).toBe(false); + expect(smartPromise.isFulfilled).toBe(true); + expect(smartPromise.isRejected).toBe(false); + }); + + it("Should handle then callbacks", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + vi.advanceTimersByTime(100); + + const result = await promise.then((value) => `${value}!!`); + expect(result).toBe("Hello, world!!!"); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); + it("Should handle catch callbacks", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => reject(new Error("An error occurred")), 100); + }); + + vi.advanceTimersByTime(100); + + const result = await promise.catch((error) => "Recovered from error"); + expect(result).toBe("Recovered from error"); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(true); + }); + it("Should handle finally callbacks", async () => + { + const promise = new SmartPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + vi.advanceTimersByTime(100); + + let finallyCalled = false; + await promise.finally(() => { finallyCalled = true; }); + + expect(finallyCalled).toBe(true); + + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); + }); +}); diff --git a/tests/models/promises/timed-promise.test.ts b/tests/models/promises/timed-promise.test.ts new file mode 100644 index 0000000..1a91eba --- /dev/null +++ b/tests/models/promises/timed-promise.test.ts @@ -0,0 +1,93 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { TimeoutException } from "../../../src/index.js"; +import { TimedPromise } from "../../../src/index.js"; + +describe("TimedPromise", () => +{ + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should resolve before timeout", async () => + { + const promise = new TimedPromise((resolve) => + { + setTimeout(() => resolve("Hello, world!"), 100); + + }, 500); + + vi.advanceTimersByTime(500); + + const result = await promise; + expect(result).toBe("Hello, world!"); + }); + it("Should reject with `TimeoutException` after timeout", async () => + { + const promise = new TimedPromise((resolve) => + { + setTimeout(() => resolve("Hello, world!"), 1_000); + + }, 500); + + vi.advanceTimersByTime(500); + + try + { + await promise; + } + catch (error) + { + expect(error).toBeInstanceOf(TimeoutException); + expect((error as TimeoutException).message).toBe("The operation has timed out."); + } + }); + it("Should reject with provided reason before timeout", async () => + { + const reason = new Error("An error occurred"); + const promise = new TimedPromise((_, reject) => + { + setTimeout(() => reject(reason), 100); + + }, 500); + + vi.advanceTimersByTime(500); + + try + { + await promise; + } + catch (error) + { + expect(error).toBe(reason); + } + }); + + it("Should resolve immediately if no timeout is provided", async () => + { + const result = await new TimedPromise((resolve) => + { + resolve("Immediate resolve"); + }); + + expect(result).toBe("Immediate resolve"); + }); + it("Should reject immediately if no timeout is provided", async () => + { + const promise = new TimedPromise((resolve, reject) => + { + setTimeout(() => resolve("Hello, world!"), 100); + }); + + vi.advanceTimersByTime(100); + + try + { + await promise; + } + catch (error) + { + expect(error).toBeInstanceOf(TimeoutException); + expect((error as TimeoutException).message).toBe("The operation has timed out."); + } + }); +}); diff --git a/tests/models/timers/countdown.test.ts b/tests/models/timers/countdown.test.ts index d0f8768..f742deb 100644 --- a/tests/models/timers/countdown.test.ts +++ b/tests/models/timers/countdown.test.ts @@ -48,11 +48,12 @@ describe("Countdown", () => countdown.onStop(_callback); const { rejects } = expect(countdown.start()); - countdown.stop("This is a test!"); + const reason = new Error("An error occurred"); + countdown.stop(reason); - await rejects.toBe("This is a test!"); + await rejects.toBe(reason); - expect(_callback).toHaveBeenCalledWith("This is a test!"); + expect(_callback).toHaveBeenCalledWith(reason); }); it("Should throw `RuntimeException` if stop is called before start", () => { diff --git a/tests/models/game-loop.test.ts b/tests/models/timers/game-loop.test.ts similarity index 94% rename from tests/models/game-loop.test.ts rename to tests/models/timers/game-loop.test.ts index a2ad9f8..298491d 100644 --- a/tests/models/game-loop.test.ts +++ b/tests/models/timers/game-loop.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { FatalErrorException, RuntimeException } from "../../src/index.js"; -import { GameLoop } from "../../src/index.js"; +import { FatalErrorException, RuntimeException } from "../../../src/index.js"; +import { GameLoop } from "../../../src/index.js"; describe("GameLoop", () => { From 83ca2295470bdc1d9e00405a12708dec969ceb20 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Thu, 30 Jan 2025 21:35:39 +0100 Subject: [PATCH 15/22] fix: Minor fixes. --- .editorconfig | 5 +---- package.json | 1 + vitest.config.ts | 9 +++++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.editorconfig b/.editorconfig index 4bb7fad..4f144d9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,10 +10,7 @@ indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true -[{config.js,*.config.js}] -indent_size = 2 - -[.eslintrc.{cjs,js}] +[*.config.{js,mjs,ts,mts}] indent_size = 2 [*.{json,yml}] diff --git a/package.json b/package.json index fff4f94..35ee34a 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ }, "devDependencies": { "@byloth/eslint-config-typescript": "^3.0.3", + "@eslint/compat": "^1.2.5", "@types/node": "^22.10.10", "husky": "^9.1.7", "jsdom": "^26.0.0", diff --git a/vitest.config.ts b/vitest.config.ts index 69100be..4fd1437 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,10 @@ -import { defineConfig } from "vitest/config"; +import { fileURLToPath } from "node:url"; +import { defineConfig, configDefaults } from "vitest/config"; export default defineConfig({ - test: { environment: "jsdom" } + test: { + environment: "jsdom", + exclude: configDefaults.exclude, + root: fileURLToPath(new URL("./", import.meta.url)) + } }); From 1b4b13e27db326d39f2b6ebd3881c4e8a9a9594f Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Thu, 30 Jan 2025 23:36:57 +0100 Subject: [PATCH 16/22] fix: Various fixes. --- .../aggregators/aggregated-async-iterator.ts | 34 ++++++--- src/models/aggregators/reduced-iterator.ts | 70 ++++++++++++++++++- src/models/iterators/smart-async-iterator.ts | 29 ++++---- src/utils/async.ts | 6 +- src/utils/dom.ts | 2 +- .../models/promises/deferred-promise.test.ts | 33 ++------- tests/models/promises/smart-promise.test.ts | 61 +++++++--------- tests/models/promises/timed-promise.test.ts | 44 ++++-------- 8 files changed, 159 insertions(+), 120 deletions(-) diff --git a/src/models/aggregators/aggregated-async-iterator.ts b/src/models/aggregators/aggregated-async-iterator.ts index 347e7f7..83cd5b2 100644 --- a/src/models/aggregators/aggregated-async-iterator.ts +++ b/src/models/aggregators/aggregated-async-iterator.ts @@ -192,7 +192,8 @@ export default class AggregatedAsyncIterator * * @param predicate The condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the boolean results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the boolean results for each group. */ public async every(predicate: MaybeAsyncKeyedIteratee): Promise> { @@ -236,7 +237,8 @@ export default class AggregatedAsyncIterator * * @param predicate The condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the boolean results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the boolean results for each group. */ public async some(predicate: MaybeAsyncKeyedIteratee): Promise> { @@ -395,7 +397,8 @@ export default class AggregatedAsyncIterator * * @param reducer The reducer function to apply to each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the reduced results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group. */ public async reduce(reducer: MaybeAsyncKeyedReducer): Promise>; @@ -426,7 +429,8 @@ export default class AggregatedAsyncIterator * @param reducer The reducer function to apply to each element of the iterator. * @param initialValue The initial value for the accumulator. * - * @returns A new {@link ReducedIterator} containing the reduced results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group. */ public async reduce(reducer: MaybeAsyncKeyedReducer, initialValue: MaybePromise) : Promise>; @@ -458,7 +462,8 @@ export default class AggregatedAsyncIterator * @param reducer The reducer function to apply to each element of the iterator. * @param initialValue The function that provides the initial value for the accumulator. * - * @returns A new {@link ReducedIterator} containing the reduced results for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the reduced results for each group. */ public async reduce(reducer: MaybeAsyncKeyedReducer, initialValue: (key: K) => MaybePromise) : Promise>; @@ -658,7 +663,9 @@ export default class AggregatedAsyncIterator * * @param predicate The condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing + * the first element that satisfies the condition for each group. */ public async find(predicate: MaybeAsyncKeyedIteratee): Promise>; @@ -690,7 +697,9 @@ export default class AggregatedAsyncIterator * * @param predicate The type guard condition to check for each element of the iterator. * - * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing + * the first element that satisfies the condition for each group. */ public async find(predicate: MaybeAsyncKeyedIteratee) : Promise>; @@ -796,7 +805,8 @@ export default class AggregatedAsyncIterator * console.log(await results.toObject()); // { odd: 4, even: 4 } * ``` * - * @returns A new {@link ReducedIterator} containing the number of elements for each group. + * @returns + * A {@link Promise} resolving to a new {@link ReducedIterator} containing the number of elements for each group. */ public async count(): Promise> { @@ -833,6 +843,8 @@ export default class AggregatedAsyncIterator * ``` * * @param iteratee The function to execute for each element of the iterator. + * + * @returns A {@link Promise} that will resolve once the iteration is complete. */ public async forEach(iteratee: MaybeAsyncKeyedIteratee): Promise { @@ -1001,7 +1013,7 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]] * ``` * - * @returns An {@link Array} of arrays containing the elements of the iterator. + * @returns A {@link Promise} resolving to an {@link Array} containing all the values of the iterator. */ public async toArray(): Promise { @@ -1023,7 +1035,7 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] } * ``` * - * @returns A {@link Map} containing the elements of the iterator. + * @returns A {@link Promise} resolving to a {@link Map} containing all the entries of the iterator. */ public async toMap(): Promise> { @@ -1053,7 +1065,7 @@ export default class AggregatedAsyncIterator * console.log(await aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } * ``` * - * @returns An {@link Object} containing the elements of the iterator. + * @returns A {@link Promise} resolving to an object containing all the entries of the iterator. */ public async toObject(): Promise> { diff --git a/src/models/aggregators/reduced-iterator.ts b/src/models/aggregators/reduced-iterator.ts index 04d588d..7eaf7ef 100644 --- a/src/models/aggregators/reduced-iterator.ts +++ b/src/models/aggregators/reduced-iterator.ts @@ -507,9 +507,75 @@ export default class ReducedIterator }); } - public find() + /** + * Finds the first element of the reduced iterator that satisfies the given condition. + * + * This method will iterate over all the elements of the iterator checking if they satisfy the condition. + * The first element that satisfies the condition will be returned immediately. + * + * Only the elements that are necessary to find the first + * satisfying one will be consumed from the original iterator. + * The rest of the iterator will be available for further consumption. + * + * Also note that: + * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed. + * - If the iterator is infinite and no element satisfies the condition, the method will never return. + * + * ```ts + * const results = new SmartIterator([-3, -3, -1, 0, 1, 2, 5, 6, 8]) + * .groupBy((value) => value % 2 === 0 ? "even" : "odd") + * .reduce((key, accumulator, value) => accumulator + value) + * .find((key, value) => value > 0); + * + * console.log(results); // 16 + * + * @param predicate The condition to check for each element of the iterator. + * + * @returns The first element that satisfies the condition, `undefined` otherwise. + */ + public find(predicate: KeyedIteratee): T | undefined; + + /** + * Finds the first element of the reduced iterator that satisfies the given type guard predicate. + * + * This method will iterate over all the elements of the iterator checking if they satisfy the condition. + * The first element that satisfies the condition will be returned immediately. + * + * Only the elements that are necessary to find the first + * satisfying one will be consumed from the original iterator. + * The rest of the iterator will be available for further consumption. + * + * Also note that: + * - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed. + * - If the iterator is infinite and no element satisfies the condition, the method will never return. + * + * ```ts + * const results = new SmartIterator(["-3", -3, "-1", 0, 1, 2, "5", 6, 8]) + * .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd") + * .reduce((key, accumulator, value) => accumulator + value) + * .find((key, value) => typeof value === "number"); + * + * console.log(results); // 16 + * + * @template S + * The type of the elements that satisfy the condition. + * This allows the type-system to infer the correct type of the result. + * + * It must be a subtype of the original type of the elements. + * + * @param predicate The type guard condition to check for each element of the iterator. + * + * @returns The first element that satisfies the condition, `undefined` otherwise. + */ + public find(predicate: KeyedTypeGuardPredicate): S | undefined; + public find(predicate: KeyedIteratee): T | undefined { - // TODO! + for (const [index, [key, element]] of this._elements.enumerate()) + { + if (predicate(key, element, index)) { return element; } + } + + return undefined; } /** diff --git a/src/models/iterators/smart-async-iterator.ts b/src/models/iterators/smart-async-iterator.ts index 9d72fec..5838c34 100644 --- a/src/models/iterators/smart-async-iterator.ts +++ b/src/models/iterators/smart-async-iterator.ts @@ -235,7 +235,8 @@ export default class SmartAsyncIterator implements A * * @param predicate The condition to check for each element of the iterator. * - * @returns A promise that will resolve to `true` if all elements satisfy the condition, `false` otherwise. + * @returns + * A {@link Promise} that will resolve to `true` if all elements satisfy the condition, `false` otherwise. */ public async every(predicate: MaybeAsyncIteratee): Promise { @@ -274,7 +275,8 @@ export default class SmartAsyncIterator implements A * * @param predicate The condition to check for each element of the iterator. * - * @returns A promise that will resolve to `true` if any element satisfies the condition, `false` otherwise. + * @returns + * A {@link Promise} that will resolve to `true` if any element satisfies the condition, `false` otherwise. */ public async some(predicate: MaybeAsyncIteratee): Promise { @@ -434,7 +436,7 @@ export default class SmartAsyncIterator implements A * * @param reducer The reducer function to apply to each element of the iterator. * - * @returns A promise that will resolve to the final result of the reduction. + * @returns A {@link Promise} that will resolve to the final result of the reduction. */ public async reduce(reducer: MaybeAsyncReducer): Promise; @@ -462,7 +464,7 @@ export default class SmartAsyncIterator implements A * @param reducer The reducer function to apply to each element of the iterator. * @param initialValue The initial value of the accumulator. * - * @returns A promise that will resolve to the final result of the reduction. + * @returns A {@link Promise} that will resolve to the final result of the reduction. */ public async reduce(reducer: MaybeAsyncReducer, initialValue: A): Promise; public async reduce(reducer: MaybeAsyncReducer, initialValue?: A): Promise @@ -661,7 +663,8 @@ export default class SmartAsyncIterator implements A * * @param predicate The condition to check for each element of the iterator. * - * @returns A promise that will resolve to the first element that satisfies the condition, `undefined` otherwise. + * @returns + * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise. */ public async find(predicate: MaybeAsyncIteratee): Promise; @@ -694,7 +697,8 @@ export default class SmartAsyncIterator implements A * * @param predicate The type guard condition to check for each element of the iterator. * - * @returns A promise that will resolve to the first element that satisfies the condition, `undefined` otherwise. + * @returns + * A {@link Promise} that will resolve to the first element that satisfies the condition, `undefined` otherwise. */ public async find(predicate: MaybeAsyncIteratee): Promise; public async find(predicate: MaybeAsyncIteratee): Promise @@ -793,7 +797,7 @@ export default class SmartAsyncIterator implements A * console.log(result); // 5 * ``` * - * @returns A promise that will resolve to the number of elements in the iterator. + * @returns A {@link Promise} that will resolve to the number of elements in the iterator. */ public async count(): Promise { @@ -825,7 +829,7 @@ export default class SmartAsyncIterator implements A * * @param iteratee The function to apply to each element of the iterator. * - * @returns A promise that will resolve once the iteration is complete. + * @returns A {@link Promise} that will resolve once the iteration is complete. */ public async forEach(iteratee: MaybeAsyncIteratee): Promise { @@ -864,7 +868,8 @@ export default class SmartAsyncIterator implements A * * @param values The value to pass to the next element, if required. * - * @returns A promise that will resolve to the result of the iteration, containing the value of the operation. + * @returns + * A {@link Promise} that will resolve to the result of the iteration, containing the value of the operation. */ public next(...values: N extends undefined ? [] : [N]): Promise> { @@ -896,7 +901,7 @@ export default class SmartAsyncIterator implements A * * @param value The final value of the iterator. * - * @returns A promise that will resolve to the final result of the iterator. + * @returns A {@link Promise} that will resolve to the final result of the iterator. */ public async return(value?: MaybePromise): Promise> { @@ -941,7 +946,7 @@ export default class SmartAsyncIterator implements A * * @param error The error to throw into the iterator. * - * @returns A promise that will resolve to the final result of the iterator. + * @returns A {@link Promise} that will resolve to the final result of the iterator. */ public throw(error: unknown): Promise> { @@ -1001,7 +1006,7 @@ export default class SmartAsyncIterator implements A * console.log(result); // [0, 1, 2, 3, 4] * ``` * - * @returns A promise that will resolve to an array containing all elements of the iterator. + * @returns A {@link Promise} that will resolve to an array containing all elements of the iterator. */ public toArray(): Promise { diff --git a/src/utils/async.ts b/src/utils/async.ts index 493e88b..bde8212 100644 --- a/src/utils/async.ts +++ b/src/utils/async.ts @@ -10,7 +10,7 @@ * * @param milliseconds The number of milliseconds to wait before resolving the promise. * - * @returns A promise that resolves after the specified number of milliseconds. + * @returns A {@link Promise} that resolves after the specified number of milliseconds. */ export function delay(milliseconds: number): Promise { @@ -29,7 +29,7 @@ export function delay(milliseconds: number): Promise * $el.style.opacity = "1"; * ``` * - * @returns A promise that resolves on the next animation frame. + * @returns A {@link Promise} that resolves on the next animation frame. */ export function nextAnimationFrame(): Promise { @@ -49,7 +49,7 @@ export function nextAnimationFrame(): Promise * } * ``` * - * @returns A promise that resolves on the next microtask. + * @returns A {@link Promise} that resolves on the next microtask. */ export function yieldToEventLoop(): Promise { diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 372ae78..8a6b0ec 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -10,7 +10,7 @@ * @param scriptType The type of the script to load. Default is `"text/javascript"`. * * @returns - * A promise that resolves when the script has been loaded successfully or rejects if an error occurs. + * A {@link Promise} that resolves when the script has been loaded successfully or rejects if an error occurs. */ export function loadScript(scriptUrl: string, scriptType = "text/javascript"): Promise { diff --git a/tests/models/promises/deferred-promise.test.ts b/tests/models/promises/deferred-promise.test.ts index 821d3d7..9419f84 100644 --- a/tests/models/promises/deferred-promise.test.ts +++ b/tests/models/promises/deferred-promise.test.ts @@ -7,10 +7,9 @@ describe("DeferredPromise", () => it("Should resolve with the correct value", async () => { const deferred = new DeferredPromise((value: string) => value.split(" ")); - deferred.resolve("Hello, world!"); - const result = await deferred; - expect(result).toEqual(["Hello,", "world!"]); + deferred.resolve("Hello, world!"); + deferred.then((result) => { expect(result).toEqual(["Hello,", "world!"]); }); }); it("Should reject with the correct reason", async () => @@ -19,15 +18,7 @@ describe("DeferredPromise", () => const reason = new Error("Something went wrong"); deferred.reject(reason); - - try - { - await deferred; - } - catch (error) - { - expect(error).toBe(reason); - } + deferred.catch((error) => { expect(error).toBe(reason); }); }); it("Should watch another promise and resolve when the other promise resolves", async () => @@ -38,11 +29,9 @@ describe("DeferredPromise", () => const deferred = new DeferredPromise((value: string) => value.split(" ")); deferred.watch(otherPromise); + deferred.then((result) => { expect(result).toEqual(["Hello,", "world!"]); }); - vi.advanceTimersByTime(100); - - const result = await deferred; - expect(result).toEqual(["Hello,", "world!"]); + await vi.advanceTimersByTimeAsync(100); vi.clearAllTimers(); }); @@ -56,17 +45,9 @@ describe("DeferredPromise", () => const deferred = new DeferredPromise((value: string) => value.split(" ")); deferred.watch(otherPromise); + deferred.catch((error) => { expect(error).toBe(reason); }); - vi.advanceTimersByTime(100); - - try - { - await deferred; - } - catch (error) - { - expect(error).toBe(reason); - } + await vi.advanceTimersByTimeAsync(100); vi.clearAllTimers(); }); diff --git a/tests/models/promises/smart-promise.test.ts b/tests/models/promises/smart-promise.test.ts index 17be7ad..146cc1a 100644 --- a/tests/models/promises/smart-promise.test.ts +++ b/tests/models/promises/smart-promise.test.ts @@ -26,10 +26,9 @@ describe("SmartPromise", () => setTimeout(() => resolve("Hello, world!"), 100); }); - vi.advanceTimersByTime(100); + promise.then((value) => { expect(value).toBe("Hello, world!"); }); - const result = await promise; - expect(result).toBe("Hello, world!"); + await vi.advanceTimersByTimeAsync(100); expect(promise.isPending).toBe(false); expect(promise.isFulfilled).toBe(true); @@ -42,42 +41,34 @@ describe("SmartPromise", () => setTimeout(() => reject(new Error("An error occurred")), 100); }); - vi.advanceTimersByTime(100); + promise.catch((error) => { expect(error).toEqual(new Error("An error occurred")); }); - try - { - await promise; - } - catch (error) - { - expect(error).toEqual(new Error("An error occurred")); + await vi.advanceTimersByTimeAsync(100); - expect(promise.isPending).toBe(false); - expect(promise.isFulfilled).toBe(false); - expect(promise.isRejected).toBe(true); - } + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(true); }); it("Should wrap an existing promise", async () => { - const nativePromise = new Promise((resolve, reject) => + const _promise = new Promise((resolve, reject) => { setTimeout(() => resolve("Hello, world!"), 100); }); - const smartPromise = SmartPromise.FromPromise(nativePromise); - expect(smartPromise.isPending).toBe(true); - expect(smartPromise.isFulfilled).toBe(false); - expect(smartPromise.isRejected).toBe(false); + const promise = SmartPromise.FromPromise(_promise); + expect(promise.isPending).toBe(true); + expect(promise.isFulfilled).toBe(false); + expect(promise.isRejected).toBe(false); - vi.advanceTimersByTime(100); + promise.then((value) => { expect(value).toBe("Hello, world!"); }); - const result = await smartPromise; - expect(result).toBe("Hello, world!"); + await vi.advanceTimersByTimeAsync(100); - expect(smartPromise.isPending).toBe(false); - expect(smartPromise.isFulfilled).toBe(true); - expect(smartPromise.isRejected).toBe(false); + expect(promise.isPending).toBe(false); + expect(promise.isFulfilled).toBe(true); + expect(promise.isRejected).toBe(false); }); it("Should handle then callbacks", async () => @@ -87,10 +78,10 @@ describe("SmartPromise", () => setTimeout(() => resolve("Hello, world!"), 100); }); - vi.advanceTimersByTime(100); + promise.then((value) => `${value}!!`) + .then((value) => { expect(value).toBe("Hello, world!!!"); }); - const result = await promise.then((value) => `${value}!!`); - expect(result).toBe("Hello, world!!!"); + await vi.advanceTimersByTimeAsync(100); expect(promise.isPending).toBe(false); expect(promise.isFulfilled).toBe(true); @@ -103,10 +94,10 @@ describe("SmartPromise", () => setTimeout(() => reject(new Error("An error occurred")), 100); }); - vi.advanceTimersByTime(100); + promise.catch((error) => "Recovered from error") + .then((value) => { expect(value).toBe("Recovered from error"); }); - const result = await promise.catch((error) => "Recovered from error"); - expect(result).toBe("Recovered from error"); + await vi.advanceTimersByTimeAsync(100); expect(promise.isPending).toBe(false); expect(promise.isFulfilled).toBe(false); @@ -119,10 +110,10 @@ describe("SmartPromise", () => setTimeout(() => resolve("Hello, world!"), 100); }); - vi.advanceTimersByTime(100); - let finallyCalled = false; - await promise.finally(() => { finallyCalled = true; }); + promise.finally(() => { finallyCalled = true; }); + + await vi.advanceTimersByTimeAsync(100); expect(finallyCalled).toBe(true); diff --git a/tests/models/promises/timed-promise.test.ts b/tests/models/promises/timed-promise.test.ts index 1a91eba..4b4d12a 100644 --- a/tests/models/promises/timed-promise.test.ts +++ b/tests/models/promises/timed-promise.test.ts @@ -16,10 +16,9 @@ describe("TimedPromise", () => }, 500); - vi.advanceTimersByTime(500); + promise.then((result) => { expect(result).toBe("Hello, world!"); }); - const result = await promise; - expect(result).toBe("Hello, world!"); + await vi.advanceTimersByTimeAsync(500); }); it("Should reject with `TimeoutException` after timeout", async () => { @@ -29,17 +28,13 @@ describe("TimedPromise", () => }, 500); - vi.advanceTimersByTime(500); - - try - { - await promise; - } - catch (error) + promise.catch((error) => { expect(error).toBeInstanceOf(TimeoutException); expect((error as TimeoutException).message).toBe("The operation has timed out."); - } + }); + + await vi.advanceTimersByTimeAsync(500); }); it("Should reject with provided reason before timeout", async () => { @@ -50,26 +45,19 @@ describe("TimedPromise", () => }, 500); - vi.advanceTimersByTime(500); + promise.catch((error) => { expect(error).toBe(reason); }); - try - { - await promise; - } - catch (error) - { - expect(error).toBe(reason); - } + await vi.advanceTimersByTimeAsync(500); }); it("Should resolve immediately if no timeout is provided", async () => { - const result = await new TimedPromise((resolve) => + const promise = new TimedPromise((resolve) => { resolve("Immediate resolve"); }); - expect(result).toBe("Immediate resolve"); + promise.then((result) => { expect(result).toBe("Immediate resolve"); }); }); it("Should reject immediately if no timeout is provided", async () => { @@ -78,16 +66,12 @@ describe("TimedPromise", () => setTimeout(() => resolve("Hello, world!"), 100); }); - vi.advanceTimersByTime(100); - - try - { - await promise; - } - catch (error) + promise.catch((error) => { expect(error).toBeInstanceOf(TimeoutException); expect((error as TimeoutException).message).toBe("The operation has timed out."); - } + }); + + await vi.advanceTimersByTimeAsync(100); }); }); From 6ac645a200fc18573f5d7223f17c6ae839739c56 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Thu, 30 Jan 2025 23:37:33 +0100 Subject: [PATCH 17/22] wip: First draft of tests for `models/aggregators`... --- .../aggregated-async-iterator.test.ts | 0 .../aggregators/aggregated-iterator.test.ts | 210 ++++++++++++++++++ .../aggregators/reduced-iterator.test.ts | 0 3 files changed, 210 insertions(+) create mode 100644 tests/models/aggregators/aggregated-async-iterator.test.ts create mode 100644 tests/models/aggregators/aggregated-iterator.test.ts create mode 100644 tests/models/aggregators/reduced-iterator.test.ts diff --git a/tests/models/aggregators/aggregated-async-iterator.test.ts b/tests/models/aggregators/aggregated-async-iterator.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/tests/models/aggregators/aggregated-iterator.test.ts b/tests/models/aggregators/aggregated-iterator.test.ts new file mode 100644 index 0000000..ae25584 --- /dev/null +++ b/tests/models/aggregators/aggregated-iterator.test.ts @@ -0,0 +1,210 @@ +import { describe, expect, it } from "vitest"; + +import { SmartIterator } from "../../../src/index.js"; + +describe("AggregatedIterator", () => +{ + it("Should check if every element in each group satisfies a condition", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 7]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .every((key, value) => value >= 0); + + expect(iterator.toObject()).toEqual({ odd: true, even: false }); + }); + it("Should check if some elements in each group satisfy a condition", () => + { + const iterator = new SmartIterator([-5, -4, -3, -2, -1, 0]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .some((key, value) => value >= 0); + + expect(iterator.toObject()).toEqual({ odd: false, even: true }); + }); + + it("Should filter elements by a condition", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .filter((key, value) => value >= 0); + + expect(iterator.toObject()).toEqual({ odd: [3, 5], even: [0, 2, 6, 8] }); + }); + it("Should map elements using a transformation function", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .map((key, value) => Math.abs(value)); + + expect(iterator.toObject()).toEqual({ odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }); + }); + it("Should reduce elements using a reducer function", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, accumulator, value) => accumulator + value); + + expect(iterator.toObject()).toEqual({ odd: 4, even: 16 }); + }); + + it("Should flatten elements using a transformation function", () => + { + const iterator = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) + .groupBy(([value, _]) => value % 2 === 0 ? "even" : "odd") + .flatMap((key, values) => values); + + expect(iterator.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + it("Should group elements by key and count them", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .count(); + + expect(iterator.toObject()).toEqual({ odd: 4, even: 4 }); + }); + + it("Should drop a given number of elements from the beginning of each group", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .drop(2); + + expect(iterator.toObject()).toEqual({ odd: [3, 5], even: [6, 8] }); + }); + + it("Should take a given number of elements from the beginning of each group", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .take(2); + + expect(iterator.toObject()).toEqual({ odd: [-3, -1], even: [0, 2] }); + }); + + it("Should find the first element of each group that satisfies a condition", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .find((key, value) => value > 0); + + expect(iterator.toObject()).toEqual({ odd: 3, even: 2 }); + }); + + it("Should enumerate the elements of the iterator", () => + { + const iterator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .enumerate(); + + expect(iterator.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + }); + + it("Should remove all duplicate elements from within each group", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .unique(); + + expect(iterator.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + it("Should count the number of elements within each group", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .count(); + + expect(iterator.toObject()).toEqual({ odd: 4, even: 4 }); + }); + + it("Should iterate over the elements of the iterator", () => + { + const iterator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results: [string, number, number][] = []; + iterator.forEach((key, value, index) => + { + results.push([key, value, index]); + }); + + expect(results).toEqual([ + ["odd", -3, 0], + ["even", 0, 0], + ["even", 2, 1], + ["odd", -1, 1], + ["odd", 3, 2] + ]); + }); + + it("Should change the key of each element on which the iterator is aggregated", () => + { + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .map((key, value, index) => index % 2 === 0 ? value : -value) + .reorganizeBy((key, value) => value >= 0 ? "+" : "-"); + + expect(iterator.toObject()).toEqual({ "+": [0, 3, 6], "-": [-3, -2, -5, -8] }); + }); + + it("Should return all keys of the iterator", () => + { + const keys = new SmartIterator([-3, Symbol(), "A", {}, null, [1, 2, 3], false]) + .groupBy((value) => typeof value) + .keys(); + + expect(keys.toArray()).toEqual(["number", "symbol", "string", "object", "boolean"]); + }); + + it("Should return all entries of the iterator", () => + { + const entries = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .entries(); + + expect(entries.toArray()).toEqual([ + ["odd", -3], + ["even", 0], + ["even", 2], + ["odd", -1], + ["odd", 3] + ]); + }); + + it("Should return all values of the iterator", () => + { + const values = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .values(); + + expect(values.toArray()).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); + }); + + it("Should materialize the iterator into an array of arrays", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(aggregator.toArray()).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); + }); + + it("Should materialize the iterator into a map", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(aggregator.toMap()).toEqual(new Map([ + ["odd", [-3, -1, 3, 5]], + ["even", [0, 2, 6, 8]] + ])); + }); + + it("Should materialize the iterator into an object", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(aggregator.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); +}); diff --git a/tests/models/aggregators/reduced-iterator.test.ts b/tests/models/aggregators/reduced-iterator.test.ts new file mode 100644 index 0000000..e69de29 From 14b41c363fd95ff2091fe0afabd3b4a271195e1b Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Fri, 31 Jan 2025 14:24:46 +0100 Subject: [PATCH 18/22] upd: Minor update. --- eslint.config.mjs | 10 +- package.json | 4 +- pnpm-lock.yaml | 244 +++++++++++++++++++++++----------------------- 3 files changed, 130 insertions(+), 128 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index fbb46c4..c454a1c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,9 +9,9 @@ const __dirname = path.dirname(__filename); const gitignorePath = path.resolve(__dirname, ".gitignore"); export default [includeIgnoreFile(gitignorePath), ...eslintTs, { - rules: { - "no-trailing-spaces": ["error", { "ignoreComments": true }], - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/unified-signatures": "off" - } + rules: { + "no-trailing-spaces": ["error", { "ignoreComments": true }], + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/unified-signatures": "off" + } }]; diff --git a/package.json b/package.json index 35ee34a..721de54 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,9 @@ "ci": "pnpm install --frozen-lockfile" }, "devDependencies": { - "@byloth/eslint-config-typescript": "^3.0.3", + "@byloth/eslint-config-typescript": "^3.1.0", "@eslint/compat": "^1.2.5", - "@types/node": "^22.10.10", + "@types/node": "^22.12.0", "husky": "^9.1.7", "jsdom": "^26.0.0", "typescript": "^5.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7ce378..03356ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,14 @@ importers: .: devDependencies: '@byloth/eslint-config-typescript': - specifier: ^3.0.3 - version: 3.0.3(eslint@9.19.0)(typescript@5.7.3) + specifier: ^3.1.0 + version: 3.1.0(eslint@9.19.0)(typescript@5.7.3) + '@eslint/compat': + specifier: ^1.2.5 + version: 1.2.5(eslint@9.19.0) '@types/node': - specifier: ^22.10.10 - version: 22.10.10 + specifier: ^22.12.0 + version: 22.12.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -25,21 +28,21 @@ importers: version: 5.7.3 vite: specifier: ^6.0.11 - version: 6.0.11(@types/node@22.10.10) + version: 6.0.11(@types/node@22.12.0) vitest: specifier: ^3.0.4 - version: 3.0.4(@types/node@22.10.10)(jsdom@26.0.0) + version: 3.0.4(@types/node@22.12.0)(jsdom@26.0.0) packages: '@asamuzakjp/css-color@2.8.3': resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} - '@byloth/eslint-config-typescript@3.0.3': - resolution: {integrity: sha512-97bJOula+nilxjPJDgRIuGlF8KyppcQ2HjxWyA+MVTNEOeWNF3+u3FG3R4+XqSxz2Nc4RNB4wRqhAjdVPW6gVw==} + '@byloth/eslint-config-typescript@3.1.0': + resolution: {integrity: sha512-BBET8WS/LrPfeLaPIdJiqnjcFv7oJ2CJnCrnAXH6C17BMfsAVFUBOEdzNkrb6eldsGNxkJV7u0SMpmu3+SLcTg==} - '@byloth/eslint-config@3.0.3': - resolution: {integrity: sha512-fXpIxZByU2Ux+95jGcEEweKYb4bxS571xaMZDJ24wsasrDuczjld63EGAulnm9yRAJiLpScruvV0mWLow+16tg==} + '@byloth/eslint-config@3.1.0': + resolution: {integrity: sha512-o3ku1Ca50amSB0dGVRrOUw2GTbIrv1ft/tZF+GUSDkpiO/NBE3K11QwOjSm0JDnRX6H+RyHHV9kPBhKBTRglqw==} '@csstools/color-helpers@5.0.1': resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} @@ -297,98 +300,98 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@rollup/rollup-android-arm-eabi@4.32.0': - resolution: {integrity: sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==} + '@rollup/rollup-android-arm-eabi@4.32.1': + resolution: {integrity: sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.32.0': - resolution: {integrity: sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==} + '@rollup/rollup-android-arm64@4.32.1': + resolution: {integrity: sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.32.0': - resolution: {integrity: sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==} + '@rollup/rollup-darwin-arm64@4.32.1': + resolution: {integrity: sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.32.0': - resolution: {integrity: sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==} + '@rollup/rollup-darwin-x64@4.32.1': + resolution: {integrity: sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.32.0': - resolution: {integrity: sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==} + '@rollup/rollup-freebsd-arm64@4.32.1': + resolution: {integrity: sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.32.0': - resolution: {integrity: sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==} + '@rollup/rollup-freebsd-x64@4.32.1': + resolution: {integrity: sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.32.0': - resolution: {integrity: sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==} + '@rollup/rollup-linux-arm-gnueabihf@4.32.1': + resolution: {integrity: sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.32.0': - resolution: {integrity: sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==} + '@rollup/rollup-linux-arm-musleabihf@4.32.1': + resolution: {integrity: sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.32.0': - resolution: {integrity: sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==} + '@rollup/rollup-linux-arm64-gnu@4.32.1': + resolution: {integrity: sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.32.0': - resolution: {integrity: sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==} + '@rollup/rollup-linux-arm64-musl@4.32.1': + resolution: {integrity: sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.32.0': - resolution: {integrity: sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==} + '@rollup/rollup-linux-loongarch64-gnu@4.32.1': + resolution: {integrity: sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': - resolution: {integrity: sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.32.1': + resolution: {integrity: sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.32.0': - resolution: {integrity: sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==} + '@rollup/rollup-linux-riscv64-gnu@4.32.1': + resolution: {integrity: sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.32.0': - resolution: {integrity: sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==} + '@rollup/rollup-linux-s390x-gnu@4.32.1': + resolution: {integrity: sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.32.0': - resolution: {integrity: sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==} + '@rollup/rollup-linux-x64-gnu@4.32.1': + resolution: {integrity: sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.32.0': - resolution: {integrity: sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==} + '@rollup/rollup-linux-x64-musl@4.32.1': + resolution: {integrity: sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.32.0': - resolution: {integrity: sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==} + '@rollup/rollup-win32-arm64-msvc@4.32.1': + resolution: {integrity: sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.32.0': - resolution: {integrity: sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==} + '@rollup/rollup-win32-ia32-msvc@4.32.1': + resolution: {integrity: sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.32.0': - resolution: {integrity: sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==} + '@rollup/rollup-win32-x64-msvc@4.32.1': + resolution: {integrity: sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==} cpu: [x64] os: [win32] @@ -398,8 +401,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.10.10': - resolution: {integrity: sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==} + '@types/node@22.12.0': + resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==} '@typescript-eslint/eslint-plugin@8.22.0': resolution: {integrity: sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==} @@ -668,8 +671,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.18.0: - resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fastq@1.19.0: + resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -809,8 +812,8 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - loupe@3.1.2: - resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -919,8 +922,8 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.32.0: - resolution: {integrity: sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==} + rollup@4.32.1: + resolution: {integrity: sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -937,8 +940,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.0: + resolution: {integrity: sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==} engines: {node: '>=10'} hasBin: true @@ -1172,9 +1175,9 @@ snapshots: '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 - '@byloth/eslint-config-typescript@3.0.3(eslint@9.19.0)(typescript@5.7.3)': + '@byloth/eslint-config-typescript@3.1.0(eslint@9.19.0)(typescript@5.7.3)': dependencies: - '@byloth/eslint-config': 3.0.3 + '@byloth/eslint-config': 3.1.0 '@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.22.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0)(typescript@5.7.3) '@typescript-eslint/parser': 8.22.0(eslint@9.19.0)(typescript@5.7.3) transitivePeerDependencies: @@ -1183,9 +1186,8 @@ snapshots: - supports-color - typescript - '@byloth/eslint-config@3.0.3': + '@byloth/eslint-config@3.1.0': dependencies: - '@eslint/compat': 1.2.5(eslint@9.19.0) '@eslint/js': 9.19.0 eslint: 9.19.0 globals: 15.14.0 @@ -1359,70 +1361,70 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.18.0 + fastq: 1.19.0 - '@rollup/rollup-android-arm-eabi@4.32.0': + '@rollup/rollup-android-arm-eabi@4.32.1': optional: true - '@rollup/rollup-android-arm64@4.32.0': + '@rollup/rollup-android-arm64@4.32.1': optional: true - '@rollup/rollup-darwin-arm64@4.32.0': + '@rollup/rollup-darwin-arm64@4.32.1': optional: true - '@rollup/rollup-darwin-x64@4.32.0': + '@rollup/rollup-darwin-x64@4.32.1': optional: true - '@rollup/rollup-freebsd-arm64@4.32.0': + '@rollup/rollup-freebsd-arm64@4.32.1': optional: true - '@rollup/rollup-freebsd-x64@4.32.0': + '@rollup/rollup-freebsd-x64@4.32.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.32.0': + '@rollup/rollup-linux-arm-gnueabihf@4.32.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.32.0': + '@rollup/rollup-linux-arm-musleabihf@4.32.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.32.0': + '@rollup/rollup-linux-arm64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.32.0': + '@rollup/rollup-linux-arm64-musl@4.32.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.32.0': + '@rollup/rollup-linux-loongarch64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.32.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.32.0': + '@rollup/rollup-linux-riscv64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.32.0': + '@rollup/rollup-linux-s390x-gnu@4.32.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.32.0': + '@rollup/rollup-linux-x64-gnu@4.32.1': optional: true - '@rollup/rollup-linux-x64-musl@4.32.0': + '@rollup/rollup-linux-x64-musl@4.32.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.32.0': + '@rollup/rollup-win32-arm64-msvc@4.32.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.32.0': + '@rollup/rollup-win32-ia32-msvc@4.32.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.32.0': + '@rollup/rollup-win32-x64-msvc@4.32.1': optional: true '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} - '@types/node@22.10.10': + '@types/node@22.12.0': dependencies: undici-types: 6.20.0 @@ -1481,7 +1483,7 @@ snapshots: fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.0 ts-api-utils: 2.0.0(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: @@ -1510,13 +1512,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.4(vite@6.0.11(@types/node@22.10.10))': + '@vitest/mocker@3.0.4(vite@6.0.11(@types/node@22.12.0))': dependencies: '@vitest/spy': 3.0.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.0.11(@types/node@22.10.10) + vite: 6.0.11(@types/node@22.12.0) '@vitest/pretty-format@3.0.4': dependencies: @@ -1540,7 +1542,7 @@ snapshots: '@vitest/utils@3.0.4': dependencies: '@vitest/pretty-format': 3.0.4 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 2.0.0 acorn-jsx@5.3.2(acorn@8.14.0): @@ -1592,7 +1594,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.2 + loupe: 3.1.3 pathval: 2.0.0 chalk@4.1.2: @@ -1762,7 +1764,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fastq@1.18.0: + fastq@1.19.0: dependencies: reusify: 1.0.4 @@ -1909,7 +1911,7 @@ snapshots: lodash.merge@4.6.2: {} - loupe@3.1.2: {} + loupe@3.1.3: {} lru-cache@10.4.3: {} @@ -1999,29 +2001,29 @@ snapshots: reusify@1.0.4: {} - rollup@4.32.0: + rollup@4.32.1: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.32.0 - '@rollup/rollup-android-arm64': 4.32.0 - '@rollup/rollup-darwin-arm64': 4.32.0 - '@rollup/rollup-darwin-x64': 4.32.0 - '@rollup/rollup-freebsd-arm64': 4.32.0 - '@rollup/rollup-freebsd-x64': 4.32.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.32.0 - '@rollup/rollup-linux-arm-musleabihf': 4.32.0 - '@rollup/rollup-linux-arm64-gnu': 4.32.0 - '@rollup/rollup-linux-arm64-musl': 4.32.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.32.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.32.0 - '@rollup/rollup-linux-riscv64-gnu': 4.32.0 - '@rollup/rollup-linux-s390x-gnu': 4.32.0 - '@rollup/rollup-linux-x64-gnu': 4.32.0 - '@rollup/rollup-linux-x64-musl': 4.32.0 - '@rollup/rollup-win32-arm64-msvc': 4.32.0 - '@rollup/rollup-win32-ia32-msvc': 4.32.0 - '@rollup/rollup-win32-x64-msvc': 4.32.0 + '@rollup/rollup-android-arm-eabi': 4.32.1 + '@rollup/rollup-android-arm64': 4.32.1 + '@rollup/rollup-darwin-arm64': 4.32.1 + '@rollup/rollup-darwin-x64': 4.32.1 + '@rollup/rollup-freebsd-arm64': 4.32.1 + '@rollup/rollup-freebsd-x64': 4.32.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.32.1 + '@rollup/rollup-linux-arm-musleabihf': 4.32.1 + '@rollup/rollup-linux-arm64-gnu': 4.32.1 + '@rollup/rollup-linux-arm64-musl': 4.32.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.32.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.32.1 + '@rollup/rollup-linux-riscv64-gnu': 4.32.1 + '@rollup/rollup-linux-s390x-gnu': 4.32.1 + '@rollup/rollup-linux-x64-gnu': 4.32.1 + '@rollup/rollup-linux-x64-musl': 4.32.1 + '@rollup/rollup-win32-arm64-msvc': 4.32.1 + '@rollup/rollup-win32-ia32-msvc': 4.32.1 + '@rollup/rollup-win32-x64-msvc': 4.32.1 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -2036,7 +2038,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - semver@7.6.3: {} + semver@7.7.0: {} shebang-command@2.0.0: dependencies: @@ -2104,13 +2106,13 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@3.0.4(@types/node@22.10.10): + vite-node@3.0.4(@types/node@22.12.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.2 - vite: 6.0.11(@types/node@22.10.10) + vite: 6.0.11(@types/node@22.12.0) transitivePeerDependencies: - '@types/node' - jiti @@ -2125,19 +2127,19 @@ snapshots: - tsx - yaml - vite@6.0.11(@types/node@22.10.10): + vite@6.0.11(@types/node@22.12.0): dependencies: esbuild: 0.24.2 postcss: 8.5.1 - rollup: 4.32.0 + rollup: 4.32.1 optionalDependencies: - '@types/node': 22.10.10 + '@types/node': 22.12.0 fsevents: 2.3.3 - vitest@3.0.4(@types/node@22.10.10)(jsdom@26.0.0): + vitest@3.0.4(@types/node@22.12.0)(jsdom@26.0.0): dependencies: '@vitest/expect': 3.0.4 - '@vitest/mocker': 3.0.4(vite@6.0.11(@types/node@22.10.10)) + '@vitest/mocker': 3.0.4(vite@6.0.11(@types/node@22.12.0)) '@vitest/pretty-format': 3.0.4 '@vitest/runner': 3.0.4 '@vitest/snapshot': 3.0.4 @@ -2153,11 +2155,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.0.11(@types/node@22.10.10) - vite-node: 3.0.4(@types/node@22.10.10) + vite: 6.0.11(@types/node@22.12.0) + vite-node: 3.0.4(@types/node@22.12.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.10.10 + '@types/node': 22.12.0 jsdom: 26.0.0 transitivePeerDependencies: - jiti From 9f1323d5b5d015db1e0c3d4edb3e51138a6a053e Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Fri, 31 Jan 2025 20:47:57 +0100 Subject: [PATCH 19/22] =?UTF-8?q?wip:=20**Almost**=20ended=20writing=20all?= =?UTF-8?q?=20the=20tests...=20=F0=9F=A5=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 10 +- .../aggregators/aggregated-async-iterator.ts | 6 +- src/models/aggregators/aggregated-iterator.ts | 6 +- .../aggregated-async-iterator.test.ts | 510 ++++++++++++++++++ .../aggregators/aggregated-iterator.test.ts | 161 +++--- .../aggregators/reduced-iterator.test.ts | 236 ++++++++ .../iterators/smart-async-iterator.test.ts | 4 +- tests/models/iterators/smart-iterator.test.ts | 2 +- 8 files changed, 847 insertions(+), 88 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index c454a1c..fbb46c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,9 +9,9 @@ const __dirname = path.dirname(__filename); const gitignorePath = path.resolve(__dirname, ".gitignore"); export default [includeIgnoreFile(gitignorePath), ...eslintTs, { - rules: { - "no-trailing-spaces": ["error", { "ignoreComments": true }], - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/unified-signatures": "off" - } + rules: { + "no-trailing-spaces": ["error", { "ignoreComments": true }], + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/unified-signatures": "off" + } }]; diff --git a/src/models/aggregators/aggregated-async-iterator.ts b/src/models/aggregators/aggregated-async-iterator.ts index 83cd5b2..48cebfe 100644 --- a/src/models/aggregators/aggregated-async-iterator.ts +++ b/src/models/aggregators/aggregated-async-iterator.ts @@ -517,7 +517,11 @@ export default class AggregatedAsyncIterator * * ```ts * const results = new SmartAsyncIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) - * .groupBy(async ([value, _]) => value % 2 === 0 ? "even" : "odd") + * .groupBy(async (values) => + * { + * const value = values instanceof Array ? values[0] : values; + * return value % 2 === 0 ? "even" : "odd"; + * }) * .flatMap(async (key, values) => values); * * console.log(await results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } diff --git a/src/models/aggregators/aggregated-iterator.ts b/src/models/aggregators/aggregated-iterator.ts index ee1032f..c26cc42 100644 --- a/src/models/aggregators/aggregated-iterator.ts +++ b/src/models/aggregators/aggregated-iterator.ts @@ -452,7 +452,11 @@ export default class AggregatedIterator * * ```ts * const results = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) - * .groupBy(([value, _]) => value % 2 === 0 ? "even" : "odd") + * .groupBy((values) => + * { + * const value = values instanceof Array ? values[0] : values; + * return value % 2 === 0 ? "even" : "odd"; + * }) * .flatMap((key, values) => values); * * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] } diff --git a/tests/models/aggregators/aggregated-async-iterator.test.ts b/tests/models/aggregators/aggregated-async-iterator.test.ts index e69de29..bfca8c1 100644 --- a/tests/models/aggregators/aggregated-async-iterator.test.ts +++ b/tests/models/aggregators/aggregated-async-iterator.test.ts @@ -0,0 +1,510 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { delay } from "../../../src/index.js"; + +import { SmartAsyncIterator } from "../../../src/index.js"; + +describe("AggregatedAsyncIterator", () => +{ + const _toAsync = (elements: Iterable) => + { + return async function* () + { + for (const element of elements) + { + await delay(100); + + yield element; + } + }; + }; + + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.clearAllTimers()); + + it("Should check if every element in each group satisfies a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-4, -2, 1, 3, 5, 6, 7])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.every(async (key, value) => value >= 0) + .then((results) => + { + resolved = true; + + expect(results.toObject()).toEqual({ odd: true, even: false }); + }); + + await vi.advanceTimersByTimeAsync(600); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should check if some elements in each group satisfy a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-5, -4, -3, -2, -1, 0])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.some(async (key, value) => value >= 0) + .then((results) => + { + resolved = true; + + expect(results.toObject()).toEqual({ odd: false, even: true }); + }); + + await vi.advanceTimersByTimeAsync(500); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should filter elements based on a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .filter(async (key, value) => value >= 0); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should map elements using a transformation function", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .map(async (key, value) => Math.abs(value)); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should reduce elements using a reducer function", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.reduce(async (key, acc, value) => acc + value) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 4, even: 16 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should reduce elements using a reducer function with an initial value", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.reduce(async (key, acc, value) => acc + value, 10) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 14, even: 26 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should reduce elements using a reducer function with a function that returns an initial value", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + + const reducer = async (key: string, acc: number, value: number) => acc + value; + const initializer = async (key: string) => key === "odd" ? 10 : 0; + aggregator.reduce(reducer, initializer) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 14, even: 16 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should flatten elements using a transformation function", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([[-3, -1], 0, 2, 3, 5, [6, 8]])) + .groupBy(async (values) => + { + const value = values instanceof Array ? values[0] : values; + return value % 2 === 0 ? "even" : "odd"; + }) + .flatMap(async (key, values) => values); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(500); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should drop a given number of elements from the beginning of each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .drop(2); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [3, 5], even: [6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should take a given number of elements from the beginning of each group", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .take(2); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1], even: [0, 2] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should find the first element that satisfies a condition", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.find(async (key, value) => value >= 0) + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 3, even: 0 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should enumerate elements", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .enumerate(); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should remove duplicate elements", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .unique(); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(1_300); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should group elements by key and count them", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.count() + .then((result) => + { + resolved = true; + + expect(result.toObject()).toEqual({ odd: 4, even: 4 }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should iterate over the elements of the iterator", async () => + { + const results: [string, number, number][] = []; + const aggregator = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.forEach(async (key, value, index) => { results.push([key, value, index]); }) + .then(() => + { + resolved = true; + + expect(results).toEqual([ + ["odd", -3, 0], + ["even", 0, 0], + ["even", 2, 1], + ["odd", -1, 1], + ["odd", 3, 2] + ]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should reorganize elements by a new key", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .map(async (key, value, index) => index % 2 === 0 ? value : -value) + .reorganizeBy(async (key, value) => value >= 0 ? "+" : "-"); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should return all keys of the iterator", async () => + { + const keys = new SmartAsyncIterator(_toAsync([-3, Symbol(), "A", { }, null, [1, 2, 3], false])) + .groupBy(async (value) => typeof value) + .keys(); + + let resolved = false; + keys.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual(["number", "symbol", "string", "object", "boolean"]); + }); + + await vi.advanceTimersByTimeAsync(600); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return all entries of the iterator", async () => + { + const entries = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .entries(); + + let resolved = false; + entries.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([ + ["odd", -3], + ["even", 0], + ["even", 2], + ["odd", -1], + ["odd", 3] + ]); + }); + + await vi.advanceTimersByTimeAsync(400); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should return all values of the iterator", async () => + { + const values = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") + .values(); + + let resolved = false; + values.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + + it("Should materialize the iterator into an array of arrays", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.toArray() + .then((result) => + { + resolved = true; + + expect(result).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should materialize the iterator into a map", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.toMap() + .then((result) => + { + resolved = true; + + expect(result).toEqual(new Map([ + ["odd", [-3, -1, 3, 5]], + ["even", [0, 2, 6, 8]] + ])); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); + it("Should materialize the iterator into an object", async () => + { + const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) + .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); + + let resolved = false; + aggregator.toObject() + .then((result) => + { + resolved = true; + + expect(result).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + await vi.advanceTimersByTimeAsync(700); + expect(resolved).toBe(false); + + await vi.advanceTimersByTimeAsync(100); + expect(resolved).toBe(true); + }); +}); diff --git a/tests/models/aggregators/aggregated-iterator.test.ts b/tests/models/aggregators/aggregated-iterator.test.ts index ae25584..8485a53 100644 --- a/tests/models/aggregators/aggregated-iterator.test.ts +++ b/tests/models/aggregators/aggregated-iterator.test.ts @@ -6,128 +6,134 @@ describe("AggregatedIterator", () => { it("Should check if every element in each group satisfies a condition", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 7]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .every((key, value) => value >= 0); + const aggregator = new SmartIterator([-4, -2, 1, 3, 5, 6, 7]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: true, even: false }); + const results = aggregator.every((key, value) => value >= 0); + expect(results.toObject()).toEqual({ odd: true, even: false }); }); it("Should check if some elements in each group satisfy a condition", () => { - const iterator = new SmartIterator([-5, -4, -3, -2, -1, 0]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .some((key, value) => value >= 0); + const aggregator = new SmartIterator([-5, -4, -3, -2, -1, 0]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: false, even: true }); + const results = aggregator.some((key, value) => value >= 0); + expect(results.toObject()).toEqual({ odd: false, even: true }); }); it("Should filter elements by a condition", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .filter((key, value) => value >= 0); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [3, 5], even: [0, 2, 6, 8] }); + const results = aggregator.filter((key, value) => value > 0); + expect(results.toObject()).toEqual({ odd: [3, 5], even: [2, 6, 8] }); }); it("Should map elements using a transformation function", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .map((key, value) => Math.abs(value)); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }); + const results = aggregator.map((key, value) => Math.abs(value)); + expect(results.toObject()).toEqual({ odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }); }); + it("Should reduce elements using a reducer function", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .reduce((key, accumulator, value) => accumulator + value); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: 4, even: 16 }); + const results = aggregator.reduce((key, acc, value) => acc + value); + expect(results.toObject()).toEqual({ odd: 4, even: 16 }); }); + it("Should reduce elements with an initial value", () => + { + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - it("Should flatten elements using a transformation function", () => + const results = aggregator.reduce((key, acc, value) => acc + value, 10); + expect(results.toObject()).toEqual({ odd: 14, even: 26 }); + }); + it("Should reduce elements with a function that returns an initial value", () => { - const iterator = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) - .groupBy(([value, _]) => value % 2 === 0 ? "even" : "odd") - .flatMap((key, values) => values); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + const results = aggregator.reduce((key, acc, value) => acc + value, (key) => key === "odd" ? 10 : 0); + expect(results.toObject()).toEqual({ odd: 14, even: 16 }); }); - it("Should group elements by key and count them", () => + it("Should flatten elements using a transformation function", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .count(); + const aggregator = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) + .groupBy((values) => + { + const value = values instanceof Array ? values[0] : values; + return value % 2 === 0 ? "even" : "odd"; + }); - expect(iterator.toObject()).toEqual({ odd: 4, even: 4 }); + const results = aggregator.flatMap((key, values) => values); + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); }); it("Should drop a given number of elements from the beginning of each group", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .drop(2); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [3, 5], even: [6, 8] }); + const results = aggregator.drop(2); + expect(results.toObject()).toEqual({ odd: [3, 5], even: [6, 8] }); }); - it("Should take a given number of elements from the beginning of each group", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .take(2); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [-3, -1], even: [0, 2] }); + const results = aggregator.take(2); + expect(results.toObject()).toEqual({ odd: [-3, -1], even: [0, 2] }); }); it("Should find the first element of each group that satisfies a condition", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .find((key, value) => value > 0); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: 3, even: 2 }); + const results = aggregator.find((key, value) => value > 0); + expect(results.toObject()).toEqual({ odd: 3, even: 2 }); }); it("Should enumerate the elements of the iterator", () => { - const iterator = new SmartIterator([-3, 0, 2, -1, 3]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .enumerate(); + const aggregator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + const results = aggregator.enumerate(); + expect(results.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); }); - it("Should remove all duplicate elements from within each group", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .unique(); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + const results = aggregator.unique(); + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); }); - it("Should count the number of elements within each group", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .count(); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - expect(iterator.toObject()).toEqual({ odd: 4, even: 4 }); + const results = aggregator.count(); + expect(results.toObject()).toEqual({ odd: 4, even: 4 }); }); it("Should iterate over the elements of the iterator", () => { + const results: [string, number, number][] = []; const iterator = new SmartIterator([-3, 0, 2, -1, 3]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - const results: [string, number, number][] = []; - iterator.forEach((key, value, index) => - { - results.push([key, value, index]); - }); + iterator.forEach((key, value, index) => { results.push([key, value, index]); }); expect(results).toEqual([ ["odd", -3, 0], @@ -140,29 +146,29 @@ describe("AggregatedIterator", () => it("Should change the key of each element on which the iterator is aggregated", () => { - const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .map((key, value, index) => index % 2 === 0 ? value : -value) + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = aggregator.map((key, value, index) => index % 2 === 0 ? value : -value) .reorganizeBy((key, value) => value >= 0 ? "+" : "-"); - expect(iterator.toObject()).toEqual({ "+": [0, 3, 6], "-": [-3, -2, -5, -8] }); + expect(results.toObject()).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); }); it("Should return all keys of the iterator", () => { - const keys = new SmartIterator([-3, Symbol(), "A", {}, null, [1, 2, 3], false]) - .groupBy((value) => typeof value) - .keys(); + const aggregator = new SmartIterator([-3, Symbol(), "A", { }, null, [1, 2, 3], false]) + .groupBy((value) => typeof value); + const keys = aggregator.keys(); expect(keys.toArray()).toEqual(["number", "symbol", "string", "object", "boolean"]); }); - it("Should return all entries of the iterator", () => { - const entries = new SmartIterator([-3, 0, 2, -1, 3]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .entries(); + const aggregator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const entries = aggregator.entries(); expect(entries.toArray()).toEqual([ ["odd", -3], ["even", 0], @@ -171,13 +177,12 @@ describe("AggregatedIterator", () => ["odd", 3] ]); }); - it("Should return all values of the iterator", () => { - const values = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd") - .values(); + const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const values = aggregator.values(); expect(values.toArray()).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); }); @@ -188,7 +193,6 @@ describe("AggregatedIterator", () => expect(aggregator.toArray()).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); }); - it("Should materialize the iterator into a map", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) @@ -199,7 +203,6 @@ describe("AggregatedIterator", () => ["even", [0, 2, 6, 8]] ])); }); - it("Should materialize the iterator into an object", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) diff --git a/tests/models/aggregators/reduced-iterator.test.ts b/tests/models/aggregators/reduced-iterator.test.ts index e69de29..dd5ee7b 100644 --- a/tests/models/aggregators/reduced-iterator.test.ts +++ b/tests/models/aggregators/reduced-iterator.test.ts @@ -0,0 +1,236 @@ +import { describe, expect, it } from "vitest"; + +import { SmartIterator } from "../../../src/index.js"; + +describe("ReducedIterator", () => +{ + it("Should return `true` if every value matches the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.every((key, value) => value > 0); + expect(results).toEqual(true); + }); + it("Should return `false` if not every value matches the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.every((key, value) => value > 0); + expect(results).toEqual(false); + }); + + it("Should return `true` if some values match the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.some((key, value) => value > 0); + expect(results).toEqual(true); + }); + it("Should return `false` if no values match the predicate", () => + { + const reduced = new SmartIterator([-3, -1, 0, -2, -3, -5, 6, -8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.some((key, value) => value > 0); + expect(results).toEqual(false); + }); + + it("Should filter elements based on a condition", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.filter((key, value) => value > 0); + expect(results.toObject()).toEqual({ even: 16 }); + }); + it("Should map elements using a transformation function", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.map((key, value) => Math.abs(value)); + expect(results.toObject()).toEqual({ odd: 2, even: 16 }); + }); + + it("Should reduce elements using a reducer function", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.reduce((key, acc, value) => acc + value); + expect(results).toEqual(20); + }); + it("Should reduce elements with an initial value", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.reduce((key, acc, value) => acc + value, 10); + expect(results).toEqual(30); + }); + + // + // TODO: Continue from here! + // + + it("Should flatten elements using a transformation function", () => + { + const reduced = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) + .groupBy((values) => + { + const value = values instanceof Array ? values[0] : values; + return value % 2 === 0 ? "even" : "odd"; + }); + + const results = reduced.flatMap((key, values) => values); + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + + it("Should drop a given number of elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.drop(2); + expect(results.toObject()).toEqual({ odd: [3, 5], even: [6, 8] }); + }); + it("Should take a given number of elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.take(2); + expect(results.toObject()).toEqual({ odd: [-3, -1], even: [0, 2] }); + }); + + it("Should find the first element that satisfies a condition", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.find((key, value) => value > 0); + expect(results.toObject()).toEqual({ odd: 3, even: 2 }); + }); + + it("Should enumerate elements", () => + { + const reduced = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.enumerate(); + expect(results.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + }); + it("Should remove duplicate elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.unique(); + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); + it("Should count the number of elements", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.count(); + expect(results.toObject()).toEqual({ odd: 4, even: 4 }); + }); + + it("Should iterate over all elements", () => + { + const results: [string, number, number][] = []; + const iterator = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + iterator.forEach((key, value, index) => { results.push([key, value, index]); }); + + expect(results).toEqual([ + ["odd", -3, 0], + ["even", 0, 0], + ["even", 2, 1], + ["odd", -1, 1], + ["odd", 3, 2] + ]); + }); + + it("Should reorganize elements by a new key", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const results = reduced.map((key, value, index) => index % 2 === 0 ? value : -value) + .reorganizeBy((key, value) => value >= 0 ? "+" : "-"); + + expect(results.toObject()).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); + }); + + it("Should return all keys", () => + { + const reduced = new SmartIterator([-3, Symbol(), "A", { }, null, [1, 2, 3], false]) + .groupBy((value) => typeof value); + + const keys = reduced.keys(); + expect(keys.toArray()).toEqual(["number", "symbol", "string", "object", "boolean"]); + }); + it("Should return all entries", () => + { + const reduced = new SmartIterator([-3, 0, 2, -1, 3]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const entries = reduced.entries(); + expect(entries.toArray()).toEqual([ + ["odd", -3], + ["even", 0], + ["even", 2], + ["odd", -1], + ["odd", 3] + ]); + }); + it("Should return all values", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + const values = reduced.values(); + expect(values.toArray()).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); + }); + + it("Should materialize the iterator into an array", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(reduced.toArray()).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); + }); + + it("Should materialize the iterator into a map", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(reduced.toMap()).toEqual(new Map([ + ["odd", [-3, -1, 3, 5]], + ["even", [0, 2, 6, 8]] + ])); + }); + + it("Should materialize the iterator into an object", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + + expect(reduced.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + }); +}); diff --git a/tests/models/iterators/smart-async-iterator.test.ts b/tests/models/iterators/smart-async-iterator.test.ts index e26167a..6e216f2 100644 --- a/tests/models/iterators/smart-async-iterator.test.ts +++ b/tests/models/iterators/smart-async-iterator.test.ts @@ -1,6 +1,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { delay, ValueException, type MaybePromise } from "../../../src/index.js"; +import { delay, ValueException } from "../../../src/index.js"; +import type { MaybePromise } from "../../../src/index.js"; + import { SmartAsyncIterator } from "../../../src/index.js"; describe("SmartAsyncIterator", () => diff --git a/tests/models/iterators/smart-iterator.test.ts b/tests/models/iterators/smart-iterator.test.ts index e24eaee..9f73b83 100644 --- a/tests/models/iterators/smart-iterator.test.ts +++ b/tests/models/iterators/smart-iterator.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { ValueException } from "../../../src/index.js"; import { SmartIterator } from "../../../src/index.js"; From 24311579627e90ee8bcf40dceb053c2c5844f5a0 Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Fri, 31 Jan 2025 22:08:28 +0100 Subject: [PATCH 20/22] =?UTF-8?q?tests:=20Ended=20up=20all=20the=20tests!?= =?UTF-8?q?=20**FINALLY!**=20=F0=9F=A5=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/aggregators/reduced-iterator.ts | 2 +- .../aggregated-async-iterator.test.ts | 6 +- .../aggregators/reduced-iterator.test.ts | 151 ++++++++++-------- 3 files changed, 89 insertions(+), 70 deletions(-) diff --git a/src/models/aggregators/reduced-iterator.ts b/src/models/aggregators/reduced-iterator.ts index 7eaf7ef..ba42569 100644 --- a/src/models/aggregators/reduced-iterator.ts +++ b/src/models/aggregators/reduced-iterator.ts @@ -389,7 +389,7 @@ export default class ReducedIterator * new one is and that consuming one of them will consume the other as well. * * ```ts - * const results = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + * const results = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) * .groupBy((value) => value % 2 === 0 ? "even" : "odd") * .reduce((key, accumulator, value) => accumulator.concat([value]), () => []) * .flatMap((key, value) => value); diff --git a/tests/models/aggregators/aggregated-async-iterator.test.ts b/tests/models/aggregators/aggregated-async-iterator.test.ts index bfca8c1..de369e0 100644 --- a/tests/models/aggregators/aggregated-async-iterator.test.ts +++ b/tests/models/aggregators/aggregated-async-iterator.test.ts @@ -153,9 +153,9 @@ describe("AggregatedAsyncIterator", () => let resolved = false; - const reducer = async (key: string, acc: number, value: number) => acc + value; - const initializer = async (key: string) => key === "odd" ? 10 : 0; - aggregator.reduce(reducer, initializer) + const _reducer = async (key: string, acc: number, value: number) => acc + value; + const _initializer = async (key: string) => key === "odd" ? 10 : 0; + aggregator.reduce(_reducer, _initializer) .then((result) => { resolved = true; diff --git a/tests/models/aggregators/reduced-iterator.test.ts b/tests/models/aggregators/reduced-iterator.test.ts index dd5ee7b..e94e8d3 100644 --- a/tests/models/aggregators/reduced-iterator.test.ts +++ b/tests/models/aggregators/reduced-iterator.test.ts @@ -11,7 +11,7 @@ describe("ReducedIterator", () => .reduce((key, acc, value) => acc + value); const results = reduced.every((key, value) => value > 0); - expect(results).toEqual(true); + expect(results).toBe(true); }); it("Should return `false` if not every value matches the predicate", () => { @@ -20,7 +20,7 @@ describe("ReducedIterator", () => .reduce((key, acc, value) => acc + value); const results = reduced.every((key, value) => value > 0); - expect(results).toEqual(false); + expect(results).toBe(false); }); it("Should return `true` if some values match the predicate", () => @@ -30,7 +30,7 @@ describe("ReducedIterator", () => .reduce((key, acc, value) => acc + value); const results = reduced.some((key, value) => value > 0); - expect(results).toEqual(true); + expect(results).toBe(true); }); it("Should return `false` if no values match the predicate", () => { @@ -39,7 +39,7 @@ describe("ReducedIterator", () => .reduce((key, acc, value) => acc + value); const results = reduced.some((key, value) => value > 0); - expect(results).toEqual(false); + expect(results).toBe(false); }); it("Should filter elements based on a condition", () => @@ -68,7 +68,7 @@ describe("ReducedIterator", () => .reduce((key, acc, value) => acc + value); const results = reduced.reduce((key, acc, value) => acc + value); - expect(results).toEqual(20); + expect(results).toBe(20); }); it("Should reduce elements with an initial value", () => { @@ -77,160 +77,179 @@ describe("ReducedIterator", () => .reduce((key, acc, value) => acc + value); const results = reduced.reduce((key, acc, value) => acc + value, 10); - expect(results).toEqual(30); + expect(results).toBe(30); }); - // - // TODO: Continue from here! - // - it("Should flatten elements using a transformation function", () => { - const reduced = new SmartIterator([[-3, -1], 0, 2, 3, 5, [6, 8]]) - .groupBy((values) => - { - const value = values instanceof Array ? values[0] : values; - return value % 2 === 0 ? "even" : "odd"; - }); + const _initializer = (key: string) => key === "odd" ? [] : 0; + const _reducer = (key: string, acc: number | number[], value: number) => + { + if (key === "odd") { (acc as number[]).push(value); } + else { (acc as number) += value; } + + return acc; + + }; - const results = reduced.flatMap((key, values) => values); - expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + const results = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce(_reducer, _initializer) + .flatMap((key, value) => value); + + expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [16] }); }); it("Should drop a given number of elements", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); - const results = reduced.drop(2); - expect(results.toObject()).toEqual({ odd: [3, 5], even: [6, 8] }); + const results = reduced.drop(1); + expect(results.toObject()).toEqual({ even: 16 }); }); it("Should take a given number of elements", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); - const results = reduced.take(2); - expect(results.toObject()).toEqual({ odd: [-3, -1], even: [0, 2] }); + const results = reduced.take(1); + expect(results.toObject()).toEqual({ odd: 4 }); }); it("Should find the first element that satisfies a condition", () => { - const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const results = reduced.find((key, value) => value > 0); - expect(results.toObject()).toEqual({ odd: 3, even: 2 }); + expect(results).toBe(16); + }); + it("Should return `undefined` when no matching value is found", () => + { + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); + + const results = reduced.find((key, value) => value < 0); + expect(results).toBeUndefined(); }); it("Should enumerate elements", () => { - const reduced = new SmartIterator([-3, 0, 2, -1, 3]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const results = reduced.enumerate(); - expect(results.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); + expect(results.toObject()).toEqual({ odd: [0, 4], even: [1, 16] }); }); it("Should remove duplicate elements", () => { - const reduced = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const reduced = new SmartIterator([3, 1, 0, 2, 3, 6, 3, 1, 1, 5, 6, 8, 7, 2]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const results = reduced.unique(); - expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + expect(results.toObject()).toEqual({ odd: 24 }); }); it("Should count the number of elements", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const results = reduced.count(); - expect(results.toObject()).toEqual({ odd: 4, even: 4 }); + expect(results).toBe(2); }); it("Should iterate over all elements", () => { const results: [string, number, number][] = []; - const iterator = new SmartIterator([-3, 0, 2, -1, 3]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); iterator.forEach((key, value, index) => { results.push([key, value, index]); }); expect(results).toEqual([ - ["odd", -3, 0], - ["even", 0, 0], - ["even", 2, 1], - ["odd", -1, 1], - ["odd", 3, 2] + ["odd", 4, 0], + ["even", 16, 1] ]); }); it("Should reorganize elements by a new key", () => { - const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); - const results = reduced.map((key, value, index) => index % 2 === 0 ? value : -value) - .reorganizeBy((key, value) => value >= 0 ? "+" : "-"); + const results = reduced.reorganizeBy((key, value) => value >= 0 ? "+" : "-"); - expect(results.toObject()).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); + expect(results.toObject()).toEqual({ "-": [-2], "+": [16] }); }); it("Should return all keys", () => { - const reduced = new SmartIterator([-3, Symbol(), "A", { }, null, [1, 2, 3], false]) - .groupBy((value) => typeof value); + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const keys = reduced.keys(); - expect(keys.toArray()).toEqual(["number", "symbol", "string", "object", "boolean"]); + expect(keys.toArray()).toEqual(["odd", "even"]); }); it("Should return all entries", () => { - const reduced = new SmartIterator([-3, 0, 2, -1, 3]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const entries = reduced.entries(); expect(entries.toArray()).toEqual([ - ["odd", -3], - ["even", 0], - ["even", 2], - ["odd", -1], - ["odd", 3] + ["odd", 4], + ["even", 16] ]); }); it("Should return all values", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); const values = reduced.values(); - expect(values.toArray()).toEqual([-3, -1, 0, 2, 3, 5, 6, 8]); + expect(values.toArray()).toEqual([4, 16]); }); it("Should materialize the iterator into an array", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); - expect(reduced.toArray()).toEqual([[-3, -1, 3, 5], [0, 2, 6, 8]]); + expect(reduced.toArray()).toEqual([4, 16]); }); it("Should materialize the iterator into a map", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); expect(reduced.toMap()).toEqual(new Map([ - ["odd", [-3, -1, 3, 5]], - ["even", [0, 2, 6, 8]] + ["odd", 4], + ["even", 16] ])); }); it("Should materialize the iterator into an object", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) - .groupBy((value) => value % 2 === 0 ? "even" : "odd"); + .groupBy((value) => value % 2 === 0 ? "even" : "odd") + .reduce((key, acc, value) => acc + value); - expect(reduced.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }); + expect(reduced.toObject()).toEqual({ odd: 4, even: 16 }); }); }); From 9d8b916ec792608e67aeeda24cfec7bb19c836ba Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Fri, 31 Jan 2025 22:40:38 +0100 Subject: [PATCH 21/22] =?UTF-8?q?fix:=20Polishing=20tests.=20=F0=9F=A7=BD?= =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aggregated-async-iterator.test.ts | 28 ++++++++------ .../aggregators/aggregated-iterator.test.ts | 37 +++++++++---------- .../aggregators/reduced-iterator.test.ts | 32 ++++++++-------- .../iterators/smart-async-iterator.test.ts | 26 ++++++------- tests/models/iterators/smart-iterator.test.ts | 28 +++++++------- 5 files changed, 75 insertions(+), 76 deletions(-) diff --git a/tests/models/aggregators/aggregated-async-iterator.test.ts b/tests/models/aggregators/aggregated-async-iterator.test.ts index de369e0..fa3d11e 100644 --- a/tests/models/aggregators/aggregated-async-iterator.test.ts +++ b/tests/models/aggregators/aggregated-async-iterator.test.ts @@ -239,18 +239,18 @@ describe("AggregatedAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should find the first element that satisfies a condition", async () => + it("Should find the first element of each group that satisfies a condition", async () => { const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); let resolved = false; - aggregator.find(async (key, value) => value >= 0) + aggregator.find(async (key, value) => value > 5) .then((result) => { resolved = true; - expect(result.toObject()).toEqual({ odd: 3, even: 0 }); + expect(result.toObject()).toEqual({ odd: undefined, even: 6 }); }); await vi.advanceTimersByTimeAsync(700); @@ -260,7 +260,7 @@ describe("AggregatedAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should enumerate elements", async () => + it("Should enumerate the elements with their indices within each group", async () => { const aggregator = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") @@ -281,7 +281,7 @@ describe("AggregatedAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should remove duplicate elements", async () => + it("Should remove all duplicate elements within each group", async () => { const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") @@ -302,7 +302,7 @@ describe("AggregatedAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should group elements by key and count them", async () => + it("Should count the number of elements within each group", async () => { const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); @@ -323,14 +323,16 @@ describe("AggregatedAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should iterate over the elements of the iterator", async () => + it("Should iterate over the elements", async () => { const results: [string, number, number][] = []; + const _iteratee = vi.fn((key: string, value: number, index: number) => { results.push([key, value, index]); }); + const aggregator = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd"); let resolved = false; - aggregator.forEach(async (key, value, index) => { results.push([key, value, index]); }) + aggregator.forEach(_iteratee) .then(() => { resolved = true; @@ -349,9 +351,11 @@ describe("AggregatedAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); + + expect(_iteratee).toHaveBeenCalledTimes(5); }); - it("Should reorganize elements by a new key", async () => + it("Should change the key of each element on which the iterator is aggregated", async () => { const aggregator = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") @@ -374,7 +378,7 @@ describe("AggregatedAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should return all keys of the iterator", async () => + it("Should return all keys", async () => { const keys = new SmartAsyncIterator(_toAsync([-3, Symbol(), "A", { }, null, [1, 2, 3], false])) .groupBy(async (value) => typeof value) @@ -395,7 +399,7 @@ describe("AggregatedAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should return all entries of the iterator", async () => + it("Should return all entries", async () => { const entries = new SmartAsyncIterator(_toAsync([-3, 0, 2, -1, 3])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") @@ -422,7 +426,7 @@ describe("AggregatedAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should return all values of the iterator", async () => + it("Should return all values", async () => { const values = new SmartAsyncIterator(_toAsync([-3, -1, 0, 2, 3, 5, 6, 8])) .groupBy(async (value) => value % 2 === 0 ? "even" : "odd") diff --git a/tests/models/aggregators/aggregated-iterator.test.ts b/tests/models/aggregators/aggregated-iterator.test.ts index 8485a53..1970e89 100644 --- a/tests/models/aggregators/aggregated-iterator.test.ts +++ b/tests/models/aggregators/aggregated-iterator.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { SmartIterator } from "../../../src/index.js"; @@ -21,7 +21,7 @@ describe("AggregatedIterator", () => expect(results.toObject()).toEqual({ odd: false, even: true }); }); - it("Should filter elements by a condition", () => + it("Should filter elements based on a condition", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); @@ -46,7 +46,7 @@ describe("AggregatedIterator", () => const results = aggregator.reduce((key, acc, value) => acc + value); expect(results.toObject()).toEqual({ odd: 4, even: 16 }); }); - it("Should reduce elements with an initial value", () => + it("Should reduce elements using a reducer function with an initial value", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); @@ -54,7 +54,7 @@ describe("AggregatedIterator", () => const results = aggregator.reduce((key, acc, value) => acc + value, 10); expect(results.toObject()).toEqual({ odd: 14, even: 26 }); }); - it("Should reduce elements with a function that returns an initial value", () => + it("Should reduce elements using a reducer function with a function that returns an initial value", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); @@ -98,11 +98,11 @@ describe("AggregatedIterator", () => const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - const results = aggregator.find((key, value) => value > 0); - expect(results.toObject()).toEqual({ odd: 3, even: 2 }); + const results = aggregator.find((key, value) => value > 5); + expect(results.toObject()).toEqual({ odd: undefined, even: 6 }); }); - it("Should enumerate the elements of the iterator", () => + it("Should enumerate the elements with their indices within each group", () => { const aggregator = new SmartIterator([-3, 0, 2, -1, 3]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); @@ -110,7 +110,7 @@ describe("AggregatedIterator", () => const results = aggregator.enumerate(); expect(results.toObject()).toEqual({ odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }); }); - it("Should remove all duplicate elements from within each group", () => + it("Should remove all duplicate elements within each group", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); @@ -127,21 +127,18 @@ describe("AggregatedIterator", () => expect(results.toObject()).toEqual({ odd: 4, even: 4 }); }); - it("Should iterate over the elements of the iterator", () => + it("Should iterate over all elements", () => { const results: [string, number, number][] = []; + const _iteratee = vi.fn((key: string, value: number, index: number) => { results.push([key, value, index]); }); + const iterator = new SmartIterator([-3, 0, 2, -1, 3]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); - iterator.forEach((key, value, index) => { results.push([key, value, index]); }); + iterator.forEach(_iteratee); - expect(results).toEqual([ - ["odd", -3, 0], - ["even", 0, 0], - ["even", 2, 1], - ["odd", -1, 1], - ["odd", 3, 2] - ]); + expect(results).toEqual([["odd", -3, 0], ["even", 0, 0], ["even", 2, 1], ["odd", -1, 1], ["odd", 3, 2]]); + expect(_iteratee).toHaveBeenCalledTimes(5); }); it("Should change the key of each element on which the iterator is aggregated", () => @@ -155,7 +152,7 @@ describe("AggregatedIterator", () => expect(results.toObject()).toEqual({ "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }); }); - it("Should return all keys of the iterator", () => + it("Should return all keys", () => { const aggregator = new SmartIterator([-3, Symbol(), "A", { }, null, [1, 2, 3], false]) .groupBy((value) => typeof value); @@ -163,7 +160,7 @@ describe("AggregatedIterator", () => const keys = aggregator.keys(); expect(keys.toArray()).toEqual(["number", "symbol", "string", "object", "boolean"]); }); - it("Should return all entries of the iterator", () => + it("Should return all entries", () => { const aggregator = new SmartIterator([-3, 0, 2, -1, 3]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); @@ -177,7 +174,7 @@ describe("AggregatedIterator", () => ["odd", 3] ]); }); - it("Should return all values of the iterator", () => + it("Should return all values", () => { const aggregator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd"); diff --git a/tests/models/aggregators/reduced-iterator.test.ts b/tests/models/aggregators/reduced-iterator.test.ts index e94e8d3..494793e 100644 --- a/tests/models/aggregators/reduced-iterator.test.ts +++ b/tests/models/aggregators/reduced-iterator.test.ts @@ -1,10 +1,10 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { SmartIterator } from "../../../src/index.js"; describe("ReducedIterator", () => { - it("Should return `true` if every value matches the predicate", () => + it("Should return `true` if every element matches the predicate", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -13,7 +13,7 @@ describe("ReducedIterator", () => const results = reduced.every((key, value) => value > 0); expect(results).toBe(true); }); - it("Should return `false` if not every value matches the predicate", () => + it("Should return `false` if not every element matches the predicate", () => { const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -23,7 +23,7 @@ describe("ReducedIterator", () => expect(results).toBe(false); }); - it("Should return `true` if some values match the predicate", () => + it("Should return `true` if some elements match the predicate", () => { const reduced = new SmartIterator([-3, -1, 0, 2, -3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -32,7 +32,7 @@ describe("ReducedIterator", () => const results = reduced.some((key, value) => value > 0); expect(results).toBe(true); }); - it("Should return `false` if no values match the predicate", () => + it("Should return `false` if no elements match the predicate", () => { const reduced = new SmartIterator([-3, -1, 0, -2, -3, -5, 6, -8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -70,7 +70,7 @@ describe("ReducedIterator", () => const results = reduced.reduce((key, acc, value) => acc + value); expect(results).toBe(20); }); - it("Should reduce elements with an initial value", () => + it("Should reduce elements using a reducer function with an initial value", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -100,7 +100,7 @@ describe("ReducedIterator", () => expect(results.toObject()).toEqual({ odd: [-3, -1, 3, 5], even: [16] }); }); - it("Should drop a given number of elements", () => + it("Should drop a specified number of elements", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -109,7 +109,7 @@ describe("ReducedIterator", () => const results = reduced.drop(1); expect(results.toObject()).toEqual({ even: 16 }); }); - it("Should take a given number of elements", () => + it("Should take a specified number of elements", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -128,7 +128,7 @@ describe("ReducedIterator", () => const results = reduced.find((key, value) => value > 0); expect(results).toBe(16); }); - it("Should return `undefined` when no matching value is found", () => + it("Should return `undefined` when no matching element is found", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -138,7 +138,7 @@ describe("ReducedIterator", () => expect(results).toBeUndefined(); }); - it("Should enumerate elements", () => + it("Should enumerate elements with their indices", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") @@ -169,16 +169,16 @@ describe("ReducedIterator", () => it("Should iterate over all elements", () => { const results: [string, number, number][] = []; + const _iteratee = vi.fn((key: string, value: number, index: number) => { results.push([key, value, index]); }); + const iterator = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) .groupBy((value) => value % 2 === 0 ? "even" : "odd") .reduce((key, acc, value) => acc + value); - iterator.forEach((key, value, index) => { results.push([key, value, index]); }); + iterator.forEach(_iteratee); - expect(results).toEqual([ - ["odd", 4, 0], - ["even", 16, 1] - ]); + expect(results).toEqual([["odd", 4, 0], ["even", 16, 1]]); + expect(_iteratee).toHaveBeenCalledTimes(2); }); it("Should reorganize elements by a new key", () => @@ -231,7 +231,6 @@ describe("ReducedIterator", () => expect(reduced.toArray()).toEqual([4, 16]); }); - it("Should materialize the iterator into a map", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) @@ -243,7 +242,6 @@ describe("ReducedIterator", () => ["even", 16] ])); }); - it("Should materialize the iterator into an object", () => { const reduced = new SmartIterator([-3, -1, 0, 2, 3, 5, 6, 8]) diff --git a/tests/models/iterators/smart-async-iterator.test.ts b/tests/models/iterators/smart-async-iterator.test.ts index 6e216f2..f598410 100644 --- a/tests/models/iterators/smart-async-iterator.test.ts +++ b/tests/models/iterators/smart-async-iterator.test.ts @@ -177,7 +177,7 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should filter values correctly", async () => + it("Should filter values based on a condition", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.filter(async (value) => value % 2 === 0); @@ -197,7 +197,7 @@ describe("SmartAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should map values correctly", async () => + it("Should map values using a transformation function", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.map(async (value) => value * 2); @@ -218,7 +218,7 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should reduce values correctly", async () => + it("Should reduce values using a reducer function", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); @@ -237,7 +237,7 @@ describe("SmartAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should reduce values with initial value correctly", async () => + it("Should reduce values using a reducer function with initial value", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); @@ -271,7 +271,7 @@ describe("SmartAsyncIterator", () => } }); - it("Should flatten elements with `flatMap`", async () => + it("Should flatten values using a transformation function", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, [2, 3], 4, 5, [6, 7, 8]])); const results = iterator.flatMap(async (value) => value); @@ -292,7 +292,7 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should drop the specified number of elements", async () => + it("Should drop the specified number of values", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.drop(3); @@ -312,7 +312,7 @@ describe("SmartAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should take the specified number of elements", async () => + it("Should take the specified number of values", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.take(3); @@ -333,7 +333,7 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should find the first matching value", async () => + it("Should find the first value that satisfies a condition", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); @@ -372,7 +372,7 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should enumerate elements with their indices", async () => + it("Should enumerate values with their indices", async () => { const iterator = new SmartAsyncIterator(_toAsync(["A", "B", "C"])); const results = iterator.enumerate(); @@ -392,7 +392,7 @@ describe("SmartAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should remove duplicate elements", async () => + it("Should remove duplicate values", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 2, 1, 3, 1, 4, 3, 4, 5, 5])); const results = iterator.unique(); @@ -412,7 +412,7 @@ describe("SmartAsyncIterator", () => await vi.advanceTimersByTimeAsync(100); expect(resolved).toBe(true); }); - it("Should count the number of elements", async () => + it("Should count the number of values", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5])); const results = iterator.count(); @@ -432,7 +432,7 @@ describe("SmartAsyncIterator", () => expect(resolved).toBe(true); }); - it("Should iterate over elements with `forEach`", async () => + it("Should iterate over all values", async () => { const results: number[] = []; const _iteratee = vi.fn(async (x: MaybePromise) => { results.push(await x); }); @@ -501,7 +501,7 @@ describe("SmartAsyncIterator", () => } }); - it("Should group elements by key", async () => + it("Should group values by key", async () => { const iterator = new SmartAsyncIterator(_toAsync([1, 2, 3, 4, 5, 6])); const results = iterator.groupBy(async (value) => (value % 2 === 0 ? "even" : "odd")); diff --git a/tests/models/iterators/smart-iterator.test.ts b/tests/models/iterators/smart-iterator.test.ts index 9f73b83..bfeecf7 100644 --- a/tests/models/iterators/smart-iterator.test.ts +++ b/tests/models/iterators/smart-iterator.test.ts @@ -76,14 +76,14 @@ describe("SmartIterator", () => expect(results).toBe(false); }); - it("Should filter values correctly", () => + it("Should filter values based on a condition", () => { const iterator = new SmartIterator([1, 2, 3, 4]); const results = iterator.filter((x) => x % 2 === 0); expect(results.toArray()).toEqual([2, 4]); }); - it("Should map values correctly", () => + it("Should map values using a transformation function", () => { const iterator = new SmartIterator([1, 2, 3]); @@ -91,14 +91,14 @@ describe("SmartIterator", () => expect(results.toArray()).toEqual([2, 4, 6]); }); - it("Should reduce values correctly", () => + it("Should reduce values using a reducer function", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); const results = iterator.reduce((acc, value) => acc + value); expect(results).toBe(15); }); - it("Should reduce values with initial value correctly", () => + it("Should reduce values using a reducer function with initial value", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); @@ -113,7 +113,7 @@ describe("SmartIterator", () => expect(() => iterator.reduce((acc, value) => acc + value)).toThrow(ValueException); }); - it("Should flatten elements with `flatMap`", () => + it("Should flatten values using a transformation function", () => { const iterator = new SmartIterator([1, [2, 3], 4, 5, [6, 7, 8]]); @@ -121,14 +121,14 @@ describe("SmartIterator", () => expect(results.toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); }); - it("Should drop the specified number of elements", () => + it("Should drop the specified number of values", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); const results = iterator.drop(3); expect(results.toArray()).toEqual([4, 5]); }); - it("Should take the specified number of elements", () => + it("Should take the specified number of values", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); @@ -136,7 +136,7 @@ describe("SmartIterator", () => expect(results.toArray()).toEqual([1, 2, 3]); }); - it("Should find the first matching value", () => + it("Should find the first value that satisfies a condition", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); @@ -151,21 +151,21 @@ describe("SmartIterator", () => expect(results).toBeUndefined(); }); - it("Should enumerate elements with their indices", () => + it("Should enumerate values with their indices", () => { const iterator = new SmartIterator(["A", "B", "C"]); const results = iterator.enumerate(); expect(results.toArray()).toEqual([[0, "A"], [1, "B"], [2, "C"]]); }); - it("Should remove duplicate elements", () => + it("Should remove duplicate values", () => { const iterator = new SmartIterator([1, 2, 2, 1, 3, 1, 4, 3, 4, 5, 5]); const results = iterator.unique(); expect(results.toArray()).toEqual([1, 2, 3, 4, 5]); }); - it("Should count the number of elements", () => + it("Should count the number of values", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5]); @@ -173,7 +173,7 @@ describe("SmartIterator", () => expect(results).toBe(5); }); - it("Should iterate over elements with `forEach`", () => + it("Should iterate over all values", () => { const results: number[] = []; const _iteratee = vi.fn((x: number) => { results.push(x); }); @@ -182,7 +182,7 @@ describe("SmartIterator", () => iterator.forEach(_iteratee); expect(results).toEqual([1, 2, 3]); - expect(_iteratee).toBeCalledTimes(3); + expect(_iteratee).toHaveBeenCalledTimes(3); }); it("Should handle return method correctly", () => @@ -215,7 +215,7 @@ describe("SmartIterator", () => expect(() => iterator.throw(reason)).toThrow(reason); }); - it("Should group elements by key", () => + it("Should group values by key", () => { const iterator = new SmartIterator([1, 2, 3, 4, 5, 6]); From c71c34a1b4780c535ce06489eb05f22f2056c8eb Mon Sep 17 00:00:00 2001 From: Matteo Bilotta Date: Fri, 31 Jan 2025 22:42:44 +0100 Subject: [PATCH 22/22] upd: Updated CI. + Updated dependencies. --- .github/workflows/template-build.yml | 6 +- .github/workflows/template-publish.yml | 4 +- eslint.config.mjs | 10 ++-- package.json | 4 +- pnpm-lock.yaml | 78 +++++++++++++------------- 5 files changed, 52 insertions(+), 50 deletions(-) diff --git a/.github/workflows/template-build.yml b/.github/workflows/template-build.yml index 34e5ed8..01518d0 100644 --- a/.github/workflows/template-build.yml +++ b/.github/workflows/template-build.yml @@ -14,11 +14,11 @@ jobs: with: version: latest - - name: Configuring Node.js 20 (LTS) + - name: Configuring Node.js 22 (LTS) uses: actions/setup-node@v4 with: cache: pnpm - node-version: 20 + node-version: 22 - name: Installing dependencies run: pnpm run ci @@ -29,6 +29,8 @@ jobs: - name: Checking type consistency run: pnpm run typecheck + - name: Testing the source code + run: pnpm run test - name: Building the source code run: pnpm build - name: Creating the artifact diff --git a/.github/workflows/template-publish.yml b/.github/workflows/template-publish.yml index 405580e..25b9010 100644 --- a/.github/workflows/template-publish.yml +++ b/.github/workflows/template-publish.yml @@ -25,10 +25,10 @@ jobs: with: name: byloth-core - - name: Configuring Node.js 20 (LTS) + - name: Configuring Node.js 22 (LTS) uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: ${{ inputs.registry-url }} - if: ${{ github.event.release.prerelease == true }} diff --git a/eslint.config.mjs b/eslint.config.mjs index fbb46c4..c454a1c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,9 +9,9 @@ const __dirname = path.dirname(__filename); const gitignorePath = path.resolve(__dirname, ".gitignore"); export default [includeIgnoreFile(gitignorePath), ...eslintTs, { - rules: { - "no-trailing-spaces": ["error", { "ignoreComments": true }], - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/unified-signatures": "off" - } + rules: { + "no-trailing-spaces": ["error", { "ignoreComments": true }], + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/unified-signatures": "off" + } }]; diff --git a/package.json b/package.json index 721de54..46db6dd 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,8 @@ }, "devDependencies": { "@byloth/eslint-config-typescript": "^3.1.0", - "@eslint/compat": "^1.2.5", - "@types/node": "^22.12.0", + "@eslint/compat": "^1.2.6", + "@types/node": "^22.13.0", "husky": "^9.1.7", "jsdom": "^26.0.0", "typescript": "^5.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03356ae..3bcf687 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,11 +12,11 @@ importers: specifier: ^3.1.0 version: 3.1.0(eslint@9.19.0)(typescript@5.7.3) '@eslint/compat': - specifier: ^1.2.5 - version: 1.2.5(eslint@9.19.0) + specifier: ^1.2.6 + version: 1.2.6(eslint@9.19.0) '@types/node': - specifier: ^22.12.0 - version: 22.12.0 + specifier: ^22.13.0 + version: 22.13.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -28,10 +28,10 @@ importers: version: 5.7.3 vite: specifier: ^6.0.11 - version: 6.0.11(@types/node@22.12.0) + version: 6.0.11(@types/node@22.13.0) vitest: specifier: ^3.0.4 - version: 3.0.4(@types/node@22.12.0)(jsdom@26.0.0) + version: 3.0.4(@types/node@22.13.0)(jsdom@26.0.0) packages: @@ -232,8 +232,8 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.2.5': - resolution: {integrity: sha512-5iuG/StT+7OfvhoBHPlmxkPA9om6aDUFgmD4+mWKAGsYt4vCe8rypneG03AuseyRHBmcCLXQtIH5S26tIoggLg==} + '@eslint/compat@1.2.6': + resolution: {integrity: sha512-k7HNCqApoDHM6XzT30zGoETj+D+uUcZUb+IVAJmar3u6bvHf7hhHJcWx09QHj4/a2qrKZMWU0E16tvkiAdv06Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9.10.0 @@ -241,8 +241,8 @@ packages: eslint: optional: true - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.10.0': @@ -257,8 +257,8 @@ packages: resolution: {integrity: sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/plugin-kit@0.2.5': @@ -401,8 +401,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.12.0': - resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==} + '@types/node@22.13.0': + resolution: {integrity: sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==} '@typescript-eslint/eslint-plugin@8.22.0': resolution: {integrity: sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==} @@ -995,11 +995,11 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tldts-core@6.1.75: - resolution: {integrity: sha512-AOvV5YYIAFFBfransBzSTyztkc3IMfz5Eq3YluaRiEu55nn43Fzaufx70UqEKYr8BoLCach4q8g/bg6e5+/aFw==} + tldts-core@6.1.76: + resolution: {integrity: sha512-uzhJ02RaMzgQR3yPoeE65DrcHI6LoM4saUqXOt/b5hmb3+mc4YWpdSeAQqVqRUlQ14q8ZuLRWyBR1ictK1dzzg==} - tldts@6.1.75: - resolution: {integrity: sha512-+lFzEXhpl7JXgWYaXcB6DqTYXbUArvrWAE/5ioq/X3CdWLbDjpPP4XTrQBmEJ91y3xbe4Fkw7Lxv4P3GWeJaNg==} + tldts@6.1.76: + resolution: {integrity: sha512-6U2ti64/nppsDxQs9hw8ephA3nO6nSQvVVfxwRw8wLQPFtLI1cFI1a1eP22g+LUP+1TA2pKKjUTwWB+K2coqmQ==} hasBin: true to-regex-range@5.0.1: @@ -1297,13 +1297,13 @@ snapshots: '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.5(eslint@9.19.0)': + '@eslint/compat@1.2.6(eslint@9.19.0)': optionalDependencies: eslint: 9.19.0 - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.19.2': dependencies: - '@eslint/object-schema': 2.1.5 + '@eslint/object-schema': 2.1.6 debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: @@ -1329,7 +1329,7 @@ snapshots: '@eslint/js@9.19.0': {} - '@eslint/object-schema@2.1.5': {} + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.2.5': dependencies: @@ -1424,7 +1424,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@22.12.0': + '@types/node@22.13.0': dependencies: undici-types: 6.20.0 @@ -1512,13 +1512,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.4(vite@6.0.11(@types/node@22.12.0))': + '@vitest/mocker@3.0.4(vite@6.0.11(@types/node@22.13.0))': dependencies: '@vitest/spy': 3.0.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.0.11(@types/node@22.12.0) + vite: 6.0.11(@types/node@22.13.0) '@vitest/pretty-format@3.0.4': dependencies: @@ -1691,7 +1691,7 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 + '@eslint/config-array': 0.19.2 '@eslint/core': 0.10.0 '@eslint/eslintrc': 3.2.0 '@eslint/js': 9.19.0 @@ -2072,11 +2072,11 @@ snapshots: tinyspy@3.0.2: {} - tldts-core@6.1.75: {} + tldts-core@6.1.76: {} - tldts@6.1.75: + tldts@6.1.76: dependencies: - tldts-core: 6.1.75 + tldts-core: 6.1.76 to-regex-range@5.0.1: dependencies: @@ -2084,7 +2084,7 @@ snapshots: tough-cookie@5.1.0: dependencies: - tldts: 6.1.75 + tldts: 6.1.76 tr46@5.0.0: dependencies: @@ -2106,13 +2106,13 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@3.0.4(@types/node@22.12.0): + vite-node@3.0.4(@types/node@22.13.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.2 - vite: 6.0.11(@types/node@22.12.0) + vite: 6.0.11(@types/node@22.13.0) transitivePeerDependencies: - '@types/node' - jiti @@ -2127,19 +2127,19 @@ snapshots: - tsx - yaml - vite@6.0.11(@types/node@22.12.0): + vite@6.0.11(@types/node@22.13.0): dependencies: esbuild: 0.24.2 postcss: 8.5.1 rollup: 4.32.1 optionalDependencies: - '@types/node': 22.12.0 + '@types/node': 22.13.0 fsevents: 2.3.3 - vitest@3.0.4(@types/node@22.12.0)(jsdom@26.0.0): + vitest@3.0.4(@types/node@22.13.0)(jsdom@26.0.0): dependencies: '@vitest/expect': 3.0.4 - '@vitest/mocker': 3.0.4(vite@6.0.11(@types/node@22.12.0)) + '@vitest/mocker': 3.0.4(vite@6.0.11(@types/node@22.13.0)) '@vitest/pretty-format': 3.0.4 '@vitest/runner': 3.0.4 '@vitest/snapshot': 3.0.4 @@ -2155,11 +2155,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.0.11(@types/node@22.12.0) - vite-node: 3.0.4(@types/node@22.12.0) + vite: 6.0.11(@types/node@22.13.0) + vite-node: 3.0.4(@types/node@22.13.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.12.0 + '@types/node': 22.13.0 jsdom: 26.0.0 transitivePeerDependencies: - jiti