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

Cleanup fallback codepaths for older Ember versions #1298

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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

This file was deleted.

26 changes: 0 additions & 26 deletions addon-test-support/@ember/test-helpers/-internal/is-component.ts

This file was deleted.

15 changes: 4 additions & 11 deletions addon-test-support/@ember/test-helpers/-internal/render-settled.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Ember from 'ember';
import {
macroCondition,
importSync,
Expand All @@ -11,20 +10,14 @@ if (macroCondition(dependencySatisfies('ember-source', '>=4.5.0-beta.1'))) {
//@ts-ignore
renderSettled = importSync('@ember/renderer').renderSettled;
} else if (
macroCondition(dependencySatisfies('ember-source', '>=3.27.0-alpha.1'))
macroCondition(dependencySatisfies('ember-source', '>=4.0.0-alpha.1'))
) {
//@ts-ignore
renderSettled = importSync('@ember/-internals/glimmer').renderSettled;
} else if (
macroCondition(dependencySatisfies('ember-source', '>=3.6.0-alpha.1'))
) {
renderSettled = (Ember as any).__loader.require(
'@ember/-internals/glimmer'
).renderSettled;
} else {
renderSettled = (Ember as any).__loader.require(
'ember-glimmer'
).renderSettled;
throw new Error(
'Using an unsupported version of Ember (@ember/test-helpers@3.0.0+ requires Ember 4 or higher)'
);
}

export default renderSettled;
102 changes: 28 additions & 74 deletions addon-test-support/@ember/test-helpers/setup-application-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
TestContext,
} from './setup-context';
import global from './global';
import hasEmberVersion from './has-ember-version';
import settled from './settled';
import getTestMetadata from './test-metadata';
import { runHooks } from './-internal/helper-hooks';
Expand All @@ -18,9 +17,7 @@ export interface ApplicationTestContext extends TestContext {
element?: Element | null;
}

const CAN_USE_ROUTER_EVENTS = hasEmberVersion(3, 6);
let routerTransitionsPending: boolean | null = null;
const ROUTER = new WeakMap();
const HAS_SETUP_ROUTER = new WeakMap();

