From 8e3a20b9db78335fabcc6e572ce17002f1aef3ad Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Mon, 7 Feb 2022 15:40:11 -0800 Subject: [PATCH] Future public types for @ember/component --- .../-internals/glimmer/lib/component.ts | 4 +- .../@ember/-internals/glimmer/lib/helper.ts | 27 ++++++++---- .../-internals/glimmer/lib/utils/managers.ts | 6 +-- .../component/type-tests/capabilities.test.ts | 16 +++++++ .../type-tests/get-component-template.test.ts | 8 ++++ .../type-tests/helper/helper.test.ts | 18 ++++++++ .../component/type-tests/helper/index.test.ts | 42 +++++++++++++++++++ .../@ember/component/type-tests/index.test.ts | 38 +++++++++++++++++ .../type-tests/set-component-manager.test.ts | 17 ++++++++ .../type-tests/set-component-template.test.ts | 8 ++++ .../type-tests/template-only.test.ts | 11 +++++ 11 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 packages/@ember/component/type-tests/capabilities.test.ts create mode 100644 packages/@ember/component/type-tests/get-component-template.test.ts create mode 100644 packages/@ember/component/type-tests/helper/helper.test.ts create mode 100644 packages/@ember/component/type-tests/helper/index.test.ts create mode 100644 packages/@ember/component/type-tests/set-component-manager.test.ts create mode 100644 packages/@ember/component/type-tests/set-component-template.test.ts create mode 100644 packages/@ember/component/type-tests/template-only.test.ts diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index a19c5ccecf0..3c567cd7aa6 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -1,5 +1,5 @@ import { get, PROPERTY_DID_CHANGE } from '@ember/-internals/metal'; -import { getOwner } from '@ember/-internals/owner'; +import { getOwner, Owner } from '@ember/-internals/owner'; import { TargetActionSupport } from '@ember/-internals/runtime'; import { ActionSupport, @@ -13,7 +13,7 @@ import { } from '@ember/-internals/views'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { Environment, Owner, Template, TemplateFactory } from '@glimmer/interfaces'; +import { Environment, Template, TemplateFactory } from '@glimmer/interfaces'; import { setInternalComponentManager } from '@glimmer/manager'; import { isUpdatableRef, updateRef } from '@glimmer/reference'; import { normalizeProperty } from '@glimmer/runtime'; diff --git a/packages/@ember/-internals/glimmer/lib/helper.ts b/packages/@ember/-internals/glimmer/lib/helper.ts index 2370ffe92e8..e9932fac16d 100644 --- a/packages/@ember/-internals/glimmer/lib/helper.ts +++ b/packages/@ember/-internals/glimmer/lib/helper.ts @@ -13,7 +13,11 @@ import { consumeTag, createTag, dirtyTag } from '@glimmer/validator'; export const RECOMPUTE_TAG = symbol('RECOMPUTE_TAG'); -export type HelperFunction = (positional: unknown[], named: Dict) => T; +export type HelperFunction< + T = unknown, + P extends unknown[] = unknown[], + N extends Dict = Dict +> = (positional: P, named: N) => T; export type SimpleHelperFactory = Factory>; export type ClassHelperFactory = Factory>; @@ -30,8 +34,12 @@ export interface HelperInstance { const IS_CLASSIC_HELPER: unique symbol = Symbol('IS_CLASSIC_HELPER'); -export interface SimpleHelper { - compute: HelperFunction; +export interface SimpleHelper< + T = unknown, + P extends unknown[] = unknown[], + N extends Dict = Dict +> { + compute: HelperFunction; } /** @@ -89,7 +97,7 @@ interface Helper { @public @since 1.13.0 */ - compute(params: unknown[], hash: object): unknown; + compute(params: unknown[], hash: Dict): unknown; } class Helper extends FrameworkObject { static isHelperFactory = true; @@ -199,10 +207,11 @@ export const CLASSIC_HELPER_MANAGER = getInternalHelperManager(Helper); /////////// -class Wrapper implements HelperFactory { +class Wrapper = Dict> + implements HelperFactory> { isHelperFactory: true = true; - constructor(public compute: HelperFunction) {} + constructor(public compute: HelperFunction) {} create() { // needs new instance or will leak containers @@ -256,7 +265,11 @@ setHelperManager(() => SIMPLE_CLASSIC_HELPER_MANAGER, Wrapper.prototype); @public @since 1.13.0 */ -export function helper(helperFn: HelperFunction): HelperFactory { +export function helper< + T = unknown, + P extends unknown[] = unknown[], + N extends Dict = Dict +>(helperFn: HelperFunction): HelperFactory> { return new Wrapper(helperFn); } diff --git a/packages/@ember/-internals/glimmer/lib/utils/managers.ts b/packages/@ember/-internals/glimmer/lib/utils/managers.ts index 21cc5c55ff1..3e3f3de25ae 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/managers.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/managers.ts @@ -6,10 +6,10 @@ import { setComponentManager as glimmerSetComponentManager, } from '@glimmer/manager'; -export function setComponentManager( +export function setComponentManager( manager: (owner: Owner) => ComponentManager, - obj: object -): object { + obj: T +): T { return glimmerSetComponentManager(manager, obj); } diff --git a/packages/@ember/component/type-tests/capabilities.test.ts b/packages/@ember/component/type-tests/capabilities.test.ts new file mode 100644 index 00000000000..0014713e0ae --- /dev/null +++ b/packages/@ember/component/type-tests/capabilities.test.ts @@ -0,0 +1,16 @@ +import { capabilities } from '@ember/component'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(capabilities('3.13')).toMatchTypeOf<{ + asyncLifecycleCallbacks?: boolean | undefined; + destructor?: boolean | undefined; + updateHook?: boolean | undefined; +}>(); + +capabilities('3.13', { asyncLifecycleCallbacks: true }); +capabilities('3.4', { asyncLifecycleCallbacks: true }); + +// @ts-expect-error invalid capabilities +capabilities('3.13', { asyncLifecycleCallbacks: 1 }); +// @ts-expect-error invalid verison +capabilities('3.12'); diff --git a/packages/@ember/component/type-tests/get-component-template.test.ts b/packages/@ember/component/type-tests/get-component-template.test.ts new file mode 100644 index 00000000000..6a50cf49cb5 --- /dev/null +++ b/packages/@ember/component/type-tests/get-component-template.test.ts @@ -0,0 +1,8 @@ +import { getComponentTemplate } from '@ember/component'; +import { TemplateFactory } from '@glimmer/interfaces'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(getComponentTemplate({})).toEqualTypeOf(); + +// @ts-expect-error requires param +getComponentTemplate(); diff --git a/packages/@ember/component/type-tests/helper/helper.test.ts b/packages/@ember/component/type-tests/helper/helper.test.ts new file mode 100644 index 00000000000..e1e02d26e2a --- /dev/null +++ b/packages/@ember/component/type-tests/helper/helper.test.ts @@ -0,0 +1,18 @@ +import { HelperFactory, SimpleHelper } from '@ember/-internals/glimmer/lib/helper'; +import { helper } from '@ember/component/helper'; +import { expectTypeOf } from 'expect-type'; + +// NOTE: The types for `helper` are not actually safe. Glint helps with this. + +let myHelper = helper(function ([cents]: [number], { currency }: { currency: string }) { + return `${currency}${cents * 0.01}`; +}); +expectTypeOf(myHelper).toEqualTypeOf< + HelperFactory> +>(); + +// @ts-expect-error invalid named params +helper(function ([cents]: [number], named: number) {}); + +// @ts-expect-error invalid params +helper(function (params: number) {}); diff --git a/packages/@ember/component/type-tests/helper/index.test.ts b/packages/@ember/component/type-tests/helper/index.test.ts new file mode 100644 index 00000000000..e3d3a8e075f --- /dev/null +++ b/packages/@ember/component/type-tests/helper/index.test.ts @@ -0,0 +1,42 @@ +import { Owner } from '@ember/-internals/owner'; +import { FrameworkObject } from '@ember/-internals/runtime'; +import Helper from '@ember/component/helper'; +import { expectTypeOf } from 'expect-type'; + +// Good enough for tests +let owner = {} as Owner; + +// NOTE: The types for `compute` are not actually safe. Glint helps with this. + +let helper = new Helper(owner); + +expectTypeOf(helper).toMatchTypeOf(); + +class MyHelper extends Helper { + compute([cents]: [number], { currency }: { currency: string }) { + return `${currency}${cents * 0.01}`; + } +} +new MyHelper(owner); + +class NoHash extends Helper { + compute([cents]: [number]): string { + return `${cents * 0.01}`; + } +} +new NoHash(owner); + +class NoParams extends Helper { + compute(): string { + return 'hello'; + } +} +new NoParams(owner); + +class InvalidHelper extends Helper { + // @ts-expect-error Invalid params + compute(value: boolean): string { + return String(value); + } +} +new InvalidHelper(owner); diff --git a/packages/@ember/component/type-tests/index.test.ts b/packages/@ember/component/type-tests/index.test.ts index 519f5af2f86..8538178abcc 100644 --- a/packages/@ember/component/type-tests/index.test.ts +++ b/packages/@ember/component/type-tests/index.test.ts @@ -2,12 +2,50 @@ import Component from '@ember/component'; import { CoreView } from '@ember/-internals/views'; import { expectTypeOf } from 'expect-type'; import { Owner } from '@ember/-internals/owner'; +import { View } from '@ember/-internals/glimmer/lib/renderer'; +import { action } from '@ember/object'; +import { tracked } from '@ember/-internals/metal'; // NOTE: This is invalid, but acceptable for type tests let owner = {} as Owner; let component = new Component(owner); expectTypeOf(component).toMatchTypeOf(); +expectTypeOf(component).toMatchTypeOf(); +class MyComponent extends Component { + tagName = 'em'; + classNames = ['my-class', 'my-other-class']; + classNameBindings = ['propertyA', 'propertyB']; + attributeBindings = ['href']; + @tracked propertyA = 'from-a'; + get propertyB(): string | void { + if (this.propertyA === 'from-a') { + return 'from-b'; + } + } + + @tracked href = 'https://tilde.io'; + + @action click(_event: Event): void { + // Clicked! + } +} +new MyComponent(owner); + +class BadComponent extends Component { + // @ts-expect-error invalid tag name + tagName = 1; + + // @ts-expect-error invalid classname + classNames = 'foo'; + + // @ts-expect-error invalid classNameBindings + classNameBindings = [1]; + + // @ts-expect-error invalid attributeBindings + attributeBindings = [true]; +} +new BadComponent(owner); diff --git a/packages/@ember/component/type-tests/set-component-manager.test.ts b/packages/@ember/component/type-tests/set-component-manager.test.ts new file mode 100644 index 00000000000..6e29af3c11f --- /dev/null +++ b/packages/@ember/component/type-tests/set-component-manager.test.ts @@ -0,0 +1,17 @@ +import { Owner } from '@ember/-internals/owner'; +import { setComponentManager } from '@ember/component'; +import { ComponentManager } from '@glimmer/interfaces'; +import { expectTypeOf } from 'expect-type'; + +// Obviously this is invalid, but it works for our purposes. +let manager = {} as ComponentManager; + +class Foo {} +let foo = new Foo(); + +expectTypeOf(setComponentManager((_owner: Owner) => manager, foo)).toEqualTypeOf(); + +// @ts-expect-error invalid callback +setComponentManager(() => { + return {}; +}, foo); diff --git a/packages/@ember/component/type-tests/set-component-template.test.ts b/packages/@ember/component/type-tests/set-component-template.test.ts new file mode 100644 index 00000000000..a99a3d3608e --- /dev/null +++ b/packages/@ember/component/type-tests/set-component-template.test.ts @@ -0,0 +1,8 @@ +import { setComponentTemplate } from '@ember/component'; +import { TemplateFactory } from '@glimmer/interfaces'; +import { expectTypeOf } from 'expect-type'; + +// Good enough for testing +let factory = {} as TemplateFactory; + +expectTypeOf(setComponentTemplate(factory, {})).toEqualTypeOf(); diff --git a/packages/@ember/component/type-tests/template-only.test.ts b/packages/@ember/component/type-tests/template-only.test.ts new file mode 100644 index 00000000000..a47ba4153e2 --- /dev/null +++ b/packages/@ember/component/type-tests/template-only.test.ts @@ -0,0 +1,11 @@ +import templateOnlyComponent from '@ember/component/template-only'; +import { TemplateOnlyComponentDefinition } from '@glimmer/runtime/dist/types/lib/component/template-only'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(templateOnlyComponent()).toEqualTypeOf(); + +templateOnlyComponent('myModule'); +templateOnlyComponent('myModule', 'myName'); + +// @ts-expect-error invalid params +templateOnlyComponent(1);