diff --git a/packages/environment-ember-loose/-private/intrinsics/each-in.d.ts b/packages/environment-ember-loose/-private/intrinsics/each-in.d.ts index d4261073e..97736783b 100644 --- a/packages/environment-ember-loose/-private/intrinsics/each-in.d.ts +++ b/packages/environment-ember-loose/-private/intrinsics/each-in.d.ts @@ -7,13 +7,18 @@ export type EachInKeyword = abstract new () => InstanceType< Named: { key?: string }; }; Blocks: { - default: [key: EachInKey, value: Exclude[EachInKey]]; + default: EachInIteratorPair; else?: []; }; }> >; +type EachInIteratorPair = + T extends Iterable<[infer K, infer V]> + ? [key: K, value: V] + : [key: EachInKey, value: Exclude[EachInKey]]; + // `{{each-in}}` internally uses `Object.keys`, so only string keys are included // TS, on the other hand, gives a wider result for `keyof` than many users expect // for record types: https://github.com/microsoft/TypeScript/issues/29249 -type EachInKey = keyof Exclude & string; +type EachInKey = Extract, string>; diff --git a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/each-in.test.ts b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/each-in.test.ts index dcc0a26b9..2bb743946 100644 --- a/packages/environment-ember-loose/__tests__/type-tests/intrinsics/each-in.test.ts +++ b/packages/environment-ember-loose/__tests__/type-tests/intrinsics/each-in.test.ts @@ -53,6 +53,25 @@ declare const maybeVal: { a: number; b: number } | undefined; } } +// Can render maybe undefined (map) + +declare const maybeMapVal: Map | undefined; + +{ + const component = emitComponent(eachIn(maybeMapVal)); + + { + const [key, value] = component.blockParams.default; + expectTypeOf(key).toEqualTypeOf(); + expectTypeOf(value).toEqualTypeOf(); + } + + { + const [...args] = component.blockParams.else; + expectTypeOf(args).toEqualTypeOf<[]>(); + } +} + // Can render else when undefined, null, or empty. { @@ -113,3 +132,37 @@ declare const maybeVal: { a: number; b: number } | undefined; expectTypeOf(value).toEqualTypeOf(); } } + +// Accepts a Map +{ + const component = emitComponent( + eachIn( + new Map([ + ['a', 5], + ['b', 4], + ]), + { key: 'id', ...NamedArgsMarker }, + ), + ); + { + const [key, value] = component.blockParams.default; + expectTypeOf(key).toEqualTypeOf(); + expectTypeOf(value).toEqualTypeOf(); + } +} + +// Accepts a custom iterable +{ + class CustomMap implements Iterable<[Set, bigint]> { + [Symbol.iterator](): Iterator<[Set, bigint]> { + throw new Error(); + } + } + + const component = emitComponent(eachIn(new CustomMap(), { key: 'id', ...NamedArgsMarker })); + { + const [key, value] = component.blockParams.default; + expectTypeOf(key).toEqualTypeOf>(); + expectTypeOf(value).toEqualTypeOf(); + } +}