Merge pull request #3280 from hashicorp/b-ui-links-in-tables

Links in table rows should get click priority over table rows
This commit is contained in:
Michael Lange 2017-09-29 09:37:18 -07:00 committed by GitHub
commit c9220969f9
24 changed files with 364 additions and 254 deletions

View file

@ -1,4 +1,5 @@
import Ember from 'ember'; import Ember from 'ember';
import { lazyClick } from '../helpers/lazy-click';
const { Component } = Ember; const { Component } = Ember;
@ -15,6 +16,6 @@ export default Component.extend({
onClick() {}, onClick() {},
click(event) { click(event) {
this.get('onClick')(event); lazyClick([this.get('onClick'), event]);
}, },
}); });

View file

@ -1,4 +1,5 @@
import Ember from 'ember'; import Ember from 'ember';
import { lazyClick } from '../helpers/lazy-click';
const { Component } = Ember; const { Component } = Ember;
@ -11,7 +12,7 @@ export default Component.extend({
onClick() {}, onClick() {},
click(event) { click(event) {
this.get('onClick')(event); lazyClick([this.get('onClick'), event]);
}, },
didReceiveAttrs() { didReceiveAttrs() {

View file

@ -1,4 +1,5 @@
import Ember from 'ember'; import Ember from 'ember';
import { lazyClick } from '../helpers/lazy-click';
const { Component } = Ember; const { Component } = Ember;
@ -11,7 +12,7 @@ export default Component.extend({
onClick() {}, onClick() {},
click(event) { click(event) {
this.get('onClick')(event); lazyClick([this.get('onClick'), event]);
}, },
didReceiveAttrs() { didReceiveAttrs() {

View file

@ -1,4 +1,5 @@
import Ember from 'ember'; import Ember from 'ember';
import { lazyClick } from '../helpers/lazy-click';
const { Component, inject, computed } = Ember; const { Component, inject, computed } = Ember;
@ -28,6 +29,7 @@ export default Component.extend({
}), }),
click() { click() {
this.get('router').transitionTo('servers.server', this.get('agent')); const transition = () => this.get('router').transitionTo('servers.server', this.get('agent'));
lazyClick([transition, event]);
}, },
}); });

View file

@ -1,4 +1,5 @@
import Ember from 'ember'; import Ember from 'ember';
import { lazyClick } from '../helpers/lazy-click';
const { Component } = Ember; const { Component } = Ember;
@ -12,6 +13,6 @@ export default Component.extend({
onClick() {}, onClick() {},
click(event) { click(event) {
this.get('onClick')(event); lazyClick([this.get('onClick'), event]);
}, },
}); });

View file

@ -0,0 +1,19 @@
import Ember from 'ember';
const { Helper, $ } = Ember;
/**
* Lazy Click Event
*
* Usage: {{lazy-click action}}
*
* Calls the provided action only if the target isn't an anchor
* that should be handled instead.
*/
export function lazyClick([onClick, event]) {
if (!$(event.target).is('a')) {
onClick(event);
}
}
export default Helper.helper(lazyClick);

View file

@ -69,7 +69,7 @@
Attributes Attributes
</div> </div>
<div class="boxed-section-body is-full-bleed"> <div class="boxed-section-body is-full-bleed">
{{attributes-table attributes=model.attributes.attributesStructured}} {{attributes-table attributes=model.attributes.attributesStructured class="attributes-table"}}
</div> </div>
</div> </div>
</section> </section>

View file

@ -59,6 +59,7 @@
"ember-href-to": "^1.13.0", "ember-href-to": "^1.13.0",
"ember-load-initializers": "^1.0.0", "ember-load-initializers": "^1.0.0",
"ember-moment": "^7.3.1", "ember-moment": "^7.3.1",
"ember-native-dom-helpers": "^0.5.4",
"ember-resolver": "^4.0.0", "ember-resolver": "^4.0.0",
"ember-sinon": "^0.7.0", "ember-sinon": "^0.7.0",
"ember-source": "~2.14.0", "ember-source": "~2.14.0",

View file

