Skip to content

Type environment-ember-loose intrinsics #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 13, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { expectTypeOf } from 'expect-type';
import { resolve, invokeBlock } from '@glint/template';
import Component from '@glint/environment-ember-loose/ember-component';
import { ComponentKeyword } from '../../types/intrinsics/component';

const componentKeyword = resolve({} as ComponentKeyword<LocalRegistry>);

type LocalRegistry = {
string: typeof StringComponent;
parametric: typeof ParametricComponent;
};

class StringComponent extends Component<{
Args: { value: string };
Yields: { default?: [string] };
}> {}

const NoopCurriedStringComponent = componentKeyword({}, 'string');
const ValueCurriedStringComponent = componentKeyword({ value: 'hello' }, 'string');

// Invoking the noop-curried component
invokeBlock(resolve(NoopCurriedStringComponent)({ value: 'hello' }), {});

// @ts-expect-error: Invoking the curried component but forgetting `value`
resolve(NoopCurriedStringComponent)({});

// @ts-expect-error: Invoking the curried component with an invalid value
resolve(NoopCurriedStringComponent)({ value: 123 });

// Invoking the noop-curried component with a valid block
invokeBlock(resolve(NoopCurriedStringComponent)({ value: 'hello' }), {
default(...args) {
expectTypeOf(args).toEqualTypeOf<[string]>();
},
});

// Invoking the noop-curried component with an invalid block
invokeBlock(resolve(NoopCurriedStringComponent)({ value: 'hello' }), {
default() {
/* nothing */
},
// @ts-expect-error: invalid block name
asdf() {
/* nothing */
},
});

// Invoking the curried-with-value component with no value
invokeBlock(resolve(ValueCurriedStringComponent)({}), {});

// Invoking the curried-with-value component with a valid value
invokeBlock(resolve(ValueCurriedStringComponent)({ value: 'hi' }), {});

// @ts-expect-error: Invoking the curred-with-value component with an invalid value
invokeBlock(resolve(ValueCurriedStringComponent)({ value: 123 }), {});

// @ts-expect-error: Attempting to curry a nonexistent arg
componentKeyword({ foo: true }, StringComponent);

// @ts-expect-error: Attempting to curry an arg with the wrong type
componentKeyword({ value: 123 }, StringComponent);

class ParametricComponent<T> extends Component<{
Args: { values: Array<T>; optional?: string };
Yields: { default?: [T, number] };
}> {}

const NoopCurriedParametricComponent = componentKeyword({}, 'parametric');

// The only way to fix a type parameter as part of using the component keyword is to
// say ahead of time the type you're trying to bind it as.
const BoundParametricComponent = ParametricComponent as new () => ParametricComponent<string>;

const RequiredValueCurriedParametricComponent = componentKeyword(
{ values: ['hello'] },
BoundParametricComponent
);

const OptionalValueCurriedParametricComponent = componentKeyword(
{ optional: 'hi' },
componentKeyword({}, 'parametric')
);

// Invoking the noop-curried component with number values
invokeBlock(resolve(NoopCurriedParametricComponent)({ values: [1, 2, 3] }), {
default(value) {
expectTypeOf(value).toEqualTypeOf<number>();
},
});

// Invoking the noop-curried component with string values
invokeBlock(resolve(NoopCurriedParametricComponent)({ values: ['hello'] }), {
default(value) {
expectTypeOf(value).toEqualTypeOf<string>();
},
});

invokeBlock(
resolve(NoopCurriedParametricComponent)(
// @ts-expect-error: missing required arg `values`
{}
),
{}
);

invokeBlock(
resolve(NoopCurriedParametricComponent)(
// @ts-expect-error: wrong type for `values`
{ values: 'hello' }
),
{}
);

invokeBlock(
resolve(NoopCurriedParametricComponent)({
values: [1, 2, 3],
// @ts-expect-error: extra arg
extra: 'uh oh',
}),
{}
);

// Invoking the curred component with no additional args
invokeBlock(resolve(RequiredValueCurriedParametricComponent)({}), {
default(value) {
expectTypeOf(value).toEqualTypeOf<string>();
},
});

// Invoking the curred component and overriding the given arg
invokeBlock(resolve(RequiredValueCurriedParametricComponent)({ values: ['ok'] }), {
default(value) {
expectTypeOf(value).toEqualTypeOf<string>();
},
});

invokeBlock(
resolve(RequiredValueCurriedParametricComponent)({
// @ts-expect-error: wrong type for arg override
values: [1, 2, 3],
}),
{}
);

invokeBlock(
resolve(RequiredValueCurriedParametricComponent)({
// @ts-expect-error: extra arg
extra: 'bad',
}),
{}
);

// Invoking the curried component, supplying missing required args
invokeBlock(resolve(OptionalValueCurriedParametricComponent)({ values: [1, 2, 3] }), {
default(value) {
expectTypeOf(value).toEqualTypeOf<number>();
},
});

invokeBlock(
resolve(OptionalValueCurriedParametricComponent)(
// @ts-expect-error: missing required arg `values`
{}
),
{}
);

// {{component (component BoundParametricComponent values=(array "hello")) optional="hi"}}
const DoubleCurriedComponent = componentKeyword(
{ optional: 'hi' },
RequiredValueCurriedParametricComponent
);

// Invoking the component with no args
invokeBlock(resolve(DoubleCurriedComponent)({}), {
default(value) {
expectTypeOf(value).toEqualTypeOf<string>();
},
});

