Skip to content
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

Introduce public TypeScript support #1234

Merged
merged 24 commits into from Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ff2d5ea
Upgrade to TS 4.7
chriskrycho Jul 21, 2022
db9a6d3
Introduce public TypeScript support
chriskrycho Jul 20, 2022
30e9c1f
Upgrade to Ember v4 types
chriskrycho Jul 21, 2022
16babd8
Publish types in `public-types` with `typesVersions`
chriskrycho Jul 21, 2022
979f535
Split apart tsconfig for check vs. for type emit
chriskrycho Jul 21, 2022
bd26ef1
Ignore generated types in ESLint config
chriskrycho Jul 21, 2022
2930c2f
Update `any` to `unknown` in DOM targeting
chriskrycho Jul 21, 2022
196e548
Use `TouchEventInit` instead of `object` in `tap`
chriskrycho Jul 21, 2022
5c2f0c0
Add note about types to CONTRIBUTING.md
chriskrycho Jul 21, 2022
da12a6a
Improve TestMetadata implementation and export it
chriskrycho Jul 21, 2022
05b61cd
Introduce an explicit ErrorHandlerValidation type
chriskrycho Jul 21, 2022
3f9beee
Add/fix docs for internal DebugInfo type
chriskrycho Jul 21, 2022
18fe0c1
Use `() => void` instead of `CallableFunction`
chriskrycho Jul 21, 2022
232ed5f
Fix doc for `setupApplicationContext` return type
chriskrycho Jul 21, 2022
ef1db30
Use a Record of `unknown` instead of an index of `any` for `visit`
chriskrycho Jul 21, 2022
b325ba6
Export `Warning` and `DeprecationFailure` types
chriskrycho Jul 21, 2022
302b3ff
Export `DebugInfo`, `BaseContext`, and `TestContext` types
chriskrycho Jul 21, 2022
8af3efa
Introduce type test suite for whole public API
chriskrycho Jul 21, 2022
cc3844b
Fix `deprecate` calls to use full API
chriskrycho Jul 21, 2022
a51eae8
Get rid of a couple unsafe casts
chriskrycho Jul 22, 2022
aa186f2
Use `assert` to replace two `as string` casts
chriskrycho Jul 22, 2022
8ebf813
assert only what we legally can on RouterService
chriskrycho Jul 25, 2022
02525dd
Introduce a (safe) lie in `currentURL()` types
chriskrycho Jul 25, 2022
8e7de3a
assert only what we legally can on Router
chriskrycho Jul 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -23,3 +23,4 @@

addon-test-support/**/*.js
addon-test-support/**/*.d.ts
public-types
24 changes: 24 additions & 0 deletions .github/workflows/ci-build.yml
Expand Up @@ -71,3 +71,27 @@ jobs:
run: yarn install --frozen-lockfile
- name: test
run: node_modules/.bin/ember try:one ${{ matrix.ember-try-scenario }} --skip-cleanup

types:
runs-on: ubuntu-latest

needs: test

strategy:
fail-fast: false
matrix:
ts-version:
- 4.7
- next

steps:
- uses: actions/checkout@v2
- uses: volta-cli/action@v1
with:
node-version: 12.x
- name: install dependencies
run: yarn install --frozen-lockfile
- name: install TS version
run: yarn install --dev typescript@${{matrix.ts-version}}
- name: test types
run: yarn lint:ts
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -28,3 +28,4 @@
# tyescript related output files
addon-test-support/**/*.js
addon-test-support/**/*.d.ts
public-types
2 changes: 1 addition & 1 deletion API.md
Expand Up @@ -934,7 +934,7 @@ a new test metadata object if one does not exist.

* `context` **BaseContext** the context to use

Returns **ITestMetadata** the test metadata for the provided context
Returns **TestMetadata** the test metadata for the provided context

## getDeprecations

Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Expand Up @@ -11,6 +11,10 @@
* `yarn lint`
* `yarn lint:fix`

## Types

When updating the API, you will need to update the type tests (in `tests/api.ts`) as well. The kinds of changes required will make it clear whether the change is backwards compatible or not!

## Running tests

