diff --git a/app/adapters/basic.js b/app/adapters/basic.js index a4bb23b644..0fb549ef67 100644 --- a/app/adapters/basic.js +++ b/app/adapters/basic.js @@ -11,7 +11,7 @@ * }); * ``` */ -import EmberObject, { computed } from '@ember/object'; +import EmberObject from '@ember/object'; import config from 'ember-inspector/config/environment'; @@ -24,6 +24,7 @@ export default EmberObject.extend({ */ init() { this._super(...arguments); + this._messageCallbacks = []; this._checkVersion(); }, @@ -85,11 +86,9 @@ export default EmberObject.extend({ when a message from EmberDebug is received **/ onMessageReceived(callback) { - this._messageCallbacks.pushObject(callback); + this._messageCallbacks.push(callback); }, - _messageCallbacks: computed(function() { return []; }), - _messageReceived(message) { this._messageCallbacks.forEach(callback => { callback(message); diff --git a/app/components/app-picker.js b/app/components/app-picker.js index fd42463ce3..f34ebe573a 100644 --- a/app/components/app-picker.js +++ b/app/components/app-picker.js @@ -1,16 +1,11 @@ import Component from '@ember/component'; -import { observer } from '@ember/object'; import { alias, reads } from '@ember/object/computed'; export default Component.extend({ classNames: ['app-picker'], - apps: alias('port.detectedApplications.[]'), - selectedApp: reads('port.applicationId'), - - selectedDidChange: observer('selectedApp', function() { - this.port.set('applicationId', this.selectedApp); - }), + apps: alias('port.detectedApplications'), + selectedAppId: reads('port.applicationId'), init() { this._super(...arguments); @@ -19,8 +14,7 @@ export default Component.extend({ actions: { selectApp(applicationId) { - this.set('selectedApp', applicationId); - this.port.send('app-selected', { applicationId }); + this.port.selectApplication(applicationId); } } }); diff --git a/app/controllers/component-tree.js b/app/controllers/component-tree.js index 507fa59722..e8641570e5 100644 --- a/app/controllers/component-tree.js +++ b/app/controllers/component-tree.js @@ -98,7 +98,6 @@ export default Controller.extend({ */ pinnedObjectId: null, inspectingViews: false, - viewTreeLoaded: false, /** * Bound to the search field to filter the component list. diff --git a/app/controllers/deprecations.js b/app/controllers/deprecations.js index d72b2edee5..be1d3e3f7b 100644 --- a/app/controllers/deprecations.js +++ b/app/controllers/deprecations.js @@ -1,16 +1,20 @@ -import { action, get, computed } from '@ember/object'; +import { action, computed } from '@ember/object'; import Controller from '@ember/controller'; import debounceComputed from 'ember-inspector/computed/debounce'; import searchMatch from 'ember-inspector/utils/search-match'; export default Controller.extend({ + init() { + this._super(...arguments); + this.deprecations = []; + }, + search: null, searchValue: debounceComputed('search', 300), toggleDeprecationWorkflow: false, - filtered: computed('model.@each.message', 'search', function() { - return this.model - .filter((item) => searchMatch(get(item, 'message'), this.search)); + filtered: computed('deprecations.@each.message', 'search', function() { + return this.deprecations.filter(item => searchMatch(item.message, this.search)); }), openResource: action(function(item) { diff --git a/app/initializers/setup.js b/app/initializers/setup.js index c374bc2f52..9885f182c8 100644 --- a/app/initializers/setup.js +++ b/app/initializers/setup.js @@ -1,20 +1,13 @@ -import { typeOf } from '@ember/utils'; import config from 'ember-inspector/config/environment'; import PromiseAssembler from "ember-inspector/libs/promise-assembler"; export default { name: 'setup', initialize(instance) { - // {{EMBER_DIST}} is replaced by the build process. - instance.adapter = '{{EMBER_DIST}}'; + // {{EMBER_DIST}} is replaced by the build process (basic, chrome, etc) + let Adapter = instance.resolveRegistration(`adapter:{{EMBER_DIST}}`); // register and inject adapter - let Adapter; - if (typeOf(instance.adapter) === 'string') { - Adapter = instance.resolveRegistration(`adapter:${instance.adapter}`); - } else { - Adapter = instance.adapter; - } register(instance, 'adapter:main', Adapter); instance.inject('controller:deprecations', 'adapter', 'adapter:main'); instance.inject('route:application', 'adapter', 'adapter:main'); diff --git a/app/routes/app-detected.js b/app/routes/app-detected.js index df00a99323..901627ee03 100644 --- a/app/routes/app-detected.js +++ b/app/routes/app-detected.js @@ -19,6 +19,7 @@ export default Route.extend({ this.applicationBooted = ({ booted }) => { if (booted) { port.off('general:applicationBooted', this.applicationBooted); + this.applicationBooted = null; resolve(); } }; @@ -27,6 +28,10 @@ export default Route.extend({ }); }, + afterModel() { + this.port.send('deprecation:getCount'); + }, + /** * Sets up a listener such that if ember-debug resets, the inspector app also * resets. diff --git a/app/routes/application.js b/app/routes/application.js index ff63101244..bc334e109c 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -19,7 +19,6 @@ export default Route.extend({ port.on('objectInspector:droppedObject', this, this.droppedObject); port.on('deprecation:count', this, this.setDeprecationCount); port.on('view:inspectComponent', this, this.inspectComponent); - port.send('deprecation:getCount'); }, deactivate() { diff --git a/app/routes/component-tree.js b/app/routes/component-tree.js index 41980a9d0c..a0d54e67b4 100644 --- a/app/routes/component-tree.js +++ b/app/routes/component-tree.js @@ -1,4 +1,5 @@ -import TabRoute from "ember-inspector/routes/tab"; +import { Promise } from 'rsvp'; +import TabRoute from 'ember-inspector/routes/tab'; export default TabRoute.extend({ queryParams: { @@ -8,21 +9,27 @@ export default TabRoute.extend({ }, model() { - return []; + return new Promise(resolve => { + this.port.one('view:viewTree', resolve); + this.port.send('view:getTree'); + }); }, - setupController() { + setupController(controller, message) { + this._super(...arguments); + this.setViewTree(message); + }, + + activate() { this._super(...arguments); this.port.on('view:viewTree', this, this.setViewTree); this.port.on('view:stopInspecting', this, this.stopInspecting); this.port.on('view:startInspecting', this, this.startInspecting); this.port.on('view:inspectDOMNode', this, this.inspectDOMNode); - - this.set('controller.viewTreeLoaded', false); - this.port.send('view:getTree'); }, deactivate() { + this._super(...arguments); this.port.off('view:viewTree', this, this.setViewTree); this.port.off('view:stopInspecting', this, this.stopInspecting); this.port.off('view:startInspecting', this, this.startInspecting); @@ -31,7 +38,6 @@ export default TabRoute.extend({ setViewTree(options) { this.set('controller.viewTree', options.tree); - this.set('controller.viewTreeLoaded', true); // If we're waiting for view tree to inspect a component const componentToInspect = this.get('controller.pinnedObjectId'); @@ -41,10 +47,6 @@ export default TabRoute.extend({ }, inspectComponent(viewId) { - if (!this.get('controller.viewTreeLoaded')) { - return; - } - this.controller.inspect(viewId); }, diff --git a/app/routes/deprecations.js b/app/routes/deprecations.js index 054be8811c..1960a82558 100644 --- a/app/routes/deprecations.js +++ b/app/routes/deprecations.js @@ -1,32 +1,39 @@ -import { set } from '@ember/object'; -import TabRoute from "ember-inspector/routes/tab"; +import { Promise } from 'rsvp'; +import { setProperties } from '@ember/object'; +import TabRoute from 'ember-inspector/routes/tab'; export default TabRoute.extend({ - setupController() { - let port = this.port; - port.on('deprecation:deprecationsAdded', this, this.deprecationsAdded); - port.send('deprecation:watch'); + model() { + return new Promise(resolve => { + this.port.one('deprecation:deprecationsAdded', resolve); + this.port.send('deprecation:watch'); + }); + }, + + setupController(controller, message) { this._super(...arguments); + this.deprecationsAdded(message); }, - model() { - return []; + activate() { + this._super(...arguments); + this.port.on('deprecation:deprecationsAdded', this, this.deprecationsAdded); }, deactivate() { + this._super(...arguments); this.port.off('deprecation:deprecationsAdded', this, this.deprecationsAdded); }, deprecationsAdded(message) { - let model = this.currentModel; + let { deprecations } = this.controller; + message.deprecations.forEach(item => { - let record = model.findBy('id', item.id); + let record = deprecations.findBy('id', item.id); if (record) { - set(record, 'count', item.count); - set(record, 'sources', item.sources); - set(record, 'url', item.url); + setProperties(record, item); } else { - model.pushObject(item); + deprecations.pushObject(item); } }); }, @@ -36,6 +43,5 @@ export default TabRoute.extend({ this.port.send('deprecation:clear'); this.currentModel.clear(); } - } }); diff --git a/app/services/port.js b/app/services/port.js index 35c09dd5a2..342b445f7c 100644 --- a/app/services/port.js +++ b/app/services/port.js @@ -1,3 +1,4 @@ +import { set } from '@ember/object'; import Evented from '@ember/object/evented'; import Service from '@ember/service'; @@ -9,46 +10,33 @@ export default Service.extend(Evented, { this._super(...arguments); /* - * An array of objects of the form: - * { applicationId, applicationName } + * A dictionary of the form: + * { applicationId: applicationName } */ - this.detectedApplications = []; + this.detectedApplications = {}; + this.applicationId = undefined; + this.applicationName = undefined; this.adapter.onMessageReceived(message => { if (message.type === 'apps-loaded') { - message.apps.forEach(app => { - if (!this.detectedApplications.mapBy('applicationId').includes(app.applicationId)) { - this.detectedApplications.pushObject(app); - } - }); - } - }); - - this.adapter.onMessageReceived(message => { - const { applicationId, applicationName } = message; - - if (message.type === 'app-list') { - const apps = JSON.parse(message.appList); - apps.forEach((app) => { - if (!this.detectedApplications.mapBy('applicationId').includes(app.applicationId)) { - this.detectedApplications.push(app); - } + message.apps.forEach(({ applicationId, applicationName }) => { + set(this.detectedApplications, applicationId, applicationName); }); return; } + let { applicationId, applicationName } = message; + if (!applicationId) { return; } - if (!this.applicationId) { - this.set('applicationId', applicationId); - } + // save the application, in case we haven't seen it yet + set(this.detectedApplications, applicationId, applicationName); - // save list of application ids - if (!this.detectedApplications.mapBy('applicationId').includes(applicationId)) { - this.detectedApplications.pushObject({ applicationId, applicationName }); + if (!this.applicationId) { + this.selectApplication(applicationId); } if (this.applicationId === applicationId) { @@ -56,6 +44,15 @@ export default Service.extend(Evented, { } }); }, + + selectApplication(applicationId) { + if (applicationId in this.detectedApplications && applicationId !== this.applicationId) { + let applicationName = this.detectedApplications[applicationId]; + this.setProperties({ applicationId, applicationName }); + this.send('app-selected', { applicationId, applicationName }); + } + }, + send(type, message) { message = message || {}; message.type = type; diff --git a/app/templates/components/app-picker.hbs b/app/templates/components/app-picker.hbs index d3b3c87a18..d85081da59 100644 --- a/app/templates/components/app-picker.hbs +++ b/app/templates/components/app-picker.hbs @@ -3,14 +3,14 @@ class="dropdown__select" onchange={{action "selectApp" value="target.value"}} > - {{#each apps as |app|}} + {{#each-in this.apps as |id name|}} - {{/each}} + {{/each-in}} {{svg-jar "dropdown-arrow" class="dropdown__arrow"}} diff --git a/ember_debug/adapters/basic.js b/ember_debug/adapters/basic.js index feab830bd1..ff6319561e 100644 --- a/ember_debug/adapters/basic.js +++ b/ember_debug/adapters/basic.js @@ -10,6 +10,8 @@ export default EmberObject.extend({ resolve(this.connect(), 'ember-inspector').then(() => { this.onConnectionReady(); }, null, 'ember-inspector'); + + this._messageCallbacks = []; }, /** @@ -54,7 +56,7 @@ export default EmberObject.extend({ @param {Function} callback */ onMessageReceived(callback) { - this.get('_messageCallbacks').pushObject(callback); + this._messageCallbacks.push(callback); }, /** @@ -70,10 +72,8 @@ export default EmberObject.extend({ */ inspectElement(/* elem */) {}, - _messageCallbacks: computed(function() { return A(); }), - _messageReceived(message) { - this.get('_messageCallbacks').forEach(callback => { + this._messageCallbacks.forEach(callback => { callback(message); }); }, diff --git a/ember_debug/main.js b/ember_debug/main.js index d0e156a899..a97b87ae84 100644 --- a/ember_debug/main.js +++ b/ember_debug/main.js @@ -132,7 +132,6 @@ const EmberDebug = EmberObject.extend({ this.startModule('deprecationDebug', DeprecationDebug); this.generalDebug.sendBooted(); - this.viewDebug.sendTree(); }); }, diff --git a/ember_debug/vendor/startup-wrapper.js b/ember_debug/vendor/startup-wrapper.js index e03212534e..0583377260 100644 --- a/ember_debug/vendor/startup-wrapper.js +++ b/ember_debug/vendor/startup-wrapper.js @@ -141,10 +141,11 @@ var EMBER_VERSIONS_SUPPORTED = {{EMBER_VERSIONS_SUPPORTED}}; } if (message.type === 'app-selected') { - const appInstance = getApplications().find(app => Ember.guidFor(app) === message.applicationId); + let current = Ember.EmberInspectorDebugger._application; + let selected = getApplications().find(app => Ember.guidFor(app) === message.applicationId); - if (appInstance && appInstance.__deprecatedInstance__) { - bootEmberInspector(appInstance.__deprecatedInstance__); + if (current !== selected && selected.__deprecatedInstance__) { + bootEmberInspector(selected.__deprecatedInstance__); } } }); diff --git a/tests/acceptance/app-picker-test.js b/tests/acceptance/app-picker-test.js index 3029ce4f23..9f7bbe98d5 100644 --- a/tests/acceptance/app-picker-test.js +++ b/tests/acceptance/app-picker-test.js @@ -5,44 +5,97 @@ import { } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; - -let port; +import { respondWith, disableDefaultResponseFor } from '../test-adapter'; module('App Picker', function(hooks) { setupApplicationTest(hooks); hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - port.reopen({ - detectedApplications: [ - { - applicationId: 'app-one', - applicationName: 'app-one-name' - }, - { - applicationId: 'app-two', - applicationName: 'app-two-name' - } - ], - applicationId: 'app-one' + this.currentApplicationId = null; + + disableDefaultResponseFor('general:applicationBooted'); + disableDefaultResponseFor('app-picker-loaded'); + disableDefaultResponseFor('app-selected'); + + respondWith('general:applicationBooted', { + type: 'general:applicationBooted', + applicationId: 'app-one', + applicationName: 'First App', + booted: true }); + + respondWith('app-picker-loaded', { + type: 'apps-loaded', + applicationId: null, + applicationName: null, + apps: [{ + applicationId: 'app-one', + applicationName: 'First App' + }, { + applicationId: 'app-two', + applicationName: 'Second App' + }] + }); + + respondWith('app-selected', ({ applicationId }) => { + this.currentApplicationId = applicationId; + return false; + }, { count: 3 }); }); test('Both apps show up in picker', async function(assert) { + // TODO: shouldn't this be called again when the app changes? + respondWith('view:getTree', { + type: 'view:viewTree', + applicationId: 'app-one', + applicationName: 'First App', + tree: { + name: 'application', + isComponent: false, + objectId: 'applicationView', + viewClass: 'App.ApplicationView', + duration: 10, + controller: { + name: 'App.ApplicationController', + completeName: 'App.ApplicationController', + objectId: 'applicationController', + }, + children: [] + } + }); + await visit('/component-tree'); assert.dom('.app-picker').exists('App Picker is shown'); - assert.dom(findAll('.app-picker option')[0]).hasText('app-one-name'); - assert.dom(findAll('.app-picker option')[1]).hasText('app-two-name'); - }); - test('Clicking each app in the picker switches between them', async function(assert) { - await visit('/component-tree'); + let options = findAll('.app-picker option'); + + assert.equal(options.length, 2); + assert.dom(options[0]).hasText('First App'); + assert.dom(options[1]).hasText('Second App'); + + assert.equal(this.currentApplicationId, 'app-one', 'First App is selected'); + assert.ok(options[0].selected, 'First App is selected'); + assert.ok(!options[1].selected, 'Second App is not selected'); await fillIn('.app-picker select', 'app-two'); - assert.equal(port.get('applicationId'), 'app-two'); + + assert.equal(options.length, 2); + assert.dom(options[0]).hasText('First App'); + assert.dom(options[1]).hasText('Second App'); + + assert.equal(this.currentApplicationId, 'app-two', 'Second App is selected'); + assert.ok(!options[0].selected, 'First App is not selected'); + assert.ok(options[1].selected, 'Second App is selected'); await fillIn('.app-picker select', 'app-one'); - assert.equal(port.get('applicationId'), 'app-one'); + + assert.equal(options.length, 2); + assert.dom(options[0]).hasText('First App'); + assert.dom(options[1]).hasText('Second App'); + + assert.equal(this.currentApplicationId, 'app-one', 'First App is selected'); + assert.ok(options[0].selected, 'First App is selected'); + assert.ok(!options[1].selected, 'Second App is not selected'); }); }); diff --git a/tests/acceptance/component-tree-test.js b/tests/acceptance/component-tree-test.js index bd0ea59a8d..96faee4354 100644 --- a/tests/acceptance/component-tree-test.js +++ b/tests/acceptance/component-tree-test.js @@ -3,115 +3,110 @@ import { currentURL, fillIn, findAll, - settled, triggerEvent, visit } from '@ember/test-helpers'; -import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; +import { respondWith, sendMessage } from '../test-adapter'; -let port; +function textFor(selector, context) { + return context.querySelector(selector).textContent.trim(); +} -module('Component Tab', function (hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function () { - port = this.owner.lookup('service:port'); - }); +let treeId = 0; - function textFor(selector, context) { - return context.querySelector(selector).textContent.trim(); +function viewNodeFactory(props) { + if (!props.template) { + props.template = props.name; } - - let treeId = 0; - function viewNodeFactory(props) { - if (!props.template) { - props.template = props.name; + let obj = { + value: props, + children: [], + treeId: ++treeId, + }; + return obj; +} + +function viewTreeFactory(tree) { + let children = tree.children; + delete tree.children; + let viewNode = viewNodeFactory(tree); + if (children) { + for (let i = 0; i < children.length; i++) { + viewNode.children.push(viewTreeFactory(children[i])); } - let obj = { - value: props, - children: [], - treeId: ++treeId, - }; - return obj; } + return viewNode; +} + +function defaultViewTree() { + return viewTreeFactory({ + name: 'application', + isComponent: false, + objectId: 'applicationView', + viewClass: 'App.ApplicationView', + duration: 10, + controller: { + name: 'App.ApplicationController', + completeName: 'App.ApplicationController', + objectId: 'applicationController', + }, + children: [ + { + name: 'todos', + isComponent: false, + viewClass: 'App.TodosView', + duration: 1, + objectId: 'todosView', + model: { + name: 'TodosArray', + completeName: 'TodosArray', + objectId: 'todosArray', + type: 'type-ember-object', + }, + controller: { + name: 'App.TodosController', + completeName: 'App.TodosController', + objectId: 'todosController', + }, + children: [ + { + isComponent: true, + name: 'todo-list', + objectId: 'ember392', + tagName: 'section', + template: 'app/templates/components/todo-list', + viewClass: 'todo-list', + children: [ + { + isComponent: true, + name: 'todo-item', + objectId: 'ember267', + tagName: 'li', + template: 'app/templates/components/todo-item', + viewClass: 'todo-item', + }, + ], + }, + ], + }, + ], + }); +} - function viewTreeFactory(tree) { - let children = tree.children; - delete tree.children; - let viewNode = viewNodeFactory(tree); - if (children) { - for (let i = 0; i < children.length; i++) { - viewNode.children.push(viewTreeFactory(children[i])); - } - } - return viewNode; - } +module('Component Tab', function (hooks) { + setupApplicationTest(hooks); - function defaultViewTree() { - return viewTreeFactory({ - name: 'application', - isComponent: false, - objectId: 'applicationView', - viewClass: 'App.ApplicationView', - duration: 10, - controller: { - name: 'App.ApplicationController', - completeName: 'App.ApplicationController', - objectId: 'applicationController', - }, - children: [ - { - name: 'todos', - isComponent: false, - viewClass: 'App.TodosView', - duration: 1, - objectId: 'todosView', - model: { - name: 'TodosArray', - completeName: 'TodosArray', - objectId: 'todosArray', - type: 'type-ember-object', - }, - controller: { - name: 'App.TodosController', - completeName: 'App.TodosController', - objectId: 'todosController', - }, - children: [ - { - isComponent: true, - name: 'todo-list', - objectId: 'ember392', - tagName: 'section', - template: 'app/templates/components/todo-list', - viewClass: 'todo-list', - children: [ - { - isComponent: true, - name: 'todo-item', - objectId: 'ember267', - tagName: 'li', - template: 'app/templates/components/todo-item', - viewClass: 'todo-item', - }, - ], - }, - ], - }, - ], + hooks.beforeEach(function() { + respondWith('view:getTree', { + type: 'view:viewTree', + tree: defaultViewTree() }); - } + }); test('It should correctly display the component tree', async function (assert) { - let viewTree = defaultViewTree(); - await visit('/component-tree'); - run(() => { - port.trigger('view:viewTree', { tree: viewTree }); - }); - await settled(); let treeNodes = findAll('.component-tree-item'); assert.equal(treeNodes.length, 4, 'expected some tree nodes'); @@ -133,13 +128,7 @@ module('Component Tab', function (hooks) { }); test('It allows users to expand and collapse nodes', async function (assert) { - let viewTree = defaultViewTree(); - await visit('/component-tree'); - run(() => { - port.trigger('view:viewTree', { tree: viewTree }); - }); - await settled(); let treeNodes = findAll('.component-tree-item'); assert.equal(treeNodes.length, 4, 'expected some tree nodes'); @@ -153,13 +142,7 @@ module('Component Tab', function (hooks) { }); test('It allows users to expand and collapse children with alt key', async function (assert) { - let viewTree = defaultViewTree(); - await visit('/component-tree'); - run(() => { - port.trigger('view:viewTree', { tree: viewTree }); - }); - await settled(); let expanders = findAll('.component-tree-item__expand.expanded'); assert.equal(expanders.length, 3, 'disclosure triangles all in expanded state'); @@ -183,13 +166,7 @@ module('Component Tab', function (hooks) { }); test('It should filter the view tree using the search text', async function (assert) { - let viewTree = defaultViewTree(); - await visit('/component-tree'); - run(() => { - port.trigger('view:viewTree', { tree: viewTree }); - }); - await settled(); let treeNodes = findAll('.component-tree-item'); assert.equal(treeNodes.length, 4, 'expected some tree nodes'); @@ -211,13 +188,7 @@ module('Component Tab', function (hooks) { }); test("It should clear the search filter when the clear button is clicked", async function (assert) { - let viewTree = defaultViewTree(); - await visit('/component-tree'); - run(() => { - port.trigger('view:viewTree', { tree: viewTree }); - }); - await settled(); let treeNodes = findAll('.component-tree-item'); assert.equal(treeNodes.length, 4, 'expected all tree nodes'); @@ -232,11 +203,7 @@ module('Component Tab', function (hooks) { }); test('It should update the view tree when the port triggers a change, preserving the expanded state of existing nodes', async function (assert) { - let viewTree = defaultViewTree(); - await visit('/component-tree'); - run(() => port.trigger('view:viewTree', { tree: viewTree })); - await settled(); let expanders = findAll('.component-tree-item__expand'); let expanderEl = expanders[expanders.length - 1]; @@ -245,121 +212,79 @@ module('Component Tab', function (hooks) { let treeNodes = findAll('.component-tree-item'); assert.equal(treeNodes.length, 3, 'the last node should be hidden'); - viewTree = defaultViewTree(); // resend the same view tree - run(() => port.trigger('view:viewTree', { tree: viewTree })); - await settled(); + // resend the same view tree + await sendMessage({ + type: 'view:viewTree', + tree: defaultViewTree() + }); assert.dom('.component-tree-item').exists({ count: 3 }, 'the last node should still be hidden'); }); test('Previewing / showing a view on the client', async function (assert) { - let messageSent = null; - port.reopen({ - send(name, message) { - messageSent = { name, message }; - }, + await visit('/component-tree'); + + respondWith('view:previewLayer', ({ objectId }) => { + assert.equal(objectId, 'applicationView', 'Client sent correct id to preview layer'); + return false; }); - await visit('/component-tree'); - let viewTree = defaultViewTree(); - viewTree.children = []; - run(() => port.trigger('view:viewTree', { tree: viewTree })); - await settled(); await triggerEvent('.component-tree-item', 'mouseenter'); - assert.equal( - messageSent.name, - 'view:previewLayer', - 'Client asked to preview layer' - ); - assert.equal( - messageSent.message.objectId, - 'applicationView', - 'Client sent correct id to preview layer' - ); + + respondWith('view:hidePreview', false); + await triggerEvent('.component-tree-item', 'mouseleave'); - assert.equal( - messageSent.name, - 'view:hidePreview', - 'Client asked to hide preview' - ); }); test('Scrolling an element into view', async function (assert) { - let messageSent = null; - port.reopen({ - send(name, message) { - messageSent = { name, message }; - }, - }); - await visit('/component-tree'); - let viewTree = defaultViewTree(); - run(() => port.trigger('view:viewTree', { tree: viewTree })); - await settled(); + + respondWith('view:scrollToElement', () => { + // TODO: this should assert the right elementId + assert.ok(true, 'Client asked to scroll element into view'); + return false; + }); await click('.js-scroll-into-view'); - assert.equal( - messageSent.name, - 'view:scrollToElement', - 'Client asked to scroll element into view' - ); }); test('View DOM element in Elements panel', async function (assert) { - let messageSent = null; - port.reopen({ - send(name, message) { - messageSent = { name, message }; - }, - }); - await visit('/component-tree'); - let viewTree = defaultViewTree(); - run(() => port.trigger('view:viewTree', { tree: viewTree })); - await settled(); + respondWith('view:inspectElement', () => { + // TODO: this should assert the right elementId + assert.ok(true, 'Client asked to view DOM element'); + return false; + }); await click('.js-view-dom-element'); - assert.equal( - messageSent.name, - 'view:inspectElement', - 'Client asked to view DOM element' - ); }); test('Inspects the component in the object inspector on click', async function (assert) { - let messageSent = null; - port.reopen({ - send(name, message) { - messageSent = { name, message }; - } - }); - await visit('/component-tree'); - let tree = defaultViewTree(); - run(() => { - port.trigger('view:viewTree', { tree }); + + respondWith('objectInspector:inspectById', ({ objectId }) => { + assert.equal(objectId, 'ember392', 'Client asked to inspect the right objectId'); + return false; }); - await settled(); await click('.component-tree-item--component code'); - assert.equal(messageSent.name, 'objectInspector:inspectById'); - assert.equal(messageSent.message.objectId, 'ember392'); }); test('Selects a component in the tree in response to a message from the context menu', async function (assert) { // Go to the component tree and populate it before sending the message from the context menu - let viewTree = defaultViewTree(); await visit('/component-tree'); - run(() => { - port.trigger('view:viewTree', { tree: viewTree }); + + respondWith('objectInspector:inspectById', ({ objectId }) => { + assert.equal(objectId, 'ember267', 'Client asked to inspect the right objectId'); + return false; }); - await settled(); - run(() => { - port.trigger('view:inspectComponent', { viewId: 'ember267' }); + await sendMessage({ + type: 'view:inspectComponent', + viewId: 'ember267' }); - await settled(); + assert.equal(currentURL(), '/component-tree?pinnedObjectId=ember267', 'It pins the element id as a query param'); assert.dom('.component-tree-item--selected').hasText('TodoItem', 'It selects the item in the tree corresponding to the element'); }); diff --git a/tests/acceptance/container-test.js b/tests/acceptance/container-test.js index c4a0ed2726..d89ce2d435 100644 --- a/tests/acceptance/container-test.js +++ b/tests/acceptance/container-test.js @@ -8,8 +8,7 @@ import { fillIn, currentURL } from 'ember-test-helpers'; - -let port, message, name; +import { respondWith } from '../test-adapter'; function getTypes() { return [ @@ -24,162 +23,155 @@ function getTypes() { ]; } -function getInstances() { +function getControllers() { return [ { - name: 'first', + name: 'first controller', + fullName: 'controller:first', inspectable: false }, { - name: 'second', + name: 'second controller', + fullName: 'controller:second', inspectable: true } ]; } -module('Container Tab', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - }); - - hooks.afterEach(function() { - name = null; - message = null; - }); +module('Container Tab', function(outer) { + setupApplicationTest(outer); - test("Container types are successfully listed", async function(assert) { - port.reopen({ - send(name) { - if (name === 'container:getTypes') { - this.trigger('container:types', { types: getTypes() }); - } - } + module('With default types', function(inner) { + inner.beforeEach(function() { + respondWith('container:getTypes', { + type: 'container:types', + types: getTypes() + }); }); - await visit('/container-types'); - let rows = findAll('.js-container-type'); - assert.equal(rows.length, 2); - assert.dom(findAll('.js-container-type-name')[0]).hasText('controller'); - assert.dom(findAll('.js-container-type-count')[0]).hasText('2'); - assert.dom(findAll('.js-container-type-name')[1]).hasText('route'); - assert.dom(findAll('.js-container-type-count')[1]).hasText('5'); - }); + test("Container types are successfully listed", async function(assert) { + await visit('/container-types'); - test("Container instances are successfully listed", async function(assert) { - let instances = getInstances(); + let rows = findAll('.js-container-type'); - port.reopen({ - send(n, m) { - name = n; - message = m; - if (name === 'container:getTypes') { - this.trigger('container:types', { types: getTypes() }); - } + assert.equal(rows.length, 2); + assert.dom(findAll('.js-container-type-name')[0]).hasText('controller'); + assert.dom(findAll('.js-container-type-count')[0]).hasText('2'); + assert.dom(findAll('.js-container-type-name')[1]).hasText('route'); + assert.dom(findAll('.js-container-type-count')[1]).hasText('5'); + }); - if (name === 'container:getInstances' && message.containerType === 'controller') { - //TODO: these instances are getting no names - this.trigger('container:instances', { instances, status: 200 }); + test("Container instances are successfully listed", async function(assert) { + respondWith('container:getInstances', ({ containerType }) => { + if (containerType === 'controller') { + return { + type: 'container:instances', + status: 200, + instances: getControllers() + }; } - } - }); + }); - await visit('/container-types/controller'); - let rows; + await visit('/container-types/controller'); - rows = findAll('.js-container-instance-list-item'); + let rows = findAll('.js-container-instance-list-item'); - assert.dom(rows[0]).hasText('first'); - assert.dom(rows[1]).hasText('second'); - name = null; - message = null; + assert.dom(rows[0]).hasText('first controller'); + assert.dom(rows[1]).hasText('second controller'); - await click(rows[0].querySelector('.js-instance-name')); + // Uninspectable, no messages + await click(rows[0].querySelector('.js-instance-name')); - assert.equal(name, null); - await click(rows[1].querySelector('.js-instance-name')); + // Second object is inspectable + respondWith('objectInspector:inspectByContainerLookup', ({ name }) => { + if (name === 'controller:second') { + return { + type: 'objectInspector:updateObject', + objectId: 'ember123', + name: 'second controller', + details: [], + errors: [] + }; + } + }); - assert.equal(name, 'objectInspector:inspectByContainerLookup'); + await click(rows[1].querySelector('.js-instance-name')); - await fillIn('.js-container-instance-search input', 'first'); + await fillIn('.js-container-instance-search input', 'first'); - rows = findAll('.js-container-instance-list-item'); - assert.equal(rows.length, 1); - assert.dom(rows[0]).hasText('first'); - }); + rows = findAll('.js-container-instance-list-item'); - test("It should clear the search filter when the clear button is clicked", async function(assert) { - let instances = getInstances(); + assert.equal(rows.length, 1); + assert.dom(rows[0]).hasText('first controller'); + }); - port.reopen({ - send(n, m) { - name = n; - message = m; - if (name === 'container:getTypes') { - this.trigger('container:types', { types: getTypes() }); + test("It should clear the search filter when the clear button is clicked", async function(assert) { + respondWith('container:getInstances', ({ containerType }) => { + if (containerType === 'controller') { + return { + type: 'container:instances', + status: 200, + instances: getControllers() + }; } + }); - if (name === 'container:getInstances' && message.containerType === 'controller') { - this.trigger('container:instances', { instances, status: 200 }); - } - } - }); + await visit('/container-types/controller'); - await visit('/container-types/controller'); - let rows; + let rows = findAll('.js-container-instance-list-item'); + assert.equal(rows.length, 2, 'expected all rows'); - rows = findAll('.js-container-instance-list-item'); - assert.equal(rows.length, 2, 'expected all rows'); + await fillIn('.js-container-instance-search input', 'xxxxx'); + rows = findAll('.js-container-instance-list-item'); + assert.equal(rows.length, 0, 'expected filtered rows'); - await fillIn('.js-container-instance-search input', 'xxxxx'); - rows = findAll('.js-container-instance-list-item'); - assert.equal(rows.length, 0, 'expected filtered rows'); + await click('.js-search-field-clear-button'); + rows = findAll('.js-container-instance-list-item'); + assert.equal(rows.length, 2, 'expected all rows'); + }); - await click('.js-search-field-clear-button'); - rows = findAll('.js-container-instance-list-item'); - assert.equal(rows.length, 2, 'expected all rows'); - }); + test("Successfully redirects if the container type is not found", async function(assert) { + respondWith('container:getInstances', ({ containerType }) => { + if (containerType === 'random-type') { + return { + type: 'container:instances', + status: 404 + }; + } + }); - test("Successfully redirects if the container type is not found", async function(assert) { + let adapterException = Ember.Test.adapter.exception; - port.reopen({ - send(n, m) { - name = n; - message = m; - if (name === 'container:getTypes') { - this.trigger('container:types', { types: getTypes() }); + // Failed route causes a promise unhandled rejection + // even though there's an `error` action defined :( + Ember.Test.adapter.exception = err => { + if (!err || err.status !== 404) { + return adapterException.call(Ember.Test.adapter, err); } + }; - if (name === 'container:getInstances' && message.containerType === 'random-type') { - this.trigger('container:instances', { status: 404 }); - } + try { + await visit('/container-types/random-type'); + assert.equal(currentURL(), '/container-types'); + } finally { + Ember.Test.adapter.exception = adapterException; } }); - let adapterException = Ember.Test.adapter.exception; - // Failed route causes a promise unhandled rejection - // even though there's an `error` action defined :( - Ember.Test.adapter.exception = err => { - if (!err || err.status !== 404) { - return adapterException.call(Ember.Test.adapter, err); - } - }; - await visit('/container-types/random-type'); - assert.equal(currentURL(), '/container-types'); - Ember.Test.adapter.exception = adapterException; }); test("Reload", async function(assert) { - let types = [], instances = []; + respondWith('container:getTypes', { + type: 'container:types', + types: [] + }); - port.reopen({ - send(n, m) { - if (n === 'container:getTypes') { - this.trigger('container:types', { types }); - } - if (n === 'container:getInstances' && m.containerType === 'controller') { - this.trigger('container:instances', { instances, status: 200 }); - } + respondWith('container:getInstances', ({ containerType }) => { + if (containerType === 'controller') { + return { + type: 'container:instances', + status: 200, + instances: [] + }; } }); @@ -187,8 +179,21 @@ module('Container Tab', function(hooks) { assert.dom('.js-container-type').doesNotExist(); assert.dom('.js-container-instance-list-item').doesNotExist(); - types = getTypes(); - instances = getInstances(); + + respondWith('container:getTypes', { + type: 'container:types', + types: getTypes() + }); + + respondWith('container:getInstances', ({ containerType }) => { + if (containerType === 'controller') { + return { + type: 'container:instances', + status: 200, + instances: getControllers() + }; + } + }); await click('.js-reload-container-btn'); diff --git a/tests/acceptance/data-test.js b/tests/acceptance/data-test.js index 0646cd54e4..d2a20e572a 100644 --- a/tests/acceptance/data-test.js +++ b/tests/acceptance/data-test.js @@ -5,314 +5,453 @@ import { settled, visit } from '@ember/test-helpers'; -import { guidFor } from '@ember/object/internals'; -import EmberObject from '@ember/object'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { triggerPort } from '../helpers/trigger-port'; - -let port, name; - -module('Data Tab', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - port.reopen({ - init() {}, - send(n, m) { - name = n; - if (name === 'data:getModelTypes') { - this.trigger('data:modelTypesAdded', { modelTypes: modelTypes() }); - } - if (name === 'data:getRecords') { - this.trigger('data:recordsAdded', { records: records(m.objectId) }); - } - if (name === 'data:getFilters') { - this.trigger('data:filters', { filters: getFilters() }); - } - } - }); - }); - - hooks.afterEach(function() { - name = null; - }); - - function modelTypeFactory(options) { - return { - name: options.name, - count: options.count, - columns: options.columns, - objectId: options.name - }; +import { respondWith, sendMessage } from '../test-adapter'; + +function getFilters() { + return [{ name: 'isNew', desc: 'New' }]; +} + +function getModelTypes() { + return [{ + name: 'App.Post', + count: 2, + columns: [{ name: 'id', desc: 'Id' }, { name: 'title', desc: 'Title' }, { name: 'body', desc: 'Body' }], + objectId: 'post-type' + }, { + name: 'App.Comment', + count: 1, + columns: [{ name: 'id', desc: 'Id' }, { name: 'title', desc: 'Title' }, { name: 'content', desc: 'Content' }], + objectId: 'comment-type' + }]; +} + +function recordFactory({ objectId, ...attrs }, filterValues = { isNew: false }) { + filterValues = filterValues || { isNew: false }; + let searchKeywords = []; + for (let key in attrs) { + searchKeywords.push(attrs[key]); } - - function getFilters() { - return [{ name: 'isNew', desc: 'New' }]; - } - - function modelTypes() { + return { + objectId, + columnValues: attrs, + filterValues, + searchKeywords + }; +} + +function getRecords(type) { + if (type === 'post-type') { return [ - modelTypeFactory({ - name: 'App.Post', - count: 2, - columns: [{ name: 'id', desc: 'Id' }, { name: 'title', desc: 'Title' }, { name: 'body', desc: 'Body' }] - }), - modelTypeFactory({ - name: 'App.Comment', - count: 1, - columns: [{ name: 'id', desc: 'Id' }, { name: 'title', desc: 'Title' }, { name: 'content', desc: 'Content' }] - }) + recordFactory({ objectId: 'post-1', id: 1, title: 'My Post', body: 'This is my first post' }), + recordFactory({ objectId: 'post-2', id: 2, title: 'Hello', body: '' }, { isNew: true }) ]; } - function recordFactory(attr, filterValues) { - filterValues = filterValues || { isNew: false }; - let searchKeywords = []; - for (let i in attr) { - searchKeywords.push(attr[i]); - } - let object = EmberObject.create(); - return { - columnValues: attr, - objectId: attr.objectId || guidFor(object), - filterValues, - searchKeywords - }; - } - - function records(type) { - if (type === 'App.Post') { - return [ - recordFactory({ id: 1, title: 'My Post', body: 'This is my first post' }), - recordFactory({ id: 2, title: 'Hello', body: '' }, { isNew: true }) - ]; - } else if (type === 'App.Comment') { - return [ - recordFactory({ id: 2, title: 'I am confused', content: 'I have no idea what im doing' }) - ]; - } + if (type === 'comment-type') { + return [ + recordFactory({ objectId: 'comment-2', id: 2, title: 'I am confused', content: 'I have no idea what im doing' }) + ]; } - test('Model types are successfully listed and bound', async function t(assert) { - await visit('/data/model-types'); - - assert.dom('.js-model-type').exists({ count: 2 }); - // they should be sorted alphabetically - assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); - assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); + return []; +} - assert.dom(findAll('.js-model-type-count')[0]).hasText('1'); - assert.dom(findAll('.js-model-type-count')[1]).hasText('2'); +module('Data Tab', function(outer) { + setupApplicationTest(outer); - await triggerPort(this, 'data:modelTypesUpdated', { - modelTypes: [ - modelTypeFactory({ name: 'App.Post', count: 3 }) - ] + module('Model Types', function(inner) { + inner.afterEach(function() { + respondWith('data:releaseModelTypes', false); }); - assert.dom(findAll('.js-model-type-count')[1]).hasText('3'); - }); - - test('Records are successfully listed and bound', async function t(assert) { - await visit('/data/model-types'); - await click(findAll('.js-model-type a')[1]); + test('Model types are successfully listed and bound', async function(assert) { + respondWith('data:getModelTypes', { + type: 'data:modelTypesAdded', + modelTypes: getModelTypes() + }); - let columns = findAll('[data-test-table-header-column]'); - assert.dom(columns[0]).includesText('Id'); - assert.dom(columns[1]).includesText('Title'); - assert.dom(columns[2]).includesText('Body'); + await visit('/data/model-types'); - let recordRows = findAll('[data-test-table-row]'); - assert.equal(recordRows.length, 2); + assert.dom('.js-model-type').exists({ count: 2 }); - let firstRow = recordRows[0]; - let firstRowColumns = firstRow.querySelectorAll('[data-test-table-cell]'); - assert.dom(firstRowColumns[0]).hasText('1'); - assert.dom(firstRowColumns[1]).hasText('My Post'); - assert.dom(firstRowColumns[2]).hasText('This is my first post'); + // they should be sorted alphabetically + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); - let secondRow = recordRows[1]; - let secondRowColumns = secondRow.querySelectorAll('[data-test-table-cell]'); - assert.dom(secondRowColumns[0]).hasText('2'); - assert.dom(secondRowColumns[1]).hasText('Hello'); - assert.dom(secondRowColumns[2]).hasText(''); + assert.dom(findAll('.js-model-type-count')[0]).hasText('1'); + assert.dom(findAll('.js-model-type-count')[1]).hasText('2'); - await triggerPort(this, 'data:recordsAdded', { - records: [recordFactory({ objectId: 'new-post', id: 3, title: 'Added Post', body: 'I am new here' })] - }); + await sendMessage({ + type: 'data:modelTypesUpdated', + modelTypes: [{ + name: 'App.Post', + count: 3, + objectId: 'post-type' + }] + }); - let addedRow = findAll('[data-test-table-row]')[2]; - let addedRowColumns = addedRow.querySelectorAll('[data-test-table-cell]'); - assert.dom(addedRowColumns[0]).hasText('3'); - assert.dom(addedRowColumns[1]).hasText('Added Post'); - assert.dom(addedRowColumns[2]).hasText('I am new here'); + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); - await triggerPort(this, 'data:recordsUpdated', { - records: [recordFactory({ objectId: 'new-post', id: 3, title: 'Modified Post', body: 'I am no longer new' })] + assert.dom(findAll('.js-model-type-count')[0]).hasText('1'); + assert.dom(findAll('.js-model-type-count')[1]).hasText('3'); }); - let rows = findAll('[data-test-table-row]'); - let modifiedRow = rows[rows.length - 1]; - let modifiedRowColumns = modifiedRow.querySelectorAll('[data-test-table-cell]'); - assert.dom(modifiedRowColumns[0]).hasText('3'); - assert.dom(modifiedRowColumns[1]).hasText('Modified Post'); - assert.dom(modifiedRowColumns[2]).hasText('I am no longer new'); - - await triggerPort(this, 'data:recordsRemoved', { - index: 2, - count: 1 + test('Hiding empty model types', async function(assert) { + respondWith('data:getModelTypes', { + type: 'data:modelTypesAdded', + modelTypes: [ + ...getModelTypes(), + { + name: 'App.Author', + count: 0, + columns: [{ name: 'id', desc: 'Id' }, { name: 'name', desc: 'Name' }], + objectId: 'author-type' + } + ] + }); + + await visit('/data/model-types'); + + assert.dom('.js-model-type').exists({ count: 3 }); + + // they should be sorted alphabetically + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Author'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[2]).hasText('App.Post'); + + assert.dom(findAll('.js-model-type-count')[0]).hasText('0'); + assert.dom(findAll('.js-model-type-count')[1]).hasText('1'); + assert.dom(findAll('.js-model-type-count')[2]).hasText('2'); + + // Make one model type have a count of 0 + await sendMessage({ + type: 'data:modelTypesUpdated', + modelTypes: [{ + name: 'App.Comment', + count: 0, + objectId: 'comment-type' + }] + }); + + assert.dom('.js-model-type').exists({ count: 3 }, 'All models are present'); + + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Author'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[2]).hasText('App.Post'); + + assert.dom(findAll('.js-model-type-count')[0]).hasText('0'); + assert.dom(findAll('.js-model-type-count')[1]).hasText('0'); + assert.dom(findAll('.js-model-type-count')[2]).hasText('2'); + + // Hide empty models + await click('#options-hideEmptyModelTypes'); + + assert.dom('.js-model-type').exists({ count: 1 }, 'Only non-empty models are present'); + + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Post'); + assert.dom(findAll('.js-model-type-count')[0]).hasText('2'); + + // Show empty models + await click('#options-hideEmptyModelTypes'); + + assert.dom('.js-model-type').exists({ count: 3 }, 'All models are present'); + + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Author'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[2]).hasText('App.Post'); + + assert.dom(findAll('.js-model-type-count')[0]).hasText('0'); + assert.dom(findAll('.js-model-type-count')[1]).hasText('0'); + assert.dom(findAll('.js-model-type-count')[2]).hasText('2'); }); - await settled(); - assert.dom('[data-test-table-row]').exists({ count: 2 }); - rows = findAll('[data-test-table-row]'); - let lastRow = rows[rows.length - 1]; - let lastRowColumns = lastRow.querySelectorAll('[data-test-table-cell]'); - assert.dom(lastRowColumns[0]).hasText('2', 'Records successfully removed.'); - }); + test('Order by record count', async function(assert) { + respondWith('data:getModelTypes', { + type: 'data:modelTypesAdded', + modelTypes: getModelTypes() + }); - test('Hiding empty model types', async function(assert) { - assert.expect(3); + await visit('/data/model-types'); - await visit('/data/model-types'); + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); - // Make one model type have a count of 0 - await triggerPort(this, 'data:modelTypesUpdated', { - modelTypes: [ - modelTypeFactory({ name: 'App.Post', count: 0 }) - ] - }); + // Order models by record count. + await click('#options-orderByRecordCount'); - assert.dom('.js-model-type').exists({ count: 2 }, 'All models are present'); + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Post'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Comment'); - // Hide empty models - await click('#options-hideEmptyModelTypes'); - assert.dom('.js-model-type').exists({ count: 1 }, 'Only non-empty models are present'); + // Don't order models by record count. + await click('#options-orderByRecordCount'); - // Show empty models - await click('#options-hideEmptyModelTypes'); - assert.dom('.js-model-type').exists({ count: 2 }, 'All models are present again'); - }); + assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); + assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); + }); - test('Order by record count', async function(assert) { - assert.expect(6); + test("Reload", async function(assert) { + respondWith('data:getModelTypes', { + type: 'data:modelTypesAdded', + modelTypes: [] + }); - await visit('/data/model-types'); + await visit('/data/model-types'); - assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); - assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); + assert.dom('.js-model-type').doesNotExist(); - // Order models by record count. - await click('#options-orderByRecordCount'); + respondWith('data:getModelTypes', ({ applicationId, applicationName }) => ({ + type: 'data:modelTypesAdded', + applicationId, + applicationName, + modelTypes: getModelTypes() + })); - assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Post'); - assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Comment'); + await click('.js-reload-container-btn'); - // Don't order models by record count. - await click('#options-orderByRecordCount'); - assert.dom(findAll('.js-model-type-name')[0]).hasText('App.Comment'); - assert.dom(findAll('.js-model-type-name')[1]).hasText('App.Post'); + assert.dom('.js-model-type').exists({ count: 2 }); + }); }); - test('Filtering records', async function t(assert) { - await visit('/data/model-types'); + module('Records', function(inner) { + inner.beforeEach(function() { + respondWith('data:getModelTypes', { + type: 'data:modelTypesAdded', + modelTypes: getModelTypes() + }); + + respondWith('data:getFilters', { + type: 'data:filters', + filters: getFilters() + }); + + respondWith('data:getRecords', ({ objectId }) => ({ + type: 'data:recordsAdded', + records: getRecords(objectId) + })); + }); - await click(findAll('.js-model-type a')[1]); + inner.afterEach(function() { + respondWith('data:releaseModelTypes', false); + respondWith('data:releaseRecords', false); + }); - let rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 2); - let filters = findAll('.js-filter'); - assert.equal(filters.length, 2); - let newFilter = [...filters].find((e) => e.textContent.indexOf('New') > -1); - await click(newFilter); + test('Records are successfully listed and bound', async function(assert) { + await visit('/data/model-types'); + + await click(findAll('.js-model-type a')[1]); + + let columns = findAll('[data-test-table-header-column]'); + assert.dom(columns[0]).includesText('Id'); + assert.dom(columns[1]).includesText('Title'); + assert.dom(columns[2]).includesText('Body'); + + let recordRows = findAll('[data-test-table-row]'); + assert.equal(recordRows.length, 2); + + let firstRow = recordRows[0]; + let firstRowColumns = firstRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(firstRowColumns[0]).hasText('1'); + assert.dom(firstRowColumns[1]).hasText('My Post'); + assert.dom(firstRowColumns[2]).hasText('This is my first post'); + + let secondRow = recordRows[1]; + let secondRowColumns = secondRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(secondRowColumns[0]).hasText('2'); + assert.dom(secondRowColumns[1]).hasText('Hello'); + assert.dom(secondRowColumns[2]).hasText(''); + + await sendMessage({ + type: 'data:modelTypesUpdated', + modelTypes: [{ + objectId: 'post-type', + name: 'App.Post', + count: 3 + }] + }); + + await sendMessage({ + type: 'data:recordsAdded', + records: [recordFactory({ + objectId: 'post-3', + id: 3, + title: 'Added Post', + body: 'I am new here' + }, { + isNew: true + })] + }); + + // Why is this needed? + await settled(); + + recordRows = findAll('[data-test-table-row]'); + assert.equal(recordRows.length, 3); + + firstRow = recordRows[0]; + firstRowColumns = firstRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(firstRowColumns[0]).hasText('1'); + assert.dom(firstRowColumns[1]).hasText('My Post'); + assert.dom(firstRowColumns[2]).hasText('This is my first post'); + + secondRow = recordRows[1]; + secondRowColumns = secondRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(secondRowColumns[0]).hasText('2'); + assert.dom(secondRowColumns[1]).hasText('Hello'); + assert.dom(secondRowColumns[2]).hasText(''); + + let thirdRow = recordRows[2]; + let thirdRowColumns = thirdRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(thirdRowColumns[0]).hasText('3'); + assert.dom(thirdRowColumns[1]).hasText('Added Post'); + assert.dom(thirdRowColumns[2]).hasText('I am new here'); + + await sendMessage({ + type: 'data:recordsUpdated', + records: [recordFactory({ + objectId: 'post-3', + id: 3, + title: 'Modified Post', + body: 'I am no longer new' + })] + }); + + recordRows = findAll('[data-test-table-row]'); + assert.equal(recordRows.length, 3); + + firstRow = recordRows[0]; + firstRowColumns = firstRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(firstRowColumns[0]).hasText('1'); + assert.dom(firstRowColumns[1]).hasText('My Post'); + assert.dom(firstRowColumns[2]).hasText('This is my first post'); + + secondRow = recordRows[1]; + secondRowColumns = secondRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(secondRowColumns[0]).hasText('2'); + assert.dom(secondRowColumns[1]).hasText('Hello'); + assert.dom(secondRowColumns[2]).hasText(''); + + thirdRow = recordRows[2]; + thirdRowColumns = thirdRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(thirdRowColumns[0]).hasText('3'); + assert.dom(thirdRowColumns[1]).hasText('Modified Post'); + assert.dom(thirdRowColumns[2]).hasText('I am no longer new'); + + await sendMessage({ + type: 'data:modelTypesUpdated', + modelTypes: [{ + objectId: 'post-type', + name: 'App.Post', + count: 2 + }] + }); + + await sendMessage({ + type: 'data:recordsRemoved', + index: 2, + count: 1 + }); + + // Why is this needed? + await settled(); + + recordRows = findAll('[data-test-table-row]'); + assert.equal(recordRows.length, 2); + + firstRow = recordRows[0]; + firstRowColumns = firstRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(firstRowColumns[0]).hasText('1'); + assert.dom(firstRowColumns[1]).hasText('My Post'); + assert.dom(firstRowColumns[2]).hasText('This is my first post'); + + secondRow = recordRows[1]; + secondRowColumns = secondRow.querySelectorAll('[data-test-table-cell]'); + assert.dom(secondRowColumns[0]).hasText('2'); + assert.dom(secondRowColumns[1]).hasText('Hello'); + assert.dom(secondRowColumns[2]).hasText(''); + }); - rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 1); - assert.dom(rows[0].querySelector('[data-test-table-cell]')).hasText('2'); - }); + test('Filtering records', async function(assert) { + await visit('/data/model-types'); - test('Searching records', async function t(assert) { - await visit('/data/model-types'); + await click(findAll('.js-model-type a')[1]); - await click(findAll('.js-model-type a')[1]); + let rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 2); + let filters = findAll('.js-filter'); + assert.equal(filters.length, 2); + let newFilter = [...filters].find((e) => e.textContent.indexOf('New') > -1); + await click(newFilter); - let rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 2); + rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 1); + assert.dom(rows[0].querySelector('[data-test-table-cell]')).hasText('2'); + }); - await fillIn('.js-records-search input', 'Hello'); + test('Searching records', async function(assert) { + await visit('/data/model-types'); - rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 1); - assert.dom(rows[0].querySelector('[data-test-table-cell]')).hasText('2'); + await click(findAll('.js-model-type a')[1]); - await fillIn('.js-records-search input', 'my first post'); + let rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 2); - rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 1); - assert.dom(rows[0].querySelector('[data-test-table-cell]')).hasText('1'); + await fillIn('.js-records-search input', 'Hello'); - await fillIn('.js-records-search input', ''); + rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 1); + assert.dom(rows[0].querySelector('[data-test-table-cell]')).hasText('2'); - rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 2); - }); + await fillIn('.js-records-search input', 'my first post'); - test("It should clear the search filter when the clear button is clicked", async function(assert) { - await visit('/data/model-types'); - await click(findAll('.js-model-type a')[1]); + rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 1); + assert.dom(rows[0].querySelector('[data-test-table-cell]')).hasText('1'); - let rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 2); + await fillIn('.js-records-search input', ''); - await fillIn('.js-records-search input', 'Hello'); + rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 2); + }); - rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 1); + test("It should clear the search filter when the clear button is clicked", async function(assert) { + await visit('/data/model-types'); - await click('.js-search-field-clear-button'); - rows = findAll('[data-test-table-row]'); - assert.equal(rows.length, 2); - }); + await click(findAll('.js-model-type a')[1]); - test('Columns successfully updated when switching model types', async function t(assert) { - await visit('/data/model-types/App.Post/records'); - let columns = findAll('[data-test-table-header-column]'); - assert.dom(columns[columns.length - 1]).includesText('Body'); - await visit('/data/model-types/App.Comment/records'); - columns = findAll('[data-test-table-header-column]'); - assert.dom(columns[columns.length - 1]).includesText('Content'); - }); + let rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 2); + + await fillIn('.js-records-search input', 'Hello'); - test("Reload", async function(assert) { - let types = [], getRecords = [], filters = []; - - port.reopen({ - init() {}, - send(n) { - name = n; - if (name === 'data:getModelTypes') { - this.trigger('data:modelTypesAdded', { modelTypes: types }); - } - if (name === 'data:getRecords') { - this.trigger('data:recordsAdded', { records: getRecords }); - } - if (name === 'data:getFilters') { - this.trigger('data:filters', { filters }); - } - } + rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 1); + + await click('.js-search-field-clear-button'); + rows = findAll('[data-test-table-row]'); + assert.equal(rows.length, 2); }); - await visit('/data/model-types'); + test('Columns successfully updated when switching model types', async function(assert) { + await visit('/data/model-types/App.Post/records'); + let columns = findAll('[data-test-table-header-column]'); + assert.dom(columns[columns.length - 1]).includesText('Body'); - assert.dom('.js-model-type').doesNotExist(); - types = modelTypes(); - getRecords = records('App.Post'); - filters = getFilters(); + respondWith('data:getFilters', { + type: 'data:filters', + filters: getFilters() + }); - await click('.js-reload-container-btn'); + respondWith('data:getRecords', ({ objectId }) => ({ + type: 'data:recordsAdded', + records: getRecords(objectId) + })); - assert.dom('.js-model-type').exists({ count: 2 }); + await visit('/data/model-types/App.Comment/records'); + columns = findAll('[data-test-table-header-column]'); + assert.dom(columns[columns.length - 1]).includesText('Content'); + }); }); }); diff --git a/tests/acceptance/deprecation-test.js b/tests/acceptance/deprecation-test.js index 68bd7a2302..503831ab38 100644 --- a/tests/acceptance/deprecation-test.js +++ b/tests/acceptance/deprecation-test.js @@ -1,19 +1,37 @@ import { visit, findAll, fillIn, click } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; - -let port, message, name; +import { enableOpenResource, respondWith, expectOpenResource, disableDefaultResponseFor } from '../test-adapter'; /* Toggling the source can be done by clicking the disclosure triangle, count, or message */ -const toggleDeprecationSource = function() { - return click('[data-test="deprecation-count"]'); -}; +async function toggleDeprecationSource() { + await click('[data-test="deprecation-count"]'); +} + +function deprecationsWithoutSource() { + return [{ + id: 'deprecation-1', + message: 'Deprecation 1', + url: 'http://www.emberjs.com', + count: 2, + sources: [{ + stackStr: 'stack-trace', + map: null + }, { + stackStr: 'stack-trace-2', + map: null + }] + }] +} function deprecationsWithSource() { return [{ + id: 'deprecation-1', + message: 'Deprecation 1', + url: 'http://www.emberjs.com', count: 2, hasSourceMap: true, sources: [{ @@ -30,191 +48,139 @@ function deprecationsWithSource() { line: 2, fullSource: 'http://path-to-second-file.js' } - }], - message: 'Deprecation 1', - url: 'http://www.emberjs.com' + }] }]; } -module('Deprecation Tab', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - port.reopen({ - send(n, m) { - name = n; - message = m; - } - }); - }); +module('Deprecation Tab', function(outer) { + setupApplicationTest(outer); - hooks.afterEach(function() { - name = null; - message = null; + outer.beforeEach(function() { + disableDefaultResponseFor('deprecation:getCount'); }); - test('No source map', async function(assert) { - port.reopen({ - send(name) { - if (name === 'deprecation:watch') { - port.trigger('deprecation:deprecationsAdded', { - deprecations: [{ - count: 2, - sources: [{ - stackStr: 'stack-trace', - map: null - }, { - stackStr: 'stack-trace-2', - map: null - }], - message: 'Deprecation 1', - url: 'http://www.emberjs.com' - }] - }); - } - return this._super(...arguments); - } + module('Without source map', function(inner) { + inner.beforeEach(function() { + respondWith('deprecation:getCount', { + type: 'deprecation:count', + count: 2 + }); + + respondWith('deprecation:watch', { + type: 'deprecation:deprecationsAdded', + deprecations: deprecationsWithoutSource() + }); }); - await visit('/deprecations'); + test('No source map', async function(assert) { + await visit('/deprecations'); - await toggleDeprecationSource(); + await toggleDeprecationSource(); - assert.dom('[data-test="deprecation-source"]').doesNotExist('no sources'); - assert.dom(findAll('[data-test="deprecation-message"]')[0]).hasText('Deprecation 1', 'message shown'); - assert.dom(findAll('[data-test="deprecation-count"]')[0]).hasText('2', 'Count correct'); - assert.dom('[data-test="full-trace-deprecations-btn"]').exists('Full trace button shown'); - await click('[data-test="full-trace-deprecations-btn"]'); + assert.dom('[data-test="deprecation-source"]').doesNotExist('no sources'); + assert.dom(findAll('[data-test="deprecation-message"]')[0]).hasText('Deprecation 1', 'message shown'); + assert.dom(findAll('[data-test="deprecation-count"]')[0]).hasText('2', 'Count correct'); + assert.dom('[data-test="full-trace-deprecations-btn"]').exists('Full trace button shown'); - assert.equal(name, 'deprecation:sendStackTraces'); - assert.equal(message.deprecation.message, 'Deprecation 1'); - assert.equal(message.deprecation.sources.length, 2); - }); + respondWith('deprecation:sendStackTraces', ({ deprecation }) => { + assert.equal(deprecation.message, 'Deprecation 1'); + assert.equal(deprecation.sources.length, 2); + return false; + }); - test("With source map, source found, can't open resource", async function(assert) { - port.reopen({ - send(name) { - if (name === 'deprecation:watch') { - port.trigger('deprecation:deprecationsAdded', { - deprecations: deprecationsWithSource() - }); - } - return this._super(...arguments); - } + await click('[data-test="full-trace-deprecations-btn"]'); }); + }); - await visit('/deprecations'); - - await toggleDeprecationSource(); + module('With source map', function(inner) { + inner.beforeEach(function() { + respondWith('deprecation:getCount', { + type: 'deprecation:count', + count: 2 + }); + + respondWith('deprecation:watch', { + type: 'deprecation:deprecationsAdded', + deprecations: deprecationsWithSource() + }); + }); - assert.dom('[data-test="deprecation-message"]').hasText('Deprecation 1', 'message shown'); - assert.dom('[data-test="deprecation-count"]').hasText('2', 'Count correct'); - assert.dom('[data-test="full-trace-deprecations-btn"]').doesNotExist('Full trace button not shown'); + test("With source map, source found, can't open resource", async function(assert) { + await visit('/deprecations'); - let sources = findAll('[data-test="deprecation-source"]'); - assert.equal(sources.length, 2, 'shows all sources'); - assert.notOk(sources[0].querySelector('[data-test="deprecation-source-link"]'), 'source not clickable'); - assert.dom(sources[0].querySelector('[data-test="deprecation-source-text"]')).hasText('path-to-file.js:1'); - assert.notOk(sources[1].querySelector('[data-test="deprecation-source-link"]'), 'source not clickable'); - assert.dom(sources[1].querySelector('[data-test="deprecation-source-text"]')).hasText('path-to-second-file.js:2'); + await toggleDeprecationSource(); - await click('[data-test="trace-deprecations-btn"]', sources[0]); + assert.dom('[data-test="deprecation-message"]').hasText('Deprecation 1', 'message shown'); + assert.dom('[data-test="deprecation-count"]').hasText('2', 'Count correct'); + assert.dom('[data-test="full-trace-deprecations-btn"]').doesNotExist('Full trace button not shown'); - assert.equal(name, 'deprecation:sendStackTraces'); - assert.equal(message.deprecation.message, 'Deprecation 1'); - assert.equal(message.deprecation.sources.length, 1); + let sources = findAll('[data-test="deprecation-source"]'); + assert.equal(sources.length, 2, 'shows all sources'); + assert.notOk(sources[0].querySelector('[data-test="deprecation-source-link"]'), 'source not clickable'); + assert.dom(sources[0].querySelector('[data-test="deprecation-source-text"]')).hasText('path-to-file.js:1'); + assert.notOk(sources[1].querySelector('[data-test="deprecation-source-link"]'), 'source not clickable'); + assert.dom(sources[1].querySelector('[data-test="deprecation-source-text"]')).hasText('path-to-second-file.js:2'); - await click('[data-test="trace-deprecations-btn"]', sources[1]); + respondWith('deprecation:sendStackTraces', ({ deprecation }) => { + assert.equal(deprecation.message, 'Deprecation 1'); + assert.equal(deprecation.sources.length, 1); + return false; + }); - assert.equal(name, 'deprecation:sendStackTraces'); - assert.equal(message.deprecation.message, 'Deprecation 1'); - assert.equal(message.deprecation.sources.length, 1); + await click(sources[0].querySelector('[data-test="trace-deprecations-btn"]')); - }); + respondWith('deprecation:sendStackTraces', ({ deprecation }) => { + assert.equal(deprecation.message, 'Deprecation 1'); + assert.equal(deprecation.sources.length, 1); + return false; + }); - test("With source map, source found, can open resource", async function(assert) { - let openResourceArgs = false; - port.get('adapter').reopen({ - canOpenResource: true, - openResource(...args) { - openResourceArgs = args; - } - }); - port.reopen({ - send(name) { - if (name === 'deprecation:watch') { - port.trigger('deprecation:deprecationsAdded', { - deprecations: deprecationsWithSource() - }); - } - return this._super(...arguments); - } + await click(sources[1].querySelector('[data-test="trace-deprecations-btn"]')); }); - await visit('/deprecations'); - - await toggleDeprecationSource(); + test("With source map, source found, can open resource", async function(assert) { + enableOpenResource(); - assert.dom('[data-test="deprecation-message"]').hasText('Deprecation 1', 'message shown'); - assert.dom('[data-test="deprecation-count"]').hasText('2', 'Count correct'); - assert.dom('[data-test="full-trace-deprecations-btn"]').doesNotExist('Full trace button not shown'); + await visit('/deprecations'); - let sources = findAll('[data-test="deprecation-source"]'); - assert.equal(sources.length, 2, 'shows all sources'); - assert.notOk(sources[0].querySelector('[data-test="deprecation-source-text"]'), 'source clickable'); - assert.dom(sources[0].querySelector('[data-test="deprecation-source-link"]')).hasText('path-to-file.js:1'); - assert.notOk(sources[1].querySelector('[data-test="deprecation-source-text"]'), 'source clickable'); - assert.dom(sources[1].querySelector('[data-test="deprecation-source-link"]')).hasText('path-to-second-file.js:2'); + await toggleDeprecationSource(); - openResourceArgs = false; - await click('[data-test="deprecation-source-link"]', sources[0]); + assert.dom('[data-test="deprecation-message"]').hasText('Deprecation 1', 'message shown'); + assert.dom('[data-test="deprecation-count"]').hasText('2', 'Count correct'); + assert.dom('[data-test="full-trace-deprecations-btn"]').doesNotExist('Full trace button not shown'); - assert.ok(openResourceArgs); - openResourceArgs = false; + let sources = findAll('[data-test="deprecation-source"]'); + assert.equal(sources.length, 2, 'shows all sources'); + assert.notOk(sources[0].querySelector('[data-test="deprecation-source-text"]'), 'source clickable'); + assert.dom(sources[0].querySelector('[data-test="deprecation-source-link"]')).hasText('path-to-file.js:1'); + assert.notOk(sources[1].querySelector('[data-test="deprecation-source-text"]'), 'source clickable'); + assert.dom(sources[1].querySelector('[data-test="deprecation-source-link"]')).hasText('path-to-second-file.js:2'); - await click('[data-test="deprecation-source-link"]', sources[1]); + expectOpenResource('http://path-to-file.js', 1); - assert.ok(openResourceArgs); - openResourceArgs = false; + await click(sources[0].querySelector('[data-test="deprecation-source-link"]')); - await click('[data-test="trace-deprecations-btn"]', sources[0]); + expectOpenResource('http://path-to-second-file.js', 2); - assert.equal(name, 'deprecation:sendStackTraces'); - assert.equal(message.deprecation.message, 'Deprecation 1'); - assert.equal(message.deprecation.sources.length, 1); - await click('[data-test="trace-deprecations-btn"]', sources[1]); - assert.equal(name, 'deprecation:sendStackTraces'); - assert.equal(message.deprecation.message, 'Deprecation 1'); - assert.equal(message.deprecation.sources.length, 1); - }); - - test("It should clear the search filter when the clear button is clicked", async function(assert) { - port.reopen({ - send(name) { - if (name === 'deprecation:watch') { - port.trigger('deprecation:deprecationsAdded', { - deprecations: deprecationsWithSource() - }); - } - return this._super(...arguments); - } + await click(sources[1].querySelector('[data-test="deprecation-source-link"]')); }); - await visit('/deprecations'); + test("It should clear the search filter when the clear button is clicked", async function(assert) { + await visit('/deprecations'); - await toggleDeprecationSource(); + await toggleDeprecationSource(); - let sources = findAll('[data-test="deprecation-source"]'); - assert.equal(sources.length, 2, 'shows all sources'); + let sources = findAll('[data-test="deprecation-source"]'); + assert.equal(sources.length, 2, 'shows all sources'); - await fillIn('[data-test="deprecations-search"] input', 'xxxx'); - sources = findAll('[data-test="deprecation-source"]'); - assert.equal(sources.length, 0, 'sources filtered'); + await fillIn('[data-test="deprecations-search"] input', 'xxxx'); + sources = findAll('[data-test="deprecation-source"]'); + assert.equal(sources.length, 0, 'sources filtered'); - await click('.js-search-field-clear-button'); - await click('[data-test="deprecation-item"] .disclosure-triangle'); - sources = findAll('[data-test="deprecation-source"]'); - assert.equal(sources.length, 2, 'show all sources'); + await click('.js-search-field-clear-button'); + await click('[data-test="deprecation-item"] .disclosure-triangle'); + sources = findAll('[data-test="deprecation-source"]'); + assert.equal(sources.length, 2, 'show all sources'); + }); }); }); diff --git a/tests/acceptance/info-test.js b/tests/acceptance/info-test.js index 1228d656f8..29f1063a2e 100644 --- a/tests/acceptance/info-test.js +++ b/tests/acceptance/info-test.js @@ -1,36 +1,27 @@ import { visit, findAll } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; - -let port; +import config from 'ember-inspector/config/environment'; +import { respondWith } from '../test-adapter'; module('Info Tab', function(hooks) { setupApplicationTest(hooks); - hooks.beforeEach(function() { - this.owner.lookup('config:main').VERSION = '9.9.9'; - port = this.owner.lookup('service:port'); - port.reopen({ - send(name) { - if (name === 'general:getLibraries') { - this.trigger('general:libraries', { - libraries: [ - { name: 'Ember', version: '1.0' }, - { name: 'Handlebars', version: '2.1' } - ] - }); - } - } + test("Libraries are displayed correctly", async function(assert) { + respondWith('general:getLibraries', { + type: 'general:libraries', + libraries: [ + { name: 'Ember', version: '1.0' }, + { name: 'Handlebars', version: '2.1' } + ] }); - }); - test("Libraries are displayed correctly", async function t(assert) { await visit('/info/libraries'); let libraries = findAll('.js-library-row'); assert.equal(libraries.length, 3, "The correct number of libraries is displayed"); assert.dom(libraries[0].querySelector('.js-lib-library')).hasText('Ember Inspector', 'Ember Inspector is added automatically'); - assert.dom(libraries[0].querySelector('.js-lib-version')).hasText('9.9.9'); + assert.dom(libraries[0].querySelector('.js-lib-version')).hasText(config.VERSION); assert.dom(libraries[1].querySelector('.js-lib-library')).hasText('Ember'); assert.dom(libraries[1].querySelector('.js-lib-version')).hasText('1.0'); assert.dom(libraries[2].querySelector('.js-lib-library')).hasText('Handlebars'); diff --git a/tests/acceptance/object-inspector-test.js b/tests/acceptance/object-inspector-test.js index 29cf18675b..41b20218ab 100644 --- a/tests/acceptance/object-inspector-test.js +++ b/tests/acceptance/object-inspector-test.js @@ -8,30 +8,10 @@ import { } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { triggerPort } from '../helpers/trigger-port'; +import { respondWith, sendMessage } from '../test-adapter'; -let port, message, name; - - -module('Object Inspector', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - port.reopen({ - send(n, m) { - name = n; - message = m; - } - }); - }); - - hooks.afterEach(function() { - name = null; - message = null; - }); - - const objectAttr = { +function objectFactory(props) { + return { name: 'Object Name', objectId: 1, errors: [], @@ -44,57 +24,60 @@ module('Object Inspector', function(hooks) { value: 1 }] } - ] + ], + ...props }; +} - function objectFactory(props) { - return Object.assign({}, objectAttr, props); - } - - function objectToInspect() { - return objectFactory({ - name: 'My Object', - objectId: 'objectId', - errors: [], - details: [ - { - name: 'First Detail', - expand: false, - properties: [{ - name: 'numberProperty', +function objectToInspect() { + return objectFactory({ + name: 'My Object', + objectId: 'objectId', + errors: [], + details: [ + { + name: 'First Detail', + expand: false, + properties: [{ + name: 'numberProperty', + value: { + inspect: 1, + value: 'type-number' + } + }] + }, + { + name: 'Second Detail', + properties: [ + { + name: 'objectProperty', value: { - inspect: 1, - value: 'type-number' + inspect: 'Ember Object Name', + type: 'type-ember-object' } - }] - }, - { - name: 'Second Detail', - properties: [ - { - name: 'objectProperty', - value: { - inspect: 'Ember Object Name', - type: 'type-ember-object' - } - }, { - name: 'stringProperty', - value: { - inspect: 'String Value', - type: 'type-ember-string' - } + }, { + name: 'stringProperty', + value: { + inspect: 'String Value', + type: 'type-ember-string' } - ] - } - ] - }); - } + } + ] + } + ] + }); +} + +module('Object Inspector', function(hooks) { + setupApplicationTest(hooks); test("The object displays correctly", async function (assert) { - let obj = objectFactory({ name: 'My Object' }); await visit('/'); - await triggerPort(this, 'objectInspector:updateObject', obj); + await sendMessage({ + type: 'objectInspector:updateObject', + ...objectFactory({ name: 'My Object' }) + }); assert.dom('.js-object-name').hasText('My Object'); assert.dom('.js-object-detail-name').hasText('Own Properties'); @@ -107,7 +90,10 @@ module('Object Inspector', function(hooks) { test("Object details", async function (assert) { await visit('/'); - await triggerPort(this, 'objectInspector:updateObject', objectToInspect()); + await sendMessage({ + type: 'objectInspector:updateObject', + ...objectToInspect() + }); assert.dom('.js-object-name').hasText('My Object'); let [firstDetail, secondDetail] = findAll('.js-object-detail'); @@ -135,21 +121,15 @@ module('Object Inspector', function(hooks) { }); test("Digging deeper into objects", async function (assert) { - let secondDetail; - await visit('/'); - triggerPort(this, 'objectInspector:updateObject', objectToInspect()); - - secondDetail = findAll('.js-object-detail')[1]; - await click(secondDetail.querySelector('.js-object-detail-name')); - - await click('.js-object-property .js-object-property-value'); - - assert.equal(name, 'objectInspector:digDeeper'); - assert.deepEqual(message, { objectId: 'objectId', property: 'objectProperty' }); + await sendMessage({ + type: 'objectInspector:updateObject', + ...objectToInspect() + }); - let nestedObject = { + respondWith('objectInspector:digDeeper', { + type: 'objectInspector:updateObject', parentObject: 'objectId', name: 'Nested Object', objectId: 'nestedObject', @@ -164,18 +144,28 @@ module('Object Inspector', function(hooks) { } }] }] - }; + }); - await triggerPort(this, 'objectInspector:updateObject', nestedObject); + let secondDetail = findAll('.js-object-detail')[1]; + + await click(secondDetail.querySelector('.js-object-detail-name')); + await click('.js-object-property .js-object-property-value'); assert.dom('.js-object-name').hasText('My Object', 'Title stays as the initial object.'); assert.dom('.js-object-trail').hasText('.objectProperty', 'Nested property shows below title'); assert.dom('.js-object-detail-name').hasText('Nested Detail'); + await click('.js-object-detail-name'); assert.dom('.js-object-detail').hasClass('mixin_state_expanded'); assert.dom('.js-object-property-name').hasText('nestedProp'); assert.dom('.js-object-property-value').hasText('Nested Prop'); + + respondWith('objectInspector:releaseObject', ({ objectId }) => { + assert.equal(objectId, 'nestedObject'); + return false; + }); + await click('.js-object-inspector-back'); assert.dom('.js-object-trail').doesNotExist(0); @@ -184,7 +174,8 @@ module('Object Inspector', function(hooks) { test("Computed properties", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -198,31 +189,31 @@ module('Object Inspector', function(hooks) { } }] }] - }; - - await triggerPort(this, 'objectInspector:updateObject', obj); + }); await click('.js-object-detail-name'); - await click('.js-calculate'); - assert.equal(name, 'objectInspector:calculate'); - assert.deepEqual(message, { objectId: 'myObject', property: 'computedProp', mixinIndex: 0 }); - await triggerPort(this, 'objectInspector:updateProperty', { - objectId: 'myObject', - property: 'computedProp', + respondWith('objectInspector:calculate', ({ objectId, property, mixinIndex }) => ({ + type: 'objectInspector:updateProperty', + objectId, + property, + mixinIndex, value: { inspect: 'Computed value', isCalculated: true - }, - mixinIndex: 0 - }); + } + })); + + await click('.js-calculate'); + assert.dom('.js-object-property-value').hasText('Computed value'); }); test("Service highlight", async function(assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -235,9 +226,8 @@ module('Object Inspector', function(hooks) { } }] }] - }; + }); - await triggerPort(this, 'objectInspector:updateObject', obj); await click('.js-object-detail-name'); assert.dom('.mixin__property--group').exists({ count: 1 }); @@ -251,7 +241,8 @@ module('Object Inspector', function(hooks) { test("Computed properties no dependency", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -267,24 +258,22 @@ module('Object Inspector', function(hooks) { } }] }] - }; - - await triggerPort(this, 'objectInspector:updateObject', obj); + }); await click('.js-object-detail-name'); - await click('.js-calculate'); - assert.equal(name, 'objectInspector:calculate'); - assert.deepEqual(message, { objectId: 'myObject', property: 'computedProp', mixinIndex: 0 }); - await triggerPort(this, 'objectInspector:updateProperty', { - objectId: 'myObject', - property: 'computedProp', + respondWith('objectInspector:calculate', ({ objectId, property, mixinIndex }) => ({ + type: 'objectInspector:updateProperty', + objectId, + property, + mixinIndex, value: { inspect: 'Computed value', computed: 'foo-bar' - }, - mixinIndex: 0 - }); + } + })); + + await click('.js-calculate'); assert.dom('.mixin__property--group').doesNotExist(); @@ -304,7 +293,8 @@ module('Object Inspector', function(hooks) { test("Computed properties dependency expand", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -319,22 +309,22 @@ module('Object Inspector', function(hooks) { } }] }] - }; - await triggerPort(this, 'objectInspector:updateObject', obj); + }); + await click('.js-object-detail-name'); - await click('.js-calculate'); - assert.equal(name, 'objectInspector:calculate'); - assert.deepEqual(message, { objectId: 'myObject', property: 'computedProp', mixinIndex: 0 }); - await triggerPort(this, 'objectInspector:updateProperty', { - objectId: 'myObject', - property: 'computedProp', + respondWith('objectInspector:calculate', ({ objectId, property, mixinIndex }) => ({ + type: 'objectInspector:updateProperty', + objectId, + property, + mixinIndex, value: { inspect: 'Computed value', isCalculated: true - }, - mixinIndex: 0 - }); + } + })); + + await click('.js-calculate'); assert.dom('.mixin__property--group').exists({ count: 1 }); @@ -363,7 +353,8 @@ module('Object Inspector', function(hooks) { test("Properties are bound to the application properties", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'object-id', details: [{ @@ -377,13 +368,13 @@ module('Object Inspector', function(hooks) { isCalculated: false } }] - }] - }; - await triggerPort(this, 'objectInspector:updateObject', obj); + }); assert.dom('.js-object-property-value').hasText('Teddy'); - await triggerPort(this, 'objectInspector:updateProperty', { + + await sendMessage({ + type: 'objectInspector:updateProperty', objectId: 'object-id', mixinIndex: 0, property: 'boundProp', @@ -398,23 +389,21 @@ module('Object Inspector', function(hooks) { let txtField = find('.js-object-property-value-txt'); assert.equal(txtField.value, '"Alex"'); - await fillIn(txtField, '"Joey"'); - await triggerKeyEvent('.js-object-property-value-txt', 'keyup', 13); - assert.equal(name, 'objectInspector:saveProperty'); - assert.equal(message.property, 'boundProp'); - assert.equal(message.value, 'Joey'); - - await triggerPort(this, 'objectInspector:updateProperty', { - objectId: 'object-id', + respondWith('objectInspector:saveProperty', ({ objectId, property, value }) => ({ + type: 'objectInspector:updateProperty', + objectId, + property, mixinIndex: 0, - property: 'boundProp', value: { - inspect: 'Joey', + inspect: value, type: 'type-string', isCalculated: false } - }); + })); + + await fillIn(txtField, '"Joey"'); + await triggerKeyEvent('.js-object-property-value-txt', 'keyup', 13); assert.dom('.js-object-property-value').hasText('Joey'); }); @@ -422,7 +411,8 @@ module('Object Inspector', function(hooks) { test("Stringified json should not get double parsed", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'object-id', details: [{ @@ -436,27 +426,39 @@ module('Object Inspector', function(hooks) { isCalculated: true } }] - }] - }; - await triggerPort(this, 'objectInspector:updateObject', obj); + }); + + assert.dom('.js-object-property-value').hasText('{"name":"teddy"}'); await click('.js-object-property-value'); let txtField = find('.js-object-property-value-txt'); assert.equal(txtField.value, '"{"name":"teddy"}"'); - await fillIn(txtField, '"{"name":"joey"}"'); + respondWith('objectInspector:saveProperty', ({ objectId, property, value }) => ({ + type: 'objectInspector:updateProperty', + objectId, + property, + mixinIndex: 0, + value: { + inspect: value, + type: 'type-string', + isCalculated: false + } + })); + + await fillIn(txtField, '"{"name":"joey"}"'); await triggerKeyEvent('.js-object-property-value-txt', 'keyup', 13); - assert.equal(name, 'objectInspector:saveProperty'); - assert.equal(message.property, 'boundProp'); - assert.equal(message.value, '{"name":"joey"}'); + + assert.dom('.js-object-property-value').hasText('{"name":"joey"}'); }); test("Send to console", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'object-id', details: [{ @@ -470,37 +472,43 @@ module('Object Inspector', function(hooks) { isCalculated: true } }] - }] - }; - await triggerPort(this, 'objectInspector:updateObject', obj); + }); + + respondWith('objectInspector:sendToConsole', ({ objectId, property }) => { + assert.equal(objectId, 'object-id'); + assert.equal(property, 'myProp'); + return false; + }); // Grouped View await click('.js-send-to-console-btn'); - assert.equal(name, 'objectInspector:sendToConsole'); - assert.equal(message.objectId, 'object-id'); - assert.equal(message.property, 'myProp'); + respondWith('objectInspector:sendToConsole', ({ objectId, property }) => { + assert.equal(objectId, 'object-id'); + assert.strictEqual(property, undefined); + return false; + }); await click('.js-send-object-to-console-btn'); - assert.equal(name, 'objectInspector:sendToConsole'); - assert.equal(message.objectId, 'object-id'); - assert.equal(message.property, undefined); - // All View - await click('.js-object-display-type-all'); + + respondWith('objectInspector:sendToConsole', ({ objectId, property }) => { + assert.equal(objectId, 'object-id'); + assert.strictEqual(property, undefined); + return false; + }); + await click('.js-send-object-to-console-btn'); - assert.equal(name, 'objectInspector:sendToConsole'); - assert.equal(message.objectId, 'object-id'); - assert.equal(message.property, undefined); }); test("Read only CPs cannot be edited", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'object-id', details: [{ @@ -524,9 +532,10 @@ module('Object Inspector', function(hooks) { } }] }] - }; - await triggerPort(this, 'objectInspector:updateObject', obj); + }); + await click('.js-object-property-value'); + assert.dom('.js-object-property-value-txt').doesNotExist(); let valueElements = findAll('.js-object-property-value'); @@ -538,19 +547,22 @@ module('Object Inspector', function(hooks) { test("Dropping an object due to destruction", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ name: 'Detail', properties: [] }] - }; - - await triggerPort(this, 'objectInspector:updateObject', obj); + }); assert.dom('.js-object-name').hasText('My Object'); - await triggerPort(this, 'objectInspector:droppedObject', { objectId: 'myObject' }); + + await sendMessage({ + type: 'objectInspector:droppedObject', + objectId: 'myObject' + }); assert.dom('.js-object-name').doesNotExist(); }); @@ -558,9 +570,10 @@ module('Object Inspector', function(hooks) { test("Date fields are editable", async function (assert) { await visit('/'); - let date = new Date(); + let date = new Date(2019, 7, 13); // 2019-08-13 - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -574,71 +587,91 @@ module('Object Inspector', function(hooks) { } }] }] - }; - await triggerPort(this, 'objectInspector:updateObject', obj); - assert.ok(true); + }); await click('.js-object-detail-name'); + + assert.dom('.js-object-property-value').hasText(date.toString()); + await click('.js-object-property-value'); let field = find('.js-object-property-value-date'); assert.ok(field); - await click(field); + + respondWith('objectInspector:saveProperty', ({ objectId, property, value }) => { + assert.ok(typeof value === 'number', 'sent as timestamp'); + date = new Date(value); + + return { + type: 'objectInspector:updateProperty', + objectId, + property, + mixinIndex: 0, + value: { + inspect: date.toString(), + type: 'type-date', + isCalculated: false + } + }; + }); + await fillIn(field, '2015-01-01'); await triggerKeyEvent(field, 'keydown', 13); - assert.equal(name, 'objectInspector:saveProperty'); - assert.equal(message.property, 'dateProperty'); - assert.equal(message.dataType, 'date'); - - let newDate = new Date(message.value); - assert.equal(newDate.getMonth(), 0); - assert.equal(newDate.getDate(), 1); - assert.equal(newDate.getFullYear(), 2015); + assert.dom('.js-object-property-value').hasText(date.toString()); }); test("Errors are correctly displayed", async function (assert) { - let obj = objectFactory({ + await visit('/'); + + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: '1', + details: [], errors: [ { property: 'foo' }, { property: 'bar' } ] }); - await visit('/'); - await triggerPort(this, 'objectInspector:updateObject', obj); assert.dom('.js-object-name').hasText('My Object'); assert.dom('.js-object-inspector-errors').exists({ count: 1 }); assert.dom('.js-object-inspector-error').exists({ count: 2 }); - await click('.js-send-errors-to-console'); + respondWith('objectInspector:traceErrors', ({ objectId }) => { + assert.equal(objectId, '1'); + return false; + }); - assert.equal(name, 'objectInspector:traceErrors'); - assert.equal(message.objectId, '1'); + await click('.js-send-errors-to-console'); - await triggerPort(this, 'objectInspector:updateErrors', { + await sendMessage({ + type: 'objectInspector:updateErrors', objectId: '1', errors: [ { property: 'foo' } ] }); - assert.dom('.js-object-inspector-error').exists(); + assert.dom('.js-object-inspector-errors').exists({ count: 1 }); + assert.dom('.js-object-inspector-error').exists({ count: 1 }); - await triggerPort(this, 'objectInspector:updateErrors', { + await sendMessage({ + type: 'objectInspector:updateErrors', objectId: '1', errors: [] }); assert.dom('.js-object-inspector-errors').doesNotExist(); + assert.dom('.js-object-inspector-error').doesNotExist(); }); test("Tracked properties", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -652,16 +685,15 @@ module('Object Inspector', function(hooks) { } }] }] - }; - - await triggerPort(this, 'objectInspector:updateObject', obj); + }); await click('.js-object-detail-name'); assert.dom('.mixin__property-icon--tracked').exists(); assert.dom('.js-object-property-value').hasText('123'); - await triggerPort(this, 'objectInspector:updateProperty', { + await sendMessage({ + type: 'objectInspector:updateProperty', objectId: 'myObject', property: 'trackedProp', value: { @@ -670,13 +702,15 @@ module('Object Inspector', function(hooks) { mixinIndex: 0 }); + assert.dom('.mixin__property-icon--tracked').exists(); assert.dom('.js-object-property-value').hasText('456'); }); test("Plain properties", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -690,16 +724,15 @@ module('Object Inspector', function(hooks) { } }] }] - }; - - await triggerPort(this, 'objectInspector:updateObject', obj); + }); await click('.js-object-detail-name'); assert.dom('.mixin__property-icon--property').exists(); assert.dom('.js-object-property-value').hasText('123'); - await triggerPort(this, 'objectInspector:updateProperty', { + await sendMessage({ + type: 'objectInspector:updateProperty', objectId: 'myObject', property: 'plainProp', value: { @@ -708,13 +741,15 @@ module('Object Inspector', function(hooks) { mixinIndex: 0 }); + assert.dom('.mixin__property-icon--property').exists(); assert.dom('.js-object-property-value').hasText('456'); }); test("Getters", async function (assert) { await visit('/'); - let obj = { + await sendMessage({ + type: 'objectInspector:updateObject', name: 'My Object', objectId: 'myObject', details: [{ @@ -728,16 +763,15 @@ module('Object Inspector', function(hooks) { } }] }] - }; - - await triggerPort(this, 'objectInspector:updateObject', obj); + }); await click('.js-object-detail-name'); assert.dom('.mixin__property-icon--getter').exists(); assert.dom('.js-object-property-value').hasText('123'); - await triggerPort(this, 'objectInspector:updateProperty', { + await sendMessage({ + type: 'objectInspector:updateProperty', objectId: 'myObject', property: 'getter', value: { @@ -746,6 +780,7 @@ module('Object Inspector', function(hooks) { mixinIndex: 0 }); + assert.dom('.mixin__property-icon--getter').exists(); assert.dom('.js-object-property-value').hasText('456'); }); }); diff --git a/tests/acceptance/promise-test.js b/tests/acceptance/promise-test.js index d51047fa17..472ad28a1b 100644 --- a/tests/acceptance/promise-test.js +++ b/tests/acceptance/promise-test.js @@ -8,314 +8,377 @@ import { } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { triggerPort } from '../helpers/trigger-port'; - -let port, message, name; - -module('Promise Tab', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - port.reopen({ - send(n, m) { - if (n === 'promise:getAndObservePromises') { - port.trigger('promise:promisesUpdated', { - promises: [] - }); - } - name = n; - message = m; - } +import { respondWith, sendMessage } from '../test-adapter'; + +let guids = 0; + +function generatePromise(props) { + return { + guid: `ember${++guids}`, + label: 'Generated Promise', + parent: null, + children: null, + state: 'created', + value: null, + reason: null, + createdAt: Date.now(), + hasStack: false, + ...props + }; +} + +module('Promise Tab', function(outer) { + setupApplicationTest(outer); + + outer.beforeEach(function() { + respondWith('promise:getInstrumentWithStack', { + type: 'promise:instrumentWithStack', + instrumentWithStack: false }); }); - hooks.afterEach(function() { - name = null; - message = null; - }); - - let guids = 0; - function generatePromise(props) { - return Object.assign({ - guid: ++guids, - label: 'Generated Promise', - parent: null, - children: null, - state: 'created', - value: null, - reason: null, - createdAt: Date.now(), - hasStack: false - }, props); - } - - test("Shows page refresh hint if no promises", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [] + module('No promises initially', function(inner) { + inner.beforeEach(function() { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [] + }); }); - assert.dom('.js-promise-tree').doesNotExist("no promise list"); - assert.dom('.js-page-refresh').exists("page refresh hint seen"); + test("Shows page refresh hint if no promises", async function(assert) { + await visit('/promise-tree'); - await click('.js-page-refresh-btn'); + assert.dom('.js-promise-tree').doesNotExist("no promise list"); + assert.dom('.js-page-refresh').exists("page refresh hint seen"); - assert.equal(name, 'general:refresh'); + respondWith('general:refresh', false); + respondWith('promise:releasePromises', false); - await triggerPort(this, 'promise:promisesUpdated', { - promises: [ - generatePromise({ - guid: 1, - label: 'Promise 1', - state: 'created' - }) - ] + await click('.js-page-refresh-btn'); }); - assert.dom('.js-promise-tree').exists('promise tree is seen after being populated'); - assert.dom('.js-promise-tree-item').exists({ count: 1 }, '1 promise item can be seen'); - assert.dom('.js-page-refresh').doesNotExist('page refresh hint hidden'); + test("Promise tree can update after initially showing as unavailable", async function(assert) { + await visit('/promise-tree'); - // make sure clearing does not show the refresh hint - await click('.js-clear-promises-btn'); + assert.dom('.js-promise-tree').doesNotExist("no promise list"); + assert.dom('.js-page-refresh').exists("page refresh hint seen"); - assert.dom('.js-promise-tree').exists('promise-tree can be seen'); - assert.dom('.js-promise-tree-item').doesNotExist('promise items cleared'); - assert.dom('.js-page-refresh').doesNotExist('page refresh hint hidden'); - }); + await sendMessage({ + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + label: 'Promise 1', + state: 'created' + }) + ] + }); - test("Pending promise", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [ - generatePromise({ - guid: 1, - label: 'Promise 1', - state: 'created' - }) - ] - }); - await settled(); + // Why is this needed? + await settled(); - assert.dom('.js-promise-tree-item').exists({ count: 1 }); - let row = find('.js-promise-tree-item'); - assert.dom(row.querySelector('.js-promise-label')).hasText('Promise 1'); - assert.dom(row.querySelector('.js-promise-state')).hasText('Pending'); - }); + assert.dom('.js-promise-tree').exists('promise tree is seen after being populated'); + assert.dom('.js-promise-tree-item').exists({ count: 1 }, '1 promise item can be seen'); + assert.dom('.js-page-refresh').doesNotExist('page refresh hint hidden'); + // make sure clearing does not show the refresh hint + await click('.js-clear-promises-btn'); - test("Fulfilled promise", async function(assert) { - await visit('/promise-tree'); - - let now = Date.now(); - - triggerPort(this, 'promise:promisesUpdated', { - promises: [ - generatePromise({ - guid: 1, - label: 'Promise 1', - state: 'fulfilled', - value: { - inspect: 'value', - type: 'type-string' - }, - createdAt: now, - settledAt: now + 10 - }) - ] - }); - await settled(); - - assert.dom('.js-promise-tree-item').exists({ count: 1 }); - let row = find('.js-promise-tree-item'); - assert.dom(row.querySelector('.js-promise-label')).hasText('Promise 1'); - assert.dom(row.querySelector('.js-promise-state')).hasText('Fulfilled'); - assert.dom(row.querySelector('.js-promise-value')).hasText('value'); - assert.dom(row.querySelector('.js-promise-time')).hasText('10.00ms'); - }); + assert.dom('.js-promise-tree').exists('promise-tree can be seen'); + assert.dom('.js-promise-tree-item').doesNotExist('promise items cleared'); + assert.dom('.js-page-refresh').doesNotExist('page refresh hint hidden'); - - test("Rejected promise", async function(assert) { - await visit('/promise-tree'); - - let now = Date.now(); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [ - generatePromise({ - guid: 1, - label: 'Promise 1', - state: 'rejected', - reason: { - inspect: 'reason', - type: 'type-string' - }, - createdAt: now, - settledAt: now + 20 - }) - ] + respondWith('promise:releasePromises', false); }); - - assert.dom('.js-promise-tree-item').exists({ count: 1 }); - let row = find('.js-promise-tree-item'); - assert.dom(row.querySelector('.js-promise-label')).hasText('Promise 1'); - assert.dom(row.querySelector('.js-promise-state')).hasText('Rejected'); - assert.dom(row.querySelector('.js-promise-value')).hasText('reason'); - assert.dom(row.querySelector('.js-promise-time')).hasText('20.00ms'); }); - test("Chained promises", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [ - generatePromise({ - guid: 2, - parent: 1, - label: 'Child' - }), - generatePromise({ - guid: 1, - children: [2], - label: 'Parent' - }) - ] + module('Some promises', function(inner) { + inner.afterEach(function() { + respondWith('promise:releasePromises', false); }); - let rows = findAll('.js-promise-tree-item'); - assert.equal(rows.length, 1, 'Collpased by default'); - assert.dom(rows[0].querySelector('.js-promise-label')).hasText('Parent'); - await click(rows[0].querySelector('.js-promise-label')); - - rows = findAll('.js-promise-tree-item'); - assert.equal(rows.length, 2, 'Chain now expanded'); - assert.dom(rows[1].querySelector('.js-promise-label')).hasText('Child'); - }); - - test("Can trace promise when there is a stack", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [generatePromise({ guid: 1, hasStack: true })] + test("Pending promise", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + label: 'Promise 1', + state: 'created' + }) + ] + }); + + await visit('/promise-tree'); + + assert.dom('.js-promise-tree-item').exists({ count: 1 }); + + let row = find('.js-promise-tree-item'); + assert.dom(row.querySelector('.js-promise-label')).hasText('Promise 1'); + assert.dom(row.querySelector('.js-promise-state')).hasText('Pending'); }); - await click('.js-trace-promise-btn'); - - assert.equal(name, 'promise:tracePromise'); - assert.deepEqual(message, { promiseId: 1 }); - }); - - - test("Trace button hidden if promise has no stack", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [generatePromise({ guid: 1, hasStack: false })] + test("Fulfilled promise", async function(assert) { + let now = Date.now(); + + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + label: 'Promise 1', + state: 'fulfilled', + value: { + inspect: 'value', + type: 'type-string' + }, + createdAt: now, + settledAt: now + 10 + }) + ] + }); + + await visit('/promise-tree'); + + assert.dom('.js-promise-tree-item').exists({ count: 1 }); + + let row = find('.js-promise-tree-item'); + assert.dom(row.querySelector('.js-promise-label')).hasText('Promise 1'); + assert.dom(row.querySelector('.js-promise-state')).hasText('Fulfilled'); + assert.dom(row.querySelector('.js-promise-value')).hasText('value'); + assert.dom(row.querySelector('.js-promise-time')).hasText('10.00ms'); }); - assert.dom('.js-trace-promise-btn').doesNotExist(); - }); - - test("Toggling promise trace option", async function(assert) { - assert.expect(3); - - await visit('/promise-tree'); - - let input = find('.js-with-stack input'); - assert.notOk(input.checked, 'should not be checked by default'); - await click(input); - - assert.equal(name, 'promise:setInstrumentWithStack'); - assert.equal(message.instrumentWithStack, true); - }); - - test("Logging error stack trace in the console", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [generatePromise({ - guid: 1, - state: 'rejected', - reason: { - inspect: 'some error', - type: 'type-error' - } - })] + test("Rejected promise", async function(assert) { + let now = Date.now(); + + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + label: 'Promise 1', + state: 'rejected', + reason: { + inspect: 'reason', + type: 'type-string' + }, + createdAt: now, + settledAt: now + 20 + }) + ] + }); + + await visit('/promise-tree'); + + assert.dom('.js-promise-tree-item').exists({ count: 1 }); + + let row = find('.js-promise-tree-item'); + assert.dom(row.querySelector('.js-promise-label')).hasText('Promise 1'); + assert.dom(row.querySelector('.js-promise-state')).hasText('Rejected'); + assert.dom(row.querySelector('.js-promise-value')).hasText('reason'); + assert.dom(row.querySelector('.js-promise-time')).hasText('20.00ms'); }); - let row = find('.js-promise-tree-item'); - assert.dom('.js-send-to-console-btn').hasText('Stack Trace'); - await click(row.querySelector('.js-send-to-console-btn')); + test("Chained promises", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 2, + parent: 1, + label: 'Child' + }), + generatePromise({ + guid: 1, + children: [2], + label: 'Parent' + }) + ] + }); + + await visit('/promise-tree'); + + let rows = findAll('.js-promise-tree-item'); + assert.equal(rows.length, 1, 'Collpased by default'); + assert.dom(rows[0].querySelector('.js-promise-label')).hasText('Parent'); + + await click(rows[0].querySelector('.js-promise-label')); + + rows = findAll('.js-promise-tree-item'); + assert.equal(rows.length, 2, 'Chain now expanded'); + assert.dom(rows[1].querySelector('.js-promise-label')).hasText('Child'); + }); - assert.equal(name, 'promise:sendValueToConsole'); - assert.deepEqual(message, { promiseId: 1 }); - }); + // TODO: is this test realistic? (having stack traces without turning on instrumentWithStack) + test("Can trace promise when there is a stack", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + hasStack: true + }) + ] + }); + + await visit('/promise-tree'); + + respondWith('promise:tracePromise', ({ promiseId }) => { + assert.equal(promiseId, 1, 'promiseId'); + return false; + }); + + await click('.js-trace-promise-btn'); + }); + test("Trace button hidden if promise has no stack", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + hasStack: false + }) + ] + }); - test("Send fulfillment value to console", async function(assert) { - await visit('/promise-tree'); + await visit('/promise-tree'); - await triggerPort(this, 'promise:promisesUpdated', { - promises: [generatePromise({ - guid: 1, - state: 'fulfilled', - value: { - inspect: 'some string', - type: 'type-string' - } - })] + assert.dom('.js-trace-promise-btn').doesNotExist(); }); - let row = find('.js-promise-tree-item'); - await click(row.querySelector('.js-send-to-console-btn')); - - assert.equal(name, 'promise:sendValueToConsole'); - assert.deepEqual(message, { promiseId: 1 }); - }); - - test("Sending objects to the object inspector", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [generatePromise({ - guid: 1, - state: 'fulfilled', - value: { - inspect: 'Some Object', - type: 'type-ember-object', - objectId: 100 - } - })] + test("Toggling promise trace option", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise() + ] + }); + + await visit('/promise-tree'); + + let input = find('.js-with-stack input'); + assert.notOk(input.checked, 'should not be checked by default'); + + respondWith('promise:setInstrumentWithStack', ({ + applicationId, applicationName, instrumentWithStack + }) => { + assert.strictEqual(instrumentWithStack, true, 'instrumentWithStack'); + + return { + type: 'promise:instrumentWithStack', + applicationId, + applicationName, + instrumentWithStack + }; + }); + + await click(input); }); - let row = find('.js-promise-tree-item'); - await click(row.querySelector('.js-promise-object-value')); + test("Logging error stack trace in the console", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + state: 'rejected', + reason: { + inspect: 'some error', + type: 'type-error' + } + }) + ] + }); + + await visit('/promise-tree'); + + assert.dom('.js-send-to-console-btn').hasText('Stack Trace'); + + respondWith('promise:sendValueToConsole', ({ promiseId }) => { + assert.equal(promiseId, 1, 'promiseId'); + return false; + }); + + let row = find('.js-promise-tree-item'); + await click(row.querySelector('.js-send-to-console-btn')); + }); - assert.equal(name, 'objectInspector:inspectById'); - assert.deepEqual(message, { objectId: 100 }); - }); + test("Send fulfillment value to console", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + state: 'fulfilled', + value: { + inspect: 'some string', + type: 'type-string' + } + }) + ] + }); + + await visit('/promise-tree'); + + assert.dom('.js-send-to-console-btn').exists(); + + respondWith('promise:sendValueToConsole', ({ promiseId }) => { + assert.equal(promiseId, 1, 'promiseId'); + return false; + }); + + let row = find('.js-promise-tree-item'); + await click(row.querySelector('.js-send-to-console-btn')); + }); - test("It should clear the search filter when the clear button is clicked", async function(assert) { - await visit('/promise-tree'); - - await triggerPort(this, 'promise:promisesUpdated', { - promises: [ - generatePromise({ - guid: 1, - label: 'Promise 1', - state: 'created' - }) - ] + test("Sending objects to the object inspector", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + state: 'fulfilled', + value: { + inspect: 'Some Object', + type: 'type-ember-object', + objectId: 100 + } + }) + ] + }); + + await visit('/promise-tree'); + + respondWith('objectInspector:inspectById', ({ objectId }) => { + assert.equal(objectId, 100, 'promiseId'); + return false; + }); + + let row = find('.js-promise-tree-item'); + await click(row.querySelector('.js-promise-object-value')); }); - await settled(); - assert.dom('.js-promise-tree-item').exists({ count: 1 }); - await fillIn('.js-promise-search input', 'xxxxx'); - assert.dom('.js-promise-tree-item').doesNotExist(); - await click('.js-search-field-clear-button'); - assert.dom('.js-promise-tree-item').exists({ count: 1 }); + test("It should clear the search filter when the clear button is clicked", async function(assert) { + respondWith('promise:getAndObservePromises', { + type: 'promise:promisesUpdated', + promises: [ + generatePromise({ + guid: 1, + label: 'Promise 1', + state: 'created' + }) + ] + }); + + await visit('/promise-tree'); + + assert.dom('.js-promise-tree-item').exists({ count: 1 }); + await fillIn('.js-promise-search input', 'xxxxx'); + assert.dom('.js-promise-tree-item').doesNotExist(); + await click('.js-search-field-clear-button'); + assert.dom('.js-promise-tree-item').exists({ count: 1 }); + }); }); }); diff --git a/tests/acceptance/render-tree-test.js b/tests/acceptance/render-tree-test.js index a8df930751..fe6a996fdc 100644 --- a/tests/acceptance/render-tree-test.js +++ b/tests/acceptance/render-tree-test.js @@ -1,148 +1,121 @@ import { visit, findAll, click, fillIn } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; - -let port; - -module('Render Tree Tab', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - port.reopen({ - send(/*n, m*/) {} - }); +import { respondWith } from '../test-adapter'; + +function generateProfiles() { + return [ + { + name: 'First View Rendering', + duration: 476.87, + timestamp: (new Date(2014, 5, 1, 13, 16, 22, 715)).getTime(), + children: [{ + name: 'Child view', + duration: 0.36, + timestamp: (new Date(2014, 5, 1, 13, 16, 22, 581)).getTime(), + children: [] + }] + }, + + { + name: "Second View Rendering", + duration: 10, + timestamp: (new Date(2014, 5, 1, 13, 16, 22, 759)).getTime(), + children: [] + } + ]; +} + +module('Render Tree Tab', function(outer) { + setupApplicationTest(outer); + + outer.afterEach(function() { + respondWith('render:releaseProfiles', false); }); - function generateProfiles() { - return [ - { - name: 'First View Rendering', - duration: 476.87, - timestamp: (new Date(2014, 5, 1, 13, 16, 22, 715)).getTime(), - children: [{ - name: 'Child view', - duration: 0.36, - timestamp: (new Date(2014, 5, 1, 13, 16, 22, 581)).getTime(), - children: [] - }] - }, - - { - name: "Second View Rendering", - duration: 10, - timestamp: (new Date(2014, 5, 1, 13, 16, 22, 759)).getTime(), - children: [] - } - ]; - } - - - test("No profiles collected", async function(assert) { - port.reopen({ - send(n/*, m*/) { - if (n === 'render:watchProfiles') { - this.trigger('render:profilesAdded', { - profiles: [] - }); - } - } + module('No profiles', function(inner) { + inner.beforeEach(function() { + respondWith('render:watchProfiles', { + type: 'render:profilesAdded', + profiles: [] + }); }); - await visit('/render-tree'); + test("No profiles collected", async function(assert) { + await visit('/render-tree'); - assert.dom('.js-render-tree').doesNotExist("no render tree"); - assert.dom('.js-render-tree-empty').exists("Message about empty render tree shown"); + assert.dom('.js-render-tree').doesNotExist("no render tree"); + assert.dom('.js-render-tree-empty').exists("Message about empty render tree shown"); + }); }); - test("Renders the list correctly", async function(assert) { - port.reopen({ - send(n/*, m*/) { - if (n === 'render:watchProfiles') { - this.trigger('render:profilesAdded', { - profiles: generateProfiles() - }); - } - } + module('Some profiles', function(inner) { + inner.beforeEach(function() { + respondWith('render:watchProfiles', { + type: 'render:profilesAdded', + profiles: generateProfiles() + }); }); - await visit('/render-tree'); - - assert.dom('.js-render-tree').exists(); - let rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 2, "Two rows are rendered initially"); + test("Renders the list correctly", async function(assert) { + await visit('/render-tree'); - assert.dom(rows[0].querySelector('.js-render-profile-name')).hasText('First View Rendering'); - assert.dom(rows[0].querySelector('.js-render-profile-duration')).hasText('476.87ms'); - assert.dom(rows[0].querySelector('.js-render-profile-timestamp')).hasText('13:16:22:715'); + assert.dom('.js-render-tree').exists(); + let rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 2, "Two rows are rendered initially"); - assert.dom(rows[1].querySelector('.js-render-profile-name')).hasText('Second View Rendering'); - assert.dom(rows[1].querySelector('.js-render-profile-duration')).hasText('10.00ms'); - assert.dom(rows[1].querySelector('.js-render-profile-timestamp')).hasText('13:16:22:759'); + assert.dom(rows[0].querySelector('.js-render-profile-name')).hasText('First View Rendering'); + assert.dom(rows[0].querySelector('.js-render-profile-duration')).hasText('476.87ms'); + assert.dom(rows[0].querySelector('.js-render-profile-timestamp')).hasText('13:16:22:715'); - await click('.js-render-main-cell', rows[0]); + assert.dom(rows[1].querySelector('.js-render-profile-name')).hasText('Second View Rendering'); + assert.dom(rows[1].querySelector('.js-render-profile-duration')).hasText('10.00ms'); + assert.dom(rows[1].querySelector('.js-render-profile-timestamp')).hasText('13:16:22:759'); - rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 3, "Child is shown below the parent"); + await click('.js-render-main-cell', rows[0]); - assert.dom(rows[1].querySelector('.js-render-profile-name')).hasText('Child view'); - assert.dom(rows[1].querySelector('.js-render-profile-duration')).hasText('0.36ms'); - assert.dom(rows[1].querySelector('.js-render-profile-timestamp')).hasText('13:16:22:581'); + rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 3, "Child is shown below the parent"); - await click('.js-render-main-cell', rows[0]); + assert.dom(rows[1].querySelector('.js-render-profile-name')).hasText('Child view'); + assert.dom(rows[1].querySelector('.js-render-profile-duration')).hasText('0.36ms'); + assert.dom(rows[1].querySelector('.js-render-profile-timestamp')).hasText('13:16:22:581'); - rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 2, "Child is hidden when parent collapses"); - }); + await click('.js-render-main-cell', rows[0]); - test("Searching the profiles", async function(assert) { - port.reopen({ - send(n/*, m*/) { - if (n === 'render:watchProfiles') { - this.trigger('render:profilesAdded', { - profiles: generateProfiles() - }); - } - } + rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 2, "Child is hidden when parent collapses"); }); - await visit('/render-tree'); + test("Searching the profiles", async function(assert) { + await visit('/render-tree'); - let rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 2, "Two rows are rendered initially"); + let rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 2, "Two rows are rendered initially"); - assert.dom(rows[0].querySelector('.js-render-profile-name')).hasText('First View Rendering'); - assert.dom(rows[1].querySelector('.js-render-profile-name')).hasText('Second View Rendering'); + assert.dom(rows[0].querySelector('.js-render-profile-name')).hasText('First View Rendering'); + assert.dom(rows[1].querySelector('.js-render-profile-name')).hasText('Second View Rendering'); - await fillIn('.js-render-profiles-search input', 'Second'); + await fillIn('.js-render-profiles-search input', 'Second'); - rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 1, "The second row is the only one showing"); - assert.dom(rows[0].querySelector('.js-render-profile-name')).hasText('Second View Rendering'); - }); - - test("It should clear the search filter when the clear button is clicked", async function(assert) { - port.reopen({ - send(n/*, m*/) { - if (n === 'render:watchProfiles') { - this.trigger('render:profilesAdded', { - profiles: generateProfiles() - }); - } - } + rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 1, "The second row is the only one showing"); + assert.dom(rows[0].querySelector('.js-render-profile-name')).hasText('Second View Rendering'); }); - await visit('/render-tree'); + test("It should clear the search filter when the clear button is clicked", async function(assert) { + await visit('/render-tree'); - let rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 2, "expected all rows"); + let rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 2, "expected all rows"); - await fillIn('.js-render-profiles-search input', 'xxxxxx'); - rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 0, 'expected filtered rows'); + await fillIn('.js-render-profiles-search input', 'xxxxxx'); + rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 0, 'expected filtered rows'); - await click('.js-search-field-clear-button'); - rows = findAll('.js-render-profile-item'); - assert.equal(rows.length, 2, 'expected all rows'); + await click('.js-search-field-clear-button'); + rows = findAll('.js-render-profile-item'); + assert.equal(rows.length, 2, 'expected all rows'); + }); }); }); diff --git a/tests/acceptance/route-tree-test.js b/tests/acceptance/route-tree-test.js index 3d2ce5b4bf..267a4fea7b 100644 --- a/tests/acceptance/route-tree-test.js +++ b/tests/acceptance/route-tree-test.js @@ -3,19 +3,18 @@ import { fillIn, find, findAll, - settled, triggerEvent, visit } from '@ember/test-helpers'; -import { run } from '@ember/runloop'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; +import { respondWith, sendMessage } from '../test-adapter'; -export function isObject(item) { +function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); } -export function deepAssign(target, ...sources) { +function deepAssign(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); @@ -33,45 +32,28 @@ export function deepAssign(target, ...sources) { return deepAssign(target, ...sources); } -let port; - -module('Route Tree Tab', function(hooks) { - setupApplicationTest(hooks); - - hooks.beforeEach(function() { - port = this.owner.lookup('service:port'); - }); - - hooks.afterEach(async () => { - const checkbox = find('.js-filter-hide-routes input'); - - checkbox.checked = false; - await triggerEvent(checkbox, 'change'); - - port = null; - }); - - function routeValue(name, props) { - let value = { +function routeValue(name, props) { + let value = { + name, + controller: { name, - controller: { - name, - className: `${name.replace(/\./g, '_').classify()}Controller`, - exists: true - }, - routeHandler: { - name, - className: `${name.replace(/\./g, '_').classify()}Route` - }, - template: { - name: name.replace(/\./g, '/') - } - }; - props = props || {}; - return deepAssign({}, value, props); - } + className: `${name.replace(/\./g, '_').classify()}Controller`, + exists: true + }, + routeHandler: { + name, + className: `${name.replace(/\./g, '_').classify()}Route` + }, + template: { + name: name.replace(/\./g, '/') + } + }; + props = props || {}; + return deepAssign({}, value, props); +} - let routeTree = { +function routeTree() { + return { value: routeValue('application'), children: [{ value: routeValue('post', { controller: { exists: false } }), @@ -90,188 +72,164 @@ module('Route Tree Tab', function(hooks) { }] }] }; +} - test("Route tree is successfully displayed", async function(assert) { - port.reopen({ - send(name/*, message*/) { - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } - } - }); - - await visit('route-tree'); +module('Route Tree Tab', function(outer) { + setupApplicationTest(outer); - let routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 6, 'correct number of nodes'); - - let routeNames = findAll('.js-route-name').map(function(item) { - return item.textContent.trim(); + outer.beforeEach(function() { + respondWith('route:getTree', { + type: 'route:routeTree', + tree: routeTree() }); - assert.deepEqual( - routeNames, - ['application', 'post', 'post.loading', 'post.new', 'post.edit', 'comments'], - 'route name displayed' - ); - - let routeHandlers = findAll('.js-route-handler').map(function(item) { - return item.getAttribute('title').trim(); + }); + + module('Starting at post/edit', function(inner) { + inner.beforeEach(function() { + respondWith('route:getCurrentRoute', { + type: 'route:currentRoute', + name: 'post.edit', + url: 'post/edit' + }); }); - assert.deepEqual( - routeHandlers, - ['ApplicationRoute', 'PostRoute', 'PostLoadingRoute', 'PostNewRoute', 'PostEditRoute', 'CommentsRoute'], - 'route class name in title attribute' - ); - - let controllers = findAll('.js-route-controller').map(function(item) { - return item.getAttribute('title').trim(); + + test("Route tree is successfully displayed", async function(assert) { + await visit('route-tree'); + + let routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 6, 'correct number of nodes'); + + let routeNames = findAll('.js-route-name').map(function(item) { + return item.textContent.trim(); + }); + assert.deepEqual( + routeNames, + ['application', 'post', 'post.loading', 'post.new', 'post.edit', 'comments'], + 'route name displayed' + ); + + let routeHandlers = findAll('.js-route-handler').map(function(item) { + return item.getAttribute('title').trim(); + }); + assert.deepEqual( + routeHandlers, + ['ApplicationRoute', 'PostRoute', 'PostLoadingRoute', 'PostNewRoute', 'PostEditRoute', 'CommentsRoute'], + 'route class name in title attribute' + ); + + let controllers = findAll('.js-route-controller').map(function(item) { + return item.getAttribute('title').trim(); + }); + + // "PostController" not listed because a file for it was not created on the filesystem + assert.deepEqual( + controllers, + ['ApplicationController', 'PostLoadingController', 'PostNewController', 'PostEditController', 'CommentsController'], + 'controller class name in title attribute' + ); }); - // "PostController" not listed because a file for it was not created on the filesystem - assert.deepEqual( - controllers, - ['ApplicationController', 'PostLoadingController', 'PostNewController', 'PostEditController', 'CommentsController'], - 'controller class name in title attribute' - ); - }); + test("Clicking on route handlers and controller sends an inspection message", async function(assert) { + await visit('route-tree'); - test("Clicking on route handlers and controller sends an inspection message", async function(assert) { - let name, message, applicationRow; + let applicationRow = find('.js-route-tree-item'); - port.reopen({ - send(n, m) { - name = n; - message = m; + respondWith('objectInspector:inspectRoute', ({ name }) => { + assert.equal(name, 'application', 'route name'); + return false; + }); - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } - } + await click(applicationRow.querySelector('.js-route-handler')); + + respondWith('objectInspector:inspectController', ({ name }) => { + assert.equal(name, 'application', 'controller name'); + return false; + }); + + await click(applicationRow.querySelector('.js-route-controller')); }); - await visit('route-tree'); - name = null; - message = null; - applicationRow = find('.js-route-tree-item'); - await click(applicationRow.querySelector('.js-route-handler')); - assert.equal(name, 'objectInspector:inspectRoute'); - assert.equal(message.name, 'application'); - - name = null; - message = null; - await click(applicationRow.querySelector('.js-route-controller')); - assert.equal(name, 'objectInspector:inspectController'); - assert.equal(message.name, 'application'); - }); + test("Current Route is highlighted", async function(assert) { + await visit('route-tree'); - test("Current Route is highlighted", async function(assert) { - port.reopen({ - send(name/*, message*/) { - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } else if (name === 'route:getCurrentRoute') { - this.trigger('route:currentRoute', { name: 'post.edit' }); - } - } + let routeNodes = findAll('.js-route-tree-item .js-route-name'); + let isCurrent = [...routeNodes].map(item => item.classList.contains('pill')); + assert.deepEqual(isCurrent, [true, true, false, false, true, false]); + + await sendMessage({ + type: 'route:currentRoute', + name: 'post.new', + url: 'post/new' + }); + + routeNodes = findAll('.js-route-tree-item .js-route-name'); + isCurrent = [...routeNodes].map(item => item.classList.contains('pill')); + assert.deepEqual(isCurrent, [true, true, false, true, false, false], 'Current route is bound'); }); + test("It should filter the tree using the search text", async function(assert) { + await visit('route-tree'); - let routeNodes; + let routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 6); - await visit('route-tree'); - routeNodes = findAll('.js-route-tree-item .js-route-name'); - let isCurrent = [...routeNodes].map(item => item.classList.contains('pill')); - assert.deepEqual(isCurrent, [true, true, false, false, true, false]); - - run(() => port.trigger('route:currentRoute', { name: 'post.new' })); - await settled(); - routeNodes = findAll('.js-route-tree-item .js-route-name'); - isCurrent = [...routeNodes].map(item => item.classList.contains('pill')); - assert.deepEqual(isCurrent, [true, true, false, true, false, false], 'Current route is bound'); - }); + await fillIn('.js-filter-views input', 'edit'); - test("It should filter the tree using the search text", async function(assert) { - port.reopen({ - send(name/*, message*/) { - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } else if (name === 'route:getCurrentRoute') { - this.trigger('route:currentRoute', { name: 'post.edit' }); - } - } + routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 1); + + await click('.js-search-field-clear-button'); + + routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 6); }); - await visit('route-tree'); - let routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 6); + test("Hiding non current route", async function(assert) { + await visit('route-tree'); - await fillIn('.js-filter-views input', 'edit'); - routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 1); + let routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 6); - await click('.js-search-field-clear-button'); - routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 6); - }); + let checkbox = find('.js-filter-hide-routes input'); + checkbox.checked = true; + await triggerEvent(checkbox, 'change'); - test("Hiding non current route", async function(assert) { - port.reopen({ - send(name/*, message*/) { - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } else if (name === 'route:getCurrentRoute') { - this.trigger('route:currentRoute', { name: 'post.edit' }); - } - } + routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 3); }); - await visit('route-tree'); - let routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 6); - let checkbox = find('.js-filter-hide-routes input'); - checkbox.checked = true; - await triggerEvent(checkbox, 'change'); - routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 3); - }); + test("Hiding substates", async function(assert) { + await visit('route-tree'); + + let routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 6); + + let checkbox = find('.js-filter-hide-substates input'); + checkbox.checked = true; + await triggerEvent(checkbox, 'change'); + + routeNodes = findAll('.js-route-tree-item'); + assert.equal(routeNodes.length, 5); + }); + }) test('Displaying route w/ reset namespace set to true', async function(assert) { - port.reopen({ - send(name/*, message*/) { - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } else if (name === 'route:getCurrentRoute') { - this.trigger('route:currentRoute', { name: 'post.edit.comments', url: 'post/edit/comments' }); - } - } + respondWith('route:getCurrentRoute', { + type: 'route:currentRoute', + name: 'post.edit.comments', + url: 'post/edit/comments' }); await visit('route-tree'); + let routeNodes = findAll('.js-route-tree-item'); assert.equal(routeNodes.length, 6); + const checkbox = find('.js-filter-hide-routes input'); checkbox.checked = true; await triggerEvent(checkbox, 'change'); - routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 4); - }); - test("Hiding substates", async function(assert) { - port.reopen({ - send(name/*, message*/) { - if (name === 'route:getTree') { - this.trigger('route:routeTree', { tree: routeTree }); - } - } - }); - - await visit('route-tree'); - let routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 6); - let checkbox = find('.js-filter-hide-substates input'); - checkbox.checked = true; - await triggerEvent(checkbox, 'change'); routeNodes = findAll('.js-route-tree-item'); - assert.equal(routeNodes.length, 5); + assert.equal(routeNodes.length, 4); }); }); diff --git a/tests/acceptance/whats-new-test.js b/tests/acceptance/whats-new-test.js index eb85cba4b6..e216e008df 100644 --- a/tests/acceptance/whats-new-test.js +++ b/tests/acceptance/whats-new-test.js @@ -3,8 +3,8 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import Pretender from 'pretender'; -const url = 'https://raw.githubusercontent.com/emberjs/ember-inspector/master/CHANGELOG.md'; -const response = ` +const CHANGELOG_URL = 'https://raw.githubusercontent.com/emberjs/ember-inspector/master/CHANGELOG.md'; +const CHANGELOG_RESPONSE = ` # Changelog ## [Unreleased](https://github.com/emberjs/ember-inspector/tree/HEAD) @@ -31,9 +31,15 @@ const response = ` module('Whats New', function (hooks) { setupApplicationTest(hooks); - test('Changelog is parsed and displayed', async function t(assert) { - const server = new Pretender(function () { - this.get(url, () => [200, { 'Content-Type': 'text/plain' }, response]); + hooks.afterEach(function() { + if (this.server) { + this.server.shutdown(); + } + }); + + test('Changelog is parsed and displayed', async function(assert) { + this.server = new Pretender(function () { + this.get(CHANGELOG_URL, () => [200, { 'Content-Type': 'text/plain' }, CHANGELOG_RESPONSE]); }); await visit('/info/whats-new'); @@ -45,19 +51,15 @@ module('Whats New', function (hooks) { 'v3.3.0', 'correct section of markdown is rendered' ); - - server.shutdown(); }); - test('Error message is displayed on request failure', async function t(assert) { - const server = new Pretender(function () { - this.get(url, () => [404, {}, '']); + test('Error message is displayed on request failure', async function(assert) { + this.server = new Pretender(function () { + this.get(CHANGELOG_URL, () => [404, {}, '']); }); await visit('/info/whats-new'); assert.dom('.whats-new p').exists({ count: 1 }, 'Changelog could not be loaded'); - - server.shutdown(); }); }); diff --git a/tests/ember_debug/view-debug-test.js b/tests/ember_debug/view-debug-test.js index f6ca179b08..dc67fadfe6 100644 --- a/tests/ember_debug/view-debug-test.js +++ b/tests/ember_debug/view-debug-test.js @@ -250,7 +250,7 @@ module('Ember Debug - View', function(hooks) { }); test('Supports applications that don\'t have the ember-application CSS class', async function t(assert) { - let name = null; + let name, message; let rootElement = document.body; await visit('/simple'); @@ -265,15 +265,20 @@ module('Ember Debug - View', function(hooks) { EmberDebug.start(); port = EmberDebug.port; + await visit('/simple'); + port.reopen({ - send(n/*, m*/) { + send(n, m) { name = n; + message = m; } }); - await visit('/simple'); + run(port, 'trigger', 'view:getTree'); + await settled(); - assert.equal(name, 'view:viewTree'); + assert.equal(name, 'view:viewTree', 'view tree is sent'); + assert.ok(message.tree, 'view tree is sent'); }); test('Does not list nested {{yield}} views', async function t(assert) { diff --git a/tests/test-adapter.js b/tests/test-adapter.js new file mode 100644 index 0000000000..463cac8abe --- /dev/null +++ b/tests/test-adapter.js @@ -0,0 +1,292 @@ +/* eslint-disable no-console */ +import QUnit from 'qunit'; +import { next } from '@ember/runloop'; +import BasicAdapter from '../adapters/basic'; + +let adapter = null; +let resourcesEnabled = false; +let resources = []; +let responders = []; + +// Some default responders that are part of the normal application boot cycle +QUnit.testStart(() => { + respondWith('check-version', false, { isDefault: true }); + + respondWith('general:applicationBooted', { + type: 'general:applicationBooted', + applicationId: 'my-app', + applicationName: 'My App', + booted: true + }, { isDefault: true }); + + respondWith('app-picker-loaded', { + type: 'apps-loaded', + applicationId: null, + applicationName: null, + apps: [{ + applicationId: 'my-app', + applicationName: 'My App' + }] + }, { isDefault: true }); + + respondWith('app-selected', false, { isDefault: true }); + + respondWith('deprecation:getCount', ({ applicationId, applicationName }) => ({ + type: 'deprecation:count', + applicationId, + applicationName, + count: 0 + }), { isDefault: true }); +}); + +// Ensure all expectations are met and reset the global states +QUnit.testDone(({ failed }) => { + if (failed === 0) { + for (let { file, line, actual, expected, reject } of resources) { + if (!isNaN(expected) && actual !== expected) { + QUnit.assert.strictEqual(actual, expected, `Expceting resouce ${file}:${line} to be opened ${expected} time(s)`); + reject(`Expceting resouce ${file}:${line} to be opened ${expected} time(s), was opened ${actual} time(s)`); + } + } + + for (let { type, isDefault, actual, expected, reject } of responders) { + if (!isDefault && !isNaN(expected) && actual !== expected) { + QUnit.assert.strictEqual(actual, expected, `The correct amount of ${type} messages are sent`); + reject(`Expecting ${expected} ${type} messages, got ${actual}`); + } + } + } + + adapter = null; + resourcesEnabled = false; + resources.length = 0; + responders.length = 0; +}); + +/** + * Allow `openResouce` to be called on the adapter. + * + * @method enableOpenResource + */ +export function enableOpenResource() { + resourcesEnabled = true; +} + +/** + * Expect `openResouce` to be called on the adapter with a specific filename and + * line number. Must call `enableOpenResource` first. + * + * @method expectOpenResource + * @param {String} file The filename. + * @param {number} line The line number. + * @param {Object} options + * - {number | false} count How many calls to allow. `false` for unlimited. + * Defaults to 1. + * @return {Promise} Resolves when all the expected calls are made, or + * rejects at the end of the current test if not called + * enough times. + */ +export function expectOpenResource(file, line, options = {}) { + if (!resourcesEnabled) { + throw new Error('call enableOpenResource first'); + } + + return new Promise((resolve, reject) => { + let { count } = { count: 1, ...options }; + resources.push({ + file, + line, + actual: 0, + expected: count === false ? NaN : count, + resolve, + reject, + }); + }); +} + +/** + * Send a message to the adapter. + * + * @method expectOpenResource + * @param {Object} message The message. + * @return {Promise} Resolves when the message is delivered. + */ +export function sendMessage(message) { + if (adapter === null) { + throw new Error('Cannot call sendMessage outside of a test'); + } + + return new Promise(resolve => { + next(() => { + let normalized = { + applicationId: 'my-app', + applicationName: 'My App', + ...message, + from: 'inspectedWindow' + }; + adapter._messageReceived(normalized); + resolve(normalized); + }); + }); +} + +/** + * Expect a message from the adapter of the given type, and respond to the message + * with the given payload. + * + * @method respondWith + * @param {String} type The incoming message type. + * @param { false | Object | Function } payload The payload. + * - Pass `false` to acknoledge the message but don't send a response. + * - Pass an object to send a response (`message` parameter of `sendMessage`). + * - Pass a callback to dynamically respond with one of the above, or `undefined`, + * in which case the incoming messages is considered unhandled and the remaining + * responders will be tried instead. The callback is given the incoming message + * as an argument. + * @param {Object} options + * - {number | false} count How many calls to allow. `false` for unlimited. + * Defaults to 1. + * @return {Promise} Resolves when all the expected calls are made, or + * rejects at the end of the current test if not called + * enough times. + */ +export function respondWith(type, payload, options = {}) { + return new Promise((resolve, reject) => { + let { count, isDefault } = { count: 1, isDefault: false, ...options }; + let callback = (typeof payload === 'function') ? payload : () => payload; + + responders.push({ + type, + isDefault, + callback, + actual: 0, + expected: count === false ? NaN : count, + resolve, + reject, + }); + }); +} + +/** + * Disable the default responder for the given type. + * + * @method disableDefaultResponseFor + */ +export function disableDefaultResponseFor(type) { + for (let [i, responder] of responders.entries()) { + if (responder.type === type && responder.isDefault) { + if (responder.actual > 0) { + throw new Error(`Cannot remove default responder for ${type}: a response has already been sent!`); + } + + responders.splice(i, 1); + return; + } + } + + throw new Error(`Cannot remove default responder for ${type}: no such responder!`); +} + +export default class TestAdapter extends BasicAdapter { + constructor() { + super(...arguments); + adapter = this; + } + + get name() { + return 'test'; + } + + get canOpenResource() { + return resourcesEnabled; + } + + openResource(file, line) { + if (!resourcesEnabled) { + throw new Error('openResource called unexpectedly'); + } + + console.debug('Opening resource', { file, line }); + + if (!file) { + QUnit.assert.ok(file, `resource has valid "file" field: ${JSON.stringify(file)}`); + return; + } + + if (!line) { + QUnit.assert.ok(file, `resource has valid "line" field: ${JSON.stringify(line)}`); + return; + } + + for (let resource of resources) { + let { actual, expected, resolve } = resource; + + if (actual === expected) { + continue; + } + + if (file === resource.file && line === resource.line) { + console.debug('Opened resource', { file, line }); + resource.actual = ++actual; + resolve(); + return; + } + } + + console.error('Unknown resource', { file, line }); + + QUnit.assert.deepEqual({ file, line }, {}, 'Unknown resource'); + } + + sendMessage(message) { + console.debug('Sending message (devtools -> inspectedWindow)', message); + + if (!message.type) { + QUnit.assert.ok(false, `message has valid "type" field: ${JSON.stringify(message)}`); + return; + } + + if (message.from !== 'devtools') { + QUnit.assert.equal(message.from, 'devtools', `message has valid "from" field: ${JSON.stringify(message)}`); + return; + } + + for (let responder of responders) { + let { type, callback, actual, expected, resolve } = responder; + + if (actual === expected) { + continue; + } + + if (type === message.type) { + let response = callback(message); + + if (response !== undefined) { + responder.actual = ++actual; + } + + let didRespond; + + if (response) { + console.debug('Received response (inspectedWindow -> devtools)', response); + didRespond = sendMessage(response); + } else if (response === false) { + console.debug('Ignoreing message (devtools -> inspectedWindow)', message); + didRespond = Promise.resolve(); + } + + if (didRespond) { + if (actual === expected) { + didRespond.then(resolve); + } + + return; + } + } + } + + console.error('Unexpected message', message); + + QUnit.assert.deepEqual(message, {}, 'Unexpected message'); + } +} diff --git a/tests/test-helper.js b/tests/test-helper.js index 2b7b579f25..7740977eaa 100644 --- a/tests/test-helper.js +++ b/tests/test-helper.js @@ -1,7 +1,15 @@ import Application from 'ember-inspector/app'; -import config from '../config/environment'; +import config from 'ember-inspector/config/environment'; import { setApplication } from '@ember/test-helpers'; import { start } from 'ember-qunit'; +import TestAdapter from './test-adapter'; + +Application.initializer({ + name: `00-override-adapter`, + initialize(app) { + app.register('adapter:main', TestAdapter); + } +}); Application.instanceInitializer({ name: '00-force-memory-storage-backend', @@ -12,15 +20,6 @@ Application.instanceInitializer({ } }); -Application.instanceInitializer({ - name: '01-detect-ember-application', - initialize(instance) { - instance.lookup('route:app-detected').reopen({ - model() { } - }); - } -}); - setApplication(Application.create(config.APP)); window.NO_EMBER_DEBUG = true; start();