Skip to content

Commit

Permalink
Unit and integration tests for TopoViz component
Browse files Browse the repository at this point in the history
  • Loading branch information
DingoEatingFuzz committed Oct 15, 2020
1 parent 3a48dbf commit 13415df
Show file tree
Hide file tree
Showing 6 changed files with 374 additions and 16 deletions.
13 changes: 0 additions & 13 deletions ui/app/components/topo-viz.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import { run } from '@ember/runloop';
import { scaleLinear } from 'd3-scale';
import { extent, deviation, mean } from 'd3-array';
import { line, curveBasis } from 'd3-shape';
import RSVP from 'rsvp';

export default class TopoViz extends Component {
@tracked heightScale = null;
@tracked isLoaded = false;
@tracked element = null;
@tracked topology = { datacenters: [] };

Expand Down Expand Up @@ -124,16 +121,6 @@ export default class TopoViz extends Component {
this.topology = topology;
}

@action
async loadNodes() {
await RSVP.all(this.args.nodes.map(node => node.reload()));

this.heightScale = scaleLinear()
.range([15, 40])
.domain(extent(this.args.nodes.map(node => node.resources.memory)));
this.isLoaded = true;
}

@action
captureElement(element) {
this.element = element;
Expand Down
6 changes: 3 additions & 3 deletions ui/app/templates/components/topo-viz.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="topo-viz {{if this.isSingleColumn "is-single-column"}}" {{did-insert this.buildTopology}} {{did-insert this.captureElement}}>
<div data-test-topo-viz class="topo-viz {{if this.isSingleColumn "is-single-column"}}" {{did-insert this.buildTopology}} {{did-insert this.captureElement}}>
<FlexMasonry
@columns={{if this.isSingleColumn 1 2}}
@items={{this.topology.datacenters}}
Expand All @@ -13,10 +13,10 @@
</FlexMasonry>

{{#if this.activeAllocation}}
<svg class="chart topo-viz-edges" {{window-resize this.computedActiveEdges}}>
<svg data-test-allocation-associations class="chart topo-viz-edges" {{window-resize this.computedActiveEdges}}>
<g transform="translate({{this.edgeOffset.x}},{{this.edgeOffset.y}})">
{{#each this.activeEdges as |edge|}}
<path class="edge" d={{edge}} />
<path data-test-allocation-association class="edge" d={{edge}} />
{{/each}}
</g>
</svg>
Expand Down
25 changes: 25 additions & 0 deletions ui/tests/helpers/glimmer-factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Look up the component class in the glimmer component manager and return a
// function to construct components as if they were functions.
const glimmerComponentInstantiator = (owner, componentKey) => args => {
const componentManager = owner.lookup('component-manager:glimmer');
const componentClass = owner.factoryFor(`component:${componentKey}`).class;
return componentManager.createComponent(componentClass, { named: args });
};

// Use like
//
// setupGlimmerComponentFactory(hooks, 'my-component')
//
// test('testing my component', function(assert) {
// const component = this.createComponent({ hello: 'world' });
// assert.equal(component.args.hello, 'world');
// });
export default function setupGlimmerComponentFactory(hooks, componentKey) {
hooks.beforeEach(function() {
this.createComponent = glimmerComponentInstantiator(this.owner, componentKey);
});

hooks.afterEach(function() {
delete this.createComponent;
});
}
144 changes: 144 additions & 0 deletions ui/tests/integration/components/topo-viz-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
import { create } from 'ember-cli-page-object';
import sinon from 'sinon';
import faker from 'nomad-ui/mirage/faker';
import topoVizPageObject from 'nomad-ui/tests/pages/components/topo-viz';

const TopoViz = create(topoVizPageObject());

const alloc = (nodeId, jobId, taskGroupName, memory, cpu, props = {}) => ({
id: faker.random.uuid(),
taskGroupName,
isScheduled: true,
allocatedResources: {
cpu,
memory,
},
belongsTo: type => ({
id: () => (type === 'job' ? jobId : nodeId),
}),
...props,
});

const node = (datacenter, id, memory, cpu) => ({
datacenter,
id,
resources: { memory, cpu },
});

module('Integration | Component | TopoViz', function(hooks) {
setupRenderingTest(hooks);

const commonTemplate = hbs`
<TopoViz
@nodes={{this.nodes}}
@allocations={{this.allocations}}
@onAllocationSelect={{this.onAllocationSelect}}
@onNodeSelect={{this.onNodeSelect}} />
`;

test('presents as a FlexMasonry of datacenters', async function(assert) {
this.setProperties({
nodes: [node('dc1', 'node0', 1000, 500), node('dc2', 'node1', 1000, 500)],

allocations: [
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
],
});

await this.render(commonTemplate);

assert.equal(TopoViz.datacenters.length, 2);
assert.equal(TopoViz.datacenters[0].nodes.length, 1);
assert.equal(TopoViz.datacenters[1].nodes.length, 1);
assert.equal(TopoViz.datacenters[0].nodes[0].memoryRects.length, 2);
assert.equal(TopoViz.datacenters[1].nodes[0].memoryRects.length, 1);

await componentA11yAudit(this.element, assert);
});

test('clicking on a node in a deeply nested TopoViz::Node will toggle node selection and call @onNodeSelect', async function(assert) {
this.setProperties({
// TopoViz must be dense for node selection to be a feature
nodes: Array(55)
.fill(null)
.map((_, index) => node('dc1', `node${index}`, 1000, 500)),
allocations: [],
onNodeSelect: sinon.spy(),
});

await this.render(commonTemplate);

await TopoViz.datacenters[0].nodes[0].selectNode();
assert.ok(this.onNodeSelect.calledOnce);
assert.equal(this.onNodeSelect.getCall(0).args[0].node, this.nodes[0]);

await TopoViz.datacenters[0].nodes[0].selectNode();
assert.ok(this.onNodeSelect.calledTwice);
assert.equal(this.onNodeSelect.getCall(1).args[0], null);
});

test('clicking on an allocation in a deeply nested TopoViz::Node will update the topology object with selections and call @onAllocationSelect and @onNodeSelect', async function(assert) {
this.setProperties({
nodes: [node('dc1', 'node0', 1000, 500)],
allocations: [alloc('node0', 'job1', 'group', 100, 100)],
onNodeSelect: sinon.spy(),
onAllocationSelect: sinon.spy(),
});

await this.render(commonTemplate);

await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.ok(this.onAllocationSelect.calledOnce);
assert.equal(this.onAllocationSelect.getCall(0).args[0], this.allocations[0]);
assert.ok(this.onNodeSelect.calledOnce);

await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.ok(this.onAllocationSelect.calledTwice);
assert.equal(this.onAllocationSelect.getCall(1).args[0], null);
assert.ok(this.onNodeSelect.calledTwice);
assert.ok(this.onNodeSelect.alwaysCalledWith(null));
});

test('clicking on an allocation in a deeply nested TopoViz::Node will associate sibling allocations with curves', async function(assert) {
this.setProperties({
nodes: [
node('dc1', 'node0', 1000, 500),
node('dc1', 'node1', 1000, 500),
node('dc2', 'node2', 1000, 500),
],
allocations: [
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node2', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'groupTwo', 100, 100),
alloc('node1', 'job2', 'group', 100, 100),
alloc('node2', 'job2', 'groupTwo', 100, 100),
],
onNodeSelect: sinon.spy(),
onAllocationSelect: sinon.spy(),
});

const selectedAllocations = this.allocations.filter(
alloc => alloc.belongsTo('job').id() === 'job1' && alloc.taskGroupName === 'group'
);

await this.render(commonTemplate);

assert.notOk(TopoViz.allocationAssociationsArePresent);

await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();

assert.ok(TopoViz.allocationAssociationsArePresent);
assert.equal(TopoViz.allocationAssociations.length, selectedAllocations.length * 2);

await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.notOk(TopoViz.allocationAssociationsArePresent);
});
});
11 changes: 11 additions & 0 deletions ui/tests/pages/components/topo-viz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { collection, isPresent } from 'ember-cli-page-object';
import TopoVizDatacenter from './topo-viz/datacenter';

export default scope => ({
scope,

datacenters: collection('[data-test-topo-viz-datacenter]', TopoVizDatacenter()),

allocationAssociationsArePresent: isPresent('[data-test-allocation-associations]'),
allocationAssociations: collection('[data-test-allocation-association]'),
});
Loading

0 comments on commit 13415df

Please sign in to comment.