Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/assert/array-length/array-length.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ export type ArrayOfLength<T, N extends number> = N extends 0
length: N;
};

export type ReadableArrayOfLength<T, N extends number> = Readonly<
ArrayOfLength<T, N>
>;

export type ReadonlyArrayOfLength<T, N extends number> = Readonly<
ArrayOfLength<T, N>
>;
Expand Down
69 changes: 69 additions & 0 deletions src/assert/array-min-length/array-min-length.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReadonlyItem>();
expectTypeOf(container.items[1]).toEqualTypeOf<ReadonlyItem>();
expectTypeOf(container.items[1].id).toEqualTypeOf<string>();
expectTypeOf(container.items[0].enabled).toEqualTypeOf<boolean>();
expectTypeOf(container.items[1].enabled).toEqualTypeOf<boolean>();

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"];

Expand Down Expand Up @@ -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<ReadonlyItem>();
expectTypeOf(container.items[1]).toEqualTypeOf<ReadonlyItem>();
expectTypeOf(container.items[1].id).toEqualTypeOf<string>();
expectTypeOf(container.items[0].enabled).toEqualTypeOf<boolean>();
expectTypeOf(container.items[1].enabled).toEqualTypeOf<boolean>();

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);

Expand Down
11 changes: 5 additions & 6 deletions src/assert/array-min-length/array-min-length.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,11 @@ export type ReadonlyArrayOfMinLength<T, N extends number> = Readonly<
>;

type IsKnownReadonlyArray<TActual> =
Extract<NonNullable<TActual>, readonly unknown[]> extends Extract<
NonNullable<TActual>,
unknown[]
>
? false
: true;
NonNullable<TActual> extends readonly unknown[]
? NonNullable<TActual> extends unknown[]
? false
: true
: false;

type ArrayOfMinLengthMatchForMutability<TActual, TElement, N extends number> =
IsKnownReadonlyArray<TActual> extends true
Expand Down