// Invoking the component overriding an arg correctly
invokeBlock(resolve(DoubleCurriedComponent)({ values: ['a', 'b'] }), {});

invokeBlock(
resolve(DoubleCurriedComponent)({
// @ts-expect-error: invalid arg override
values: [1, 2, 3],
}),
{}
);

invokeBlock(
resolve(DoubleCurriedComponent)({
// @ts-expect-error: unexpected args
foo: 'bar',
}),
{}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';

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

// Basic plumbing
expectTypeOf(concat({})).toEqualTypeOf<string>();
expectTypeOf(concat({}, 1, true, 'three')).toEqualTypeOf<string>();

// @ts-expect-error: invalid named arg
concat({ hello: 'hi' });
22 changes: 22 additions & 0 deletions packages/environment-ember-loose/__tests__/intrinsics/fn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';

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

// @ts-expect-error: extra named arg
fn({ foo: true }, () => true);

// @ts-expect-error: invalid arg
fn({}, (t: string) => t, 123);

expectTypeOf(fn({}, () => true)).toEqualTypeOf<() => boolean>();
expectTypeOf(fn({}, (arg: string) => arg.length)).toEqualTypeOf<(arg: string) => number>();
expectTypeOf(fn({}, (arg: string) => arg.length, 'hi')).toEqualTypeOf<() => number>();

let identity = <T>(x: T): T => x;

// Bound type parameters are reflected in the output
expectTypeOf(fn({}, identity, 'hi')).toEqualTypeOf<() => string>();

// Unbound type parameters survive to the output
expectTypeOf(fn({}, identity)).toEqualTypeOf<<T>(x: T) => T>();
19 changes: 19 additions & 0 deletions packages/environment-ember-loose/__tests__/intrinsics/get.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';

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

// Getting a known key
expectTypeOf(get({}, { foo: 'hello' }, 'foo')).toEqualTypeOf<string>();

// Getting an unknown key
expectTypeOf(get({}, { foo: 'hello' }, 'baz')).toEqualTypeOf<unknown>();

get(
{
// @ts-expect-error: invalid named arg
hello: 'hi',
},
{},
'hi'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';

let input = resolve(Globals['input']);
let Input = resolve(Globals['Input']);

// Both casings have the same signature
expectTypeOf(input).toEqualTypeOf(Input);

Input({}), {};
Input({ value: 'hello' });
Input({ type: 'string', value: 'hello' });
Input({ type: 'checkbox', checked: true });

// @ts-expect-error: `checked` only works with `@type=checkbox`
Input({ checked: true });

// @ts-expect-error: `checked` only works with `@type=checkbox`
Input({ type: 'text', checked: true });

// Event handlers
Input({
enter: (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
},
'insert-newline': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
},
'escape-press': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
},
'focus-in': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<FocusEvent>();
},
'focus-out': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<FocusEvent>();
},
'key-down': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
},
'key-press': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
},
'key-up': (value, event) => {
expectTypeOf(value).toEqualTypeOf<string>();
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';

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

// Basic plumbing
expectTypeOf(mount({}, 'engine-name')).toEqualTypeOf<void>();

// @ts-expect-error: missing engine name
mount({});

// @ts-expect-error: invalid named arg
mount({ hello: 'hi' }, 'engine-name');
18 changes: 18 additions & 0 deletions packages/environment-ember-loose/__tests__/intrinsics/mut.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';
import { Mut } from '../../types/intrinsics/mut';

let fn = resolve(Globals['fn']);
let mut = resolve(Globals['mut']);

// Basic plumbing
expectTypeOf(mut({}, 'hello')).toEqualTypeOf<Mut<string>>();

// `{{fn (mut this.value)}}` returns an updater
expectTypeOf(fn({}, mut({}, 'hello'))).toEqualTypeOf<(value: string) => void>();

// @ts-expect-error: missing value
mut({});

// @ts-expect-error: invalid named arg
mut({ hello: 'hi' }, 'hello');
29 changes: 29 additions & 0 deletions packages/environment-ember-loose/__tests__/intrinsics/on.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expectTypeOf } from 'expect-type';
import { Globals, invokeModifier, resolve } from '@glint/environment-ember-loose/types';

const on = resolve(Globals['on']);

// @ts-expect-error: extra named arg
on({ foo: 'bar' }, 'click', () => {});

// @ts-expect-error: missing positional arg
on({}, 'click');

// @ts-expect-error: extra positional arg
on({}, 'click', () => {}, 'hello');

on({ capture: true, once: true, passive: true }, 'scroll', () => {});

on({}, 'unknown', (event) => {
expectTypeOf(event).toEqualTypeOf<Event>();
});

on({}, 'click', (event) => {
expectTypeOf(event).toEqualTypeOf<MouseEvent>();
});

on({}, 'keyup', (event) => {
expectTypeOf(event).toEqualTypeOf<KeyboardEvent>();
});

expectTypeOf(invokeModifier(on({}, 'click', () => {}))).toEqualTypeOf<void>();
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expectTypeOf } from 'expect-type';
import { Globals, resolve } from '@glint/environment-ember-loose/types';

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

// Named outlet
expectTypeOf(outlet({}, 'outlet-name')).toEqualTypeOf<void>();

// Nameless main outlet
outlet({});

// @ts-expect-error: invalid named arg
outlet({ hello: 'hi' }, 'outlet-name');
Loading