* `ember test` – Runs the test suite on the current Ember version
Expand Down
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -9,6 +9,9 @@ Compatibility
- Ember 3.8 or above
- Ember CLI 3.8 or above
- Node.js 10 or above
- TypeScript 4.7
- SemVer policy: [simple majors](https://www.semver-ts.org/#simple-majors)
- the public API is defined by [API.md](./API.md).


Installation
Expand Down
@@ -1,4 +1,4 @@
import type Resolver from '@ember/application/resolver';
import type Resolver from 'ember-resolver';
import ApplicationInstance from '@ember/application/instance';
import Application from '@ember/application';
import EmberObject from '@ember/object';
Expand Down Expand Up @@ -29,11 +29,12 @@ function exposeRegistryMethodsWithoutDeprecations(container: any) {
];

for (let i = 0, l = methods.length; i < l; i++) {
let method = methods[i];
let methodName = methods[i];

if (method in container) {
container[method] = function (...args: unknown[]) {
return container._registry[method](...args);
if (methodName && methodName in container) {
const knownMethod = methodName;
container[knownMethod] = function (...args: unknown[]) {
return container._registry[knownMethod](...args);
};
}
}
Expand All @@ -57,10 +58,10 @@ const Owner = EmberObject.extend(RegistryProxyMixin, ContainerProxyMixin, {
* @see {@link https://github.com/emberjs/ember.js/blob/v4.5.0-alpha.5/packages/%40ember/engine/instance.ts#L152-L167}
*/
unregister(fullName: string) {
this.__container__.reset(fullName);
this['__container__'].reset(fullName);

// We overwrote this method from RegistryProxyMixin.
this.__registry__.unregister(fullName);
this['__registry__'].unregister(fullName);
},
});

Expand Down
61 changes: 29 additions & 32 deletions addon-test-support/@ember/test-helpers/-internal/debug-info.ts
@@ -1,23 +1,17 @@
import { _backburner } from '@ember/runloop';
import {
_backburner,
DebugInfo as BackburnerDebugInfo,
QueueItem,
DeferredActionQueues,
} from '@ember/runloop';
} from '@ember/runloop/-private/backburner';
import { DebugInfoHelper, debugInfoHelpers } from './debug-info-helpers';
import {
getPendingWaiterState,
PendingWaiterState,
TestWaiterDebugInfo,
} from '@ember/test-waiters';
import { getPendingWaiterState, PendingWaiterState } from '@ember/test-waiters';

const PENDING_AJAX_REQUESTS = 'Pending AJAX requests';
const PENDING_TEST_WAITERS = 'Pending test waiters';
const SCHEDULED_ASYNC = 'Scheduled async';
const SCHEDULED_AUTORUN = 'Scheduled autorun';

type MaybeDebugInfo = BackburnerDebugInfo | null;
type WaiterDebugInfo = true | unknown[];

interface SettledState {
hasPendingTimers: boolean;
Expand All @@ -37,18 +31,22 @@ interface SummaryInfo {
pendingTimersCount: number;
hasPendingTimers: boolean;
pendingTimersStackTraces: (string | undefined)[];
pendingScheduledQueueItemCount: Number;
pendingScheduledQueueItemCount: number;
pendingScheduledQueueItemStackTraces: (string | undefined)[];
hasRunLoop: boolean;
isRenderPending: boolean;
}

/**
* The base functionality which may be present on the `SettledState` interface
* in the `settled` module (**not** the one in this module).
*/
export default interface DebugInfo {
toConsole: () => void;
}

/**
* Determins if the `getDebugInfo` method is available in the
* Determines if the `getDebugInfo` method is available in the
* running verison of backburner.
*
* @returns {boolean} True if `getDebugInfo` is present in backburner, otherwise false.
Expand Down Expand Up @@ -112,31 +110,30 @@ export class TestDebugInfo implements DebugInfo {
this._summaryInfo.pendingScheduledQueueItemCount =
this._debugInfo.instanceStack
.filter((q) => q)
.reduce((total: Number, item) => {
Object.keys(item).forEach((queueName: string) => {
total += item[queueName].length;
.reduce((total, item) => {
Object.keys(item).forEach((queueName) => {
// SAFETY: this cast is *not* safe, but the underlying type is
// not currently able to be safer than this because it was
// built as a bag-of-queues *and* a structured item originally.
total += (item[queueName] as QueueItem[]).length;
});

return total;
}, 0);
this._summaryInfo.pendingScheduledQueueItemStackTraces =
this._debugInfo.instanceStack
.filter((q) => q)
.reduce(
(
stacks: string[],
deferredActionQueues: DeferredActionQueues
) => {
Object.keys(deferredActionQueues).forEach((queue) => {
deferredActionQueues[queue].forEach(
(queueItem: QueueItem) =>
queueItem.stack && stacks.push(queueItem.stack)
);
});
return stacks;
},
[]
);
.reduce((stacks, deferredActionQueues) => {
Object.keys(deferredActionQueues).forEach((queue) => {
// SAFETY: this cast is *not* safe, but the underlying type is
// not currently able to be safer than this because it was
// built as a bag-of-queues *and* a structured item originally.
(deferredActionQueues[queue] as QueueItem[]).forEach(
(queueItem) => queueItem.stack && stacks.push(queueItem.stack)
);
});
return stacks;
}, [] as string[]);
}

if (this._summaryInfo.hasPendingTestWaiters) {
Expand Down Expand Up @@ -165,12 +162,12 @@ export class TestDebugInfo implements DebugInfo {

Object.keys(summary.pendingTestWaiterInfo.waiters).forEach(
(waiterName) => {
let waiterDebugInfo: WaiterDebugInfo =
let waiterDebugInfo =
summary.pendingTestWaiterInfo.waiters[waiterName];

if (Array.isArray(waiterDebugInfo)) {
_console.group(waiterName);
waiterDebugInfo.forEach((debugInfo: TestWaiterDebugInfo) => {
waiterDebugInfo.forEach((debugInfo) => {
_console.log(
`${debugInfo.label ? debugInfo.label : 'stack'}: ${
debugInfo.stack
Expand Down Expand Up @@ -221,7 +218,7 @@ export class TestDebugInfo implements DebugInfo {
});
}

_formatCount(title: string, count: Number): string {
_formatCount(title: string, count: number): string {
return `${title}: ${count}`;
}
}
10 changes: 5 additions & 5 deletions addon-test-support/@ember/test-helpers/-internal/deprecations.ts
Expand Up @@ -13,7 +13,7 @@ export interface DeprecationOptions {

export interface DeprecationFailure {
message: string;
options: DeprecationOptions;
options?: DeprecationOptions;
}

const DEPRECATIONS = new WeakMap<BaseContext, Array<DeprecationFailure>>();
Expand Down Expand Up @@ -53,12 +53,12 @@ export function getDeprecationsForContext(
*
* @private
* @param {BaseContext} [context] the test context
* @param {CallableFunction} [callback] The callback that when executed will have its DeprecationFailure recorded
* @param {Function} [callback] The callback that when executed will have its DeprecationFailure recorded
* @return {Array<DeprecationFailure>} The Deprecation Failures associated with the corresponding baseContext which occured while the CallbackFunction was executed
*/
export function getDeprecationsDuringCallbackForContext(
context: BaseContext,
callback: CallableFunction
callback: () => void
): Array<DeprecationFailure> | Promise<Array<DeprecationFailure>> {
if (!context) {
throw new TypeError(
Expand Down Expand Up @@ -92,7 +92,7 @@ if (typeof URLSearchParams !== 'undefined') {
// those deprecations will be squelched
if (disabledDeprecations) {
registerDeprecationHandler((message, options, next) => {
if (!disabledDeprecations.includes(options.id)) {
if (!options || !disabledDeprecations.includes(options.id)) {
next.apply(null, [message, options]);
}
});
Expand All @@ -102,7 +102,7 @@ if (typeof URLSearchParams !== 'undefined') {
// `some-other-thing` deprecation is triggered, this `debugger` will be hit`
if (debugDeprecations) {
registerDeprecationHandler((message, options, next) => {
if (debugDeprecations.includes(options.id)) {
if (options && debugDeprecations.includes(options.id)) {
debugger; // eslint-disable-line no-debugger
}

Expand Down
8 changes: 4 additions & 4 deletions addon-test-support/@ember/test-helpers/-internal/warnings.ts
Expand Up @@ -48,12 +48,12 @@ export function getWarningsForContext(context: BaseContext): Array<Warning> {
*
* @private
* @param {BaseContext} [context] the test context
* @param {CallableFunction} [callback] The callback that when executed will have its warnings recorded
* @param {Function} [callback] The callback that when executed will have its warnings recorded
* @return {Array<Warning>} The warnings associated with the corresponding baseContext which occured while the CallbackFunction was executed
*/
export function getWarningsDuringCallbackForContext(
context: BaseContext,
callback: CallableFunction
callback: () => void
): Array<Warning> | Promise<Array<Warning>> {
if (!context) {
throw new TypeError(
Expand Down Expand Up @@ -89,7 +89,7 @@ if (typeof URLSearchParams !== 'undefined') {
// those warnings will be squelched
if (disabledWarnings) {
registerWarnHandler((message, options, next) => {
if (!disabledWarnings.includes(options.id)) {
if (!options || !disabledWarnings.includes(options.id)) {
next.apply(null, [message, options]);
}
});
Expand All @@ -99,7 +99,7 @@ if (typeof URLSearchParams !== 'undefined') {
// `some-other-thing` warning is triggered, this `debugger` will be hit`
if (debugWarnings) {
registerWarnHandler((message, options, next) => {
if (debugWarnings.includes(options.id)) {
if (options && debugWarnings.includes(options.id)) {
debugger; // eslint-disable-line no-debugger
}

Expand Down
3 changes: 2 additions & 1 deletion addon-test-support/@ember/test-helpers/build-owner.ts
@@ -1,5 +1,5 @@
import Application from '@ember/application';
import type Resolver from '@ember/application/resolver';
import type Resolver from 'ember-resolver';

import { Promise } from './-utils';

Expand All @@ -13,6 +13,7 @@ export interface Owner
ContainerProxyMixin,
RegistryProxyMixin {
_emberTestHelpersMockOwner?: boolean;
rootElement?: string | Element;

_lookupFactory?(key: string): any;

Expand Down
16 changes: 12 additions & 4 deletions addon-test-support/@ember/test-helpers/dom/-target.ts
Expand Up @@ -7,8 +7,12 @@ export interface HTMLElementContentEditable extends HTMLElement {
}

// eslint-disable-next-line require-jsdoc
export function isElement(target: any): target is Element {
return target.nodeType === Node.ELEMENT_NODE;
export function isElement(target: unknown): target is Element {
return (
target !== null &&
typeof target === 'object' &&
Reflect.get(target, 'nodeType') === Node.ELEMENT_NODE
);
}

// eslint-disable-next-line require-jsdoc
Expand All @@ -17,8 +21,12 @@ export function isWindow(target: Target): target is Window {
}

// eslint-disable-next-line require-jsdoc
export function isDocument(target: any): target is Document {
return target.nodeType === Node.DOCUMENT_NODE;
export function isDocument(target: unknown): target is Document {
return (
target !== null &&
typeof target === 'object' &&
Reflect.get(target, 'nodeType') === Node.DOCUMENT_NODE
);
}

// eslint-disable-next-line require-jsdoc
Expand Down
@@ -1,4 +1,4 @@
import { getContext } from '../setup-context';
import { getContext, isTestContext } from '../setup-context';
import { isDocument, isElement } from './-target';

/**
Expand All @@ -9,14 +9,15 @@ import { isDocument, isElement } from './-target';
*/
export default function getRootElement(): Element | Document {
let context = getContext();
let owner = context && context.owner;

if (!owner) {
if (!context || !isTestContext(context) || !context.owner) {
throw new Error(
'Must setup rendering context before attempting to interact with elements.'
);
}

let owner = context.owner;
chriskrycho marked this conversation as resolved.
Show resolved Hide resolved

let rootElement;
// When the host app uses `setApplication` (instead of `setResolver`) the owner has
// a `rootElement` set on it with the element or id to be used
Expand Down
3 changes: 1 addition & 2 deletions addon-test-support/@ember/test-helpers/dom/tab.ts
Expand Up @@ -87,8 +87,7 @@ function compileFocusAreas(root: Element = document.body) {
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
},
},
false
}
);

let node: Node | null;
Expand Down