import { moduleFor, ApplicationTestCase } from 'internal-test-helpers';
import { inject as injectService } from '@ember/service';
import { Object as EmberObject, RSVP, onerrorDefault } from '@ember/-internals/runtime';
import { later } from '@ember/runloop';
import Application from '@ember/application';
import ApplicationInstance from '@ember/application/instance';
import Engine from '@ember/engine';
import { Route } from '@ember/-internals/routing';
import { Component, helper, isSerializationFirstNode } from '@ember/-internals/glimmer';
import { compile } from 'ember-template-compiler';
import { ENV } from '@ember/-internals/environment';
function expectAsyncError() {
RSVP.off('error');
}
moduleFor(
'Application - visit()',
class extends ApplicationTestCase {
teardown() {
RSVP.on('error', onerrorDefault);
ENV._APPLICATION_TEMPLATE_WRAPPER = false;
super.teardown();
}
createApplication(options) {
return super.createApplication(options, Application.extend());
}
assertEmptyFixture(message) {
this.assert.strictEqual(
document.getElementById('qunit-fixture').children.length,
0,
`there are no elements in the fixture element ${message ? message : ''}`
);
}
[`@test does not add serialize-mode markers by default`](assert) {
let templateContent = '
Hi, Mom!
';
this.addTemplate('index', templateContent);
let rootElement = document.createElement('div');
let bootOptions = {
isBrowser: false,
rootElement,
};
ENV._APPLICATION_TEMPLATE_WRAPPER = false;
return this.visit('/', bootOptions).then(() => {
assert.equal(
rootElement.innerHTML,
templateContent,
'without serialize flag renders as expected'
);
});
}
[`@test _renderMode: rehydration`](assert) {
assert.expect(2);
let indexTemplate = '
Hi, Mom!
';
this.addTemplate('index', indexTemplate);
let rootElement = document.createElement('div');
let bootOptions = {
isBrowser: false,
rootElement,
_renderMode: 'serialize',
};
ENV._APPLICATION_TEMPLATE_WRAPPER = false;
return this.visit('/', bootOptions)
.then(instance => {
assert.ok(
isSerializationFirstNode(instance.rootElement.firstChild),
'glimmer-vm comment node was not found'
);
})
.then(() => {
return this.runTask(() => {
this.applicationInstance.destroy();
this.applicationInstance = null;
});
})
.then(() => {
bootOptions = {
isBrowser: false,
rootElement,
_renderMode: 'rehydrate',
};
this.application.visit('/', bootOptions).then(instance => {
assert.equal(
instance.rootElement.innerHTML,
indexTemplate,
'was not properly rehydrated'
);
});
});
}
// This tests whether the application is "autobooted" by registering an
// instance initializer and asserting it never gets run. Since this is
// inherently testing that async behavior *doesn't* happen, we set a
// 500ms timeout to verify that when autoboot is set to false, the
// instance initializer that would normally get called on DOM ready
// does not fire.
[`@test Applications with autoboot set to false do not autoboot`](assert) {
function delay(time) {
return new RSVP.Promise(resolve => later(resolve, time));
}
let appBooted = 0;
let instanceBooted = 0;
this.application.initializer({
name: 'assert-no-autoboot',
initialize() {
appBooted++;
},
});
this.application.instanceInitializer({
name: 'assert-no-autoboot',
initialize() {
instanceBooted++;
},
});
assert.ok(!this.applicationInstance, 'precond - no instance');
assert.ok(appBooted === 0, 'precond - not booted');
assert.ok(instanceBooted === 0, 'precond - not booted');
// Continue after 500ms
return delay(500)
.then(() => {
assert.ok(appBooted === 0, '500ms elapsed without app being booted');
assert.ok(instanceBooted === 0, '500ms elapsed without instances being booted');
return this.runTask(() => this.application.boot());
})
.then(() => {
assert.ok(appBooted === 1, 'app should boot when manually calling `app.boot()`');
assert.ok(
instanceBooted === 0,
'no instances should be booted automatically when manually calling `app.boot()'
);
});
}
[`@test calling visit() on an app without first calling boot() should boot the app`](assert) {
let appBooted = 0;
let instanceBooted = 0;
this.application.initializer({
name: 'assert-no-autoboot',
initialize() {
appBooted++;
},
});
this.application.instanceInitializer({
name: 'assert-no-autoboot',
initialize() {
instanceBooted++;
},
});
return this.visit('/').then(() => {
assert.ok(appBooted === 1, 'the app should be booted`');
assert.ok(instanceBooted === 1, 'an instances should be booted');
});
}
[`@test calling visit() on an already booted app should not boot it again`](assert) {
let appBooted = 0;
let instanceBooted = 0;
this.application.initializer({
name: 'assert-no-autoboot',
initialize() {
appBooted++;
},
});
this.application.instanceInitializer({
name: 'assert-no-autoboot',
initialize() {
instanceBooted++;
},
});
return this.runTask(() => this.application.boot())
.then(() => {
assert.ok(appBooted === 1, 'the app should be booted');
assert.ok(instanceBooted === 0, 'no instances should be booted');
return this.visit('/');
})
.then(() => {
assert.ok(appBooted === 1, 'the app should not be booted again');
assert.ok(instanceBooted === 1, 'an instance should be booted');
/*
* Destroy the instance.
*/
return this.runTask(() => {
this.applicationInstance.destroy();
this.applicationInstance = null;
});
})
.then(() => {
/*
* Visit on the application a second time. The application should remain
* booted, but a new instance will be created.
*/
return this.application.visit('/').then(instance => {
this.applicationInstance = instance;
});
})
.then(() => {
assert.ok(appBooted === 1, 'the app should not be booted again');
assert.ok(instanceBooted === 2, 'another instance should be booted');
});
}
[`@test visit() rejects on application boot failure`](assert) {
this.application.initializer({
name: 'error',
initialize() {
throw new Error('boot failure');
},
});
expectAsyncError();
return this.visit('/').then(
() => {
assert.ok(false, 'It should not resolve the promise');
},
error => {
assert.ok(error instanceof Error, 'It should reject the promise with the boot error');
assert.equal(error.message, 'boot failure');
}
);
}
[`@test visit() rejects on instance boot failure`](assert) {
this.application.instanceInitializer({
name: 'error',
initialize() {
throw new Error('boot failure');
},
});
expectAsyncError();
return this.visit('/').then(
() => {
assert.ok(false, 'It should not resolve the promise');
},
error => {
assert.ok(error instanceof Error, 'It should reject the promise with the boot error');
assert.equal(error.message, 'boot failure');
}
);
}
[`@test visit() follows redirects`](assert) {
this.router.map(function() {
this.route('a');
this.route('b', { path: '/b/:b' });
this.route('c', { path: '/c/:c' });
});
this.add(
'route:a',
Route.extend({
afterModel() {
this.replaceWith('b', 'zomg');
},
})
);
this.add(
'route:b',
Route.extend({
afterModel(params) {
this.transitionTo('c', params.b);
},
})
);
/*
* First call to `visit` is `this.application.visit` and returns the
* applicationInstance.
*/
return this.visit('/a').then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.equal(instance.getURL(), '/c/zomg', 'It should follow all redirects');
});
}
[`@test visit() rejects if an error occurred during a transition`](assert) {
this.router.map(function() {
this.route('a');
this.route('b', { path: '/b/:b' });
this.route('c', { path: '/c/:c' });
});
this.add(
'route:a',
Route.extend({
afterModel() {
this.replaceWith('b', 'zomg');
},
})
);
this.add(
'route:b',
Route.extend({
afterModel(params) {
this.transitionTo('c', params.b);
},
})
);
this.add(
'route:c',
Route.extend({
afterModel() {
throw new Error('transition failure');
},
})
);
expectAsyncError();
return this.visit('/a').then(
() => {
assert.ok(false, 'It should not resolve the promise');
},
error => {
assert.ok(error instanceof Error, 'It should reject the promise with the boot error');
assert.equal(error.message, 'transition failure');
}
);
}
[`@test visit() chain`](assert) {
this.router.map(function() {
this.route('a');
this.route('b');
this.route('c');
});
return this.visit('/')
.then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.equal(instance.getURL(), '/');
return instance.visit('/a');
})
.then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.equal(instance.getURL(), '/a');
return instance.visit('/b');
})
.then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.equal(instance.getURL(), '/b');
return instance.visit('/c');
})
.then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.equal(instance.getURL(), '/c');
});
}
[`@test visit() returns a promise that resolves when the view has rendered`](assert) {
this.addTemplate('application', `
Hello world
`);
this.assertEmptyFixture();
return this.visit('/').then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.equal(
this.element.textContent,
'Hello world',
'the application was rendered once the promise resolves'
);
});
}
[`@test visit() returns a promise that resolves without rendering when shouldRender is set to false`](
assert
) {
assert.expect(3);
this.addTemplate('application', '
Hello world
');
this.assertEmptyFixture();
return this.visit('/', { shouldRender: false }).then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
this.assertEmptyFixture('after visit');
});
}
[`@test visit() renders a template when shouldRender is set to true`](assert) {
assert.expect(3);
this.addTemplate('application', '
Hello world
');
this.assertEmptyFixture();
return this.visit('/', { shouldRender: true }).then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
assert.strictEqual(
document.querySelector('#qunit-fixture').children.length,
1,
'there is 1 element in the fixture element after visit'
);
});
}
[`@test visit() returns a promise that resolves without rendering when shouldRender is set to false with Engines`](
assert
) {
assert.expect(3);
this.router.map(function() {
this.mount('blog');
});
this.addTemplate('application', '
Hello world
');
// Register engine
let BlogEngine = Engine.extend();
this.add('engine:blog', BlogEngine);
// Register engine route map
let BlogMap = function() {};
this.add('route-map:blog', BlogMap);
this.assertEmptyFixture();
return this.visit('/blog', { shouldRender: false }).then(instance => {
assert.ok(
instance instanceof ApplicationInstance,
'promise is resolved with an ApplicationInstance'
);
this.assertEmptyFixture('after visit');
});
}
[`@test visit() does not setup the event_dispatcher:main if isInteractive is false (with Engines) GH#15615`](
assert
) {
assert.expect(3);
this.router.map(function() {
this.mount('blog');
});
this.addTemplate('application', '
Hello world
{{outlet}}');
this.add('event_dispatcher:main', {
create() {
throw new Error('should not happen!');
},
});
// Register engine
let BlogEngine = Engine.extend({
init(...args) {
this._super.apply(this, args);
this.register('template:application', compile('{{cache-money}}'));
this.register(
'template:components/cache-money',
compile(`