github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/integration/components/das/recommendation-card-test.js (about)

     1  import { module, test } from 'qunit';
     2  import { setupRenderingTest } from 'ember-qunit';
     3  import { render, settled } from '@ember/test-helpers';
     4  import { hbs } from 'ember-cli-htmlbars';
     5  import Service from '@ember/service';
     6  import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
     7  
     8  import RecommendationCardComponent from 'nomad-ui/tests/pages/components/recommendation-card';
     9  import { create } from 'ember-cli-page-object';
    10  const RecommendationCard = create(RecommendationCardComponent);
    11  
    12  import { tracked } from '@glimmer/tracking';
    13  import { action } from '@ember/object';
    14  import { set } from '@ember/object';
    15  
    16  module('Integration | Component | das/recommendation-card', function (hooks) {
    17    setupRenderingTest(hooks);
    18  
    19    hooks.beforeEach(function () {
    20      const mockRouter = Service.extend({
    21        init() {
    22          this._super(...arguments);
    23        },
    24  
    25        urlFor(route, slug, { queryParams: { namespace } }) {
    26          return `${route}:${slug}?namespace=${namespace}`;
    27        },
    28      });
    29  
    30      this.owner.register('service:router', mockRouter);
    31    });
    32  
    33    test('it renders a recommendation card', async function (assert) {
    34      assert.expect(49);
    35  
    36      const task1 = {
    37        name: 'jortle',
    38        reservedCPU: 150,
    39        reservedMemory: 128,
    40      };
    41  
    42      const task2 = {
    43        name: 'tortle',
    44        reservedCPU: 125,
    45        reservedMemory: 256,
    46      };
    47  
    48      this.set(
    49        'summary',
    50        new MockRecommendationSummary({
    51          jobNamespace: 'namespace',
    52          recommendations: [
    53            {
    54              resource: 'MemoryMB',
    55              stats: {},
    56              task: task1,
    57              value: 192,
    58              currentValue: task1.reservedMemory,
    59            },
    60            {
    61              resource: 'CPU',
    62              stats: {},
    63              task: task1,
    64              value: 50,
    65              currentValue: task1.reservedCPU,
    66            },
    67            {
    68              resource: 'CPU',
    69              stats: {},
    70              task: task2,
    71              value: 150,
    72              currentValue: task2.reservedCPU,
    73            },
    74            {
    75              resource: 'MemoryMB',
    76              stats: {},
    77              task: task2,
    78              value: 320,
    79              currentValue: task2.reservedMemory,
    80            },
    81          ],
    82  
    83          taskGroup: {
    84            count: 2,
    85            name: 'group-name',
    86            job: {
    87              name: 'job-name',
    88              namespace: {
    89                name: 'namespace',
    90              },
    91            },
    92            reservedCPU: task1.reservedCPU + task2.reservedCPU,
    93            reservedMemory: task1.reservedMemory + task2.reservedMemory,
    94          },
    95        })
    96      );
    97  
    98      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
    99  
   100      assert.equal(RecommendationCard.slug.jobName, 'job-name');
   101      assert.equal(RecommendationCard.slug.groupName, 'group-name');
   102  
   103      assert.equal(RecommendationCard.namespace, 'namespace');
   104  
   105      assert.equal(RecommendationCard.totalsTable.current.cpu.text, '275 MHz');
   106      assert.equal(RecommendationCard.totalsTable.current.memory.text, '384 MiB');
   107  
   108      RecommendationCard.totalsTable.recommended.cpu.as((RecommendedCpu) => {
   109        assert.equal(RecommendedCpu.text, '200 MHz');
   110        assert.ok(RecommendedCpu.isDecrease);
   111      });
   112  
   113      RecommendationCard.totalsTable.recommended.memory.as(
   114        (RecommendedMemory) => {
   115          assert.equal(RecommendedMemory.text, '512 MiB');
   116          assert.ok(RecommendedMemory.isIncrease);
   117        }
   118      );
   119  
   120      assert.equal(RecommendationCard.totalsTable.unitDiff.cpu, '-75 MHz');
   121      assert.equal(RecommendationCard.totalsTable.unitDiff.memory, '+128 MiB');
   122  
   123      // Expected signal has a minus character, not a hyphen.
   124      assert.equal(RecommendationCard.totalsTable.percentDiff.cpu, '−27%');
   125      assert.equal(RecommendationCard.totalsTable.percentDiff.memory, '+33%');
   126  
   127      assert.equal(RecommendationCard.copyButton.text, 'job-name / group-name');
   128      assert.ok(
   129        RecommendationCard.copyButton.clipboardText.endsWith(
   130          'optimize.summary:job-name/group-name?namespace=namespace'
   131        )
   132      );
   133  
   134      assert.equal(
   135        RecommendationCard.activeTask.totalsTable.current.cpu.text,
   136        '150 MHz'
   137      );
   138      assert.equal(
   139        RecommendationCard.activeTask.totalsTable.current.memory.text,
   140        '128 MiB'
   141      );
   142  
   143      RecommendationCard.activeTask.totalsTable.recommended.cpu.as(
   144        (RecommendedCpu) => {
   145          assert.equal(RecommendedCpu.text, '50 MHz');
   146          assert.ok(RecommendedCpu.isDecrease);
   147        }
   148      );
   149  
   150      RecommendationCard.activeTask.totalsTable.recommended.memory.as(
   151        (RecommendedMemory) => {
   152          assert.equal(RecommendedMemory.text, '192 MiB');
   153          assert.ok(RecommendedMemory.isIncrease);
   154        }
   155      );
   156  
   157      assert.equal(RecommendationCard.activeTask.charts.length, 2);
   158      assert.equal(
   159        RecommendationCard.activeTask.charts[0].resource,
   160        'CPU',
   161        'CPU chart should be first when present'
   162      );
   163  
   164      assert.ok(RecommendationCard.activeTask.cpuChart.isDecrease);
   165      assert.ok(RecommendationCard.activeTask.memoryChart.isIncrease);
   166  
   167      assert.equal(RecommendationCard.togglesTable.tasks.length, 2);
   168  
   169      await RecommendationCard.togglesTable.tasks[0].as(async (FirstTask) => {
   170        assert.equal(FirstTask.name, 'jortle');
   171        assert.ok(FirstTask.isActive);
   172  
   173        assert.equal(FirstTask.cpu.title, 'CPU for jortle');
   174        assert.ok(FirstTask.cpu.isActive);
   175  
   176        assert.equal(FirstTask.memory.title, 'Memory for jortle');
   177        assert.ok(FirstTask.memory.isActive);
   178  
   179        await FirstTask.cpu.toggle();
   180  
   181        assert.notOk(FirstTask.cpu.isActive);
   182        assert.ok(RecommendationCard.activeTask.cpuChart.isDisabled);
   183      });
   184  
   185      assert.notOk(RecommendationCard.togglesTable.tasks[1].isActive);
   186  
   187      assert.equal(RecommendationCard.activeTask.name, 'jortle task');
   188  
   189      RecommendationCard.totalsTable.recommended.cpu.as((RecommendedCpu) => {
   190        assert.equal(RecommendedCpu.text, '300 MHz');
   191        assert.ok(RecommendedCpu.isIncrease);
   192      });
   193  
   194      RecommendationCard.activeTask.totalsTable.recommended.cpu.as(
   195        (RecommendedCpu) => {
   196          assert.equal(RecommendedCpu.text, '150 MHz');
   197          assert.ok(RecommendedCpu.isNeutral);
   198        }
   199      );
   200  
   201      await RecommendationCard.togglesTable.toggleAllMemory.toggle();
   202  
   203      assert.notOk(RecommendationCard.togglesTable.tasks[0].memory.isActive);
   204      assert.notOk(RecommendationCard.togglesTable.tasks[1].memory.isActive);
   205  
   206      RecommendationCard.totalsTable.recommended.memory.as(
   207        (RecommendedMemory) => {
   208          assert.equal(RecommendedMemory.text, '384 MiB');
   209          assert.ok(RecommendedMemory.isNeutral);
   210        }
   211      );
   212  
   213      await RecommendationCard.togglesTable.tasks[1].click();
   214  
   215      assert.notOk(RecommendationCard.togglesTable.tasks[0].isActive);
   216      assert.ok(RecommendationCard.togglesTable.tasks[1].isActive);
   217  
   218      assert.equal(RecommendationCard.activeTask.name, 'tortle task');
   219      assert.equal(
   220        RecommendationCard.activeTask.totalsTable.current.cpu.text,
   221        '125 MHz'
   222      );
   223  
   224      await componentA11yAudit(this.element, assert);
   225    });
   226  
   227    test('it doesn’t have header toggles when there’s only one task', async function (assert) {
   228      const task1 = {
   229        name: 'jortle',
   230        reservedCPU: 150,
   231        reservedMemory: 128,
   232      };
   233  
   234      this.set(
   235        'summary',
   236        new MockRecommendationSummary({
   237          recommendations: [
   238            {
   239              resource: 'CPU',
   240              stats: {},
   241              task: task1,
   242              value: 50,
   243            },
   244            {
   245              resource: 'MemoryMB',
   246              stats: {},
   247              task: task1,
   248              value: 192,
   249            },
   250          ],
   251  
   252          taskGroup: {
   253            count: 1,
   254            reservedCPU: task1.reservedCPU,
   255            reservedMemory: task1.reservedMemory,
   256          },
   257        })
   258      );
   259  
   260      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
   261  
   262      assert.notOk(RecommendationCard.togglesTable.toggleAllIsPresent);
   263      assert.notOk(RecommendationCard.togglesTable.toggleAllCPU.isPresent);
   264      assert.notOk(RecommendationCard.togglesTable.toggleAllMemory.isPresent);
   265    });
   266  
   267    test('it disables the accept button when all recommendations are disabled', async function (assert) {
   268      const task1 = {
   269        name: 'jortle',
   270        reservedCPU: 150,
   271        reservedMemory: 128,
   272      };
   273  
   274      this.set(
   275        'summary',
   276        new MockRecommendationSummary({
   277          recommendations: [
   278            {
   279              resource: 'CPU',
   280              stats: {},
   281              task: task1,
   282              value: 50,
   283            },
   284            {
   285              resource: 'MemoryMB',
   286              stats: {},
   287              task: task1,
   288              value: 192,
   289            },
   290          ],
   291  
   292          taskGroup: {
   293            count: 1,
   294            reservedCPU: task1.reservedCPU,
   295            reservedMemory: task1.reservedMemory,
   296          },
   297        })
   298      );
   299  
   300      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
   301  
   302      await RecommendationCard.togglesTable.tasks[0].cpu.toggle();
   303      await RecommendationCard.togglesTable.tasks[0].memory.toggle();
   304  
   305      assert.ok(RecommendationCard.acceptButton.isDisabled);
   306    });
   307  
   308    test('it doesn’t show a toggle or chart when there’s no recommendation for that resource', async function (assert) {
   309      const task1 = {
   310        name: 'jortle',
   311        reservedCPU: 150,
   312        reservedMemory: 128,
   313      };
   314  
   315      this.set(
   316        'summary',
   317        new MockRecommendationSummary({
   318          recommendations: [
   319            {
   320              resource: 'CPU',
   321              stats: {},
   322              task: task1,
   323              value: 50,
   324            },
   325          ],
   326  
   327          taskGroup: {
   328            count: 2,
   329            name: 'group-name',
   330            job: {
   331              name: 'job-name',
   332            },
   333            reservedCPU: task1.reservedCPU,
   334            reservedMemory: task1.reservedMemory,
   335          },
   336        })
   337      );
   338  
   339      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
   340  
   341      assert.equal(
   342        RecommendationCard.totalsTable.recommended.memory.text,
   343        '128 MiB'
   344      );
   345      assert.equal(RecommendationCard.totalsTable.unitDiff.memory, '0 MiB');
   346      assert.equal(RecommendationCard.totalsTable.percentDiff.memory, '+0%');
   347  
   348      assert.equal(
   349        RecommendationCard.narrative.trim(),
   350        'Applying the selected recommendations will save an aggregate 200 MHz of CPU across 2 allocations.'
   351      );
   352  
   353      assert.ok(RecommendationCard.togglesTable.tasks[0].memory.isDisabled);
   354      assert.notOk(RecommendationCard.activeTask.memoryChart.isPresent);
   355    });
   356  
   357    test('it disables a resource’s toggle all toggle when there are no recommendations for it', async function (assert) {
   358      const task1 = {
   359        name: 'jortle',
   360        reservedCPU: 150,
   361        reservedMemory: 128,
   362      };
   363  
   364      const task2 = {
   365        name: 'tortle',
   366        reservedCPU: 150,
   367        reservedMemory: 128,
   368      };
   369  
   370      this.set(
   371        'summary',
   372        new MockRecommendationSummary({
   373          recommendations: [
   374            {
   375              resource: 'CPU',
   376              stats: {},
   377              task: task1,
   378              value: 50,
   379            },
   380            {
   381              resource: 'CPU',
   382              stats: {},
   383              task: task2,
   384              value: 50,
   385            },
   386          ],
   387  
   388          taskGroup: {
   389            count: 2,
   390            name: 'group-name',
   391            job: {
   392              name: 'job-name',
   393            },
   394            reservedCPU: task1.reservedCPU + task2.reservedCPU,
   395            reservedMemory: task1.reservedMemory + task2.reservedMemory,
   396          },
   397        })
   398      );
   399  
   400      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
   401  
   402      assert.ok(RecommendationCard.togglesTable.toggleAllMemory.isDisabled);
   403      assert.notOk(RecommendationCard.togglesTable.toggleAllMemory.isActive);
   404      assert.notOk(RecommendationCard.activeTask.memoryChart.isPresent);
   405    });
   406  
   407    test('it renders diff calculations in a sentence', async function (assert) {
   408      const task1 = {
   409        name: 'jortle',
   410        reservedCPU: 150,
   411        reservedMemory: 128,
   412      };
   413  
   414      const task2 = {
   415        name: 'tortle',
   416        reservedCPU: 125,
   417        reservedMemory: 256,
   418      };
   419  
   420      this.set(
   421        'summary',
   422        new MockRecommendationSummary({
   423          recommendations: [
   424            {
   425              resource: 'CPU',
   426              stats: {},
   427              task: task1,
   428              value: 50,
   429              currentValue: task1.reservedCPU,
   430            },
   431            {
   432              resource: 'MemoryMB',
   433              stats: {},
   434              task: task1,
   435              value: 192,
   436              currentValue: task1.reservedMemory,
   437            },
   438            {
   439              resource: 'CPU',
   440              stats: {},
   441              task: task2,
   442              value: 150,
   443              currentValue: task2.reservedCPU,
   444            },
   445            {
   446              resource: 'MemoryMB',
   447              stats: {},
   448              task: task2,
   449              value: 320,
   450              currentValue: task2.reservedMemory,
   451            },
   452          ],
   453  
   454          taskGroup: {
   455            count: 10,
   456            name: 'group-name',
   457            job: {
   458              name: 'job-name',
   459              namespace: {
   460                name: 'namespace',
   461              },
   462            },
   463            reservedCPU: task1.reservedCPU + task2.reservedCPU,
   464            reservedMemory: task1.reservedMemory + task2.reservedMemory,
   465          },
   466        })
   467      );
   468  
   469      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
   470  
   471      const [cpuRec1, memRec1, cpuRec2, memRec2] = this.summary.recommendations;
   472  
   473      assert.equal(
   474        RecommendationCard.narrative.trim(),
   475        'Applying the selected recommendations will save an aggregate 750 MHz of CPU and add an aggregate 1.25 GiB of memory across 10 allocations.'
   476      );
   477  
   478      this.summary.toggleRecommendation(cpuRec1);
   479      await settled();
   480  
   481      assert.equal(
   482        RecommendationCard.narrative.trim(),
   483        'Applying the selected recommendations will add an aggregate 250 MHz of CPU and 1.25 GiB of memory across 10 allocations.'
   484      );
   485  
   486      this.summary.toggleRecommendation(memRec1);
   487      await settled();
   488  
   489      assert.equal(
   490        RecommendationCard.narrative.trim(),
   491        'Applying the selected recommendations will add an aggregate 250 MHz of CPU and 640 MiB of memory across 10 allocations.'
   492      );
   493  
   494      this.summary.toggleRecommendation(cpuRec2);
   495      await settled();
   496  
   497      assert.equal(
   498        RecommendationCard.narrative.trim(),
   499        'Applying the selected recommendations will add an aggregate 640 MiB of memory across 10 allocations.'
   500      );
   501  
   502      this.summary.toggleRecommendation(cpuRec1);
   503      this.summary.toggleRecommendation(memRec2);
   504      await settled();
   505  
   506      assert.equal(
   507        RecommendationCard.narrative.trim(),
   508        'Applying the selected recommendations will save an aggregate 1 GHz of CPU across 10 allocations.'
   509      );
   510  
   511      this.summary.toggleRecommendation(cpuRec1);
   512      await settled();
   513  
   514      assert.equal(RecommendationCard.narrative.trim(), '');
   515  
   516      this.summary.toggleRecommendation(cpuRec1);
   517      await settled();
   518  
   519      assert.equal(
   520        RecommendationCard.narrative.trim(),
   521        'Applying the selected recommendations will save an aggregate 1 GHz of CPU across 10 allocations.'
   522      );
   523  
   524      this.summary.toggleRecommendation(memRec2);
   525      set(memRec2, 'value', 128);
   526      await settled();
   527  
   528      assert.equal(
   529        RecommendationCard.narrative.trim(),
   530        'Applying the selected recommendations will save an aggregate 1 GHz of CPU and 1.25 GiB of memory across 10 allocations.'
   531      );
   532    });
   533  
   534    test('it renders diff calculations in a sentence with no aggregation for one allocatio', async function (assert) {
   535      const task1 = {
   536        name: 'jortle',
   537        reservedCPU: 150,
   538        reservedMemory: 128,
   539      };
   540  
   541      const task2 = {
   542        name: 'tortle',
   543        reservedCPU: 125,
   544        reservedMemory: 256,
   545      };
   546  
   547      this.set(
   548        'summary',
   549        new MockRecommendationSummary({
   550          recommendations: [
   551            {
   552              resource: 'CPU',
   553              stats: {},
   554              task: task1,
   555              value: 50,
   556              currentValue: task1.reservedCPU,
   557            },
   558            {
   559              resource: 'MemoryMB',
   560              stats: {},
   561              task: task1,
   562              value: 192,
   563              currentValue: task1.reservedMemory,
   564            },
   565            {
   566              resource: 'CPU',
   567              stats: {},
   568              task: task2,
   569              value: 150,
   570              currentValue: task2.reservedCPU,
   571            },
   572            {
   573              resource: 'MemoryMB',
   574              stats: {},
   575              task: task2,
   576              value: 320,
   577              currentValue: task2.reservedMemory,
   578            },
   579          ],
   580  
   581          taskGroup: {
   582            count: 1,
   583            name: 'group-name',
   584            job: {
   585              name: 'job-name',
   586              namespace: {
   587                name: 'namespace',
   588              },
   589            },
   590            reservedCPU: task1.reservedCPU + task2.reservedCPU,
   591            reservedMemory: task1.reservedMemory + task2.reservedMemory,
   592          },
   593        })
   594      );
   595  
   596      await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
   597  
   598      assert.equal(
   599        RecommendationCard.narrative.trim(),
   600        'Applying the selected recommendations will save 75 MHz of CPU and add 128 MiB of memory.'
   601      );
   602    });
   603  });
   604  
   605  class MockRecommendationSummary {
   606    @tracked excludedRecommendations = [];
   607  
   608    constructor(attributes) {
   609      Object.assign(this, attributes);
   610    }
   611  
   612    get slug() {
   613      return `${this.taskGroup?.job?.name}/${this.taskGroup?.name}`;
   614    }
   615  
   616    @action
   617    toggleRecommendation(recommendation) {
   618      if (this.excludedRecommendations.includes(recommendation)) {
   619        this.excludedRecommendations.removeObject(recommendation);
   620      } else {
   621        this.excludedRecommendations.pushObject(recommendation);
   622      }
   623    }
   624  
   625    @action
   626    toggleAllRecommendationsForResource(resource, enabled) {
   627      if (enabled) {
   628        this.excludedRecommendations = this.excludedRecommendations.rejectBy(
   629          'resource',
   630          resource
   631        );
   632      } else {
   633        this.excludedRecommendations.pushObjects(
   634          this.recommendations.filterBy('resource', resource)
   635        );
   636      }
   637    }
   638  }