github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/integration/components/topo-viz/node-test.js (about)

     1  import { findAll } from '@ember/test-helpers';
     2  import { module, test } from 'qunit';
     3  import { setupRenderingTest } from 'ember-qunit';
     4  import hbs from 'htmlbars-inline-precompile';
     5  import { create } from 'ember-cli-page-object';
     6  import sinon from 'sinon';
     7  import faker from 'nomad-ui/mirage/faker';
     8  import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
     9  import { setupMirage } from 'ember-cli-mirage/test-support';
    10  import topoVisNodePageObject from 'nomad-ui/tests/pages/components/topo-viz/node';
    11  
    12  const TopoVizNode = create(topoVisNodePageObject());
    13  
    14  const nodeGen = (name, datacenter, memory, cpu, flags = {}) => ({
    15    datacenter,
    16    memory,
    17    cpu,
    18    isSelected: !!flags.isSelected,
    19    node: {
    20      name,
    21      isEligible: flags.isEligible || flags.isEligible == null,
    22      isDraining: !!flags.isDraining,
    23    },
    24  });
    25  
    26  const allocGen = (node, memory, cpu, isScheduled = true) => ({
    27    memory,
    28    cpu,
    29    isSelected: false,
    30    memoryPercent: memory / node.memory,
    31    cpuPercent: cpu / node.cpu,
    32    allocation: {
    33      id: faker.random.uuid(),
    34      isScheduled,
    35    },
    36  });
    37  
    38  const props = overrides => ({
    39    isDense: false,
    40    heightScale: () => 50,
    41    onAllocationSelect: sinon.spy(),
    42    onNodeSelect: sinon.spy(),
    43    onAllocationFocus: sinon.spy(),
    44    onAllocationBlur: sinon.spy(),
    45    ...overrides,
    46  });
    47  
    48  module('Integration | Component | TopoViz::Node', function(hooks) {
    49    setupRenderingTest(hooks);
    50    setupMirage(hooks);
    51  
    52    const commonTemplate = hbs`
    53      <TopoViz::Node
    54        @node={{this.node}}
    55        @isDense={{this.isDense}}
    56        @heightScale={{this.heightScale}}
    57        @onAllocationSelect={{this.onAllocationSelect}}
    58        @onAllocationFocus={{this.onAllocationFocus}}
    59        @onAllocationBlur={{this.onAllocationBlur}}
    60        @onNodeSelect={{this.onNodeSelect}} />
    61    `;
    62  
    63    test('presents as a div with a label and an svg with CPU and memory rows', async function(assert) {
    64      const node = nodeGen('Node One', 'dc1', 1000, 1000);
    65      this.setProperties(
    66        props({
    67          node: {
    68            ...node,
    69            allocations: [
    70              allocGen(node, 100, 100),
    71              allocGen(node, 250, 250),
    72              allocGen(node, 300, 300, false),
    73            ],
    74          },
    75        })
    76      );
    77  
    78      await this.render(commonTemplate);
    79  
    80      assert.ok(TopoVizNode.isPresent);
    81      assert.equal(
    82        TopoVizNode.memoryRects.length,
    83        this.node.allocations.filterBy('allocation.isScheduled').length
    84      );
    85      assert.ok(TopoVizNode.cpuRects.length);
    86  
    87      await componentA11yAudit(this.element, assert);
    88    });
    89  
    90    test('the label contains aggregate information about the node', async function(assert) {
    91      const node = nodeGen('Node One', 'dc1', 1000, 1000);
    92      this.setProperties(
    93        props({
    94          node: {
    95            ...node,
    96            allocations: [
    97              allocGen(node, 100, 100),
    98              allocGen(node, 250, 250),
    99              allocGen(node, 300, 300, false),
   100            ],
   101          },
   102        })
   103      );
   104  
   105      await this.render(commonTemplate);
   106  
   107      assert.ok(TopoVizNode.label.includes(node.node.name));
   108      assert.ok(
   109        TopoVizNode.label.includes(
   110          `${this.node.allocations.filterBy('allocation.isScheduled').length} Allocs`
   111        )
   112      );
   113      assert.ok(TopoVizNode.label.includes(`${this.node.memory} MiB`));
   114      assert.ok(TopoVizNode.label.includes(`${this.node.cpu} MHz`));
   115    });
   116  
   117    test('the status icon indicates when the node is draining', async function(assert) {
   118      const node = nodeGen('Node One', 'dc1', 1000, 1000, { isDraining: true });
   119      this.setProperties(
   120        props({
   121          node: {
   122            ...node,
   123            allocations: [],
   124          },
   125        })
   126      );
   127  
   128      await this.render(commonTemplate);
   129  
   130      assert.ok(TopoVizNode.statusIcon.includes('icon-is-clock-outline'));
   131      assert.equal(TopoVizNode.statusIconLabel, 'Client is draining');
   132    });
   133  
   134    test('the status icon indicates when the node is ineligible for scheduling', async function(assert) {
   135      const node = nodeGen('Node One', 'dc1', 1000, 1000, { isEligible: false });
   136      this.setProperties(
   137        props({
   138          node: {
   139            ...node,
   140            allocations: [],
   141          },
   142        })
   143      );
   144  
   145      await this.render(commonTemplate);
   146  
   147      assert.ok(TopoVizNode.statusIcon.includes('icon-is-lock-closed'));
   148      assert.equal(TopoVizNode.statusIconLabel, 'Client is ineligible');
   149    });
   150  
   151    test('when isDense is false, clicking the node does nothing', async function(assert) {
   152      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   153      this.setProperties(
   154        props({
   155          isDense: false,
   156          node: {
   157            ...node,
   158            allocations: [],
   159          },
   160        })
   161      );
   162  
   163      await this.render(commonTemplate);
   164      await TopoVizNode.selectNode();
   165  
   166      assert.notOk(TopoVizNode.nodeIsInteractive);
   167      assert.notOk(this.onNodeSelect.called);
   168    });
   169  
   170    test('when isDense is true, clicking the node calls onNodeSelect', async function(assert) {
   171      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   172      this.setProperties(
   173        props({
   174          isDense: true,
   175          node: {
   176            ...node,
   177            allocations: [],
   178          },
   179        })
   180      );
   181  
   182      await this.render(commonTemplate);
   183      await TopoVizNode.selectNode();
   184  
   185      assert.ok(TopoVizNode.nodeIsInteractive);
   186      assert.ok(this.onNodeSelect.called);
   187      assert.ok(this.onNodeSelect.calledWith(this.node));
   188    });
   189  
   190    test('the node gets the is-selected class when the node is selected', async function(assert) {
   191      const node = nodeGen('Node One', 'dc1', 1000, 1000, { isSelected: true });
   192      this.setProperties(
   193        props({
   194          isDense: true,
   195          node: {
   196            ...node,
   197            allocations: [],
   198          },
   199        })
   200      );
   201  
   202      await this.render(commonTemplate);
   203  
   204      assert.ok(TopoVizNode.nodeIsSelected);
   205    });
   206  
   207    test('the node gets its height form the @heightScale arg', async function(assert) {
   208      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   209      const height = 50;
   210      const heightSpy = sinon.spy();
   211      this.setProperties(
   212        props({
   213          heightScale: (...args) => {
   214            heightSpy(...args);
   215            return height;
   216          },
   217          node: {
   218            ...node,
   219            allocations: [allocGen(node, 100, 100), allocGen(node, 250, 250)],
   220          },
   221        })
   222      );
   223  
   224      await this.render(commonTemplate);
   225  
   226      assert.ok(heightSpy.called);
   227      assert.ok(heightSpy.calledWith(this.node.memory));
   228      assert.equal(TopoVizNode.memoryRects[0].height, `${height}px`);
   229    });
   230  
   231    test('each allocation gets a memory rect and a cpu rect', async function(assert) {
   232      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   233      this.setProperties(
   234        props({
   235          node: {
   236            ...node,
   237            allocations: [allocGen(node, 100, 100), allocGen(node, 250, 250)],
   238          },
   239        })
   240      );
   241  
   242      await this.render(commonTemplate);
   243  
   244      assert.equal(TopoVizNode.memoryRects.length, this.node.allocations.length);
   245      assert.equal(TopoVizNode.cpuRects.length, this.node.allocations.length);
   246    });
   247  
   248    test('each allocation is sized according to its percentage of utilization', async function(assert) {
   249      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   250      this.setProperties(
   251        props({
   252          node: {
   253            ...node,
   254            allocations: [allocGen(node, 100, 100), allocGen(node, 250, 250)],
   255          },
   256        })
   257      );
   258  
   259      await this.render(hbs`
   260        <div style="width:100px">
   261          <TopoViz::Node
   262            @node={{this.node}}
   263            @isDense={{this.isDense}}
   264            @heightScale={{this.heightScale}}
   265            @onAllocationSelect={{this.onAllocationSelect}}
   266            @onNodeSelect={{this.onNodeSelect}} />
   267        </div>
   268      `);
   269  
   270      // Remove the width of the padding and the label from the SVG width
   271      const width = 100 - 5 - 5 - 20;
   272      this.node.allocations.forEach((alloc, index) => {
   273        const memWidth = alloc.memoryPercent * width - (index === 0 ? 0.5 : 1);
   274        const cpuWidth = alloc.cpuPercent * width - (index === 0 ? 0.5 : 1);
   275        assert.equal(TopoVizNode.memoryRects[index].width, `${memWidth}px`);
   276        assert.equal(TopoVizNode.cpuRects[index].width, `${cpuWidth}px`);
   277      });
   278    });
   279  
   280    test('clicking either the memory or cpu rect for an allocation will call onAllocationSelect', async function(assert) {
   281      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   282      this.setProperties(
   283        props({
   284          node: {
   285            ...node,
   286            allocations: [allocGen(node, 100, 100), allocGen(node, 250, 250)],
   287          },
   288        })
   289      );
   290  
   291      await this.render(commonTemplate);
   292  
   293      await TopoVizNode.memoryRects[0].select();
   294      assert.equal(this.onAllocationSelect.callCount, 1);
   295      assert.ok(this.onAllocationSelect.calledWith(this.node.allocations[0]));
   296  
   297      await TopoVizNode.cpuRects[0].select();
   298      assert.equal(this.onAllocationSelect.callCount, 2);
   299  
   300      await TopoVizNode.cpuRects[1].select();
   301      assert.equal(this.onAllocationSelect.callCount, 3);
   302      assert.ok(this.onAllocationSelect.calledWith(this.node.allocations[1]));
   303  
   304      await TopoVizNode.memoryRects[1].select();
   305      assert.equal(this.onAllocationSelect.callCount, 4);
   306    });
   307  
   308    test('hovering over a memory or cpu rect for an allocation will call onAllocationFocus', async function(assert) {
   309      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   310      this.setProperties(
   311        props({
   312          node: {
   313            ...node,
   314            allocations: [allocGen(node, 100, 100), allocGen(node, 250, 250)],
   315          },
   316        })
   317      );
   318  
   319      await this.render(commonTemplate);
   320  
   321      await TopoVizNode.memoryRects[0].hover();
   322      assert.equal(this.onAllocationFocus.callCount, 1);
   323      assert.equal(
   324        this.onAllocationFocus.getCall(0).args[0].allocation,
   325        this.node.allocations[0].allocation
   326      );
   327      assert.equal(this.onAllocationFocus.getCall(0).args[1], findAll('[data-test-memory-rect]')[0]);
   328  
   329      await TopoVizNode.cpuRects[1].hover();
   330      assert.equal(this.onAllocationFocus.callCount, 2);
   331      assert.equal(
   332        this.onAllocationFocus.getCall(1).args[0].allocation,
   333        this.node.allocations[1].allocation
   334      );
   335      assert.equal(this.onAllocationFocus.getCall(1).args[1], findAll('[data-test-cpu-rect]')[1]);
   336    });
   337  
   338    test('leaving the entire node will call onAllocationBlur, which allows for the tooltip transitions', async function(assert) {
   339      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   340      this.setProperties(
   341        props({
   342          node: {
   343            ...node,
   344            allocations: [allocGen(node, 100, 100), allocGen(node, 250, 250)],
   345          },
   346        })
   347      );
   348  
   349      await this.render(commonTemplate);
   350  
   351      await TopoVizNode.memoryRects[0].hover();
   352      assert.equal(this.onAllocationFocus.callCount, 1);
   353      assert.equal(this.onAllocationBlur.callCount, 0);
   354  
   355      await TopoVizNode.memoryRects[0].mouseleave();
   356      assert.equal(this.onAllocationBlur.callCount, 0);
   357  
   358      await TopoVizNode.mouseout();
   359      assert.equal(this.onAllocationBlur.callCount, 1);
   360    });
   361  
   362    test('allocations are sorted by smallest to largest delta of memory to cpu percent utilizations', async function(assert) {
   363      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   364  
   365      const evenAlloc = allocGen(node, 100, 100);
   366      const mediumMemoryAlloc = allocGen(node, 200, 150);
   367      const largeMemoryAlloc = allocGen(node, 300, 50);
   368      const mediumCPUAlloc = allocGen(node, 150, 200);
   369      const largeCPUAlloc = allocGen(node, 50, 300);
   370  
   371      this.setProperties(
   372        props({
   373          node: {
   374            ...node,
   375            allocations: [
   376              largeCPUAlloc,
   377              mediumCPUAlloc,
   378              evenAlloc,
   379              mediumMemoryAlloc,
   380              largeMemoryAlloc,
   381            ],
   382          },
   383        })
   384      );
   385  
   386      await this.render(commonTemplate);
   387  
   388      const expectedOrder = [
   389        evenAlloc,
   390        mediumCPUAlloc,
   391        mediumMemoryAlloc,
   392        largeCPUAlloc,
   393        largeMemoryAlloc,
   394      ];
   395      expectedOrder.forEach((alloc, index) => {
   396        assert.equal(TopoVizNode.memoryRects[index].id, alloc.allocation.id);
   397        assert.equal(TopoVizNode.cpuRects[index].id, alloc.allocation.id);
   398      });
   399    });
   400  
   401    test('when there are no allocations, a "no allocations" note is shown', async function(assert) {
   402      const node = nodeGen('Node One', 'dc1', 1000, 1000);
   403      this.setProperties(
   404        props({
   405          node: {
   406            ...node,
   407            allocations: [],
   408          },
   409        })
   410      );
   411  
   412      await this.render(commonTemplate);
   413      assert.equal(TopoVizNode.emptyMessage, 'Empty Client');
   414    });
   415  });