From 2ed5b28805c59fef10d64ca37ffc0f3d2c2cf8b1 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Sun, 21 Mar 2021 23:01:22 -0700 Subject: [PATCH] Tests for PrimaryMetric::Node --- .../primary-metric/current-value.hbs | 1 + ui/mirage/factories/node.js | 7 + .../components/primary-metric/node-test.js | 69 +++++++++ .../primary-metric/primary-metric.js | 138 ++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 ui/tests/integration/components/primary-metric/node-test.js create mode 100644 ui/tests/integration/components/primary-metric/primary-metric.js diff --git a/ui/app/components/primary-metric/current-value.hbs b/ui/app/components/primary-metric/current-value.hbs index e14a8e499..60ae3d61d 100644 --- a/ui/app/components/primary-metric/current-value.hbs +++ b/ui/app/components/primary-metric/current-value.hbs @@ -2,6 +2,7 @@
diff --git a/ui/mirage/factories/node.js b/ui/mirage/factories/node.js index 151536e18..f670db32c 100644 --- a/ui/mirage/factories/node.js +++ b/ui/mirage/factories/node.js @@ -68,6 +68,13 @@ export default Factory.extend({ hostVolumes: () => ({}), }), + reserved: trait({ + reservedResources: generateResources({ + CPU: 1000, + MemoryMB: 2000, + }), + }), + drainStrategy: null, drivers: makeDrivers, diff --git a/ui/tests/integration/components/primary-metric/node-test.js b/ui/tests/integration/components/primary-metric/node-test.js new file mode 100644 index 000000000..17fbba87d --- /dev/null +++ b/ui/tests/integration/components/primary-metric/node-test.js @@ -0,0 +1,69 @@ +import { setupRenderingTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import { find, render } from '@ember/test-helpers'; +import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; +import hbs from 'htmlbars-inline-precompile'; +import { setupPrimaryMetricMocks, primaryMetric } from './primary-metric'; +import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; +import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; + +module('Integration | Component | PrimaryMetric::Node', function(hooks) { + setupRenderingTest(hooks); + setupPrimaryMetricMocks(hooks); + + hooks.beforeEach(function() { + fragmentSerializerInitializer(this.owner); + this.store = this.owner.lookup('service:store'); + this.server = startMirage(); + this.server.create('namespace'); + this.server.create('node'); + }); + + hooks.afterEach(function() { + this.server.shutdown(); + }); + + const template = hbs` + + `; + + const preload = async store => { + await store.findAll('node'); + }; + + const findResource = store => store.peekAll('node').get('firstObject'); + + test('Must pass an accessibility audit', async function(assert) { + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric: 'cpu' }); + + await render(template); + await componentA11yAudit(this.element, assert); + }); + + test('When the node has a reserved amount for the metric, a horizontal annotation is shown', async function(assert) { + this.server.create('node', 'reserved', { id: 'withAnnotation' }); + await preload(this.store); + + const resource = this.store.peekRecord('node', 'withAnnotation'); + this.setProperties({ resource, metric: 'cpu' }); + + await render(template); + + assert.ok(find('[data-test-annotation]')); + assert.equal( + find('[data-test-annotation]').textContent.trim(), + `${resource.reserved.cpu} MHz reserved` + ); + }); + + primaryMetric({ + template, + preload, + findResource, + }); +}); diff --git a/ui/tests/integration/components/primary-metric/primary-metric.js b/ui/tests/integration/components/primary-metric/primary-metric.js new file mode 100644 index 000000000..a4e8f7f70 --- /dev/null +++ b/ui/tests/integration/components/primary-metric/primary-metric.js @@ -0,0 +1,138 @@ +import EmberObject, { computed } from '@ember/object'; +import Service from '@ember/service'; +import { find, render, clearRender } from '@ember/test-helpers'; +import { test } from 'qunit'; +import { task } from 'ember-concurrency'; +import sinon from 'sinon'; + +export function setupPrimaryMetricMocks(hooks, tasks = []) { + hooks.beforeEach(function() { + const getTrackerSpy = (this.getTrackerSpy = sinon.spy()); + const trackerPollSpy = (this.trackerPollSpy = sinon.spy()); + const trackerSignalPauseSpy = (this.trackerSignalPauseSpy = sinon.spy()); + + const MockTracker = EmberObject.extend({ + poll: task(function*() { + yield trackerPollSpy(); + }), + signalPause: task(function*() { + yield trackerSignalPauseSpy(); + }), + + cpu: computed(function() { + return []; + }), + memory: computed(function() { + return []; + }), + tasks, + }); + + const mockStatsTrackersRegistry = Service.extend({ + getTracker(...args) { + getTrackerSpy(...args); + return MockTracker.create(); + }, + }); + + this.owner.register('service:stats-trackers-registry', mockStatsTrackersRegistry); + this.statsTrackersRegistry = this.owner.lookup('service:stats-trackers-registry'); + }); +} + +export function primaryMetric({ template, findResource, preload }) { + test('Contains a line chart, a percentage bar, a percentage figure, and an absolute usage figure', async function(assert) { + const metric = 'cpu'; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + + await render(template); + + assert.ok(find('[data-test-line-chart]'), 'Line chart'); + assert.ok(find('[data-test-percentage-bar]'), 'Percentage bar'); + assert.ok(find('[data-test-percentage]'), 'Percentage figure'); + assert.ok(find('[data-test-absolute-value]'), 'Absolute usage figure'); + }); + + test('The CPU metric maps to is-info', async function(assert) { + const metric = 'cpu'; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + + await render(template); + + assert.ok( + find('[data-test-current-value]').classList.contains('is-info'), + 'Info class for CPU metric' + ); + }); + + test('The Memory metric maps to is-danger', async function(assert) { + const metric = 'memory'; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + + await render(template); + + assert.ok( + find('[data-test-current-value]').classList.contains('is-danger'), + 'Danger class for Memory metric' + ); + }); + + test('Gets the tracker from the tracker registry', async function(assert) { + const metric = 'cpu'; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + + await render(template); + + assert.ok( + this.getTrackerSpy.calledWith(resource) || this.getTrackerSpy.calledWith(resource.allocation), + 'Uses the tracker registry to get the tracker for the provided resource' + ); + }); + + test('Immediately polls the tracker', async function(assert) { + const metric = 'cpu'; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + + await render(template); + + assert.ok(this.trackerPollSpy.calledOnce, 'The tracker is polled immediately'); + }); + + test('A pause signal is sent to the tracker when the component is destroyed', async function(assert) { + const metric = 'cpu'; + + // Capture a reference to the spy before the component is destroyed + const trackerSignalPauseSpy = this.trackerSignalPauseSpy; + + await preload(this.store); + + const resource = findResource(this.store); + this.setProperties({ resource, metric }); + await render(template); + + assert.notOk(trackerSignalPauseSpy.called, 'No pause signal has been sent yet'); + await clearRender(); + + assert.ok(trackerSignalPauseSpy.calledOnce, 'A pause signal is sent to the tracker'); + }); +}