Skip to content

Commit 9b0b461

Browse files
committed
more stuffs
1 parent 61a1b35 commit 9b0b461

File tree

10 files changed

+160
-18
lines changed

10 files changed

+160
-18
lines changed

packages/diagnostic/.eslintrc.cjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = {
1515
base.rules(),
1616
imports.rules(),
1717
isolation.rules({
18-
allowedImports: ['@ember/debug', '@ember/test-helpers'],
18+
allowedImports: ['@ember/debug', '@ember/test-helpers', '@glimmer/manager', '@ember/runloop'],
1919
}),
2020
{}
2121
),

packages/diagnostic/package.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
"peerDependencies": {
7676
"@ember/test-helpers": ">= 3.3.0",
7777
"@embroider/addon-shim": ">= 1.8.7",
78-
"ember-cli-test-loader": ">= 3.1.0"
78+
"ember-cli-test-loader": ">= 3.1.0",
79+
"ember-source": ">= 4.12.0"
7980
},
8081
"peerDependenciesMeta": {
8182
"@ember/test-helpers": {
@@ -86,12 +87,16 @@
8687
},
8788
"@embroider/addon-shim": {
8889
"optional": true
90+
},
91+
"ember-source": {
92+
"optional": true
8993
}
9094
},
9195
"dependencies": {
9296
"chalk": "^5.3.0",
9397
"debug": "^4.3.4",
9498
"pnpm-sync-dependencies-meta-injected": "0.0.10",
99+
"ember-cli-htmlbars": "^6.3.0",
95100
"tmp": "^0.2.1"
96101
},
97102
"devDependencies": {

packages/diagnostic/rollup.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default {
1414
// You can augment this if you need to.
1515
output: addon.output(),
1616

17-
external: external(['@ember/test-helpers', 'ember-cli-test-loader/test-support/index']),
17+
external: external(['@ember/runloop', '@ember/test-helpers', 'ember-cli-test-loader/test-support/index']),
1818

1919
plugins: [
2020
// These are the modules that users should be able to import from your
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @ts-expect-error: types for this API is not consistently available (via transitive
2+
// deps) and we do not currently want to make it an explicit dependency. It
3+
// does, however, consistently work at runtime. :sigh:
4+
import { getInternalComponentManager as getComponentManager } from '@glimmer/manager';
5+
6+
export type ComponentLike = object;
7+
8+
/**
9+
* We should ultimately get a new API from @glimmer/runtime that provides this functionality
10+
* (see https://github.com/emberjs/rfcs/pull/785 for more info).
11+
* @private
12+
* @param {Object} maybeComponent The thing you think might be a component
13+
* @returns {boolean} True if it's a component, false if not
14+
*/
15+
function isComponent(maybeComponent: object): maybeComponent is ComponentLike {
16+
return !!getComponentManager(maybeComponent, true);
17+
}
18+
19+
export default isComponent;

packages/diagnostic/src/ember.ts

+113-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1+
import { run } from '@ember/runloop';
12
import type { SetupContextOptions, TestContext as EmberTestContext } from '@ember/test-helpers';
23
import { getTestMetadata, hasCalledSetupRenderingContext, setupContext, teardownContext } from '@ember/test-helpers';
34
import type { Owner } from '@ember/test-helpers/build-owner';
45

6+
import { hbs } from 'ember-cli-htmlbars';
57
import AbstractTestLoader from 'ember-cli-test-loader/test-support/index';
68

79
import { module as _module, skip as _skip, test as _test, todo as _todo } from './-define';
10+
import isComponent from './-ember/is-component';
811
import type { Hooks, ModuleCallback, TestCallback } from './-types';
912
import { setupGlobalHooks } from './internals/config';
1013
import { PublicTestInfo } from './internals/run';
1114

15+
// const OUTLET_TEMPLATE = hbs`{{outlet}}`;
16+
const INVOKE_PROVIDED_COMPONENT = hbs`<this.ProvidedComponent />` as object;
17+
1218
export interface TestContext extends EmberTestContext {
1319
element: HTMLDivElement;
1420
}
21+
export interface RenderingTestContext extends TestContext {
22+
[hasCalledSetupRenderingContext]: boolean;
23+
render(template: object): Promise<void>;
24+
}
1525

1626
export function module<TC extends TestContext = TestContext>(name: string, cb: ModuleCallback<TC>): void {
1727
return _module<TC>(name, cb);
@@ -67,12 +77,13 @@ export function setupTest<TC extends TestContext>(hooks: Hooks<TC>, opts?: Setup
6777
});
6878
}
6979

70-
function upgradeContext(context: TestContext): asserts context is TestContext & {
71-
[hasCalledSetupRenderingContext]: boolean;
80+
type Outlet = { appendTo: (element: Element) => void; setOutletState: (state: object) => void };
81+
82+
function upgradeContext(context: TestContext): asserts context is RenderingTestContext & {
7283
[PublicTestInfo]: { id: string; name: string };
7384
rootElement: HTMLDivElement;
7485
} {
75-
(context as unknown as { [hasCalledSetupRenderingContext]: boolean })[hasCalledSetupRenderingContext] = true;
86+
(context as unknown as RenderingTestContext)[hasCalledSetupRenderingContext] = true;
7687
}
7788

7889
function upgradeOwner(owner: Owner): asserts owner is FullOwner {}
@@ -85,6 +96,7 @@ export function setupRenderingTest<TC extends TestContext>(hooks: Hooks<TC>, opt
8596

8697
hooks.beforeEach(async function () {
8798
upgradeContext(this);
99+
this.render = (template: object) => render(this, template);
88100
const opts = Object.assign({}, _options);
89101
const testMetadata = getTestMetadata(this);
90102
testMetadata.setupTypes.push('setupRenderingContext');
@@ -102,7 +114,7 @@ export function setupRenderingTest<TC extends TestContext>(hooks: Hooks<TC>, opt
102114
const { owner } = this;
103115
upgradeOwner(owner);
104116

105-
const OutletView = owner.factoryFor('view:-outlet');
117+
const OutletView = owner.factoryFor('view:-outlet')!;
106118
const environment = owner.lookup('-environment:main');
107119
const template = owner.lookup('template:-outlet');
108120
testContainer.setAttribute('test-id', this[PublicTestInfo].id);
@@ -111,7 +123,7 @@ export function setupRenderingTest<TC extends TestContext>(hooks: Hooks<TC>, opt
111123
const toplevelView = OutletView.create({
112124
template,
113125
environment,
114-
}) as { appendTo: (element: Element) => void };
126+
}) as Outlet;
115127

116128
owner.register('-top-level-view:main', {
117129
create() {
@@ -177,3 +189,99 @@ export function configure() {
177189

178190
loadTests();
179191
}
192+
193+
export function isRenderingTestContext(context: TestContext): context is RenderingTestContext {
194+
return hasCalledSetupRenderingContext in context;
195+
}
196+
197+
function isTemplateFunction(template: unknown): template is (owner: Owner) => object {
198+
return typeof template === 'function';
199+
}
200+
201+
function lookupTemplate(owner: Owner, templateFullName: RegistryKey): object | undefined {
202+
upgradeOwner(owner);
203+
const template = owner.lookup(templateFullName) as object | ((owner: Owner) => object) | undefined;
204+
if (isTemplateFunction(template)) return template(owner);
205+
return template;
206+
}
207+
208+
function lookupOutletTemplate(owner: Owner): object {
209+
upgradeOwner(owner);
210+
const OutletTemplate = lookupTemplate(owner, 'template:-outlet');
211+
if (!OutletTemplate) {
212+
throw new Error(`Could not find -outlet template`);
213+
// owner.register('template:-outlet', OUTLET_TEMPLATE);
214+
// OutletTemplate = lookupTemplate(owner, 'template:-outlet');
215+
}
216+
217+
return OutletTemplate;
218+
}
219+
220+
let templateId = 0;
221+
// eslint-disable-next-line @typescript-eslint/require-await
222+
export async function render(context: TestContext, template: object): Promise<void> {
223+
if (!template) {
224+
throw new Error('you must pass a template to `render()`');
225+
}
226+
227+
if (!context || !isRenderingTestContext(context)) {
228+
throw new Error('Cannot call `render` without having first called `setupRenderingContext`.');
229+
}
230+
231+
const { owner } = context;
232+
upgradeOwner(owner);
233+
const testMetadata = getTestMetadata(context);
234+
testMetadata.usedHelpers.push('render');
235+
236+
// SAFETY: this is all wildly unsafe, because it is all using private API.
237+
// At some point we should define a path forward for this kind of internal
238+
// API. For now, just flagging it as *NOT* being safe!
239+
240+
const toplevelView = owner.lookup('-top-level-view:main') as Outlet;
241+
const OutletTemplate = lookupOutletTemplate(owner);
242+
243+
let controllerContext: object = context;
244+
if (isComponent(template)) {
245+
controllerContext = {
246+
ProvidedComponent: template,
247+
};
248+
template = INVOKE_PROVIDED_COMPONENT;
249+
}
250+
251+
templateId += 1;
252+
const templateFullName = `template:-undertest-${templateId}` as const;
253+
owner.register(templateFullName, template);
254+
const finalTemplate = lookupTemplate(owner, templateFullName);
255+
256+
const outletState = {
257+
render: {
258+
owner,
259+
into: undefined,
260+
outlet: 'main',
261+
name: 'application',
262+
controller: undefined,
263+
ViewClass: undefined,
264+
template: OutletTemplate,
265+
},
266+
267+
outlets: {
268+
main: {
269+
render: {
270+
owner,
271+
into: undefined,
272+
outlet: 'main',
273+
name: 'index',
274+
controller: controllerContext,
275+
ViewClass: undefined,
276+
template: finalTemplate,
277+
outlets: {},
278+
},
279+
outlets: {},
280+
},
281+
},
282+
};
283+
284+
run(() => {
285+
toplevelView.setOutletState(outletState);
286+
});
287+
}

packages/diagnostic/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"declaration": true,
2626
"declarationMap": true,
2727
"inlineSourceMap": true,
28-
"inlineSources": true
28+
"inlineSources": true,
29+
"types": ["ember-source/types"]
2930
}
3031
}

pnpm-lock.yaml

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/warp-drive__ember/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"_build:production": "bun run build:tests -e production",
2020
"lint": "eslint . --quiet --cache --cache-strategy=content --ext .js,.ts,.mjs,.cjs --report-unused-disable-directives",
2121
"check:types": "tsc --noEmit",
22-
"start": "ember test --port=0 --serve --no-launch",
22+
"start": "bun run build:tests --watch",
2323
"test": "bun ./diagnostic.js",
2424
"_test:production": "bun ./diagnostic.js",
2525
"_syncPnpm": "bun run sync-dependencies-meta-injected"

tests/warp-drive__ember/tests/integration/get-promise-state-test.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { render, rerender, settled } from '@ember/test-helpers';
1+
import { rerender, settled } from '@ember/test-helpers';
22

33
import { hbs } from 'ember-cli-htmlbars';
44

55
import { createDeferred, setPromiseResult } from '@ember-data/request';
6+
import type { RenderingTestContext } from '@warp-drive/diagnostic/ember';
67
import { module, setupRenderingTest, test } from '@warp-drive/diagnostic/ember';
78
import type { PromiseState } from '@warp-drive/ember';
89
import { getPromiseState } from '@warp-drive/ember';
910

1011
module('Integration | get-promise-state', function (hooks) {
1112
setupRenderingTest(hooks);
1213

13-
test('it renders each stage of a promise resolving in a new microtask queue', async function (assert) {
14+
test('it renders each stage of a promise resolving in a new microtask queue', async function (this: RenderingTestContext, assert) {
1415
const defer = createDeferred();
1516

1617
let state: PromiseState;
@@ -26,7 +27,7 @@ module('Integration | get-promise-state', function (hooks) {
2627
return ++counter;
2728
});
2829

29-
await render(
30+
await this.render(
3031
hbs`{{#let (this.getPromiseState this.promise) as |state|}}{{state.result}}<br>Count: {{this.countFor state.result}}{{/let}}`
3132
);
3233
assert.equal(state!.result, null);
@@ -41,7 +42,7 @@ module('Integration | get-promise-state', function (hooks) {
4142
assert.equal(this.element.textContent?.trim(), 'Our DataCount: 2');
4243
});
4344

44-
test('it renders each stage of a promise resolving in the same microtask queue', async function (assert) {
45+
test('it renders each stage of a promise resolving in the same microtask queue', async function (this: RenderingTestContext, assert) {
4546
const promise = Promise.resolve().then(() => 'Our Data');
4647

4748
let state: PromiseState;
@@ -57,16 +58,20 @@ module('Integration | get-promise-state', function (hooks) {
5758
return ++counter;
5859
});
5960

60-
await render(
61+
await this.render(
6162
hbs`{{#let (this.getPromiseState this.promise) as |state|}}{{state.result}}<br>Count: {{this.countFor state.result}}{{/let}}`
6263
);
6364
assert.equal(state!, getPromiseState(promise));
65+
assert.equal(state!.result, null);
66+
assert.equal(counter, 1);
67+
assert.equal(this.element.textContent?.trim(), 'Count: 1');
68+
await rerender();
6469
assert.equal(state!.result, 'Our Data');
6570
assert.equal(counter, 2);
6671
assert.equal(this.element.textContent?.trim(), 'Our DataCount: 2');
6772
});
6873

69-
test('it renders only once when the promise already has a result cached', async function (assert) {
74+
test('it renders only once when the promise already has a result cached', async function (this: RenderingTestContext, assert) {
7075
const promise = Promise.resolve().then(() => 'Our Data');
7176

7277
const result = await promise;
@@ -80,7 +85,7 @@ module('Integration | get-promise-state', function (hooks) {
8085
return ++counter;
8186
});
8287

83-
await render(
88+
await this.render(
8489
hbs`{{#let (this.getPromiseState this.promise) as |state|}}{{state.result}}<br>Count: {{this.countFor state.result}}{{/let}}`
8590
);
8691

tests/warp-drive__ember/tests/test-helper.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ setApplication(Application.create(config.APP));
1818
void start({
1919
tryCatch: false,
2020
debug: false,
21-
concurrency: 10,
21+
concurrency: 1,
2222
groupLogs: false,
2323
instrument: true,
2424
hideReport: false,

0 commit comments

Comments
 (0)