206 lines
6.2 KiB
JavaScript
206 lines
6.2 KiB
JavaScript
|
import { currentURL } from '@ember/test-helpers';
|
||
|
import { module, test } from 'qunit';
|
||
|
import { setupApplicationTest } from 'ember-qunit';
|
||
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||
|
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
||
|
import Clients from 'nomad-ui/tests/pages/jobs/job/clients';
|
||
|
|
||
|
let job;
|
||
|
let clients;
|
||
|
|
||
|
const makeSearchableClients = (server, job) => {
|
||
|
Array(10)
|
||
|
.fill(null)
|
||
|
.map((_, index) => {
|
||
|
const node = server.create('node', {
|
||
|
id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`,
|
||
|
datacenter: 'dc1',
|
||
|
status: 'ready',
|
||
|
});
|
||
|
server.create('allocation', { jobId: job.id, nodeId: node.id });
|
||
|
});
|
||
|
};
|
||
|
|
||
|
module('Acceptance | job clients', function(hooks) {
|
||
|
setupApplicationTest(hooks);
|
||
|
setupMirage(hooks);
|
||
|
|
||
|
hooks.beforeEach(function() {
|
||
|
clients = server.createList('node', 12, {
|
||
|
datacenter: 'dc1',
|
||
|
status: 'ready',
|
||
|
});
|
||
|
// Job with 1 task group.
|
||
|
job = server.create('job', {
|
||
|
status: 'running',
|
||
|
datacenters: ['dc1'],
|
||
|
type: 'sysbatch',
|
||
|
resourceSpec: ['M: 256, C: 500'],
|
||
|
createAllocations: false,
|
||
|
});
|
||
|
clients.forEach(c => {
|
||
|
server.create('allocation', { jobId: job.id, nodeId: c.id });
|
||
|
});
|
||
|
|
||
|
// Create clients without allocations to have some 'not scheduled' job status.
|
||
|
clients = clients.concat(
|
||
|
server.createList('node', 3, {
|
||
|
datacenter: 'dc1',
|
||
|
status: 'ready',
|
||
|
})
|
||
|
);
|
||
|
});
|
||
|
|
||
|
test('it passes an accessibility audit', async function(assert) {
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await a11yAudit(assert);
|
||
|
});
|
||
|
|
||
|
test('lists all clients for the job', async function(assert) {
|
||
|
await Clients.visit({ id: job.id });
|
||
|
assert.equal(Clients.clients.length, 15, 'Clients are shown in a table');
|
||
|
|
||
|
const clientIDs = clients.sortBy('id').map(c => c.id);
|
||
|
const clientsInTable = Clients.clients.map(c => c.id).sort();
|
||
|
assert.deepEqual(clientsInTable, clientIDs);
|
||
|
|
||
|
assert.equal(document.title, `Job ${job.name} clients - Nomad`);
|
||
|
});
|
||
|
|
||
|
test('dates have tooltip', async function(assert) {
|
||
|
await Clients.visit({ id: job.id });
|
||
|
|
||
|
Clients.clients.forEach((clientRow, index) => {
|
||
|
const jobStatus = Clients.clientFor(clientRow.id).status;
|
||
|
|
||
|
['createTime', 'modifyTime'].forEach(col => {
|
||
|
if (jobStatus === 'not scheduled') {
|
||
|
assert.equal(clientRow[col].text, '-', `row ${index} doesn't have ${col} tooltip`);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const hasTooltip = clientRow[col].tooltip.isPresent;
|
||
|
const tooltipText = clientRow[col].tooltip.text;
|
||
|
assert.true(hasTooltip, `row ${index} has ${col} tooltip`);
|
||
|
assert.ok(tooltipText, `row ${index} has ${col} tooltip content ${tooltipText}`);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
test('clients table is sortable', async function(assert) {
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await Clients.sortBy('node.name');
|
||
|
|
||
|
assert.equal(
|
||
|
currentURL(),
|
||
|
`/jobs/${job.id}/clients?desc=true&sort=node.name`,
|
||
|
'the URL persists the sort parameter'
|
||
|
);
|
||
|
|
||
|
const sortedClients = clients.sortBy('name').reverse();
|
||
|
Clients.clients.forEach((client, index) => {
|
||
|
const shortId = sortedClients[index].id.split('-')[0];
|
||
|
assert.equal(
|
||
|
client.shortId,
|
||
|
shortId,
|
||
|
`Client ${index} is ${shortId} with name ${sortedClients[index].name}`
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
test('clients table is searchable', async function(assert) {
|
||
|
makeSearchableClients(server, job);
|
||
|
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await Clients.search('ffffff');
|
||
|
|
||
|
assert.equal(Clients.clients.length, 5, 'List is filtered by search term');
|
||
|
});
|
||
|
|
||
|
test('when a search yields no results, the search box remains', async function(assert) {
|
||
|
makeSearchableClients(server, job);
|
||
|
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await Clients.search('^nothing will ever match this long regex$');
|
||
|
|
||
|
assert.equal(
|
||
|
Clients.emptyState.headline,
|
||
|
'No Matches',
|
||
|
'List is empty and the empty state is about search'
|
||
|
);
|
||
|
|
||
|
assert.ok(Clients.hasSearchBox, 'Search box is still shown');
|
||
|
});
|
||
|
|
||
|
test('when the job for the clients is not found, an error message is shown, but the URL persists', async function(assert) {
|
||
|
await Clients.visit({ id: 'not-a-real-job' });
|
||
|
|
||
|
assert.equal(
|
||
|
server.pretender.handledRequests
|
||
|
.filter(request => !request.url.includes('policy'))
|
||
|
.findBy('status', 404).url,
|
||
|
'/v1/job/not-a-real-job',
|
||
|
'A request to the nonexistent job is made'
|
||
|
);
|
||
|
assert.equal(currentURL(), '/jobs/not-a-real-job/clients', 'The URL persists');
|
||
|
assert.ok(Clients.error.isPresent, 'Error message is shown');
|
||
|
assert.equal(Clients.error.title, 'Not Found', 'Error message is for 404');
|
||
|
});
|
||
|
|
||
|
test('clicking row goes to client details', async function(assert) {
|
||
|
const client = clients[0];
|
||
|
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await Clients.clientFor(client.id).click();
|
||
|
assert.equal(currentURL(), `/clients/${client.id}`);
|
||
|
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await Clients.clientFor(client.id).visit();
|
||
|
assert.equal(currentURL(), `/clients/${client.id}`);
|
||
|
|
||
|
await Clients.visit({ id: job.id });
|
||
|
await Clients.clientFor(client.id).visitRow();
|
||
|
assert.equal(currentURL(), `/clients/${client.id}`);
|
||
|
});
|
||
|
|
||
|
testFacet('Job Status', {
|
||
|
facet: Clients.facets.jobStatus,
|
||
|
paramName: 'jobStatus',
|
||
|
expectedOptions: [
|
||
|
'Queued',
|
||
|
'Not Scheduled',
|
||
|
'Starting',
|
||
|
'Running',
|
||
|
'Complete',
|
||
|
'Degraded',
|
||
|
'Failed',
|
||
|
'Lost',
|
||
|
],
|
||
|
async beforeEach() {
|
||
|
await Clients.visit({ id: job.id });
|
||
|
},
|
||
|
});
|
||
|
|
||
|
function testFacet(label, { facet, paramName, beforeEach, expectedOptions }) {
|
||
|
test(`the ${label} facet has the correct options`, async function(assert) {
|
||
|
await beforeEach();
|
||
|
await facet.toggle();
|
||
|
|
||
|
let expectation;
|
||
|
if (typeof expectedOptions === 'function') {
|
||
|
expectation = expectedOptions();
|
||
|
} else {
|
||
|
expectation = expectedOptions;
|
||
|
}
|
||
|
|
||
|
assert.deepEqual(
|
||
|
facet.options.map(option => option.label.trim()),
|
||
|
expectation,
|
||
|
`Options for facet ${paramName} are as expected`
|
||
|
);
|
||
|
});
|
||
|
|
||
|
// TODO: add facet tests for actual list filtering
|
||
|
}
|
||
|
});
|