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 });