Skip to content

Commit

Permalink
Merge pull request #119 from typed-ember/ember-loose-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
dfreeman authored Mar 31, 2021
2 parents fe3c140 + c7cc5c2 commit a7a8218
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 5 deletions.
3 changes: 2 additions & 1 deletion packages/environment-ember-loose/-private/dsl/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as VM from '@glint/template/-private/keywords';
import { ActionKeyword } from '../intrinsics/action';
import { ComponentKeyword } from '../intrinsics/component';
import { ConcatHelper } from '../intrinsics/concat';
import { EachKeyword } from '../intrinsics/each';
import { EachInKeyword } from '../intrinsics/each-in';
import { FnHelper } from '../intrinsics/fn';
import { GetHelper } from '../intrinsics/get';
Expand Down Expand Up @@ -59,7 +60,7 @@ interface Keywords {
[the API documentation]: https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/each?anchor=each
*/
each: VM.EachKeyword;
each: EachKeyword;

/**
The `{{each-in}}` helper loops over properties on an object.
Expand Down
11 changes: 11 additions & 0 deletions packages/environment-ember-loose/-private/intrinsics/each.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AcceptsBlocks, DirectInvokable } from '@glint/template/-private/integration';
import EmberArray from '@ember/array';

type ArrayLike<T> = ReadonlyArray<T> | Iterable<T> | EmberArray<T>;

export type EachKeyword = DirectInvokable<{
<T>(args: { key?: string }, items: ArrayLike<T>): AcceptsBlocks<{
default: [T, number];
inverse?: [];
}>;
}>;
2 changes: 2 additions & 0 deletions packages/environment-ember-loose/-private/intrinsics/get.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { DirectInvokable, EmptyObject } from '@glint/template/-private/integration';
import ObjectProxy from '@ember/object/proxy';

export type GetHelper = DirectInvokable<{
<T, K extends keyof T>(args: EmptyObject, obj: T, key: K): T[K];
<T extends object, K extends keyof T>(args: EmptyObject, obj: ObjectProxy<T>, key: K): T[K];
(args: EmptyObject, obj: unknown, key: string): unknown;
}>;
15 changes: 15 additions & 0 deletions packages/environment-ember-loose/__tests__/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,18 @@ import { EmptyObject } from '@glint/template/-private/integration';
expectTypeOf(repeat({ value: 'hi' })).toEqualTypeOf<Array<string>>();
expectTypeOf(repeat({ value: 123, count: 3 })).toEqualTypeOf<Array<number>>();
}

// Class-based helpers can return undefined
{
class MaybeStringHelper extends Helper<{ Return: string | undefined }> {
compute(): string | undefined {
if (Math.random() > 0.5) {
return 'ok';
}
}
}

let maybeString = resolve(MaybeStringHelper);

expectTypeOf(maybeString).toEqualTypeOf<(args: EmptyObject) => string | undefined>();
}
54 changes: 54 additions & 0 deletions packages/environment-ember-loose/__tests__/intrinsics/each.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve, invokeBlock } from '@glint/environment-ember-loose/-private/dsl';
import ArrayProxy from '@ember/array/proxy';

let each = resolve(Globals['each']);

// Yield out array values and indices

invokeBlock(each({}, ['a', 'b', 'c']), {
default(value, index) {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(index).toEqualTypeOf<number>();
},
inverse(...args) {
expectTypeOf(args).toEqualTypeOf<[]>();
},
});

// Works for Ember arrays

declare const proxiedArray: ArrayProxy<string>;

invokeBlock(each({}, proxiedArray), {
default(value, index) {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(index).toEqualTypeOf<number>();
},
});

// Works for other iterables

invokeBlock(each({}, new Map<string, symbol>()), {
default([key, value], index) {
expectTypeOf(key).toEqualTypeOf<string>();
expectTypeOf(value).toEqualTypeOf<symbol>();
expectTypeOf(index).toEqualTypeOf<number>();
},
});

// Works for `readonly` arrays

invokeBlock(each({}, ['a', 'b', 'c'] as readonly string[]), {
default(value, index) {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(index).toEqualTypeOf<number>();
},
});

// Accept a `key` string
invokeBlock(each({ key: 'id' }, [{ id: 1 }]), {
default() {
// Don't yield
},
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/-private/dsl';
import ObjectProxy from '@ember/object/proxy';

let get = resolve(Globals['get']);

Expand All @@ -17,3 +18,10 @@ get(
{},
'hi'
);

// Getting a value off an ObjectProxy
declare const proxiedObject: ObjectProxy<{ name: string }>;

expectTypeOf(get({}, proxiedObject, 'content')).toEqualTypeOf<{ name: string }>();
expectTypeOf(get({}, proxiedObject, 'name')).toEqualTypeOf<string>();
expectTypeOf(get({}, proxiedObject, 'unknownKey')).toEqualTypeOf<unknown>();
6 changes: 2 additions & 4 deletions packages/environment-ember-loose/ember-component/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ type EmberHelperConstructor = typeof import('@ember/component/helper').default;
const EmberHelper = Ember.Helper;
const emberHelper = Ember.Helper.helper;

type Get<T, Key, Otherwise = EmptyObject> = Key extends keyof T
? Exclude<T[Key], undefined>
: Otherwise;
type Get<T, Key, Otherwise = EmptyObject> = Key extends keyof T ? T[Key] : Otherwise;

type HelperFactory = <Positional extends unknown[] = [], Named = EmptyObject, Return = unknown>(
fn: (params: Positional, hash: Named) => Return
Expand Down Expand Up @@ -39,7 +37,7 @@ interface Helper<T extends HelperSignature> extends Omit<EmberHelper, 'compute'>

[Invoke]: (
named: Get<T, 'NamedArgs'>,
...positional: Get<T, 'PositionalArgs', []>
...positional: Exclude<Get<T, 'PositionalArgs', []>, undefined>
) => Get<T, 'Return'>;
}

Expand Down

0 comments on commit a7a8218

Please sign in to comment.