// eslint-disable-next-line require-jsdoc
Expand All @@ -37,33 +34,7 @@ export function isApplicationTestContext(
@returns {(boolean|null)} if there are pending transitions
*/
export function hasPendingTransitions(): boolean | null {
if (CAN_USE_ROUTER_EVENTS) {
return routerTransitionsPending;
}

let context = getContext();

// there is no current context, we cannot check
if (context === undefined) {
return null;
}

let router = ROUTER.get(context);

if (router === undefined) {
// if there is no router (e.g. no `visit` calls made yet), we cannot
// check for pending transitions but this is explicitly not an error
// condition
return null;
}

let routerMicrolib = router._routerMicrolib || router.router;

if (routerMicrolib === undefined) {
return null;
}

return !!routerMicrolib.activeTransition;
return routerTransitionsPending;
}

/**
Expand All @@ -90,28 +61,20 @@ export function setupRouterSettlednessTracking() {

let { owner } = context;
let router: Router | RouterService;
if (CAN_USE_ROUTER_EVENTS) {
// SAFETY: unfortunately we cannot `assert` here at present because the
// class is not exported, only the type, since it is not designed to be
// sub-classed. The most we can do at present is assert that it at least
// *exists* and assume that if it does exist, it is bound correctly.
let routerService = owner.lookup('service:router');
assert('router service is not set up correctly', !!routerService);
router = routerService as RouterService;

// track pending transitions via the public routeWillChange / routeDidChange APIs
// routeWillChange can fire many times and is only useful to know when we have _started_
// transitioning, we can then use routeDidChange to signal that the transition has settled
router.on('routeWillChange', () => (routerTransitionsPending = true));
router.on('routeDidChange', () => (routerTransitionsPending = false));
} else {
// SAFETY: similarly, this cast cannot be made safer because on the versions
// where we fall into this path, this is *also* not an exported class.
let mainRouter = owner.lookup('router:main');
assert('router:main is not available', !!mainRouter);
router = mainRouter as Router;
ROUTER.set(context, router);
}

// SAFETY: unfortunately we cannot `assert` here at present because the
// class is not exported, only the type, since it is not designed to be
// sub-classed. The most we can do at present is assert that it at least
// *exists* and assume that if it does exist, it is bound correctly.
let routerService = owner.lookup('service:router');
assert('router service is not set up correctly', !!routerService);
router = routerService as RouterService;

// track pending transitions via the public routeWillChange / routeDidChange APIs
// routeWillChange can fire many times and is only useful to know when we have _started_
// transitioning, we can then use routeDidChange to signal that the transition has settled
router.on('routeWillChange', () => (routerTransitionsPending = true));
router.on('routeDidChange', () => (routerTransitionsPending = false));

// hook into teardown to reset local settledness state
let ORIGINAL_WILL_DESTROY = router.willDestroy;
Expand Down Expand Up @@ -204,8 +167,6 @@ export function currentRouteName(): string {
return currentRouteName;
}

const HAS_CURRENT_URL_ON_ROUTER = hasEmberVersion(2, 13);

/**
@public
@returns {string} the applications current url
Expand All @@ -220,29 +181,22 @@ export function currentURL(): string {

let router = context.owner.lookup('router:main');

if (HAS_CURRENT_URL_ON_ROUTER) {
let routerCurrentURL = get(router, 'currentURL');
let routerCurrentURL = get(router, 'currentURL');

// SAFETY: this path is a lie for the sake of the public-facing types. The
// framework itself sees this path, but users never do. A user who calls the
// API without calling `visit()` will see an `UnrecognizedURLError`, rather
// than getting back `null`.
if (routerCurrentURL === null) {
return routerCurrentURL as never as string;
}
// SAFETY: this path is a lie for the sake of the public-facing types. The
// framework itself sees this path, but users never do. A user who calls the
// API without calling `visit()` will see an `UnrecognizedURLError`, rather
// than getting back `null`.
if (routerCurrentURL === null) {
return routerCurrentURL as never as string;
}

assert(
`currentUrl should be a string, but was ${typeof routerCurrentURL}`,
typeof routerCurrentURL === 'string'
);
assert(
`currentUrl should be a string, but was ${typeof routerCurrentURL}`,
typeof routerCurrentURL === 'string'
);

return routerCurrentURL;
} else {
// SAFETY: this is *positively ancient* and should probably be removed at
// some point; old routers which don't have `currentURL` *should* have a
// `location` with `getURL()` per the docs for 2.12.
return (get(router, 'location') as any).getURL();
}
return routerCurrentURL;
}

/**
Expand Down
109 changes: 48 additions & 61 deletions addon-test-support/@ember/test-helpers/setup-rendering-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ import { Owner } from './build-owner';
import getTestMetadata from './test-metadata';
import { assert } from '@ember/debug';
import { runHooks } from './-internal/helper-hooks';
import hasEmberVersion from './has-ember-version';
import isComponent from './-internal/is-component';
import { macroCondition, dependencySatisfies } from '@embroider/macros';
import { ComponentRenderMap, SetUsage } from './setup-context';
import { ensureSafeComponent } from '@embroider/util';
import type { ComponentInstance } from '@glimmer/interfaces';
import type {
ComponentInstance,
InternalComponentManager,
} from '@glimmer/interfaces';
import { importSync } from '@embroider/macros';

let getInternalComponentManager: (
definition: object
) => InternalComponentManager;

// this would be much better as a regular `import` statement, but there really isn't any way to make typescript happy with thayar
getInternalComponentManager =
//@ts-ignore
importSync('@glimmer/manager').getInternalComponentManager;
chriskrycho marked this conversation as resolved.
Show resolved Hide resolved

const OUTLET_TEMPLATE = hbs`{{outlet}}`;
const EMPTY_TEMPLATE = hbs``;
Expand All @@ -33,6 +43,19 @@ export interface RenderingTestContext extends TestContext {
[hasCalledSetupRenderingContext]?: true;
}

/**
* We should ultimately get a new API from @glimmer/runtime that provides this functionality
* (see https://github.com/emberjs/rfcs/pull/785 for more info).
* @private
* @param {Object} maybeComponent The thing you think might be a component
* @returns {boolean} True if it's a component, false if not
*/
function isComponent(maybeComponent: object): boolean {
let manager = getInternalComponentManager(maybeComponent);

return !!manager;
}

// Isolates the notion of transforming a TextContext into a RenderingTestContext.
// eslint-disable-next-line require-jsdoc
function prepare(context: TestContext): RenderingTestContext {
Expand Down Expand Up @@ -131,60 +154,38 @@ export function render(
let OutletTemplate = lookupOutletTemplate(owner);
let ownerToRenderFrom = options?.owner || owner;

if (macroCondition(dependencySatisfies('ember-source', '<3.24.0'))) {
// Pre 3.24, we just don't support rendering components at all, so we error
// if we find anything that isn't a template.
const isTemplate =
('__id' in templateOrComponent && '__meta' in templateOrComponent) ||
('id' in templateOrComponent && 'meta' in templateOrComponent);
if (isComponent(templateOrComponent)) {
// We use this to track when `render` is used with a component so that we can throw an
// assertion if `this.{set,setProperty} is used in the same test
ComponentRenderMap.set(context, true);

if (!isTemplate) {
throw new Error(
`Using \`render\` with something other than a pre-compiled template is not supported until Ember 3.24 (you are on ${Ember.VERSION}).`
const setCalls = SetUsage.get(context);

if (setCalls !== undefined) {
assert(
`You cannot call \`this.set\` or \`this.setProperties\` when passing a component to \`render\`, but they were called for the following properties:\n${setCalls
.map((key) => ` - ${key}`)
.join('\n')}`
);
}

let ProvidedComponent = ensureSafeComponent(
templateOrComponent,
context
);

context = {
ProvidedComponent,
};
templateOrComponent = INVOKE_PROVIDED_COMPONENT;
} else {
templateId += 1;
let templateFullName = `template:-undertest-${templateId}` as const;
ownerToRenderFrom.register(templateFullName, templateOrComponent);
templateOrComponent = lookupTemplate(
ownerToRenderFrom,
templateFullName
);
} else {
if (isComponent(templateOrComponent, owner)) {
// We use this to track when `render` is used with a component so that we can throw an
// assertion if `this.{set,setProperty} is used in the same test
ComponentRenderMap.set(context, true);

const setCalls = SetUsage.get(context);

if (setCalls !== undefined) {
assert(
`You cannot call \`this.set\` or \`this.setProperties\` when passing a component to \`render\`, but they were called for the following properties:\n${setCalls
.map((key) => ` - ${key}`)
.join('\n')}`
);
}

let ProvidedComponent = ensureSafeComponent(
templateOrComponent,
context
);

context = {
ProvidedComponent,
};
templateOrComponent = INVOKE_PROVIDED_COMPONENT;
} else {
templateId += 1;
let templateFullName = `template:-undertest-${templateId}` as const;
ownerToRenderFrom.register(templateFullName, templateOrComponent);
templateOrComponent = lookupTemplate(
ownerToRenderFrom,
templateFullName
);
}
}

let outletState = {
Expand Down Expand Up @@ -216,20 +217,6 @@ export function render(
};
toplevelView.setOutletState(outletState);

// Ember's rendering engine is integration with the run loop so that when a run
// loop starts, the rendering is scheduled to be done.
//
// Ember should be ensuring an instance on its own here (the act of
// setting outletState should ensureInstance, since we know we need to
// render), but on Ember < 3.23 that is not guaranteed.
if (!hasEmberVersion(3, 23)) {
// SAFETY: this was correct and type checked on the Ember v3 types, but
// since the `run` namespace does not exist in Ember v4, this no longer
// can be type checked. When (eventually) dropping support for Ember v3,
// and therefore for versions before 3.23, this can be removed entirely.
(run as any).backburner.ensureInstance();
}

// returning settled here because the actual rendering does not happen until
// the renderer detects it is dirty (which happens on backburner's end
// hook), see the following implementation details:
Expand Down
Loading