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