import { ENV } from 'ember-environment';
import Controller from '@ember/controller';
import { moduleFor, ApplicationTest } from '../../utils/test-case';
import { strip } from '../../utils/abstract-test-case';
import { Route } from 'ember-routing';
import { Component } from 'ember-glimmer';
moduleFor(
'Application test: rendering',
class extends ApplicationTest {
constructor() {
super(...arguments);
this._APPLICATION_TEMPLATE_WRAPPER = ENV._APPLICATION_TEMPLATE_WRAPPER;
}
teardown() {
super.teardown();
ENV._APPLICATION_TEMPLATE_WRAPPER = this._APPLICATION_TEMPLATE_WRAPPER;
}
['@test it can render the application template with a wrapper']() {
ENV._APPLICATION_TEMPLATE_WRAPPER = true;
this.addTemplate('application', 'Hello world!');
return this.visit('/').then(() => {
this.assertComponentElement(this.element, { content: 'Hello world!' });
});
}
['@test it can render the application template without a wrapper']() {
ENV._APPLICATION_TEMPLATE_WRAPPER = false;
this.addTemplate('application', 'Hello world!');
return this.visit('/').then(() => {
this.assertInnerHTML('Hello world!');
});
}
['@test it can access the model provided by the route']() {
this.add(
'route:application',
Route.extend({
model() {
return ['red', 'yellow', 'blue'];
},
})
);
this.addTemplate(
'application',
strip`
{{#each model as |item|}}
- {{item}}
{{/each}}
`
);
return this.visit('/').then(() => {
this.assertInnerHTML(strip`
`);
});
}
['@test it can render a nested route']() {
this.router.map(function() {
this.route('lists', function() {
this.route('colors', function() {
this.route('favorite');
});
});
});
// The "favorite" route will inherit the model
this.add(
'route:lists.colors',
Route.extend({
model() {
return ['red', 'yellow', 'blue'];
},
})
);
this.addTemplate(
'lists.colors.favorite',
strip`
{{#each model as |item|}}
- {{item}}
{{/each}}
`
);
return this.visit('/lists/colors/favorite').then(() => {
this.assertInnerHTML(strip`
`);
});
}
['@test it can render into named outlets']() {
this.router.map(function() {
this.route('colors');
});
this.addTemplate(
'application',
strip`
{{outlet}}
`
);
this.addTemplate(
'nav',
strip`
Ember
`
);
this.add(
'route:application',
Route.extend({
renderTemplate() {
this.render();
this.render('nav', {
into: 'application',
outlet: 'nav',
});
},
})
);
this.add(
'route:colors',
Route.extend({
model() {
return ['red', 'yellow', 'blue'];
},
})
);
this.addTemplate(
'colors',
strip`
{{#each model as |item|}}
- {{item}}
{{/each}}
`
);
return this.visit('/colors').then(() => {
this.assertInnerHTML(strip`
`);
});
}
['@test it can render into named outlets']() {
this.router.map(function() {
this.route('colors');
});
this.addTemplate(
'application',
strip`
{{outlet}}
`
);
this.addTemplate(
'nav',
strip`
Ember
`
);
this.add(
'route:application',
Route.extend({
renderTemplate() {
this.render();
this.render('nav', {
into: 'application',
outlet: 'nav',
});
},
})
);
this.add(
'route:colors',
Route.extend({
model() {
return ['red', 'yellow', 'blue'];
},
})
);
this.addTemplate(
'colors',
strip`
{{#each model as |item|}}
- {{item}}
{{/each}}
`
);
return this.visit('/colors').then(() => {
this.assertInnerHTML(strip`
`);
});
}
['@test it should update the outlets when switching between routes']() {
this.router.map(function() {
this.route('a');
this.route('b', function() {
this.route('c');
this.route('d');
});
});
this.addTemplate('a', 'A{{outlet}}');
this.addTemplate('b', 'B{{outlet}}');
this.addTemplate('b.c', 'C');
this.addTemplate('b.d', 'D');
return this.visit('/b/c')
.then(() => {
// this.assertComponentElement(this.firstChild, { content: 'BC' });
this.assertText('BC');
return this.visit('/a');
})
.then(() => {
// this.assertComponentElement(this.firstChild, { content: 'A' });
this.assertText('A');
return this.visit('/b/d');
})
.then(() => {
this.assertText('BD');
// this.assertComponentElement(this.firstChild, { content: 'BD' });
});
}
['@test it should produce a stable DOM when the model changes']() {
this.router.map(function() {
this.route('color', { path: '/colors/:color' });
});
this.add(
'route:color',
Route.extend({
model(params) {
return params.color;
},
})
);
this.addTemplate('color', 'color: {{model}}');
return this.visit('/colors/red')
.then(() => {
this.assertInnerHTML('color: red');
this.takeSnapshot();
return this.visit('/colors/green');
})
.then(() => {
this.assertInnerHTML('color: green');
this.assertInvariants();
});
}
['@test it should have the right controller in scope for the route template']() {
this.router.map(function() {
this.route('a');
this.route('b');
});
this.add(
'controller:a',
Controller.extend({
value: 'a',
})
);
this.add(
'controller:b',
Controller.extend({
value: 'b',
})
);
this.addTemplate('a', '{{value}}');
this.addTemplate('b', '{{value}}');
return this.visit('/a')
.then(() => {
this.assertText('a');
return this.visit('/b');
})
.then(() => this.assertText('b'));
}
['@test it should update correctly when the controller changes']() {
this.router.map(function() {
this.route('color', { path: '/colors/:color' });
});
this.add(
'route:color',
Route.extend({
model(params) {
return { color: params.color };
},
renderTemplate(controller, model) {
this.render({ controller: model.color, model });
},
})
);
this.add(
'controller:red',
Controller.extend({
color: 'red',
})
);
this.add(
'controller:green',
Controller.extend({
color: 'green',
})
);
this.addTemplate('color', 'model color: {{model.color}}, controller color: {{color}}');
return this.visit('/colors/red')
.then(() => {
this.assertInnerHTML('model color: red, controller color: red');
return this.visit('/colors/green');
})
.then(() => {
this.assertInnerHTML('model color: green, controller color: green');
});
}
['@test it should produce a stable DOM when two routes render the same template']() {
this.router.map(function() {
this.route('a');
this.route('b');
});
this.add(
'route:a',
Route.extend({
model() {
return 'A';
},
renderTemplate(controller, model) {
this.render('common', { controller: 'common', model });
},
})
);
this.add(
'route:b',
Route.extend({
model() {
return 'B';
},
renderTemplate(controller, model) {
this.render('common', { controller: 'common', model });
},
})
);
this.add(
'controller:common',
Controller.extend({
prefix: 'common',
})
);
this.addTemplate('common', '{{prefix}} {{model}}');
return this.visit('/a')
.then(() => {
this.assertInnerHTML('common A');
this.takeSnapshot();
return this.visit('/b');
})
.then(() => {
this.assertInnerHTML('common B');
this.assertInvariants();
});
}
// Regression test, glimmer child outlets tried to assume the first element.
// but the if put-args clobbered the args used by did-create-element.
// I wish there was a way to assert that the OutletComponentManager did not
// receive a didCreateElement.
['@test a child outlet is always a fragment']() {
this.addTemplate('application', '{{outlet}}');
this.addTemplate('index', '{{#if true}}1{{/if}}2
');
return this.visit('/').then(() => {
this.assertInnerHTML('12
');
});
}
['@test it allows a transition during route activate']() {
this.router.map(function() {
this.route('a');
});
this.add(
'route:index',
Route.extend({
activate() {
this.transitionTo('a');
},
})
);
this.addTemplate('a', 'Hello from A!');
return this.visit('/').then(() => {
this.assertInnerHTML('Hello from A!');
});
}
['@test it emits a useful backtracking re-render assertion message']() {
this.router.map(function() {
this.route('routeWithError');
});
this.add(
'route:routeWithError',
Route.extend({
model() {
return { name: 'Alex' };
},
})
);
this.addTemplate('routeWithError', 'Hi {{model.name}} {{x-foo person=model}}');
this.addComponent('x-foo', {
ComponentClass: Component.extend({
init() {
this._super(...arguments);
this.set('person.name', 'Ben');
},
}),
template: 'Hi {{person.name}} from component',
});
let expectedBacktrackingMessage = /modified "model\.name" twice on \[object Object\] in a single render\. It was rendered in "template:my-app\/templates\/routeWithError.hbs" and modified in "component:x-foo"/;
return this.visit('/').then(() => {
expectAssertion(() => {
this.visit('/routeWithError');
}, expectedBacktrackingMessage);
});
}
['@test route templates with {{{undefined}}} [GH#14924] [GH#16172]']() {
this.router.map(function() {
this.route('first');
this.route('second');
});
this.addTemplate('first', 'first');
this.addTemplate('second', '{{{undefined}}}second');
return this.visit('/first')
.then(() => {
this.assertText('first');
return this.visit('/second');
})
.then(() => {
this.assertText('second');
return this.visit('/first');
})
.then(() => {
this.assertText('first');
});
}
}
);