@ -1,7 +1,11 @@
import Ember from 'ember';
import { click, findAll, currentURL, find, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
import moment from 'moment'; import moment from 'moment';
const { $ } = Ember;
let job; let job;
let node; let node;
let allocation; let allocation;
@ -23,32 +27,27 @@ moduleForAcceptance('Acceptance | allocation detail', {
test('/allocation/:id should name the allocation and link to the corresponding job and node', function( test('/allocation/:id should name the allocation and link to the corresponding job and node', function(
assert assert
) { ) {
assert.ok(find('h1').textContent.includes(allocation.name), 'Allocation name is in the heading');
assert.ok(find('h3').textContent.includes(job.name), 'Job name is in the subheading');
assert.ok( assert.ok(
find('h1') find('h3').textContent.includes(node.id.split('-')[0]),
.text()
.includes(allocation.name),
'Allocation name is in the heading'
);
assert.ok(
find('h3')
.text()
.includes(job.name),
'Job name is in the subheading'
);
assert.ok(
find('h3')
.text()
.includes(node.id.split('-')[0]),
'Node short id is in the subheading' 'Node short id is in the subheading'
); );
click('h3 a:eq(0)'); andThen(() => {
click(findAll('h3 a')[0]);
});
andThen(() => { andThen(() => {
assert.equal(currentURL(), `/jobs/${job.id}`, 'Job link navigates to the job'); assert.equal(currentURL(), `/jobs/${job.id}`, 'Job link navigates to the job');
}); });
visit(`/allocations/${allocation.id}`); visit(`/allocations/${allocation.id}`);
click('h3 a:eq(1)');
andThen(() => {
click(findAll('h3 a')[1]);
});
andThen(() => { andThen(() => {
assert.equal(currentURL(), `/nodes/${node.id}`, 'Node link navigates to the node'); assert.equal(currentURL(), `/nodes/${node.id}`, 'Node link navigates to the node');
}); });
@ -56,7 +55,7 @@ test('/allocation/:id should name the allocation and link to the corresponding j
test('/allocation/:id should list all tasks for the allocation', function(assert) { test('/allocation/:id should list all tasks for the allocation', function(assert) {
assert.equal( assert.equal(
find('.tasks tbody tr').length, findAll('.tasks tbody tr').length,
server.db.taskStates.where({ allocationId: allocation.id }).length, server.db.taskStates.where({ allocationId: allocation.id }).length,
'Table lists all tasks' 'Table lists all tasks'
); );
@ -68,7 +67,7 @@ test('each task row should list high-level information for the task', function(a
.map(id => server.db.taskResources.find(id)) .map(id => server.db.taskResources.find(id))
.sortBy('name')[0]; .sortBy('name')[0];
const reservedPorts = taskResources.resources.Networks[0].ReservedPorts; const reservedPorts = taskResources.resources.Networks[0].ReservedPorts;
const taskRow = find('.tasks tbody tr:eq(0)'); const taskRow = $(findAll('.tasks tbody tr')[0]);
const events = server.db.taskEvents.where({ taskStateId: task.id }); const events = server.db.taskEvents.where({ taskStateId: task.id });
const event = events[events.length - 1]; const event = events[events.length - 1];
@ -117,7 +116,7 @@ test('each task row should list high-level information for the task', function(a
test('/allocation/:id should list recent events for each task', function(assert) { test('/allocation/:id should list recent events for each task', function(assert) {
const tasks = server.db.taskStates.where({ allocationId: allocation.id }); const tasks = server.db.taskStates.where({ allocationId: allocation.id });
assert.equal( assert.equal(
find('.task-state-events').length, findAll('.task-state-events').length,
tasks.length, tasks.length,
'A task state event block per task' 'A task state event block per task'
); );
@ -127,7 +126,7 @@ test('each recent events list should include the name, state, and time info for
assert assert
) { ) {
const task = server.db.taskStates.where({ allocationId: allocation.id })[0]; const task = server.db.taskStates.where({ allocationId: allocation.id })[0];
const recentEventsSection = find('.task-state-events:eq(0)'); const recentEventsSection = $(findAll('.task-state-events')[0]);
const heading = recentEventsSection const heading = recentEventsSection
.find('.message-header') .find('.message-header')
.text() .text()
@ -146,7 +145,7 @@ test('each recent events list should list all recent events for the task', funct
const events = server.db.taskEvents.where({ taskStateId: task.id }); const events = server.db.taskEvents.where({ taskStateId: task.id });
assert.equal( assert.equal(
find('.task-state-events:eq(0) .task-events tbody tr').length, findAll('.task-state-events')[0].querySelectorAll('.task-events tbody tr').length,
events.length, events.length,
`Lists ${events.length} events` `Lists ${events.length} events`
); );
@ -157,7 +156,7 @@ test('each recent event should list the time, type, and description of the event
) { ) {
const task = server.db.taskStates.where({ allocationId: allocation.id })[0]; const task = server.db.taskStates.where({ allocationId: allocation.id })[0];
const event = server.db.taskEvents.where({ taskStateId: task.id })[0]; const event = server.db.taskEvents.where({ taskStateId: task.id })[0];
const recentEvent = find('.task-state-events:eq(0) .task-events tbody tr:last'); const recentEvent = $('.task-state-events:eq(0) .task-events tbody tr:last');
assert.equal( assert.equal(
recentEvent recentEvent

View file

@ -1,6 +1,10 @@
import Ember from 'ember';
import { click, find, findAll, currentURL, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const { $ } = Ember;
let node; let node;
moduleForAcceptance('Acceptance | client detail', { moduleForAcceptance('Acceptance | client detail', {
@ -18,52 +22,45 @@ moduleForAcceptance('Acceptance | client detail', {
}); });
test('/nodes/:id should have a breadrcumb trail linking back to nodes', function(assert) { test('/nodes/:id should have a breadrcumb trail linking back to nodes', function(assert) {
assert.equal(find('.breadcrumb:eq(0)').text(), 'Nodes', 'First breadcrumb says nodes'); assert.equal(findAll('.breadcrumb')[0].textContent, 'Nodes', 'First breadcrumb says nodes');
assert.equal( assert.equal(
find('.breadcrumb:eq(1)').text(), findAll('.breadcrumb')[1].textContent,
node.id.split('-')[0], node.id.split('-')[0],
'Second breadcrumb says the node short id' 'Second breadcrumb says the node short id'
); );
click('.breadcrumb:eq(0)'); click(findAll('.breadcrumb')[0]);
andThen(() => { andThen(() => {
assert.equal(currentURL(), '/nodes', 'First breadcrumb links back to nodes'); assert.equal(currentURL(), '/nodes', 'First breadcrumb links back to nodes');
}); });
}); });
test('/nodes/:id should list immediate details for the node in the title', function(assert) { test('/nodes/:id should list immediate details for the node in the title', function(assert) {
assert.ok(find('.title').textContent.includes(node.name), 'Title includes name');
assert.ok(find('.title').textContent.includes(node.id), 'Title includes id');
assert.ok( assert.ok(
find('.title') findAll(`.title .node-status-light.${node.status}`).length,
.text() 'Title includes status light'
.includes(node.name),
'Title includes name'
); );
assert.ok(
find('.title')
.text()
.includes(node.id),
'Title includes id'
);
assert.ok(find(`.title .node-status-light.${node.status}`).length, 'Title includes status light');
}); });
test('/nodes/:id should list additional detail for the node below the title', function(assert) { test('/nodes/:id should list additional detail for the node below the title', function(assert) {
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(0)').text(), findAll('.inline-definitions .pair')[0].textContent,
`Status ${node.status}`, `Status ${node.status}`,
'Status is in additional details' 'Status is in additional details'
); );
assert.ok( assert.ok(
find('.inline-definitions .pair:eq(0) .status-text').hasClass(`node-${node.status}`), $('.inline-definitions .pair:eq(0) .status-text').hasClass(`node-${node.status}`),
'Status is decorated with a status class' 'Status is decorated with a status class'
); );
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(1)').text(), findAll('.inline-definitions .pair')[1].textContent,
`Address ${node.httpAddr}`, `Address ${node.httpAddr}`,
'Address is in additional detals' 'Address is in additional detals'
); );
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(2)').text(), findAll('.inline-definitions .pair')[2].textContent,
`Datacenter ${node.datacenter}`, `Datacenter ${node.datacenter}`,
'Datacenter is in additional details' 'Datacenter is in additional details'
); );
@ -72,14 +69,14 @@ test('/nodes/:id should list additional detail for the node below the title', fu
test('/nodes/:id should list all allocations on the node', function(assert) { test('/nodes/:id should list all allocations on the node', function(assert) {
const allocationsCount = server.db.allocations.where({ nodeId: node.id }).length; const allocationsCount = server.db.allocations.where({ nodeId: node.id }).length;
assert.equal( assert.equal(
find('.allocations tbody tr').length, findAll('.allocations tbody tr').length,
allocationsCount, allocationsCount,
`Allocations table lists all ${allocationsCount} associated allocations` `Allocations table lists all ${allocationsCount} associated allocations`
); );
}); });
test('each allocation should have high-level details for the allocation', function(assert) { test('each allocation should have high-level details for the allocation', function(assert) {
const allocationRow = find('.allocations tbody tr:eq(0)'); const allocationRow = $(findAll('.allocations tbody tr')[0]);
const allocation = server.db.allocations const allocation = server.db.allocations
.where({ nodeId: node.id }) .where({ nodeId: node.id })
.sortBy('modifyIndex') .sortBy('modifyIndex')
@ -156,7 +153,7 @@ test('each allocation should link to the allocation detail page', function(asser
.sortBy('modifyIndex') .sortBy('modifyIndex')
.reverse()[0]; .reverse()[0];
click('.allocations tbody tr:eq(0) td:eq(0) a'); click($('.allocations tbody tr:eq(0) td:eq(0) a').get(0));
andThen(() => { andThen(() => {
assert.equal( assert.equal(
@ -170,7 +167,7 @@ test('each allocation should link to the allocation detail page', function(asser
test('each allocation should link to the job the allocation belongs to', function(assert) { test('each allocation should link to the job the allocation belongs to', function(assert) {
const allocation = server.db.allocations.where({ nodeId: node.id })[0]; const allocation = server.db.allocations.where({ nodeId: node.id })[0];
const job = server.db.jobs.find(allocation.jobId); const job = server.db.jobs.find(allocation.jobId);
click('.allocations tbody tr:eq(0) td:eq(3) a'); click($('.allocations tbody tr:eq(0) td:eq(3) a').get(0));
andThen(() => { andThen(() => {
assert.equal( assert.equal(

View file

@ -1,9 +1,10 @@
import { findAll, currentURL, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
let job; let job;
moduleForAcceptance('Acceptance | job detail', { moduleForAcceptance('Acceptance | job definition', {
beforeEach() { beforeEach() {
server.create('node'); server.create('node');
server.create('job'); server.create('job');
@ -17,7 +18,7 @@ test('visiting /jobs/:job_id/definition', function(assert) {
}); });
test('the job definition page contains a json viewer component', function(assert) { test('the job definition page contains a json viewer component', function(assert) {
assert.ok(find('.json-viewer').length, 'JSON viewer found'); assert.ok(findAll('.json-viewer').length, 'JSON viewer found');
}); });
test('the job definition page requests the job to display in an unmutated form', function(assert) { test('the job definition page requests the job to display in an unmutated form', function(assert) {

View file

@ -1,9 +1,10 @@
import { click, findAll, find, visit } from 'ember-native-dom-helpers';
import Ember from 'ember'; import Ember from 'ember';
import { test } from 'qunit'; import { test } from 'qunit';
import moment from 'moment'; import moment from 'moment';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const { get } = Ember; const { get, $ } = Ember;
const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0); const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0);
let job; let job;
@ -32,7 +33,7 @@ test('/jobs/:id/deployments should list all job deployments', function(assert) {
visit(`/jobs/${job.id}/deployments`); visit(`/jobs/${job.id}/deployments`);
andThen(() => { andThen(() => {
assert.ok( assert.ok(
find('.timeline-object').length, findAll('.timeline-object').length,
deployments.length, deployments.length,
'Each deployment gets a row in the timeline' 'Each deployment gets a row in the timeline'
); );
@ -50,7 +51,7 @@ test('each deployment mentions the deployment shortId, status, version, and time
jobId: deployment.jobId, jobId: deployment.jobId,
version: deployment.versionNumber, version: deployment.versionNumber,
}); });
const deploymentRow = find('.timeline-object:eq(0)'); const deploymentRow = $(findAll('.timeline-object')[0]);
assert.ok(deploymentRow.text().includes(deployment.id.split('-')[0]), 'Short ID'); assert.ok(deploymentRow.text().includes(deployment.id.split('-')[0]), 'Short ID');
assert.equal(deploymentRow.find('.tag').text(), deployment.status, 'Status'); assert.equal(deploymentRow.find('.tag').text(), deployment.status, 'Status');
@ -89,7 +90,7 @@ test('when the deployment is running and needs promotion, the deployment item sa
visit(`/jobs/${job.id}/deployments`); visit(`/jobs/${job.id}/deployments`);
andThen(() => { andThen(() => {
const deploymentRow = find('.timeline-object:eq(0)'); const deploymentRow = $(findAll('.timeline-object')[0]);
assert.ok( assert.ok(
deploymentRow.find('.badge:contains("Requires Promotion")').length, deploymentRow.find('.badge:contains("Requires Promotion")').length,
'Requires Promotion badge found' 'Requires Promotion badge found'
@ -103,11 +104,11 @@ test('each deployment item can be opened to show details', function(assert) {
visit(`/jobs/${job.id}/deployments`); visit(`/jobs/${job.id}/deployments`);
andThen(() => { andThen(() => {
deploymentRow = find('.timeline-object:eq(0)'); deploymentRow = $(findAll('.timeline-object')[0]);
assert.ok(deploymentRow.find('.boxed-section-body').length === 0, 'No deployment body'); assert.ok(deploymentRow.find('.boxed-section-body').length === 0, 'No deployment body');
click(deploymentRow.find('button')); click(deploymentRow.find('button').get(0));
andThen(() => { andThen(() => {
assert.ok(deploymentRow.find('.boxed-section-body').length, 'Deployment body found'); assert.ok(deploymentRow.find('.boxed-section-body').length, 'Deployment body found');
@ -120,18 +121,18 @@ test('when open, a deployment shows the deployment metrics', function(assert) {
andThen(() => { andThen(() => {
const deployment = sortedDeployments.models[0]; const deployment = sortedDeployments.models[0];
const deploymentRow = find('.timeline-object:eq(0)'); const deploymentRow = $(findAll('.timeline-object')[0]);
const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id =>
server.db.deploymentTaskGroupSummaries.find(id) server.db.deploymentTaskGroupSummaries.find(id)
); );
click(deploymentRow.find('button')); click(deploymentRow.find('button').get(0));
andThen(() => { andThen(() => {
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Canaries") + .value') $('.deployment-metrics .label:contains("Canaries") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
`${sum(taskGroupSummaries, 'placedCanaries')} / ${sum( `${sum(taskGroupSummaries, 'placedCanaries')} / ${sum(
taskGroupSummaries, taskGroupSummaries,
'desiredCanaries' 'desiredCanaries'
@ -140,41 +141,39 @@ test('when open, a deployment shows the deployment metrics', function(assert) {
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Placed") + .value') $('.deployment-metrics .label:contains("Placed") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'placedAllocs'), sum(taskGroupSummaries, 'placedAllocs'),
'Placed allocs aggregates across task groups' 'Placed allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Desired") + .value') $('.deployment-metrics .label:contains("Desired") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'desiredTotal'), sum(taskGroupSummaries, 'desiredTotal'),
'Desired allocs aggregates across task groups' 'Desired allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Healthy") + .value') $('.deployment-metrics .label:contains("Healthy") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'healthyAllocs'), sum(taskGroupSummaries, 'healthyAllocs'),
'Healthy allocs aggregates across task groups' 'Healthy allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Unhealthy") + .value') $('.deployment-metrics .label:contains("Unhealthy") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'unhealthyAllocs'), sum(taskGroupSummaries, 'unhealthyAllocs'),
'Unhealthy allocs aggregates across task groups' 'Unhealthy allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .notification') find('.deployment-metrics .notification').textContent.trim(),
.text()
.trim(),
deployment.statusDescription, deployment.statusDescription,
'Status description is in the metrics block' 'Status description is in the metrics block'
); );
@ -189,12 +188,12 @@ test('when open, a deployment shows a list of all task groups and their respecti
andThen(() => { andThen(() => {
const deployment = sortedDeployments.models[0]; const deployment = sortedDeployments.models[0];
const deploymentRow = find('.timeline-object:eq(0)'); const deploymentRow = $(findAll('.timeline-object')[0]);
const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id => const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id =>
server.db.deploymentTaskGroupSummaries.find(id) server.db.deploymentTaskGroupSummaries.find(id)
); );
click(deploymentRow.find('button')); click(deploymentRow.find('button').get(0));
andThen(() => { andThen(() => {
assert.ok( assert.ok(
@ -282,13 +281,13 @@ test('when open, a deployment shows a list of all allocations for the deployment
andThen(() => { andThen(() => {
const deployment = sortedDeployments.models[0]; const deployment = sortedDeployments.models[0];
const deploymentRow = find('.timeline-object:eq(0)'); const deploymentRow = $(findAll('.timeline-object')[0]);
// TODO: Make this less brittle. This logic is copied from the mirage config, // TODO: Make this less brittle. This logic is copied from the mirage config,
// since there is no reference to allocations on the deployment model. // since there is no reference to allocations on the deployment model.
const allocations = server.db.allocations.where({ jobId: deployment.jobId }).slice(0, 3); const allocations = server.db.allocations.where({ jobId: deployment.jobId }).slice(0, 3);
click(deploymentRow.find('button')); click(deploymentRow.find('button').get(0));
andThen(() => { andThen(() => {
assert.ok( assert.ok(

View file

@ -1,9 +1,10 @@
import { click, findAll, currentURL, find, visit } from 'ember-native-dom-helpers';
import Ember from 'ember'; import Ember from 'ember';
import moment from 'moment'; import moment from 'moment';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const { get } = Ember; const { get, $ } = Ember;
const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0); const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0);
let job; let job;
@ -22,39 +23,28 @@ test('visiting /jobs/:job_id', function(assert) {
}); });
test('breadcrumbs includes job name and link back to the jobs list', function(assert) { test('breadcrumbs includes job name and link back to the jobs list', function(assert) {
assert.equal(find('.breadcrumb:eq(0)').text(), 'Jobs', 'First breadcrumb says jobs'); assert.equal(findAll('.breadcrumb')[0].textContent, 'Jobs', 'First breadcrumb says jobs');
assert.equal(find('.breadcrumb:eq(1)').text(), job.name, 'Second breadcrumb says the job name'); assert.equal(
findAll('.breadcrumb')[1].textContent,
job.name,
'Second breadcrumb says the job name'
);
click('.breadcrumb:eq(0)'); click(findAll('.breadcrumb')[0]);
andThen(() => { andThen(() => {
assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs'); assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs');
}); });
}); });
test('the job detail page should contain basic information about the job', function(assert) { test('the job detail page should contain basic information about the job', function(assert) {
assert.ok( assert.ok(findAll('.title .tag')[0].textContent.includes(job.status), 'Status');
find('.title .tag:eq(0)') assert.ok(findAll('.job-stats span')[0].textContent.includes(job.type), 'Type');
.text() assert.ok(findAll('.job-stats span')[1].textContent.includes(job.priority), 'Priority');
.includes(job.status),
'Status'
);
assert.ok(
find('.job-stats span:eq(0)')
.text()
.includes(job.type),
'Type'
);
assert.ok(
find('.job-stats span:eq(1)')
.text()
.includes(job.priority),
'Priority'
);
}); });
test('the job detail page should list all task groups', function(assert) { test('the job detail page should list all task groups', function(assert) {
assert.equal( assert.equal(
find('.task-group-row').length, findAll('.task-group-row').length,
server.db.taskGroups.where({ jobId: job.id }).length server.db.taskGroups.where({ jobId: job.id }).length
); );
}); });
@ -63,7 +53,7 @@ test('each row in the task group table should show basic information about the t
assert assert
) { ) {
const taskGroup = job.taskGroupIds.map(id => server.db.taskGroups.find(id)).sortBy('name')[0]; const taskGroup = job.taskGroupIds.map(id => server.db.taskGroups.find(id)).sortBy('name')[0];
const taskGroupRow = find('.task-group-row:eq(0)'); const taskGroupRow = $(findAll('.task-group-row')[0]);
const tasks = server.db.tasks.where({ taskGroupId: taskGroup.id }); const tasks = server.db.tasks.where({ taskGroupId: taskGroup.id });
const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0); const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0);
@ -104,37 +94,37 @@ test('the allocations diagram lists all allocation status figures', function(ass
); );
assert.equal( assert.equal(
legend.find('li.queued .value').text(), legend.querySelector('li.queued .value').textContent,
statusCounts.queued, statusCounts.queued,
`${statusCounts.queued} are queued` `${statusCounts.queued} are queued`
); );
assert.equal( assert.equal(
legend.find('li.starting .value').text(), legend.querySelector('li.starting .value').textContent,
statusCounts.starting, statusCounts.starting,
`${statusCounts.starting} are starting` `${statusCounts.starting} are starting`
); );
assert.equal( assert.equal(
legend.find('li.running .value').text(), legend.querySelector('li.running .value').textContent,
statusCounts.running, statusCounts.running,
`${statusCounts.running} are running` `${statusCounts.running} are running`
); );
assert.equal( assert.equal(
legend.find('li.complete .value').text(), legend.querySelector('li.complete .value').textContent,
statusCounts.complete, statusCounts.complete,
`${statusCounts.complete} are complete` `${statusCounts.complete} are complete`
); );
assert.equal( assert.equal(
legend.find('li.failed .value').text(), legend.querySelector('li.failed .value').textContent,
statusCounts.failed, statusCounts.failed,
`${statusCounts.failed} are failed` `${statusCounts.failed} are failed`
); );
assert.equal( assert.equal(
legend.find('li.lost .value').text(), legend.querySelector('li.lost .value').textContent,
statusCounts.lost, statusCounts.lost,
`${statusCounts.lost} are lost` `${statusCounts.lost} are lost`
); );
@ -149,7 +139,7 @@ test('there is no active deployment section when the job has no active deploymen
visit(`/jobs/${job.id}`); visit(`/jobs/${job.id}`);
andThen(() => { andThen(() => {
assert.ok(find('.active-deployment').length === 0, 'No active deployment'); assert.ok(findAll('.active-deployment').length === 0, 'No active deployment');
}); });
}); });
@ -168,27 +158,27 @@ test('the active deployment section shows up for the currently running deploymen
visit(`/jobs/${job.id}`); visit(`/jobs/${job.id}`);
andThen(() => { andThen(() => {
assert.ok(find('.active-deployment').length === 1, 'Active deployment'); assert.ok(findAll('.active-deployment').length === 1, 'Active deployment');
assert.equal( assert.equal(
find('.active-deployment > .boxed-section-head .badge') $('.active-deployment > .boxed-section-head .badge')
.text() .get(0)
.trim(), .textContent.trim(),
deployment.id.split('-')[0], deployment.id.split('-')[0],
'The active deployment is the most recent running deployment' 'The active deployment is the most recent running deployment'
); );
assert.equal( assert.equal(
find('.active-deployment > .boxed-section-head .submit-time') $('.active-deployment > .boxed-section-head .submit-time')
.text() .get(0)
.trim(), .textContent.trim(),
moment(version.submitTime / 1000000).fromNow(), moment(version.submitTime / 1000000).fromNow(),
'Time since the job was submitted is in the active deployment header' 'Time since the job was submitted is in the active deployment header'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Canaries") + .value') $('.deployment-metrics .label:contains("Canaries") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
`${sum(taskGroupSummaries, 'placedCanaries')} / ${sum( `${sum(taskGroupSummaries, 'placedCanaries')} / ${sum(
taskGroupSummaries, taskGroupSummaries,
'desiredCanaries' 'desiredCanaries'
@ -197,41 +187,41 @@ test('the active deployment section shows up for the currently running deploymen
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Placed") + .value') $('.deployment-metrics .label:contains("Placed") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'placedAllocs'), sum(taskGroupSummaries, 'placedAllocs'),
'Placed allocs aggregates across task groups' 'Placed allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Desired") + .value') $('.deployment-metrics .label:contains("Desired") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'desiredTotal'), sum(taskGroupSummaries, 'desiredTotal'),
'Desired allocs aggregates across task groups' 'Desired allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Healthy") + .value') $('.deployment-metrics .label:contains("Healthy") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'healthyAllocs'), sum(taskGroupSummaries, 'healthyAllocs'),
'Healthy allocs aggregates across task groups' 'Healthy allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .label:contains("Unhealthy") + .value') $('.deployment-metrics .label:contains("Unhealthy") + .value')
.text() .get(0)
.trim(), .textContent.trim(),
sum(taskGroupSummaries, 'unhealthyAllocs'), sum(taskGroupSummaries, 'unhealthyAllocs'),
'Unhealthy allocs aggregates across task groups' 'Unhealthy allocs aggregates across task groups'
); );
assert.equal( assert.equal(
find('.deployment-metrics .notification') $('.deployment-metrics .notification')
.text() .get(0)
.trim(), .textContent.trim(),
deployment.statusDescription, deployment.statusDescription,
'Status description is in the metrics block' 'Status description is in the metrics block'
); );
@ -246,24 +236,26 @@ test('the active deployment section can be expanded to show task groups and allo
andThen(() => { andThen(() => {
assert.ok( assert.ok(
find('.active-deployment .boxed-section-head:contains("Task Groups")').length === 0, $('.active-deployment .boxed-section-head:contains("Task Groups")').length === 0,
'Task groups not found' 'Task groups not found'
); );
assert.ok( assert.ok(
find('.active-deployment .boxed-section-head:contains("Allocations")').length === 0, $('.active-deployment .boxed-section-head:contains("Allocations")').length === 0,
'Allocations not found' 'Allocations not found'
); );
}); });
click('.active-deployment-details-toggle'); andThen(() => {
click('.active-deployment-details-toggle');
});
andThen(() => { andThen(() => {
assert.ok( assert.ok(
find('.active-deployment .boxed-section-head:contains("Task Groups")').length === 1, $('.active-deployment .boxed-section-head:contains("Task Groups")').length === 1,
'Task groups found' 'Task groups found'
); );
assert.ok( assert.ok(
find('.active-deployment .boxed-section-head:contains("Allocations")').length === 1, $('.active-deployment .boxed-section-head:contains("Allocations")').length === 1,
'Allocations found' 'Allocations found'
); );
}); });

View file

@ -1,7 +1,11 @@
import Ember from 'ember';
import { findAll, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
import moment from 'moment'; import moment from 'moment';
const { $ } = Ember;
let job; let job;
let versions; let versions;
@ -16,7 +20,7 @@ moduleForAcceptance('Acceptance | job versions', {
test('/jobs/:id/versions should list all job versions', function(assert) { test('/jobs/:id/versions should list all job versions', function(assert) {
assert.ok( assert.ok(
find('.timeline-object').length, findAll('.timeline-object').length,
versions.length, versions.length,
'Each version gets a row in the timeline' 'Each version gets a row in the timeline'
); );
@ -26,7 +30,7 @@ test('each version mentions the version number, the stability, and the submitted
assert assert
) { ) {
const version = versions.sortBy('submitTime').reverse()[0]; const version = versions.sortBy('submitTime').reverse()[0];
const versionRow = find('.timeline-object:eq(0)'); const versionRow = $(findAll('.timeline-object')[0]);
assert.ok(versionRow.text().includes(`Version #${version.version}`), 'Version #'); assert.ok(versionRow.text().includes(`Version #${version.version}`), 'Version #');
assert.equal( assert.equal(

View file

@ -1,6 +1,10 @@
import Ember from 'ember';
import { click, findAll, currentURL, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const { $ } = Ember;
moduleForAcceptance('Acceptance | jobs list', { moduleForAcceptance('Acceptance | jobs list', {
beforeEach() { beforeEach() {
// Required for placing allocations (a result of creating jobs) // Required for placing allocations (a result of creating jobs)
@ -25,10 +29,10 @@ test('/jobs should list the first page of jobs sorted by modify index', function
andThen(() => { andThen(() => {
const sortedJobs = server.db.jobs.sortBy('modifyIndex').reverse(); const sortedJobs = server.db.jobs.sortBy('modifyIndex').reverse();
assert.equal(find('.job-row').length, pageSize); assert.equal(findAll('.job-row').length, pageSize);
for (var jobNumber = 0; jobNumber < pageSize; jobNumber++) { for (var jobNumber = 0; jobNumber < pageSize; jobNumber++) {
assert.equal( assert.equal(
find(`.job-row:eq(${jobNumber}) td:eq(0)`).text(), $(`.job-row:eq(${jobNumber}) td:eq(0)`).text(),
sortedJobs[jobNumber].name, sortedJobs[jobNumber].name,
'Jobs are ordered' 'Jobs are ordered'
); );
@ -44,11 +48,18 @@ test('each job row should contain information about the job', function(assert) {
visit('/jobs'); visit('/jobs');
andThen(() => { andThen(() => {
const jobRow = find('.job-row:eq(0)'); const jobRow = $(findAll('.job-row')[0]);
assert.equal(jobRow.find('td:eq(0)').text(), job.name, 'Name'); assert.equal(jobRow.find('td:eq(0)').text(), job.name, 'Name');
assert.equal(jobRow.find('td:eq(0) a').attr('href'), `/ui/jobs/${job.id}`, 'Detail Link'); assert.equal(jobRow.find('td:eq(0) a').attr('href'), `/ui/jobs/${job.id}`, 'Detail Link');
assert.equal(jobRow.find('td:eq(1)').text().trim(), job.status, 'Status'); assert.equal(
jobRow
.find('td:eq(1)')
.text()
.trim(),
job.status,
'Status'
);
assert.equal(jobRow.find('td:eq(2)').text(), job.type, 'Type'); assert.equal(jobRow.find('td:eq(2)').text(), job.type, 'Type');
assert.equal(jobRow.find('td:eq(3)').text(), job.priority, 'Priority'); assert.equal(jobRow.find('td:eq(3)').text(), job.priority, 'Priority');
assert.equal(jobRow.find('td:eq(4)').text(), taskGroups.length, '# Groups'); assert.equal(jobRow.find('td:eq(4)').text(), taskGroups.length, '# Groups');
@ -60,7 +71,10 @@ test('each job row should link to the corresponding job', function(assert) {
const job = server.db.jobs[0]; const job = server.db.jobs[0];
visit('/jobs'); visit('/jobs');
click('.job-row:eq(0) td:eq(0) a');
andThen(() => {
click($('.job-row:eq(0) td:eq(0) a').get(0));
});
andThen(() => { andThen(() => {
assert.equal(currentURL(), `/jobs/${job.id}`); assert.equal(currentURL(), `/jobs/${job.id}`);

View file

@ -1,8 +1,12 @@
import Ember from 'ember';
import { click, findAll, currentURL, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
import { findLeader } from '../../mirage/config'; import { findLeader } from '../../mirage/config';
import ipParts from 'nomad-ui/utils/ip-parts'; import ipParts from 'nomad-ui/utils/ip-parts';
const { $ } = Ember;
function minimumSetup() { function minimumSetup() {
server.createList('node', 1); server.createList('node', 1);
server.createList('agent', 1); server.createList('agent', 1);
@ -21,14 +25,14 @@ test('/nodes should list one page of clients', function(assert) {
visit('/nodes'); visit('/nodes');
andThen(() => { andThen(() => {
assert.equal(find('.client-node-row').length, pageSize); assert.equal(findAll('.client-node-row').length, pageSize);
assert.ok(find('.pagination').length, 'Pagination found on the page'); assert.ok(findAll('.pagination').length, 'Pagination found on the page');
const sortedNodes = server.db.nodes.sortBy('modifyIndex').reverse(); const sortedNodes = server.db.nodes.sortBy('modifyIndex').reverse();
for (var nodeNumber = 0; nodeNumber < pageSize; nodeNumber++) { for (var nodeNumber = 0; nodeNumber < pageSize; nodeNumber++) {
assert.equal( assert.equal(
find(`.client-node-row:eq(${nodeNumber}) td:eq(0)`).text(), $(`.client-node-row:eq(${nodeNumber}) td:eq(0)`).text(),
sortedNodes[nodeNumber].id.split('-')[0], sortedNodes[nodeNumber].id.split('-')[0],
'Nodes are ordered' 'Nodes are ordered'
); );
@ -43,7 +47,7 @@ test('each client record should show high-level info of the client', function(as
visit('/nodes'); visit('/nodes');
andThen(() => { andThen(() => {
const nodeRow = find('.client-node-row:eq(0)'); const nodeRow = $(findAll('.client-node-row')[0]);
const allocations = server.db.allocations.where({ nodeId: node.id }); const allocations = server.db.allocations.where({ nodeId: node.id });
const { address, port } = ipParts(node.httpAddr); const { address, port } = ipParts(node.httpAddr);
@ -62,7 +66,9 @@ test('each client should link to the client detail page', function(assert) {
const node = server.db.nodes[0]; const node = server.db.nodes[0];
visit('/nodes'); visit('/nodes');
click('.client-node-row:eq(0)'); andThen(() => {
click(findAll('.client-node-row')[0]);
});
andThen(() => { andThen(() => {
assert.equal(currentURL(), `/nodes/${node.id}`); assert.equal(currentURL(), `/nodes/${node.id}`);
@ -81,7 +87,7 @@ test('/servers should list all servers', function(assert) {
visit('/servers'); visit('/servers');
andThen(() => { andThen(() => {
assert.equal(find('.server-agent-row').length, pageSize); assert.equal(findAll('.server-agent-row').length, pageSize);
const sortedAgents = server.db.agents const sortedAgents = server.db.agents
.sort((a, b) => { .sort((a, b) => {
@ -96,7 +102,7 @@ test('/servers should list all servers', function(assert) {
for (var agentNumber = 0; agentNumber < 8; agentNumber++) { for (var agentNumber = 0; agentNumber < 8; agentNumber++) {
assert.equal( assert.equal(
find(`.server-agent-row:eq(${agentNumber}) td:eq(0)`).text(), $(`.server-agent-row:eq(${agentNumber}) td:eq(0)`).text(),
sortedAgents[agentNumber].name, sortedAgents[agentNumber].name,
'Clients are ordered' 'Clients are ordered'
); );
@ -111,7 +117,7 @@ test('each server should show high-level info of the server', function(assert) {
visit('/servers'); visit('/servers');
andThen(() => { andThen(() => {
const agentRow = find('.server-agent-row:eq(0)'); const agentRow = $(findAll('.server-agent-row')[0]);
assert.equal(agentRow.find('td:eq(0)').text(), agent.name, 'Name'); assert.equal(agentRow.find('td:eq(0)').text(), agent.name, 'Name');
assert.equal(agentRow.find('td:eq(1)').text(), agent.status, 'Status'); assert.equal(agentRow.find('td:eq(1)').text(), agent.status, 'Status');
@ -127,7 +133,9 @@ test('each server should link to the server detail page', function(assert) {
const agent = server.db.agents[0]; const agent = server.db.agents[0];
visit('/servers'); visit('/servers');
click('.server-agent-row:eq(0)'); andThen(() => {
click(findAll('.server-agent-row')[0]);
});
andThen(() => { andThen(() => {
assert.equal(currentURL(), `/servers/${agent.name}`); assert.equal(currentURL(), `/servers/${agent.name}`);

View file

@ -1,6 +1,10 @@
import Ember from 'ember';
import { findAll, currentURL, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const { $ } = Ember;
let agent; let agent;
moduleForAcceptance('Acceptance | server detail', { moduleForAcceptance('Acceptance | server detail', {
@ -18,22 +22,22 @@ test('visiting /servers/:server_name', function(assert) {
test('the server detail page should list all tags for the server', function(assert) { test('the server detail page should list all tags for the server', function(assert) {
const tags = agent.tags; const tags = agent.tags;
assert.equal(find('.server-tags tbody tr').length, Object.keys(tags).length, '# of tags'); assert.equal(findAll('.server-tags tbody tr').length, Object.keys(tags).length, '# of tags');
Object.keys(tags).forEach((key, index) => { Object.keys(tags).forEach((key, index) => {
const row = find(`.server-tags tbody tr:eq(${index})`); const row = $(`.server-tags tbody tr:eq(${index})`);
assert.equal(row.find('td:eq(0)').text(), key, `Label: ${key}`); assert.equal(row.find('td:eq(0)').text(), key, `Label: ${key}`);
assert.equal(row.find('td:eq(1)').text(), tags[key], `Value: ${tags[key]}`); assert.equal(row.find('td:eq(1)').text(), tags[key], `Value: ${tags[key]}`);
}); });
}); });
test('the list of servers from /servers should still be present', function(assert) { test('the list of servers from /servers should still be present', function(assert) {
assert.equal(find('.server-agent-row').length, server.db.agents.length, '# of servers'); assert.equal(findAll('.server-agent-row').length, server.db.agents.length, '# of servers');
}); });
test('the active server should be denoted in the table', function(assert) { test('the active server should be denoted in the table', function(assert) {
assert.equal(find('.server-agent-row.is-active').length, 1, 'Only one active server'); assert.equal(findAll('.server-agent-row.is-active').length, 1, 'Only one active server');
assert.equal( assert.equal(
find('.server-agent-row.is-active td:eq(0)').text(), findAll('.server-agent-row.is-active td')[0].textContent,
agent.name, agent.name,
'Active server matches current route' 'Active server matches current route'
); );

View file

@ -1,6 +1,10 @@
import Ember from 'ember';
import { click, findAll, currentURL, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const { $ } = Ember;
let job; let job;
let taskGroup; let taskGroup;
let tasks; let tasks;
@ -10,6 +14,7 @@ const sum = (total, n) => total + n;
moduleForAcceptance('Acceptance | task group detail', { moduleForAcceptance('Acceptance | task group detail', {
beforeEach() { beforeEach() {
server.create('agent');
server.create('node', 'forceIPv4'); server.create('node', 'forceIPv4');
job = server.create('job', { job = server.create('job', {
@ -46,39 +51,43 @@ test('/jobs/:id/:task-group should list high-level metrics for the allocation',
const totalDisk = taskGroup.ephemeralDisk.SizeMB; const totalDisk = taskGroup.ephemeralDisk.SizeMB;
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(0)').text(), findAll('.inline-definitions .pair')[0].textContent,
`# Tasks ${tasks.length}`, `# Tasks ${tasks.length}`,
'# Tasks' '# Tasks'
); );
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(1)').text(), findAll('.inline-definitions .pair')[1].textContent,
`Reserved CPU ${totalCPU} MHz`, `Reserved CPU ${totalCPU} MHz`,
'Aggregated CPU reservation for all tasks' 'Aggregated CPU reservation for all tasks'
); );
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(2)').text(), findAll('.inline-definitions .pair')[2].textContent,
`Reserved Memory ${totalMemory} MiB`, `Reserved Memory ${totalMemory} MiB`,
'Aggregated Memory reservation for all tasks' 'Aggregated Memory reservation for all tasks'
); );
assert.equal( assert.equal(
find('.inline-definitions .pair:eq(3)').text(), findAll('.inline-definitions .pair')[3].textContent,
`Reserved Disk ${totalDisk} MiB`, `Reserved Disk ${totalDisk} MiB`,
'Aggregated Disk reservation for all tasks' 'Aggregated Disk reservation for all tasks'
); );
}); });
test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', function(assert) { test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', function(assert) {
assert.equal(find('.breadcrumb:eq(0)').text(), 'Jobs', 'First breadcrumb says jobs'); assert.equal(findAll('.breadcrumb')[0].textContent, 'Jobs', 'First breadcrumb says jobs');
assert.equal(find('.breadcrumb:eq(1)').text(), job.name, 'Second breadcrumb says the job name');
assert.equal( assert.equal(
find('.breadcrumb:eq(2)').text(), findAll('.breadcrumb')[1].textContent,
job.name,
'Second breadcrumb says the job name'
);
assert.equal(
findAll('.breadcrumb')[2].textContent,
taskGroup.name, taskGroup.name,
'Third breadcrumb says the job name' 'Third breadcrumb says the job name'
); );
}); });
test('/jobs/:id/:task-group first breadcrumb should link to jobs', function(assert) { test('/jobs/:id/:task-group first breadcrumb should link to jobs', function(assert) {
click('.breadcrumb:eq(0)'); click(findAll('.breadcrumb')[0]);
andThen(() => { andThen(() => {
assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs'); assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs');
}); });
@ -87,7 +96,7 @@ test('/jobs/:id/:task-group first breadcrumb should link to jobs', function(asse
test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', function( test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', function(
assert assert
) { ) {
click('.breadcrumb:eq(1)'); click(findAll('.breadcrumb')[1]);
andThen(() => { andThen(() => {
assert.equal( assert.equal(
currentURL(), currentURL(),
@ -117,7 +126,7 @@ test('/jobs/:id/:task-group should list one page of allocations for the task gro
); );
assert.equal( assert.equal(
find('.allocations tbody tr').length, findAll('.allocations tbody tr').length,
pageSize, pageSize,
'All allocations for the task group' 'All allocations for the task group'
); );
@ -126,7 +135,7 @@ test('/jobs/:id/:task-group should list one page of allocations for the task gro
test('each allocation should show basic information about the allocation', function(assert) { test('each allocation should show basic information about the allocation', function(assert) {
const allocation = allocations.sortBy('name')[0]; const allocation = allocations.sortBy('name')[0];
const allocationRow = find('.allocations tbody tr:eq(0)'); const allocationRow = $(findAll('.allocations tbody tr')[0]);
assert.equal( assert.equal(
allocationRow allocationRow
@ -158,15 +167,21 @@ test('each allocation should show basic information about the allocation', funct
.text() .text()
.trim(), .trim(),
server.db.nodes.find(allocation.nodeId).id.split('-')[0], server.db.nodes.find(allocation.nodeId).id.split('-')[0],
'Node name' 'Node ID'
); );
click(allocationRow.find('td:eq(3) a').get(0));
andThen(() => {
assert.equal(currentURL(), `/nodes/${allocation.nodeId}`, 'Node links to node page');
});
}); });
test('each allocation should show stats about the allocation, retrieved directly from the node', function( test('each allocation should show stats about the allocation, retrieved directly from the node', function(
assert assert
) { ) {
const allocation = allocations.sortBy('name')[0]; const allocation = allocations.sortBy('name')[0];
const allocationRow = find('.allocations tbody tr:eq(0)'); const allocationRow = $(findAll('.allocations tbody tr')[0]);
const allocStats = server.db.clientAllocationStats.find(allocation.id); const allocStats = server.db.clientAllocationStats.find(allocation.id);
const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id));

View file

@ -1,3 +1,4 @@
import { fillIn, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit'; import { test } from 'qunit';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
@ -22,14 +23,18 @@ test('the token form sets the token in session storage', function(assert) {
assert.ok(window.sessionStorage.nomadTokenSecret == null, 'No token secret set'); assert.ok(window.sessionStorage.nomadTokenSecret == null, 'No token secret set');
assert.ok(window.sessionStorage.nomadTokenAccessor == null, 'No token accessor set'); assert.ok(window.sessionStorage.nomadTokenAccessor == null, 'No token accessor set');
fillIn('.token-secret', secret); andThen(() => {
fillIn('.token-secret', secret);
});
andThen(() => { andThen(() => {
assert.equal(window.sessionStorage.nomadTokenSecret, secret, 'Token secret was set'); assert.equal(window.sessionStorage.nomadTokenSecret, secret, 'Token secret was set');
assert.ok(window.sessionStorage.nomadTokenAccessor == null, 'Token accessor was not set'); assert.ok(window.sessionStorage.nomadTokenAccessor == null, 'Token accessor was not set');
}); });
fillIn('.token-accessor', accessor); andThen(() => {
fillIn('.token-accessor', accessor);
});
andThen(() => { andThen(() => {
assert.equal(window.sessionStorage.nomadTokenAccessor, accessor, 'Token accessor was set'); assert.equal(window.sessionStorage.nomadTokenAccessor, accessor, 'Token accessor was set');
@ -55,7 +60,9 @@ test('the X-Nomad-Token header gets sent with requests once it is set', function
}); });
visit('/settings/tokens'); visit('/settings/tokens');
fillIn('.token-secret', secret); andThen(() => {
fillIn('.token-secret', secret);
});
visit(`/jobs/${job.id}`); visit(`/jobs/${job.id}`);
visit(`/nodes/${node.id}`); visit(`/nodes/${node.id}`);

View file

@ -1,3 +1,4 @@
import { findAll } from 'ember-native-dom-helpers';
import { test, moduleForComponent } from 'ember-qunit'; import { test, moduleForComponent } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
import flat from 'npm:flat'; import flat from 'npm:flat';
@ -42,23 +43,39 @@ test('should render the full path of key/value pair from the root of the object'
this.render(hbs`{{attributes-table attributes=attributes}}`); this.render(hbs`{{attributes-table attributes=attributes}}`);
assert.equal( assert.equal(
this.$('tbody tr:eq(0) td:eq(0)').text().trim(), this.$('tbody tr:eq(0) td')
.get(0)
.textContent.trim(),
'key', 'key',
'Simple row renders only the key' 'Simple row renders only the key'
); );
assert.equal(this.$('tbody tr:eq(0) td:eq(1)').text().trim(), 'value'); assert.equal(
this.$('tbody tr:eq(0) td')
.get(1)
.textContent.trim(),
'value'
);
assert.equal( assert.equal(
this.$('tbody tr:eq(8) td:eq(0)').text().trim(), this.$('tbody tr:eq(8) td')
.get(0)
.textContent.trim(),
'so.are.deeply.nested', 'so.are.deeply.nested',
'Complex row renders the full path to the key' 'Complex row renders the full path to the key'
); );
assert.equal( assert.equal(
this.$('tbody tr:eq(8) td:eq(0) .is-faded').text().trim(), this.$('tbody tr:eq(8) td:eq(0) .is-faded')
.get(0)
.textContent.trim(),
'so.are.deeply.', 'so.are.deeply.',
'The prefix is faded to put emphasis on the attribute' 'The prefix is faded to put emphasis on the attribute'
); );
assert.equal(this.$('tbody tr:eq(8) td:eq(1)').text().trim(), 'properties'); assert.equal(
this.$('tbody tr:eq(8) td')
.get(1)
.textContent.trim(),
'properties'
);
}); });
test('should render a row for key/value pairs even when the value is another object', function( test('should render a row for key/value pairs even when the value is another object', function(
@ -69,7 +86,7 @@ test('should render a row for key/value pairs even when the value is another obj
const countOfParentRows = countOfParentKeys(commonAttributes); const countOfParentRows = countOfParentKeys(commonAttributes);
assert.equal( assert.equal(
this.$('tbody tr td[colspan="2"]').length, findAll('tbody tr td[colspan="2"]').length,
countOfParentRows, countOfParentRows,
'Each key for a nested object gets a row with no value' 'Each key for a nested object gets a row with no value'
); );

View file

@ -1,3 +1,4 @@
import { findAll, find } from 'ember-native-dom-helpers';
import { test, moduleForComponent } from 'ember-qunit'; import { test, moduleForComponent } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
@ -28,22 +29,22 @@ test('job field diffs', function(assert) {
this.render(commonTemplate); this.render(commonTemplate);
assert.equal( assert.equal(
this.$('.diff-section-label').length, findAll('.diff-section-label').length,
5, 5,
'A section label for each line, plus one for the group' 'A section label for each line, plus one for the group'
); );
assert.equal( assert.equal(
cleanWhitespace(this.$('.diff-section-label .diff-section-label.is-added').text()), cleanWhitespace(find('.diff-section-label .diff-section-label.is-added').textContent),
'+ Added Field: "Foobar"', '+ Added Field: "Foobar"',
'Added field is rendered correctly' 'Added field is rendered correctly'
); );
assert.equal( assert.equal(
cleanWhitespace(this.$('.diff-section-label .diff-section-label.is-edited').text()), cleanWhitespace(find('.diff-section-label .diff-section-label.is-edited').textContent),
'+/- Edited Field: "256" => "512"', '+/- Edited Field: "256" => "512"',
'Edited field is rendered correctly' 'Edited field is rendered correctly'
); );
assert.equal( assert.equal(
cleanWhitespace(this.$('.diff-section-label .diff-section-label.is-deleted').text()), cleanWhitespace(find('.diff-section-label .diff-section-label.is-deleted').textContent),
'- Removed Field: "12"', '- Removed Field: "12"',
'Removed field is rendered correctly' 'Removed field is rendered correctly'
); );
@ -96,51 +97,51 @@ test('job object diffs', function(assert) {
this.render(commonTemplate); this.render(commonTemplate);
assert.ok( assert.ok(
cleanWhitespace(this.$('.diff-section-label > .diff-section-label.is-added').text()).startsWith( cleanWhitespace(
'+ DeepConfiguration {' find('.diff-section-label > .diff-section-label.is-added').textContent
), ).startsWith('+ DeepConfiguration {'),
'Added object starts with a JSON block' 'Added object starts with a JSON block'
); );
assert.ok( assert.ok(
cleanWhitespace( cleanWhitespace(
this.$('.diff-section-label > .diff-section-label.is-edited').text() find('.diff-section-label > .diff-section-label.is-edited').textContent
).startsWith('+/- ComplexProperty {'), ).startsWith('+/- ComplexProperty {'),
'Edited object starts with a JSON block' 'Edited object starts with a JSON block'
); );
assert.ok( assert.ok(
cleanWhitespace( cleanWhitespace(
this.$('.diff-section-label > .diff-section-label.is-deleted').text() find('.diff-section-label > .diff-section-label.is-deleted').textContent
).startsWith('- DatedStuff {'), ).startsWith('- DatedStuff {'),
'Removed object starts with a JSON block' 'Removed object starts with a JSON block'
); );
assert.ok( assert.ok(
cleanWhitespace(this.$('.diff-section-label > .diff-section-label.is-added').text()).endsWith( cleanWhitespace(
'}' find('.diff-section-label > .diff-section-label.is-added').textContent
), ).endsWith('}'),
'Added object ends the JSON block' 'Added object ends the JSON block'
); );
assert.ok( assert.ok(
cleanWhitespace(this.$('.diff-section-label > .diff-section-label.is-edited').text()).endsWith( cleanWhitespace(
'}' find('.diff-section-label > .diff-section-label.is-edited').textContent
), ).endsWith('}'),
'Edited object starts with a JSON block' 'Edited object starts with a JSON block'
); );
assert.ok( assert.ok(
cleanWhitespace(this.$('.diff-section-label > .diff-section-label.is-deleted').text()).endsWith( cleanWhitespace(
'}' find('.diff-section-label > .diff-section-label.is-deleted').textContent
), ).endsWith('}'),
'Removed object ends the JSON block' 'Removed object ends the JSON block'
); );
assert.equal( assert.equal(
this.$('.diff-section-label > .diff-section-label.is-added > .diff-section-label').length, findAll('.diff-section-label > .diff-section-label.is-added > .diff-section-label').length,
this.get('diff').Objects[1].Objects.length + this.get('diff').Objects[1].Fields.length, this.get('diff').Objects[1].Objects.length + this.get('diff').Objects[1].Fields.length,
'Edited block contains each nested field and object' 'Edited block contains each nested field and object'
); );
assert.equal( assert.equal(
this.$( findAll(
'.diff-section-label > .diff-section-label.is-added > .diff-section-label > .diff-section-label .diff-section-table-row' '.diff-section-label > .diff-section-label.is-added > .diff-section-label > .diff-section-label .diff-section-table-row'
).length, ).length,
this.get('diff').Objects[1].Objects[0].Fields.length, this.get('diff').Objects[1].Objects[0].Fields.length,

View file

@ -1,3 +1,4 @@
import { findAll, find } from 'ember-native-dom-helpers';
import { test, skip, moduleForComponent } from 'ember-qunit'; import { test, skip, moduleForComponent } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
@ -12,7 +13,9 @@ const defaults = {
spread: 2, spread: 2,
}; };
const list100 = Array(100).fill(null).map((_, i) => i); const list100 = Array(100)
.fill(null)
.map((_, i) => i);
test('the source property', function(assert) { test('the source property', function(assert) {
this.set('source', list100); this.set('source', list100);
@ -33,31 +36,31 @@ test('the source property', function(assert) {
{{/list-pagination}} {{/list-pagination}}
`); `);
assert.ok(!this.$('.first').length, 'On the first page, there is no first link'); assert.ok(!findAll('.first').length, 'On the first page, there is no first link');
assert.ok(!this.$('.prev').length, 'On the first page, there is no prev link'); assert.ok(!findAll('.prev').length, 'On the first page, there is no prev link');
assert.equal( assert.equal(
this.$('.link').length, findAll('.link').length,
defaults.spread + 1, defaults.spread + 1,
'Pages links spread to the right by the spread amount' 'Pages links spread to the right by the spread amount'
); );
for (var pageNumber = 1; pageNumber <= defaults.spread + 1; pageNumber++) { for (var pageNumber = 1; pageNumber <= defaults.spread + 1; pageNumber++) {
assert.ok(this.$(`.link.page-${pageNumber}`).length, `Page link includes ${pageNumber}`); assert.ok(findAll(`.link.page-${pageNumber}`).length, `Page link includes ${pageNumber}`);
} }
assert.ok(this.$('.next').length, 'While not on the last page, there is a next link'); assert.ok(findAll('.next').length, 'While not on the last page, there is a next link');
assert.ok(this.$('.last').length, 'While not on the last page, there is a last link'); assert.ok(findAll('.last').length, 'While not on the last page, there is a last link');
assert.ok( assert.ok(
this.$('.item').length, findAll('.item').length,
defaults.size, defaults.size,
`Only ${defaults.size} (the default) number of items are rendered` `Only ${defaults.size} (the default) number of items are rendered`
); );
for (var item = 0; item < defaults.size; item++) { for (var item = 0; item < defaults.size; item++) {
assert.equal( assert.equal(
this.$(`.item:eq(${item})`).text(), findAll('.item')[item].textContent,
item, item,
'Rendered items are in the current page' 'Rendered items are in the current page'
); );
@ -76,7 +79,7 @@ test('the size property', function(assert) {
`); `);
const totalPages = Math.ceil(this.get('source').length / this.get('size')); const totalPages = Math.ceil(this.get('source').length / this.get('size'));
assert.equal(this.$('.page-info').text(), `1 of ${totalPages}`, `${totalPages} total pages`); assert.equal(find('.page-info').textContent, `1 of ${totalPages}`, `${totalPages} total pages`);
}); });
test('the spread property', function(assert) { test('the spread property', function(assert) {
@ -144,14 +147,14 @@ test('there are no pagination links when source is less than page size', functio
{{/list-pagination}} {{/list-pagination}}
`); `);
assert.ok(!this.$('.first').length, 'No first link'); assert.ok(!findAll('.first').length, 'No first link');
assert.ok(!this.$('.prev').length, 'No prev link'); assert.ok(!findAll('.prev').length, 'No prev link');
assert.ok(!this.$('.next').length, 'No next link'); assert.ok(!findAll('.next').length, 'No next link');
assert.ok(!this.$('.last').length, 'No last link'); assert.ok(!findAll('.last').length, 'No last link');
assert.equal(this.$('.page-info').text(), '1 of 1', 'Only one page'); assert.equal(find('.page-info').textContent, '1 of 1', 'Only one page');
assert.equal( assert.equal(
this.$('.item').length, findAll('.item').length,
this.get('source.length'), this.get('source.length'),
'Number of items equals length of source' 'Number of items equals length of source'
); );
@ -180,10 +183,10 @@ test('when there are no items in source', function(assert) {
`); `);
assert.ok( assert.ok(
!this.$('.page-info, .first, .prev, .link, .next, .last, .item').length, !findAll('.page-info, .first, .prev, .link, .next, .last, .item').length,
'Nothing in the yield renders' 'Nothing in the yield renders'
); );
assert.ok(this.$('.empty-state').length, 'Empty state is rendered'); assert.ok(findAll('.empty-state').length, 'Empty state is rendered');
}); });
// when there is less pages than the total spread amount // when there is less pages than the total spread amount
@ -210,13 +213,13 @@ test('when there is less pages than the total spread amount', function(assert) {
{{/list-pagination}} {{/list-pagination}}
`); `);
assert.ok(this.$('.first').length, 'First page still exists'); assert.ok(findAll('.first').length, 'First page still exists');
assert.ok(this.$('.prev').length, 'Prev page still exists'); assert.ok(findAll('.prev').length, 'Prev page still exists');
assert.ok(this.$('.next').length, 'Next page still exists'); assert.ok(findAll('.next').length, 'Next page still exists');
assert.ok(this.$('.last').length, 'Last page still exists'); assert.ok(findAll('.last').length, 'Last page still exists');
assert.equal(this.$('.link').length, totalPages, 'Every page gets a page link'); assert.equal(findAll('.link').length, totalPages, 'Every page gets a page link');
for (var pageNumber = 1; pageNumber < totalPages; pageNumber++) { for (var pageNumber = 1; pageNumber < totalPages; pageNumber++) {
assert.ok(this.$(`.link.page-${pageNumber}`).length, `Page link for ${pageNumber} exists`); assert.ok(findAll(`.link.page-${pageNumber}`).length, `Page link for ${pageNumber} exists`);
} }
}); });
@ -224,7 +227,7 @@ function testSpread(assert) {
const { spread, currentPage } = this.getProperties('spread', 'currentPage'); const { spread, currentPage } = this.getProperties('spread', 'currentPage');
for (var pageNumber = currentPage - spread; pageNumber <= currentPage + spread; pageNumber++) { for (var pageNumber = currentPage - spread; pageNumber <= currentPage + spread; pageNumber++) {
assert.ok( assert.ok(
this.$(`.link.page-${pageNumber}`).length, findAll(`.link.page-${pageNumber}`).length,
`Page links for currentPage (${currentPage}) +/- spread of ${spread} (${pageNumber})` `Page links for currentPage (${currentPage}) +/- spread of ${spread} (${pageNumber})`
); );
} }
@ -234,7 +237,7 @@ function testItems(assert) {
const { currentPage, size } = this.getProperties('currentPage', 'size'); const { currentPage, size } = this.getProperties('currentPage', 'size');
for (var item = 0; item < size; item++) { for (var item = 0; item < size; item++) {
assert.equal( assert.equal(
this.$(`.item:eq(${item})`).text(), findAll('.item')[item].textContent,
item + (currentPage - 1) * size, item + (currentPage - 1) * size,
`Rendered items are in the current page, ${currentPage} (${item + (currentPage - 1) * size})` `Rendered items are in the current page, ${currentPage} (${item + (currentPage - 1) * size})`
); );

View file

@ -1,3 +1,4 @@
import { findAll, find } from 'ember-native-dom-helpers';
import { test, skip, moduleForComponent } from 'ember-qunit'; import { test, skip, moduleForComponent } from 'ember-qunit';
import { faker } from 'ember-cli-mirage'; import { faker } from 'ember-cli-mirage';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
@ -6,11 +7,13 @@ moduleForComponent('list-table', 'Integration | Component | list table', {
integration: true, integration: true,
}); });
const commonTable = Array(10).fill(null).map(() => ({ const commonTable = Array(10)
firstName: faker.name.firstName(), .fill(null)
lastName: faker.name.lastName(), .map(() => ({
age: faker.random.number({ min: 18, max: 60 }), firstName: faker.name.firstName(),
})); lastName: faker.name.lastName(),
age: faker.random.number({ min: 18, max: 60 }),
}));
// thead // thead
test('component exposes a thead contextual component', function(assert) { test('component exposes a thead contextual component', function(assert) {
@ -25,12 +28,8 @@ test('component exposes a thead contextual component', function(assert) {
{{/list-table}} {{/list-table}}
`); `);
assert.ok(this.$('.head').length, 'Table head is rendered'); assert.ok(findAll('.head').length, 'Table head is rendered');
assert.equal( assert.equal(find('.head').tagName.toLowerCase(), 'thead', 'Table head is a thead element');
this.$('.head').prop('tagName').toLowerCase(),
'thead',
'Table head is a thead element'
);
}); });
// tbody // tbody
@ -52,22 +51,39 @@ test('component exposes a tbody contextual component', function(assert) {
{{/list-table}} {{/list-table}}
`); `);
assert.ok(this.$('.body').length, 'Table body is rendered'); assert.ok(findAll('.body').length, 'Table body is rendered');
assert.equal( assert.equal(find('.body').tagName.toLowerCase(), 'tbody', 'Table body is a tbody element');
this.$('.body').prop('tagName').toLowerCase(),
'tbody',
'Table body is a tbody element'
);
assert.equal(this.$('.item').length, this.get('source.length'), 'Each item gets its own row'); assert.equal(findAll('.item').length, this.get('source.length'), 'Each item gets its own row');
// list-table is not responsible for sorting, only dispatching sort events. The table is still // list-table is not responsible for sorting, only dispatching sort events. The table is still
// rendered in index-order. // rendered in index-order.
this.get('source').forEach((item, index) => { this.get('source').forEach((item, index) => {
const $item = this.$(`.item:eq(${index})`); const $item = this.$(`.item:eq(${index})`);
assert.equal($item.find('td:eq(0)').text().trim(), item.firstName, 'First name'); assert.equal(
assert.equal($item.find('td:eq(1)').text().trim(), item.lastName, 'Last name'); $item
assert.equal($item.find('td:eq(2)').text().trim(), item.age, 'Age'); .find('td:eq(0)')
.text()
.trim(),
item.firstName,
'First name'
);
assert.equal(
$item
.find('td:eq(1)')
.text()
.trim(),
item.lastName,
'Last name'
);
assert.equal(
$item
.find('td:eq(2)')
.text()
.trim(),
item.age,
'Age'
);
}); });
}); });

View file

@ -3103,6 +3103,13 @@ ember-moment@^7.3.1:
ember-getowner-polyfill "^2.0.1" ember-getowner-polyfill "^2.0.1"
ember-macro-helpers "^0.15.1" ember-macro-helpers "^0.15.1"
ember-native-dom-helpers@^0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/ember-native-dom-helpers/-/ember-native-dom-helpers-0.5.4.tgz#0bc1506a643fb7adc0abf1d09c44a7914459296b"
dependencies:
broccoli-funnel "^1.1.0"
ember-cli-babel "^6.6.0"
ember-qunit@^2.1.3: ember-qunit@^2.1.3:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-2.2.0.tgz#3cdf400031c93a38de781a7304819738753b7f99" resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-2.2.0.tgz#3cdf400031c93a38de781a7304819738753b7f99"