> {
+ private state = { ready: false };
+
+ private wrapperClicked(event: MouseEvent): void {
+ console.log('clicked', event.x, event.y);
+ }
+
+ /**
+ * ```hbs
+ * {{#let this.state.ready as |isReady|}}
+ *
+ * {{yield isReady @value to="body"}}
+ *
+ * {{/let}}
+ * ```
+ */
+ static {
+ templateForBackingValue(this, function (__glintRef__) {
+ const component = emitComponent(resolve(globals.let)(__glintRef__.this.state.ready));
+ const [isReady] = component.blockParams.default;
+
+ {
+ const __glintY__ = emitElement('div');
+ expectTypeOf(__glintY__).toEqualTypeOf<{ element: HTMLDivElement }>();
+ applyModifier(
+ resolve(globals.on)(__glintY__.element, 'click', __glintRef__.this.wrapperClicked),
+ );
+ }
+
+ yieldToBlock(__glintRef__, 'body')(isReady, __glintRef__.args.value);
+
+ yieldToBlock(
+ __glintRef__,
+ // @ts-expect-error: bad block
+ 'bad',
+ )(isReady, __glintRef__.args.value);
+
+ // @ts-expect-error: missing params
+ yieldToBlock(__glintRef__, 'body')();
+
+ yieldToBlock(__glintRef__, 'body')(
+ isReady,
+ // @ts-expect-error: wrong param type
+ Symbol(),
+ );
+ });
+ }
+}
+
+/**
+ * Instantiate `T` to `string` and verify it's threaded through:
+ *
+ * hbs```
+ *
+ * <:body as |isReady value|>
+ * Ready? {{value}}: {{isReady}}
+ *
+ *
+ */
+{
+ const component = emitComponent(resolve(MyComponent)({ value: 'hi', ...NamedArgsMarker }));
+
+ {
+ const [isReady, value] = component.blockParams.body;
+ expectTypeOf(isReady).toEqualTypeOf();
+ expectTypeOf(value).toEqualTypeOf();
+
+ emitContent(resolveOrReturn(value)());
+ emitContent(resolveOrReturn(isReady)());
+ }
+}
+
+/**
+ * Instantiate `T` to `number` and verify it's threaded through:
+ *
+ * hbs```
+ *
+ * <:body as |isReady value|>
+ * Ready? {{value}}: {{isReady}}
+ *
+ *
+ */
+{
+ const component = emitComponent(resolve(MyComponent)({ value: 123, ...NamedArgsMarker }));
+
+ {
+ const [isReady, value] = component.blockParams.body;
+ expectTypeOf(isReady).toEqualTypeOf();
+ expectTypeOf(value).toEqualTypeOf();
+
+ emitContent(resolveOrReturn(value)());
+ emitContent(resolveOrReturn(isReady)());
+ }
+}
+
+/**
+ * Invoke the component inline, which is valid since it has no
+ * required blocks.
+ *
+ * hbs```
+ * {{MyComponent value=123}}
+ * ```
+ */
+emitContent(resolve(MyComponent)({ value: 123, ...NamedArgsMarker }));
+
+/**
+ * Ensure we can invoke a maybe-undefined component.
+ */
+declare const MaybeMyComponent: typeof MyComponent | undefined;
+
+emitComponent(resolve(MaybeMyComponent)({ value: 'hi', ...NamedArgsMarker }));
+
+emitComponent(resolveOrReturn(MaybeMyComponent)({ value: 'hi', ...NamedArgsMarker }));
+
+/**
+ * Invoking an `any` or `unknown` component should error at the invocation site
+ * if appropriate, but not produce cascading errors.
+ */
+{
+ let anyComponent = emitComponent({} as any);
+ let [anyComponentParam] = anyComponent.blockParams.default;
+
+ expectTypeOf(anyComponent.element).toBeAny();
+ expectTypeOf(anyComponentParam).toBeAny();
+
+ // @ts-expect-error: unknown is an invalid component
+ let unknownComponent = emitComponent({} as unknown);
+ let [unknownComponentParam] = unknownComponent.blockParams['default'];
+
+ expectTypeOf(unknownComponent.element).toBeAny();
+ expectTypeOf(unknownComponentParam).toBeAny();
+}
+
+/**
+ * Constrained type parameters can be tricky, and `expect-type` doesn't
+ * work well with type assertions directly against them, but we can assert
+ * against a property that the constraint dictates must exist to ensure
+ * that we don't break or degrade them to `unknown` or `any` when used
+ * in a template.
+ */
+export function testConstrainedTypeParameter(value: T): void {
+ let result = resolveOrReturn(value)();
+ expectTypeOf(result.foo).toEqualTypeOf<'bar'>();
+}
diff --git a/test-packages/types-template/__tests__/emit-content.test.ts b/test-packages/types-template/__tests__/emit-content.test.ts
new file mode 100644
index 000000000..e3cac4084
--- /dev/null
+++ b/test-packages/types-template/__tests__/emit-content.test.ts
@@ -0,0 +1,31 @@
+import { htmlSafe } from '@ember/template';
+import { emitContent } from '../-private/dsl';
+
+type SafeString = ReturnType;
+
+// Glimmer's SafeString interface
+let safeString = {
+ toHTML(): string {
+ return 'Foo';
+ },
+} as SafeString;
+
+emitContent(safeString);
+
+// @ember/template's SafeString
+emitContent(htmlSafe('Foo'));
+
+emitContent('hi');
+emitContent(123);
+emitContent(false);
+emitContent(undefined);
+emitContent(null);
+
+const returnsVoid = (): void => {};
+
+// Using something that returns void at the top level is reasonable
+emitContent(returnsVoid());
+
+// Emitting an HTML element inserts that element into the DOM
+emitContent(document.createElement('div'));
+emitContent(document.createElementNS('http://www.w3.org/2000/svg', 'svg'));
diff --git a/test-packages/package-test-template/__tests__/helper-like.test.ts b/test-packages/types-template/__tests__/helper-like.test.ts
similarity index 100%
rename from test-packages/package-test-template/__tests__/helper-like.test.ts
rename to test-packages/types-template/__tests__/helper-like.test.ts
diff --git a/test-packages/types-template/__tests__/keywords/component.test.ts b/test-packages/types-template/__tests__/keywords/component.test.ts
new file mode 100644
index 000000000..36dd5252d
--- /dev/null
+++ b/test-packages/types-template/__tests__/keywords/component.test.ts
@@ -0,0 +1,248 @@
+import { expectTypeOf } from 'expect-type';
+import { emitComponent, NamedArgsMarker, resolve, resolveForBind } from '../../-private/dsl';
+import { ComponentKeyword } from '../../-private/keywords';
+import TestComponent from '../test-component';
+
+const componentKeyword = resolve({} as ComponentKeyword);
+
+class StringComponent extends TestComponent<{
+ Args: { value: string };
+ Blocks: { default: [string] };
+}> {}
+
+const NoopCurriedStringComponent = componentKeyword(resolveForBind(StringComponent));
+const ValueCurriedStringComponent = componentKeyword(resolveForBind(StringComponent), {
+ value: 'hello',
+ ...NamedArgsMarker,
+});
+
+// Invoking the noop-curried component
+emitComponent(resolve(NoopCurriedStringComponent)({ value: 'hello', ...NamedArgsMarker }));
+
+resolve(NoopCurriedStringComponent)(
+ // @ts-expect-error: Invoking the curried component but forgetting `value`
+ { ...NamedArgsMarker },
+);
+
+resolve(NoopCurriedStringComponent)({
+ // @ts-expect-error: Invoking the curried component with an invalid value
+ value: 123,
+ ...NamedArgsMarker,
+});
+
+// Invoking the noop-curried component with a valid block
+{
+ const component = emitComponent(
+ resolve(NoopCurriedStringComponent)({ value: 'hello', ...NamedArgsMarker }),
+ );
+
+ {
+ const [...args] = component.blockParams.default;
+ expectTypeOf(args).toEqualTypeOf<[string]>();
+ }
+}
+
+// Invoking the noop-curried component with an invalid block
+{
+ const component = emitComponent(
+ resolve(NoopCurriedStringComponent)({ value: 'hello', ...NamedArgsMarker }),
+ );
+
+ {
+ // @ts-expect-error: invalid block name
+ component.blockParams.asdf;
+ }
+}
+
+// Invoking the curried-with-value component with no value
+emitComponent(resolve(ValueCurriedStringComponent)({ ...NamedArgsMarker }));
+
+// Invoking the curried-with-value component with a valid value
+emitComponent(resolve(ValueCurriedStringComponent)({ value: 'hi', ...NamedArgsMarker }));
+
+emitComponent(
+ resolve(ValueCurriedStringComponent)({
+ // @ts-expect-error: Invoking the curred-with-value component with an invalid value
+ value: 123,
+ ...NamedArgsMarker,
+ }),
+);
+
+componentKeyword(
+ resolveForBind(StringComponent),
+ // @ts-expect-error: Attempting to curry an arg with the wrong type
+ { value: 123, ...NamedArgsMarker },
+);
+
+class ParametricComponent extends TestComponent<{
+ Args: { values: Array; optional?: string };
+ Blocks: { default?: [T, number] };
+}> {}
+
+const NoopCurriedParametricComponent = componentKeyword(resolveForBind(ParametricComponent));
+
+// 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;
+
+const RequiredValueCurriedParametricComponent = componentKeyword(
+ resolveForBind(BoundParametricComponent),
+ {
+ values: ['hello'],
+ ...NamedArgsMarker,
+ },
+);
+
+const OptionalValueCurriedParametricComponent = componentKeyword(
+ resolveForBind(ParametricComponent),
+ {
+ optional: 'hi',
+ ...NamedArgsMarker,
+ },
+);
+
+// Invoking the noop-curried component with number values
+{
+ const component = emitComponent(
+ resolve(NoopCurriedParametricComponent)({ values: [1, 2, 3], ...NamedArgsMarker }),
+ );
+
+ {
+ const [value] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ }
+}
+
+// Invoking the noop-curried component with string values
+{
+ const component = emitComponent(
+ resolve(NoopCurriedParametricComponent)({ values: ['hello'], ...NamedArgsMarker }),
+ );
+
+ {
+ const [value] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ }
+}
+
+emitComponent(
+ resolve(NoopCurriedParametricComponent)(
+ // @ts-expect-error: missing required arg `values`
+ { ...NamedArgsMarker },
+ ),
+);
+
+emitComponent(
+ resolve(NoopCurriedParametricComponent)({
+ // @ts-expect-error: wrong type for `values`
+ values: 'hello',
+ ...NamedArgsMarker,
+ }),
+);
+
+emitComponent(
+ resolve(NoopCurriedParametricComponent)({
+ values: [1, 2, 3],
+ // @ts-expect-error: extra arg
+ extra: 'uh oh',
+ ...NamedArgsMarker,
+ }),
+);
+
+// Invoking the curred component with no additional args
+{
+ /** hello {@link RequiredValueCurriedParametricComponent} */
+ const component = emitComponent(
+ resolve(RequiredValueCurriedParametricComponent)({ ...NamedArgsMarker }),
+ );
+
+ {
+ const [value] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ }
+}
+
+// Invoking the curred component and overriding the given arg
+{
+ const component = emitComponent(
+ resolve(RequiredValueCurriedParametricComponent)({ values: ['ok'], ...NamedArgsMarker }),
+ );
+
+ {
+ const [value] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ }
+}
+
+emitComponent(
+ resolve(RequiredValueCurriedParametricComponent)({
+ // @ts-expect-error: wrong type for arg override
+ values: [1, 2, 3],
+ ...NamedArgsMarker,
+ }),
+);
+
+emitComponent(
+ resolve(RequiredValueCurriedParametricComponent)({
+ // @ts-expect-error: extra arg
+ extra: 'bad',
+ ...NamedArgsMarker,
+ }),
+);
+
+// Invoking the curried component, supplying missing required args
+{
+ const component = emitComponent(
+ resolve(OptionalValueCurriedParametricComponent)({ values: [1, 2, 3], ...NamedArgsMarker }),
+ );
+
+ {
+ const [value] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ }
+}
+
+emitComponent(
+ resolve(OptionalValueCurriedParametricComponent)(
+ // @ts-expect-error: missing required arg `values`
+ { ...NamedArgsMarker },
+ ),
+);
+
+// {{component (component BoundParametricComponent values=(array "hello")) optional="hi"}}
+const DoubleCurriedComponent = componentKeyword(
+ resolveForBind(RequiredValueCurriedParametricComponent),
+ {
+ optional: 'hi',
+ ...NamedArgsMarker,
+ },
+);
+
+// Invoking the component with no args
+{
+ const component = emitComponent(resolve(DoubleCurriedComponent)({ ...NamedArgsMarker }));
+
+ {
+ const [value] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ }
+}
+
+// Invoking the component overriding an arg correctly
+emitComponent(resolve(DoubleCurriedComponent)({ values: ['a', 'b'], ...NamedArgsMarker }));
+
+emitComponent(
+ resolve(DoubleCurriedComponent)({
+ // @ts-expect-error: invalid arg override
+ values: [1, 2, 3],
+ ...NamedArgsMarker,
+ }),
+);
+
+emitComponent(
+ resolve(DoubleCurriedComponent)({
+ // @ts-expect-error: unexpected args
+ foo: 'bar',
+ ...NamedArgsMarker,
+ }),
+);
diff --git a/test-packages/types-template/__tests__/keywords/debugger.test.ts b/test-packages/types-template/__tests__/keywords/debugger.test.ts
new file mode 100644
index 000000000..c1d089fcc
--- /dev/null
+++ b/test-packages/types-template/__tests__/keywords/debugger.test.ts
@@ -0,0 +1,10 @@
+import { emitContent, resolve } from '../../-private/dsl';
+import { DebuggerKeyword } from '../../-private/keywords';
+
+const debuggerKeyword = resolve({} as DebuggerKeyword);
+
+// Can be invoked as {{debugger}}
+emitContent(debuggerKeyword());
+
+// @ts-expect-error: Rejects any additional arguments
+debuggerKeyword('hello');
diff --git a/test-packages/types-template/__tests__/keywords/each.test.ts b/test-packages/types-template/__tests__/keywords/each.test.ts
new file mode 100644
index 000000000..bd1a5c60e
--- /dev/null
+++ b/test-packages/types-template/__tests__/keywords/each.test.ts
@@ -0,0 +1,32 @@
+import { expectTypeOf } from 'expect-type';
+import { emitComponent, NamedArgsMarker, resolve } from '../../-private/dsl';
+import { EachKeyword } from '../../-private/keywords';
+
+const eachKeyword = resolve({} as EachKeyword);
+
+// Yield out array values and indices
+
+{
+ const component = emitComponent(eachKeyword(['a', 'b', 'c']));
+
+ {
+ const [value, index] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ expectTypeOf(index).toEqualTypeOf();
+ }
+}
+
+// Works for `readonly` arrays
+
+{
+ const component = emitComponent(eachKeyword(['a', 'b', 'c'] as readonly string[]));
+
+ {
+ const [value, index] = component.blockParams.default;
+ expectTypeOf(value).toEqualTypeOf();
+ expectTypeOf(index).toEqualTypeOf();
+ }
+}
+
+// Accept a `key` string
+emitComponent(eachKeyword([{ id: 1 }], { key: 'id', ...NamedArgsMarker }));
diff --git a/test-packages/types-template/__tests__/keywords/in-element.test.ts b/test-packages/types-template/__tests__/keywords/in-element.test.ts
new file mode 100644
index 000000000..54a9282df
--- /dev/null
+++ b/test-packages/types-template/__tests__/keywords/in-element.test.ts
@@ -0,0 +1,30 @@
+import { emitComponent, NamedArgsMarker, resolve } from '../../-private/dsl';
+import { InElementKeyword } from '../../-private/keywords';
+
+const inElementKeyword = resolve({} as InElementKeyword);
+
+declare const element: HTMLElement;
+declare const shadow: ShadowRoot;
+
+// Can be invoked with an element
+emitComponent(inElementKeyword(element));
+
+// Can be invoked with a ShadowRoot
+emitComponent(inElementKeyword(shadow));
+
+// Accepts an `insertBefore` argument
+emitComponent(inElementKeyword(element, { insertBefore: null, ...NamedArgsMarker }));
+emitComponent(inElementKeyword(element, { insertBefore: undefined, ...NamedArgsMarker }));
+
+// @ts-expect-error: rejects invocation with `undefined`
+inElementKeyword();
+
+inElementKeyword(
+ // @ts-expect-error: rejects invocation with `null`
+ null,
+);
+
+inElementKeyword(element, {
+ // @ts-expect-error: rejects any other `insertBefore` values
+ insertBefore: new HTMLDivElement(),
+});
diff --git a/test-packages/types-template/__tests__/keywords/let.test.ts b/test-packages/types-template/__tests__/keywords/let.test.ts
new file mode 100644
index 000000000..73df13c29
--- /dev/null
+++ b/test-packages/types-template/__tests__/keywords/let.test.ts
@@ -0,0 +1,16 @@
+import { expectTypeOf } from 'expect-type';
+import { emitComponent, resolve } from '../../-private/dsl';
+import { LetKeyword } from '../../-private/keywords';
+
+const letKeyword = resolve({} as LetKeyword);
+
+// Yields out the given values
+{
+ const component = emitComponent(letKeyword('hello', 123));
+
+ {
+ const [str, num] = component.blockParams.default;
+ expectTypeOf(str).toEqualTypeOf();
+ expectTypeOf(num).toEqualTypeOf();
+ }
+}
diff --git a/test-packages/types-template/__tests__/keywords/with.test.ts b/test-packages/types-template/__tests__/keywords/with.test.ts
new file mode 100644
index 000000000..5c99641cf
--- /dev/null
+++ b/test-packages/types-template/__tests__/keywords/with.test.ts
@@ -0,0 +1,25 @@
+import { expectTypeOf } from 'expect-type';
+import { emitComponent, resolve } from '../../-private/dsl';
+import { WithKeyword } from '../../-private/keywords';
+
+const withKeyword = resolve({} as WithKeyword);
+
+// Yields out the given value
+{
+ const component = emitComponent(withKeyword('hello'));
+
+ {
+ const [str] = component.blockParams.default;
+ expectTypeOf(str).toEqualTypeOf();
+ }
+
+ {
+ component.blockParams.else;
+ }
+}
+
+withKeyword(
+ 'hello',
+ // @ts-expect-error: Rejects multiple values
+ 'goodbye',
+);
diff --git a/test-packages/package-test-template/__tests__/modifier-like.test.ts b/test-packages/types-template/__tests__/modifier-like.test.ts
similarity index 100%
rename from test-packages/package-test-template/__tests__/modifier-like.test.ts
rename to test-packages/types-template/__tests__/modifier-like.test.ts
diff --git a/test-packages/types-template/__tests__/resolution.test.ts b/test-packages/types-template/__tests__/resolution.test.ts
new file mode 100644
index 000000000..8ec1e6f3b
--- /dev/null
+++ b/test-packages/types-template/__tests__/resolution.test.ts
@@ -0,0 +1,92 @@
+import { expectTypeOf } from 'expect-type';
+import {
+ ComponentReturn,
+ DirectInvokable,
+ NamedArgs,
+ TemplateContext,
+} from '../-private/integration';
+import {
+ emitComponent,
+ resolve,
+ resolveOrReturn,
+ templateForBackingValue,
+ yieldToBlock,
+} from '../-private/dsl';
+import TestComponent, { globals } from './test-component';
+
+declare function value(): T;
+
+// Component with a template
+{
+ interface MyArgs {
+ value: T;
+ }
+
+ interface MyBlocks {
+ body: [someFlag: boolean, someValue: T];
+ }
+
+ class MyComponent extends TestComponent<{ Args: MyArgs; Blocks: MyBlocks }> {
+ private state = { ready: false };
+
+ /**
+ * ```hbs
+ * {{#let this.state.ready as |isReady|}}
+ * {{yield isReady @value to="body"}}
+ * {{/let}}
+ * ```
+ */
+ static {
+ templateForBackingValue(this, function (__glintRef__) {
+ {
+ const component = emitComponent(resolve(globals.let)(__glintRef__.this.state.ready));
+
+ {
+ const [isReady] = component.blockParams.default;
+ yieldToBlock(__glintRef__, 'body')(isReady, __glintRef__.args.value);
+ }
+ }
+ });
+ }
+ }
+
+ type ExpectedSignature = (args: NamedArgs>) => ComponentReturn<{
+ body: [boolean, T];
+ }>;
+
+ type ExpectedContext = TemplateContext, MyArgs, MyBlocks, null>;
+
+ // Template has the correct type
+ expectTypeOf(resolve(MyComponent)).toEqualTypeOf();
+
+ // Template context is inferred correctly
+ templateForBackingValue(MyComponent, function (context) {
+ expectTypeOf(context).toEqualTypeOf>();
+ });
+
+ templateForBackingValue(MyComponent, function (context) {
+ expectTypeOf(context).toEqualTypeOf>();
+ });
+}
+
+// A raw InvokableInstance value
+{
+ type TestSignature = (
+ args: { value: T; values: T[] },
+ positional: string,
+ ) => ComponentReturn<{
+ foo: [T[], string];
+ otherwise: [];
+ }>;
+
+ expectTypeOf(resolve(value>())).toEqualTypeOf();
+}
+
+// Values of type `any` or `never` (themselves typically the product of other type errors)
+// shouldn't unnecessarily blow things up by producing an `unknown` signature.
+{
+ expectTypeOf(resolveOrReturn({} as any)).toEqualTypeOf();
+ expectTypeOf(resolveOrReturn({} as never)).toEqualTypeOf();
+ expectTypeOf(resolve({} as any)).toEqualTypeOf();
+ expectTypeOf(resolve({} as never)).toEqualTypeOf();
+}
diff --git a/test-packages/types-template/__tests__/signature.test.ts b/test-packages/types-template/__tests__/signature.test.ts
new file mode 100644
index 000000000..fa59161d1
--- /dev/null
+++ b/test-packages/types-template/__tests__/signature.test.ts
@@ -0,0 +1,155 @@
+import { expectTypeOf } from 'expect-type';
+import {
+ ComponentSignatureArgs,
+ ComponentSignatureBlocks,
+ ComponentSignatureElement,
+} from '../-private/signature';
+
+type LegacyArgs = {
+ foo: number;
+};
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: LegacyArgs;
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
+
+// Here, we are testing that the types propertly distribute over union types,
+// generics which extend other types, etc.
+type LegacyArgsDistributive = { foo: number } | { bar: string; baz: boolean };
+
+expectTypeOf>().toEqualTypeOf<
+ | { Named: { foo: number }; Positional: [] }
+ | { Named: { bar: string; baz: boolean }; Positional: [] }
+>();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface ArgsOnly {
+ Args: LegacyArgs;
+}
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: LegacyArgs;
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface ElementOnly {
+ Element: HTMLParagraphElement;
+}
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: {};
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface Blocks {
+ default: [name: string];
+ inverse: [];
+}
+
+interface BlockOnlySig {
+ Blocks: Blocks;
+}
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: {};
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{
+ default: {
+ Params: {
+ Positional: [name: string];
+ };
+ };
+ inverse: {
+ Params: {
+ Positional: [];
+ };
+ };
+}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface ArgsAndBlocks {
+ Args: LegacyArgs;
+ Blocks: Blocks;
+}
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: LegacyArgs;
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{
+ default: {
+ Params: {
+ Positional: [name: string];
+ };
+ };
+ inverse: {
+ Params: {
+ Positional: [];
+ };
+ };
+}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface ArgsAndEl {
+ Args: LegacyArgs;
+ Element: HTMLParagraphElement;
+}
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: LegacyArgs;
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface FullShortSig {
+ Args: LegacyArgs;
+ Element: HTMLParagraphElement;
+ Blocks: Blocks;
+}
+
+expectTypeOf>().toEqualTypeOf<{
+ Named: LegacyArgs;
+ Positional: [];
+}>();
+expectTypeOf>().toEqualTypeOf<{
+ default: {
+ Params: {
+ Positional: [name: string];
+ };
+ };
+ inverse: {
+ Params: {
+ Positional: [];
+ };
+ };
+}>();
+expectTypeOf>().toEqualTypeOf();
+
+interface FullLongSig {
+ Args: {
+ Named: LegacyArgs;
+ Positional: [];
+ };
+ Element: HTMLParagraphElement;
+ Blocks: {
+ default: {
+ Params: {
+ Positional: [name: string];
+ };
+ };
+ };
+}
+
+expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf();
+expectTypeOf>().toEqualTypeOf();
diff --git a/test-packages/types-template/__tests__/test-component.ts b/test-packages/types-template/__tests__/test-component.ts
new file mode 100644
index 000000000..cac098332
--- /dev/null
+++ b/test-packages/types-template/__tests__/test-component.ts
@@ -0,0 +1,28 @@
+// This module contains a `@glimmer/component`-like base class and the
+// declarations necessary for it to be used as a component in glint, as
+// well as simple examples of a helper and modifier.
+
+import { ComponentLike, ModifierLike } from '../-private/index';
+import { Context, TemplateContext } from '../-private/integration';
+import { LetKeyword } from '../-private/keywords';
+
+export default TestComponent;
+export declare const globals: {
+ let: LetKeyword;
+ on: abstract new () => InstanceType<
+ ModifierLike<{
+ Element: Element;
+ Args: {
+ Positional: [eventName: T, callback: (event: HTMLElementEventMap[T]) => void];
+ };
+ }>
+ >;
+};
+
+type Get = K extends keyof T ? Exclude : Otherwise;
+
+interface TestComponent extends InstanceType> {}
+declare class TestComponent {
+ readonly args: Get;
+ [Context]: TemplateContext, Get, Get>;
+}
diff --git a/test-packages/types-template/package.json b/test-packages/types-template/package.json
new file mode 100644
index 000000000..a4fa49b4a
--- /dev/null
+++ b/test-packages/types-template/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "type-tests-glint-template",
+ "private": true,
+ "scripts": {
+ "test": "echo 'no standalone tests within this project'",
+ "test:typecheck": "tsc",
+ "test:tsc": "echo 'no standalone typecheck within this project'"
+ },
+ "devDependencies": {
+ "@glint/template": "workspace:*",
+ "@glint/environment-ember-loose": "workspace:*",
+ "@glimmer/component": "^1.1.2",
+ "@glimmer/runtime": "^0.94.10",
+ "ember-source": "^6.3.0",
+ "expect-type": "^0.15.0",
+ "sums-up": "^2.1.0",
+ "typescript": "^5.8.2"
+ }
+}
diff --git a/test-packages/types-template/tsconfig.json b/test-packages/types-template/tsconfig.json
new file mode 100644
index 000000000..02e62063c
--- /dev/null
+++ b/test-packages/types-template/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "allowJs": true,
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "strictNullChecks": true,
+ "strictPropertyInitialization": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noEmitOnError": false,
+ "noEmit": true,
+ "declaration": true,
+ "declarationMap": true,
+ "inlineSourceMap": true,
+ "inlineSources": true,
+ "baseUrl": ".",
+ "module": "es6",
+ "experimentalDecorators": true,
+ "noEmit": true,
+ "lib": ["es2020", "dom"],
+ "types": ["ember-source/types"],
+ },
+ "references": [
+ { "path": "../../packages/template" },
+ { "path": "../../packages/environment-ember-loose" }
+ ]
+}