From b1f0f7e703a72e5830437b3e8dfa8ebc6d1f8b21 Mon Sep 17 00:00:00 2001 From: Hugh Grigg Date: Wed, 8 Jul 2026 12:27:59 +0100 Subject: [PATCH] feat: array-min-length calling scope type incorporation --- src/assert/array-length/array-length.type.ts | 4 -- .../array-min-length/array-min-length.test.ts | 69 +++++++++++++++++++ .../array-min-length/array-min-length.type.ts | 11 ++- 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/src/assert/array-length/array-length.type.ts b/src/assert/array-length/array-length.type.ts index e223157..2369c83 100644 --- a/src/assert/array-length/array-length.type.ts +++ b/src/assert/array-length/array-length.type.ts @@ -44,10 +44,6 @@ export type ArrayOfLength = N extends 0 length: N; }; -export type ReadableArrayOfLength = Readonly< - ArrayOfLength ->; - export type ReadonlyArrayOfLength = Readonly< ArrayOfLength >; diff --git a/src/assert/array-min-length/array-min-length.test.ts b/src/assert/array-min-length/array-min-length.test.ts index e9302fa..be94ff6 100644 --- a/src/assert/array-min-length/array-min-length.test.ts +++ b/src/assert/array-min-length/array-min-length.test.ts @@ -173,6 +173,40 @@ describe("array-min-length", () => { expect(output.HostedZones[0].Name).toBe("a.example.com."); }); + it("does not narrow readonly array elements to never", () => { + interface ReadonlyItem { + readonly id: string; + readonly enabled: boolean; + } + + interface ReadonlyContainer { + readonly items: readonly ReadonlyItem[]; + } + + const container: ReadonlyContainer = { + items: [ + { id: "first", enabled: true }, + { id: "second", enabled: false }, + ], + }; + + assertArrayMinLength(container.items, 2); + + expectTypeOf(container.items).toEqualTypeOf< + readonly ReadonlyItem[] & + readonly [ReadonlyItem, ReadonlyItem, ...ReadonlyItem[]] + >(); + expectTypeOf(container.items[0]).toEqualTypeOf(); + expectTypeOf(container.items[1]).toEqualTypeOf(); + expectTypeOf(container.items[1].id).toEqualTypeOf(); + expectTypeOf(container.items[0].enabled).toEqualTypeOf(); + expectTypeOf(container.items[1].enabled).toEqualTypeOf(); + + expect(container.items[1].id).toBe("second"); + expect(container.items[0].enabled).toBe(true); + expect(container.items[1].enabled).toBe(false); + }); + it("narrows unknown values to an array with at least the expected length", () => { const value: unknown = ["foo", "bar"]; @@ -213,6 +247,41 @@ describe("array-min-length", () => { expect(["string", "undefined"]).toContain(typeof foo.bar.foobar[2]); }); + it("does not narrow readonly array elements to never when used as a matcher", () => { + interface ReadonlyItem { + readonly id: string; + readonly enabled: boolean; + } + + interface ReadonlyContainer { + readonly items: readonly ReadonlyItem[]; + } + + const container: ReadonlyContainer = { + items: [ + { id: "first", enabled: true }, + { id: "second", enabled: false }, + ], + }; + + assertObjectMatches(container, { + items: arrayOfMinLength(2), + }); + + expectTypeOf(container.items).toEqualTypeOf< + readonly [ReadonlyItem, ReadonlyItem, ...ReadonlyItem[]] + >(); + expectTypeOf(container.items[0]).toEqualTypeOf(); + expectTypeOf(container.items[1]).toEqualTypeOf(); + expectTypeOf(container.items[1].id).toEqualTypeOf(); + expectTypeOf(container.items[0].enabled).toEqualTypeOf(); + expectTypeOf(container.items[1].enabled).toEqualTypeOf(); + + expect(container.items[1].id).toBe("second"); + expect(container.items[0].enabled).toBe(true); + expect(container.items[1].enabled).toBe(false); + }); + it("matches arrays with exactly the minimum length", () => { const matcher = arrayOfMinLength(3); diff --git a/src/assert/array-min-length/array-min-length.type.ts b/src/assert/array-min-length/array-min-length.type.ts index 26bb1d9..cc9266e 100644 --- a/src/assert/array-min-length/array-min-length.type.ts +++ b/src/assert/array-min-length/array-min-length.type.ts @@ -39,12 +39,11 @@ export type ReadonlyArrayOfMinLength = Readonly< >; type IsKnownReadonlyArray = - Extract, readonly unknown[]> extends Extract< - NonNullable, - unknown[] - > - ? false - : true; + NonNullable extends readonly unknown[] + ? NonNullable extends unknown[] + ? false + : true + : false; type ArrayOfMinLengthMatchForMutability = IsKnownReadonlyArray extends true