UNPKG

7.71 kBJavaScriptView Raw
1import './setup';
2import {Container} from 'aurelia-dependency-injection';
3import {HtmlBehaviorResource} from '../src/html-behavior';
4import {CompositionEngine} from '../src/composition-engine';
5import {CompositionTransactionOwnershipToken} from '../src/composition-transaction';
6import {ViewResources} from '../src/view-resources';
7import {DOM} from 'aurelia-pal';
8import { ViewSlot } from '../src/view-slot';
9
10describe('CompositionEngine', () => {
11 /**@type {Container} */
12 let container;
13 let mockModule;
14 /**@type {CompositionEngine} */
15 let compositionEngine;
16
17 function createCompositionContext(viewModel) {
18 let host = document.createElement('div');
19 let compositionContext = new CompositionContext({
20 host: host,
21 viewSlot: new ViewSlot(host, true),
22 container: container,
23 viewModel: viewModel
24 });
25 return compositionContext;
26 }
27
28 beforeEach(() => {
29 container = new Container();
30 compositionEngine = container.get(CompositionEngine);
31 });
32
33 describe('ensureViewModel()', () => {
34
35 it('ensures view model when view model\'s a string', done => {
36 class MyClass {
37 message = 'My class';
38 }
39
40 mockModule = {
41 MyClass: MyClass
42 };
43 spyOn(compositionEngine.viewEngine.loader, 'loadModule')
44 .and
45 .callFake(() => new Promise(resolve => setTimeout(() => resolve(mockModule), 50)));
46
47 let compositionContext = createCompositionContext('');
48 container.registerInstance(DOM.Element, compositionContext.host);
49
50 compositionEngine.ensureViewModel(compositionContext).then((context) => {
51 expect(context).toBe(compositionContext);
52 expect(context.viewModel instanceof MyClass).toBe(true);
53 done();
54 });
55 });
56
57 it('ensures view model when view model is a class', done => {
58 class MyClass {
59 message = 'My class';
60 }
61
62 let compositionContext = createCompositionContext(MyClass);
63 container.registerInstance(DOM.Element, compositionContext.host);
64
65 compositionEngine.ensureViewModel(compositionContext).then((context) => {
66 expect(context).toBe(compositionContext);
67 expect(context.viewModel instanceof MyClass).toBe(true);
68 }).then(done).catch(done.fail);
69 });
70
71 it('registers instances with the "childContainer" only', done => {
72 // instances are scoped to their own container
73 class MyClass {
74 message = 'My class';
75 }
76
77 Promise.all([
78 compositionEngine.ensureViewModel(createCompositionContext(MyClass)),
79 compositionEngine.ensureViewModel(createCompositionContext(MyClass))
80 ]).then(contexts => {
81 let childContainerOne = contexts[0].childContainer;
82 let childContainerTwo = contexts[1].childContainer;
83
84 expect(childContainerOne.hasResolver(MyClass)).toBe(true);
85 expect(childContainerTwo.hasResolver(MyClass)).toBe(true);
86 expect(container.hasResolver(MyClass)).toBe(false);
87 expect(childContainerOne.get(MyClass)).not.toBe(childContainerTwo.get(MyClass));
88 done();
89 }).catch(done.fail);
90 });
91
92 it('ensures view model when view model is an object', done => {
93 class MyClass {
94 message = 'My class';
95 }
96
97 let compositionContext = createCompositionContext(new MyClass());
98 container.registerInstance(DOM.Element, compositionContext.host);
99
100 compositionEngine.ensureViewModel(compositionContext).then((context) => {
101 expect(context).toBe(compositionContext);
102 expect(context.viewModel).toBe(compositionContext.viewModel);
103 expect(container.hasResolver(MyClass)).toBe(false);
104 expect(context.viewModel instanceof MyClass).toBe(true);
105 done();
106 });
107 });
108 });
109
110 describe('compose', () => {
111
112 describe('when viewModel is specified', () => {
113
114 it('composes', done => {
115 class MyClass {
116 static $view = '<template></template>';
117 }
118
119 compositionEngine
120 .compose(createCompositionContext(MyClass))
121 .then(controller => {
122 expect(controller.viewModel instanceof MyClass).toBe(true);
123 done();
124 })
125 .catch(done.fail);
126 });
127
128 it('waits for composition transaction to complete before binding. Fixes https://github.com/aurelia/templating/issues/632', done => {
129 let track = 0;
130
131 const originalWait = CompositionTransactionOwnershipToken.prototype.waitForCompositionComplete;
132 CompositionTransactionOwnershipToken.prototype.waitForCompositionComplete = function() {
133 track = 1;
134 return originalWait.apply(this, arguments);
135 }
136
137 class MyClass {
138 static $view = '<template></template>';
139
140 bind() {
141 expect(track).toBe(1);
142 }
143 }
144
145 compositionEngine
146 .compose(createCompositionContext(MyClass))
147 .then(controller => {
148 CompositionTransactionOwnershipToken.prototype.waitForCompositionComplete = originalWait;
149 expect(track).not.toBe(0);
150 done();
151 })
152 .catch((ex) => {
153 CompositionTransactionOwnershipToken.prototype.waitForCompositionComplete = originalWait;
154 done.fail(ex);
155 })
156 });
157 });
158 });
159
160
161 /**
162 * Instructs the composition engine how to dynamically compose a component.
163 */
164 class CompositionContext {
165 /**
166 * @param {Partial<CompositionContext>} context
167 */
168 constructor(context) {
169
170 /**
171 * The parent Container for the component creation.
172 * @type {Container}
173 */
174 this.container = undefined;
175
176 /**
177 * The child Container for the component creation. One will be created from the parent if not provided.
178 * @type {Container}
179 */
180 this.childContainer = undefined;
181
182 /**
183 * The context in which the view model is executed in.
184 */
185 this.bindingContext = undefined;
186
187 /**
188 * A secondary binding context that can override the standard context.
189 * @type {OverrideContext}
190 */
191 this.overrideContext = undefined;
192
193 /**
194 * The view model url or instance for the component.
195 * @type {string | object}
196 */
197 this.viewModel = undefined;
198
199 /**
200 * Data to be passed to the "activate" hook on the view model.
201 */
202 this.model = undefined;
203
204 /**
205 * The HtmlBehaviorResource for the component.
206 * @type {HtmlBehaviorResource}
207 */
208 this.viewModelResource = undefined;
209
210 /**
211 * The view resources for the view in which the component should be created.
212 * @type {ViewResources}
213 */
214 this.viewResources = undefined;
215
216 /**
217 * The view inside which this composition is happening.
218 * @type {View}
219 */
220 this.owningView = undefined;
221
222 /**
223 * The view url or view strategy to override the default view location convention.
224 * @type {string | ViewStrategy}
225 */
226 this.view = undefined;
227
228 /**
229 * The slot to push the dynamically composed component into.
230 * @type {ViewSlot}
231 */
232 this.viewSlot = undefined;
233
234 /**
235 * Should the composition system skip calling the "activate" hook on the view model.
236 */
237 this.skipActivation = false;
238
239 /**
240 * The element that will parent the dynamic component.
241 * It will be registered in the child container of this composition.
242 * @type {Element}
243 */
244 this.host = null;
245 Object.assign(this, context);
246 }
247 }
248});