1
+ import { run } from '@ember/runloop' ;
1
2
import type { SetupContextOptions , TestContext as EmberTestContext } from '@ember/test-helpers' ;
2
3
import { getTestMetadata , hasCalledSetupRenderingContext , setupContext , teardownContext } from '@ember/test-helpers' ;
3
4
import type { Owner } from '@ember/test-helpers/build-owner' ;
4
5
6
+ import { hbs } from 'ember-cli-htmlbars' ;
5
7
import AbstractTestLoader from 'ember-cli-test-loader/test-support/index' ;
6
8
7
9
import { module as _module , skip as _skip , test as _test , todo as _todo } from './-define' ;
10
+ import isComponent from './-ember/is-component' ;
8
11
import type { Hooks , ModuleCallback , TestCallback } from './-types' ;
9
12
import { setupGlobalHooks } from './internals/config' ;
10
13
import { PublicTestInfo } from './internals/run' ;
11
14
15
+ // const OUTLET_TEMPLATE = hbs`{{outlet}}`;
16
+ const INVOKE_PROVIDED_COMPONENT = hbs `<this.ProvidedComponent />` as object ;
17
+
12
18
export interface TestContext extends EmberTestContext {
13
19
element : HTMLDivElement ;
14
20
}
21
+ export interface RenderingTestContext extends TestContext {
22
+ [ hasCalledSetupRenderingContext ] : boolean ;
23
+ render ( template : object ) : Promise < void > ;
24
+ }
15
25
16
26
export function module < TC extends TestContext = TestContext > ( name : string , cb : ModuleCallback < TC > ) : void {
17
27
return _module < TC > ( name , cb ) ;
@@ -67,12 +77,13 @@ export function setupTest<TC extends TestContext>(hooks: Hooks<TC>, opts?: Setup
67
77
} ) ;
68
78
}
69
79
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 & {
72
83
[ PublicTestInfo ] : { id : string ; name : string } ;
73
84
rootElement : HTMLDivElement ;
74
85
} {
75
- ( context as unknown as { [ hasCalledSetupRenderingContext ] : boolean } ) [ hasCalledSetupRenderingContext ] = true ;
86
+ ( context as unknown as RenderingTestContext ) [ hasCalledSetupRenderingContext ] = true ;
76
87
}
77
88
78
89
function upgradeOwner ( owner : Owner ) : asserts owner is FullOwner { }
@@ -85,6 +96,7 @@ export function setupRenderingTest<TC extends TestContext>(hooks: Hooks<TC>, opt
85
96
86
97
hooks . beforeEach ( async function ( ) {
87
98
upgradeContext ( this ) ;
99
+ this . render = ( template : object ) => render ( this , template ) ;
88
100
const opts = Object . assign ( { } , _options ) ;
89
101
const testMetadata = getTestMetadata ( this ) ;
90
102
testMetadata . setupTypes . push ( 'setupRenderingContext' ) ;
@@ -102,7 +114,7 @@ export function setupRenderingTest<TC extends TestContext>(hooks: Hooks<TC>, opt
102
114
const { owner } = this ;
103
115
upgradeOwner ( owner ) ;
104
116
105
- const OutletView = owner . factoryFor ( 'view:-outlet' ) ;
117
+ const OutletView = owner . factoryFor ( 'view:-outlet' ) ! ;
106
118
const environment = owner . lookup ( '-environment:main' ) ;
107
119
const template = owner . lookup ( 'template:-outlet' ) ;
108
120
testContainer . setAttribute ( 'test-id' , this [ PublicTestInfo ] . id ) ;
@@ -111,7 +123,7 @@ export function setupRenderingTest<TC extends TestContext>(hooks: Hooks<TC>, opt
111
123
const toplevelView = OutletView . create ( {
112
124
template,
113
125
environment,
114
- } ) as { appendTo : ( element : Element ) => void } ;
126
+ } ) as Outlet ;
115
127
116
128
owner . register ( '-top-level-view:main' , {
117
129
create ( ) {
@@ -177,3 +189,99 @@ export function configure() {
177
189
178
190
loadTests ( ) ;
179
191
}
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
+ }
0 commit comments