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

     1  import {
     2    find,
     3    findAll,
     4    click,
     5    render,
     6    triggerEvent,
     7  } from '@ember/test-helpers';
     8  import { module, test } from 'qunit';
     9  import { setupRenderingTest } from 'ember-qunit';
    10  import hbs from 'htmlbars-inline-precompile';
    11  import sinon from 'sinon';
    12  import moment from 'moment';
    13  import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
    14  
    15  const REF_DATE = new Date();
    16  
    17  module('Integration | Component | line-chart', function (hooks) {
    18    setupRenderingTest(hooks);
    19  
    20    test('when a chart has annotations, they are rendered in order', async function (assert) {
    21      assert.expect(4);
    22  
    23      const annotations = [
    24        { x: 2, type: 'info' },
    25        { x: 1, type: 'error' },
    26        { x: 3, type: 'info' },
    27      ];
    28      this.setProperties({
    29        annotations,
    30        data: [
    31          { x: 1, y: 1 },
    32          { x: 10, y: 10 },
    33        ],
    34      });
    35  
    36      await render(hbs`
    37        <LineChart
    38          @xProp="x"
    39          @yProp="y"
    40          @data={{this.data}}>
    41          <:after as |c|>
    42            <c.VAnnotations @annotations={{this.annotations}} />
    43          </:after>
    44        </LineChart>
    45      `);
    46  
    47      const sortedAnnotations = annotations.sortBy('x');
    48      findAll('[data-test-annotation]').forEach((annotation, idx) => {
    49        const datum = sortedAnnotations[idx];
    50        assert.equal(
    51          annotation.querySelector('button').getAttribute('title'),
    52          `${datum.type} event at ${datum.x}`
    53        );
    54      });
    55  
    56      await componentA11yAudit(this.element, assert);
    57    });
    58  
    59    test('when a chart has annotations and is timeseries, annotations are sorted reverse-chronologically', async function (assert) {
    60      assert.expect(3);
    61  
    62      const annotations = [
    63        {
    64          x: moment(REF_DATE).add(2, 'd').toDate(),
    65          type: 'info',
    66        },
    67        {
    68          x: moment(REF_DATE).add(1, 'd').toDate(),
    69          type: 'error',
    70        },
    71        {
    72          x: moment(REF_DATE).add(3, 'd').toDate(),
    73          type: 'info',
    74        },
    75      ];
    76      this.setProperties({
    77        annotations,
    78        data: [
    79          { x: 1, y: 1 },
    80          { x: 10, y: 10 },
    81        ],
    82      });
    83  
    84      await render(hbs`
    85        <LineChart
    86          @xProp="x"
    87          @yProp="y"
    88          @timeseries={{true}}
    89          @data={{this.data}}>
    90          <:after as |c|>
    91            <c.VAnnotations @annotations={{this.annotations}} />
    92          </:after>
    93        </LineChart>
    94      `);
    95  
    96      const sortedAnnotations = annotations.sortBy('x').reverse();
    97      findAll('[data-test-annotation]').forEach((annotation, idx) => {
    98        const datum = sortedAnnotations[idx];
    99        assert.equal(
   100          annotation.querySelector('button').getAttribute('title'),
   101          `${datum.type} event at ${moment(datum.x).format('MMM DD, HH:mm')}`
   102        );
   103      });
   104    });
   105  
   106    test('clicking annotations calls the onAnnotationClick action with the annotation as an argument', async function (assert) {
   107      const annotations = [{ x: 2, type: 'info', meta: { data: 'here' } }];
   108      this.setProperties({
   109        annotations,
   110        data: [
   111          { x: 1, y: 1 },
   112          { x: 10, y: 10 },
   113        ],
   114        click: sinon.spy(),
   115      });
   116  
   117      await render(hbs`
   118        <LineChart
   119          @xProp="x"
   120          @yProp="y"
   121          @data={{this.data}}>
   122          <:after as |c|>
   123            <c.VAnnotations @annotations={{this.annotations}} @annotationClick={{this.click}} />
   124          </:after>
   125        </LineChart>
   126      `);
   127  
   128      await click('[data-test-annotation] button');
   129      assert.ok(this.click.calledWith(annotations[0]));
   130    });
   131  
   132    test('annotations will have staggered heights when too close to be positioned side-by-side', async function (assert) {
   133      assert.expect(4);
   134  
   135      const annotations = [
   136        { x: 2, type: 'info' },
   137        { x: 2.4, type: 'error' },
   138        { x: 9, type: 'info' },
   139      ];
   140      this.setProperties({
   141        annotations,
   142        data: [
   143          { x: 1, y: 1 },
   144          { x: 10, y: 10 },
   145        ],
   146        click: sinon.spy(),
   147      });
   148  
   149      await render(hbs`
   150        <div style="width:200px;">
   151          <LineChart
   152            @xProp="x"
   153            @yProp="y"
   154            @data={{this.data}}>
   155            <:after as |c|>
   156              <c.VAnnotations @annotations={{this.annotations}} />
   157            </:after>
   158          </LineChart>
   159        </div>
   160      `);
   161  
   162      const annotationEls = findAll('[data-test-annotation]');
   163      assert.notOk(annotationEls[0].classList.contains('is-staggered'));
   164      assert.ok(annotationEls[1].classList.contains('is-staggered'));
   165      assert.notOk(annotationEls[2].classList.contains('is-staggered'));
   166  
   167      await componentA11yAudit(this.element, assert);
   168    });
   169  
   170    test('horizontal annotations render in order', async function (assert) {
   171      assert.expect(3);
   172  
   173      const annotations = [
   174        { y: 2, label: 'label one' },
   175        { y: 9, label: 'label three' },
   176        { y: 2.4, label: 'label two' },
   177      ];
   178      this.setProperties({
   179        annotations,
   180        data: [
   181          { x: 1, y: 1 },
   182          { x: 10, y: 10 },
   183        ],
   184      });
   185  
   186      await render(hbs`
   187        <LineChart
   188          @xProp="x"
   189          @yProp="y"
   190          @data={{this.data}}>
   191          <:after as |c|>
   192            <c.HAnnotations @annotations={{this.annotations}} @labelProp="label" />
   193          </:after>
   194        </LineChart>
   195      `);
   196  
   197      const annotationEls = findAll('[data-test-annotation]');
   198      annotations
   199        .sortBy('y')
   200        .reverse()
   201        .forEach((annotation, index) => {
   202          assert.equal(annotationEls[index].textContent.trim(), annotation.label);
   203        });
   204    });
   205  
   206    test('the tooltip includes information on the data closest to the mouse', async function (assert) {
   207      assert.expect(8);
   208  
   209      const series1 = [
   210        { x: 1, y: 2 },
   211        { x: 3, y: 3 },
   212        { x: 5, y: 4 },
   213      ];
   214      const series2 = [
   215        { x: 2, y: 10 },
   216        { x: 4, y: 9 },
   217        { x: 6, y: 8 },
   218      ];
   219      this.setProperties({
   220        data: [
   221          { series: 'One', data: series1 },
   222          { series: 'Two', data: series2 },
   223        ],
   224      });
   225  
   226      await render(hbs`
   227        <div style="width:500px;margin-top:100px">
   228          <LineChart
   229            @xProp="x"
   230            @yProp="y"
   231            @dataProp="data"
   232            @data={{this.data}}>
   233            <:svg as |c|>
   234              {{#each this.data as |series idx|}}
   235                <c.Area @data={{series.data}} @colorScale="blues" @index={{idx}} />
   236              {{/each}}
   237            </:svg>
   238            <:after as |c|>
   239              <c.Tooltip as |series datum index|>
   240                <li>
   241                  <span class="label"><span class="color-swatch swatch-blues swatch-blues-{{index}}" />{{series.series}}</span>
   242                  <span class="value">{{datum.formattedY}}</span>
   243                </li>
   244              </c.Tooltip>
   245            </:after>
   246          </LineChart>
   247        </div>
   248      `);
   249  
   250      // All tooltip events are attached to the hover target
   251      const hoverTarget = find('[data-test-hover-target]');
   252  
   253      // Mouse to data mapping happens based on the clientX of the MouseEvent
   254      const bbox = hoverTarget.getBoundingClientRect();
   255      // The MouseEvent needs to be translated based on the location of the hover target
   256      const xOffset = bbox.x;
   257      // An interval here is the width between x values given the fixed dimensions of the line chart
   258      // and the domain of the data
   259      const interval = bbox.width / 5;
   260  
   261      // MouseEnter triggers the tooltip visibility
   262      await triggerEvent(hoverTarget, 'mouseenter');
   263      // MouseMove positions the tooltip and updates the active datum
   264      await triggerEvent(hoverTarget, 'mousemove', {
   265        clientX: xOffset + interval * 1 + 5,
   266      });
   267      assert.equal(findAll('[data-test-chart-tooltip] li').length, 1);
   268      assert.equal(
   269        find('[data-test-chart-tooltip] .label').textContent.trim(),
   270        this.data[1].series
   271      );
   272      assert.equal(
   273        find('[data-test-chart-tooltip] .value').textContent.trim(),
   274        series2.find((d) => d.x === 2).y
   275      );
   276  
   277      // When the mouse falls between points and each series has points with different x values,
   278      // points will only be shown in the tooltip if they are close enough to the closest point
   279      // to the cursor.
   280      // This event is intentionally between points such that both points are within proximity.
   281      const expected = [
   282        { label: this.data[0].series, value: series1.find((d) => d.x === 3).y },
   283        { label: this.data[1].series, value: series2.find((d) => d.x === 2).y },
   284      ];
   285      await triggerEvent(hoverTarget, 'mousemove', {
   286        clientX: xOffset + interval * 1.5 + 5,
   287      });
   288      assert.equal(findAll('[data-test-chart-tooltip] li').length, 2);
   289      findAll('[data-test-chart-tooltip] li').forEach((tooltipEntry, index) => {
   290        assert.equal(
   291          tooltipEntry.querySelector('.label').textContent.trim(),
   292          expected[index].label
   293        );
   294        assert.equal(
   295          tooltipEntry.querySelector('.value').textContent.trim(),
   296          expected[index].value
   297        );
   298      });
   299    });
   300  });