import { d3, setMouseEvent, initChart } from './c3-helper' describe('c3 chart interaction', function() { 'use strict' var chart, args const moveMouseOut = () => setMouseEvent(chart, 'mouseout', 0, 0, d3.select('.c3-event-rect').node()) const moveMouse = (x = 0, y = 0) => setMouseEvent(chart, 'mousemove', x, y, d3.select('.c3-event-rect').node()) const clickMouse = (x = 0, y = 0) => setMouseEvent(chart, 'click', x, y, d3.select('.c3-event-rect').node()) beforeEach(function(done) { chart = initChart(chart, args, done) }) describe('generate event rects', function() { describe('custom x', function() { beforeAll(function() { args = { data: { x: 'x', columns: [ ['x', 0, 1000, 3000, 10000], ['data', 10, 10, 10, 10] ], type: 'bar' } } }) it('should have only 1 event rect properly', function() { var eventRects = d3.selectAll('.c3-event-rect') expect(eventRects.size()).toBe(1) eventRects.each(function() { var box = (d3.select(this).node() as any).getBoundingClientRect() expect(box.left).toBeCloseTo(40.5, -2) expect(box.width).toBeCloseTo(598, -2) }) }) describe('mouseover', function() { let mouseoutCounter = 0 let mouseoverCounter = 0 beforeAll(function() { args = { data: { columns: [ ['data1', 30, 200, 100, 400, -150, 250], ['data2', 50, 20, 10, 40, 15, 25], ['data3', -150, 120, 110, 140, 115, 125] ], type: 'bar', onmouseout: function() { mouseoutCounter += 1 }, onmouseover: function() { mouseoverCounter += 1 } }, axis: { rotated: false } } }) beforeEach(function() { mouseoverCounter = 0 mouseoutCounter = 0 }) it('should be undefined when not within bar', function() { moveMouseOut() expect(mouseoutCounter).toEqual(0) expect(mouseoverCounter).toEqual(0) expect(chart.internal.mouseover).toBeUndefined() }) it('should be data value when within bar', function() { moveMouse(31, 280) expect(mouseoutCounter).toEqual(0) expect(mouseoverCounter).toEqual(1) expect(chart.internal.mouseover).toEqual({ x: 0, value: 30, index: 0, id: 'data1', name: 'data1' }) }) it('should be undefined after leaving chart', function() { moveMouse(31, 280) moveMouseOut() expect(mouseoutCounter).toEqual(1) expect(mouseoverCounter).toEqual(1) expect(chart.internal.mouseover).toBeUndefined() }) it('should retrigger mouseover event when returning to same value', function() { moveMouse(31, 280) moveMouseOut() moveMouse(31, 280) expect(mouseoutCounter).toEqual(1) expect(mouseoverCounter).toEqual(2) expect(chart.internal.mouseover).toEqual({ x: 0, value: 30, index: 0, id: 'data1', name: 'data1' }) }) }) describe('should generate bar chart with only one data', function() { beforeAll(function() { args = { data: { x: 'x', columns: [ ['x', 0], ['data', 10] ], type: 'bar' } } }) it('should have 1 event rects properly', function() { var eventRects = d3.selectAll('.c3-event-rect') expect(eventRects.size()).toBe(1) eventRects.each(function() { var box = (d3.select(this).node() as any).getBoundingClientRect() expect(box.left).toBeCloseTo(40.5, -2) expect(box.width).toBeCloseTo(598, -2) }) }) }) }) describe('timeseries', function() { beforeAll(function() { args = { data: { x: 'x', columns: [ ['x', '20140101', '20140201', '20140210', '20140301'], ['data', 10, 10, 10, 10] ] } } }) it('should have only 1 event rect properly', function() { var eventRects = d3.selectAll('.c3-event-rect') expect(eventRects.size()).toBe(1) eventRects.each(function() { var box = (d3.select(this).node() as any).getBoundingClientRect() expect(box.left).toBeCloseTo(40.5, -2) expect(box.width).toBeCloseTo(598, -2) }) }) describe('should generate line chart with only 1 data timeseries', function() { beforeAll(function() { args = { data: { x: 'x', columns: [ ['x', '20140101'], ['data', 10] ] } } }) it('should have 1 event rects properly', function() { var eventRects = d3.selectAll('.c3-event-rect') expect(eventRects.size()).toBe(1) eventRects.each(function() { var box = (d3.select(this).node() as any).getBoundingClientRect() expect(box.left).toBeCloseTo(40.5, -2) expect(box.width).toBeCloseTo(598, -2) }) }) }) }) }) describe('bar chart', function() { describe('tooltip_grouped=true', function() { beforeAll(() => { args = { data: { columns: [ ['data1', 30, 200, 200, 400, 150, -250], ['data2', 130, -100, 100, 200, 150, 50], ['data3', 230, -200, 200, 0, 250, 250] ], type: 'bar', groups: [['data1', 'data2']], hide: ['data1'] }, tooltip: { grouped: true }, axis: { x: { type: 'category' }, rotated: true }, interaction: { enabled: true } } }) it('generate a single rect', () => { const eventRectList = d3.selectAll('.c3-event-rect') expect(eventRectList.size()).toBe(1) expect(eventRectList.attr('x')).toEqual('0') expect(eventRectList.attr('y')).toEqual('0') expect(eventRectList.attr('height')).toEqual('' + chart.internal.height) expect(eventRectList.attr('width')).toEqual('' + chart.internal.width) }) it('shows tooltip with visible data of currently hovered category', () => { moveMouse(20, 20) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') const tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(3) // header + data[123] expect(tooltipData[1].querySelector('.name').textContent).toBe('data3') expect(tooltipData[2].querySelector('.name').textContent).toBe('data2') }) it('shows cursor:pointer only if hovering bar', () => { const eventRect = d3.select('.c3-event-rect') moveMouse(1, 1) expect(eventRect.style('cursor')).toEqual('auto') moveMouse(360, 48) expect(eventRect.style('cursor')).toEqual('pointer') moveMouse(1, 1) expect(eventRect.style('cursor')).toEqual('auto') }) it('expands all bars of currently hovered category', () => { moveMouse(20, 20) const barList = d3.selectAll('.c3-bar') expect(barList.size()).toBeGreaterThan(0) barList.each(function() { if ( (this as any).classList.contains('c3-bar-0') && !(this as any).parentElement.classList.contains('c3-bars-data1') ) { expect((this as any).classList.contains('_expanded_')).toBeTruthy() } else { expect((this as any).classList.contains('_expanded_')).toBeFalsy() } }) moveMouse(20, 170) barList.each(function() { if ( (this as any).classList.contains('c3-bar-2') && !(this as any).parentElement.classList.contains('c3-bars-data1') ) { expect((this as any).classList.contains('_expanded_')).toBeTruthy() } else { expect((this as any).classList.contains('_expanded_')).toBeFalsy() } }) }) }) describe('tooltip_grouped=false', function() { beforeAll(() => { args = { data: { columns: [ ['data1', 30, 200, 200, 400, 150, -250], ['data2', 130, -100, 100, 200, 150, 50], ['data3', 230, -200, 200, 0, 250, 250] ], type: 'bar', groups: [['data1', 'data2']] }, tooltip: { grouped: false }, axis: { x: { type: 'category' } }, interaction: { enabled: true } } }) it('generate a single rect', () => { const eventRectList = d3.selectAll('.c3-event-rect') expect(eventRectList.size()).toBe(1) expect(eventRectList.attr('x')).toEqual('0') expect(eventRectList.attr('y')).toEqual('0') expect(eventRectList.attr('height')).toEqual('' + chart.internal.height) expect(eventRectList.attr('width')).toEqual('' + chart.internal.width) }) it('shows tooltip with only hovered data', () => { moveMouse(1, 1) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('none') moveMouse(35, 268) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') const tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(2) // header + data2 expect(tooltipData[1].querySelector('.name').textContent).toBe('data2') expect(tooltipData[1].querySelector('.value').textContent).toBe('130') }) it('expands only hovered bar', () => { moveMouse(20, 20) const barList = d3.selectAll('.c3-bar') expect(barList.size()).toBeGreaterThan(0) // nothing expanded barList.each(function() { expect((this as any).classList.contains('_expanded_')).toBeFalsy() }) moveMouse(38, 258) barList.each(function() { if ( (this as any).classList.contains('c3-bar-0') && (this as any).parentElement.classList.contains('c3-bars-data2') ) { expect((this as any).classList.contains('_expanded_')).toBeTruthy() } else { expect((this as any).classList.contains('_expanded_')).toBeFalsy() } }) }) }) }) describe('line chart', function() { describe('tooltip_grouped=false', function() { let clickedData = [] beforeAll(() => { args = { data: { columns: [ ['data1', 30, 200, 200, 400, 150, -250], ['data2', 130, -100, 100, 200, 150, 50], ['data3', 230, -200, 200, 0, 250, 250] ], type: 'line', groups: [['data1', 'data2']], onclick: function(d) { clickedData.push(d) } }, tooltip: { grouped: false }, axis: { x: { type: 'category' } }, interaction: { enabled: true }, point: { r: 2, sensitivity: 10, focus: { expand: { enabled: true, r: 8 } } } } }) beforeEach(function() { clickedData = [] }) it('shows tooltip with only hovered data', () => { moveMouse(1, 1) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('none') moveMouse(48, 184) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') const tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(2) // header + data3 expect(tooltipData[1].querySelector('.name').textContent).toBe('data3') expect(tooltipData[1].querySelector('.value').textContent).toBe('230') }) it('expands only hovered point', () => { moveMouse(1, 1) const circleList = d3.selectAll('.c3-circle') expect(circleList.size()).toBeGreaterThan(0) // nothing expanded circleList.each(function() { expect((this as any).classList.contains('_expanded_')).toBeFalsy() }) moveMouse(45, 233) circleList.each(function() { expect((this as any).classList.contains('_expanded_')).toEqual( (this as any).classList.contains('c3-circle-0') && (this as any).parentElement.classList.contains('c3-circles-data2') ) }) }) it('shows cursor:pointer only if hovering point', () => { const eventRect = d3.select('.c3-event-rect') moveMouse(1, 1) expect(eventRect.style('cursor')).toEqual('auto') moveMouse(49, 219) expect(eventRect.style('cursor')).toEqual('pointer') moveMouse(1, 1) expect(eventRect.style('cursor')).toEqual('auto') }) it('clicks only on hovered point', () => { clickMouse(144, 201) expect(clickedData).toEqual([ { x: 1, index: 1, value: 200, id: 'data1', name: 'data1' } ]) }) describe('with selection enabled', () => { beforeAll(() => { args.data.selection = { enabled: true, isselectable: function(d) { return d.id !== 'data3' } } }) it('can toggle selection', () => { expect(d3.selectAll('.c3-circle _selected_').size()).toEqual(0) clickMouse(144, 201) // index 1 @ data1 expect(d3.selectAll('.c3-circle._selected_').size()).toEqual(1) expect( d3.select('.c3-circles-data1 .c3-circle-1._selected_').size() ).toEqual(1) // data3 is not selectable clickMouse(391, 283) // index 3 @ data3 expect(d3.selectAll('.c3-circle._selected_').size()).toEqual(1) expect( d3.select('.c3-circles-data3 .c3-circle-3._selected_').size() ).toEqual(0) expect( d3.select('.c3-circles-data1 .c3-circle-1._selected_').size() ).toEqual(1) clickMouse(343, 204) // index 3 @ data2 expect(d3.selectAll('.c3-circle._selected_').size()).toEqual(2) expect( d3.select('.c3-circles-data2 .c3-circle-3._selected_').size() ).toEqual(1) expect( d3.select('.c3-circles-data1 .c3-circle-1._selected_').size() ).toEqual(1) clickMouse(144, 201) // index 1 @ data1 expect(d3.selectAll('.c3-circle._selected_').size()).toEqual(1) expect( d3.select('.c3-circles-data2 .c3-circle-3._selected_').size() ).toEqual(1) }) }) describe('with tooltip_horizontal=true', () => { beforeAll(() => { args.tooltip.horizontal = true }) it('can clicks on points', () => { // out of point sensitivity clickMouse(146, 46) clickMouse(343, 263) // click 3 data point clickMouse(147, 370) clickMouse(340, 203) clickMouse(537, 386) expect(clickedData).toEqual([ { x: 1, value: -200, id: 'data3', index: 1, name: 'data3' }, { x: 3, value: 200, id: 'data2', index: 3, name: 'data2' }, { x: 5, value: -250, id: 'data1', index: 5, name: 'data1' } ]) }) it('shows tooltip with only closest data', () => { moveMouse(1, 1) expect( (document.querySelector('.c3-tooltip-container') as any).style .display ).toEqual('none') moveMouse(146, 46) expect( (document.querySelector('.c3-tooltip-container') as any).style .display ).toEqual('block') let tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(2) // header + data1 expect(tooltipData[1].querySelector('.name').textContent).toBe( 'data1' ) expect(tooltipData[1].querySelector('.value').textContent).toBe('200') moveMouse(343, 263) expect( (document.querySelector('.c3-tooltip-container') as any).style .display ).toEqual('block') tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(2) // header + data3 expect(tooltipData[1].querySelector('.name').textContent).toBe( 'data3' ) expect(tooltipData[1].querySelector('.value').textContent).toBe('0') }) }) }) describe('tooltip_grouped=true', function() { let clickedData = [] beforeAll(() => { args = { data: { columns: [ ['data1', 30, 200, 200, 400, 150, -250], ['data2', 130, -100, 100, 200, 150, 50], ['data3', 230, -200, 200, 0, 250, 250] ], type: 'line', groups: [['data1', 'data2']], onclick: function(d) { clickedData.push(d) } }, tooltip: { grouped: true }, axis: { x: { type: 'category' } }, interaction: { enabled: true }, point: { r: 2, sensitivity: 10, focus: { expand: { enabled: true, r: 8 } } } } }) beforeEach(function() { clickedData = [] }) describe('with tooltip_horizontal=true', () => { beforeAll(() => { args.tooltip.horizontal = true }) it('can clicks on points', () => { // out of point sensitivity clickMouse(146, 46) clickMouse(343, 263) // click 3 data point clickMouse(147, 370) clickMouse(340, 203) clickMouse(537, 386) expect(clickedData).toEqual([ { x: 1, value: -200, id: 'data3', index: 1, name: 'data3' }, { x: 3, value: 200, id: 'data2', index: 3, name: 'data2' }, { x: 5, value: -250, id: 'data1', index: 5, name: 'data1' } ]) }) it('shows tooltip with all data', () => { moveMouse(1, 1) expect( (document.querySelector('.c3-tooltip-container') as any).style .display ).toEqual('block') let tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(4) // header + data[123] expect(tooltipData[1].querySelector('.name').textContent).toBe( 'data1' ) expect(tooltipData[1].querySelector('.value').textContent).toBe('30') expect(tooltipData[2].querySelector('.name').textContent).toBe( 'data3' ) expect(tooltipData[2].querySelector('.value').textContent).toBe('230') expect(tooltipData[3].querySelector('.name').textContent).toBe( 'data2' ) expect(tooltipData[3].querySelector('.value').textContent).toBe('130') moveMouse(146, 46) expect( (document.querySelector('.c3-tooltip-container') as any).style .display ).toEqual('block') tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(4) // header + data[123] expect(tooltipData[1].querySelector('.name').textContent).toBe( 'data1' ) expect(tooltipData[1].querySelector('.value').textContent).toBe('200') expect(tooltipData[2].querySelector('.name').textContent).toBe( 'data3' ) expect(tooltipData[2].querySelector('.value').textContent).toBe( '-200' ) expect(tooltipData[3].querySelector('.name').textContent).toBe( 'data2' ) expect(tooltipData[3].querySelector('.value').textContent).toBe( '-100' ) }) }) }) }) describe('line chart (multiple xs)', function() { let clickedData = [] beforeAll(() => { args = { data: { xs: { data1: 'x1', data2: 'x2' }, columns: [ ['x1', 10, 30, 45, 50, 70, 100], ['x2', 30, 50, 75, 100, 120], ['data1', 30, 200, 100, 400, 150, 250], ['data2', 20, 180, 240, 100, 190] ], type: 'line', onclick: function(d) { clickedData.push(d) } }, tooltip: { grouped: true, horizontal: true }, interaction: { enabled: true } } }) beforeEach(function() { clickedData = [] }) it('shows tooltip with all data', () => { moveMouse(1, 1) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') let tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(2) // header + data[1] expect(tooltipData[1].querySelector('.name').textContent).toBe('data1') expect(tooltipData[1].querySelector('.value').textContent).toBe('30') moveMouse(107, 95) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') tooltipData = [...(document.querySelectorAll('.c3-tooltip tr') as any)] expect(tooltipData.length).toBe(3) // header + data[12] expect(tooltipData[1].querySelector('.name').textContent).toBe('data1') expect(tooltipData[1].querySelector('.value').textContent).toBe('200') expect(tooltipData[2].querySelector('.name').textContent).toBe('data2') expect(tooltipData[2].querySelector('.value').textContent).toBe('20') moveMouse(430, 140) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') tooltipData = [...(document.querySelectorAll('.c3-tooltip tr') as any)] expect(tooltipData.length).toBe(3) // header + data[12] expect(tooltipData[1].querySelector('.name').textContent).toBe('data1') expect(tooltipData[1].querySelector('.value').textContent).toBe('250') expect(tooltipData[2].querySelector('.name').textContent).toBe('data2') expect(tooltipData[2].querySelector('.value').textContent).toBe('100') }) }) describe('scatter chart', function() { describe('tooltip_grouped=true', function() { beforeAll(() => { args = { data: { columns: [ ['data1', 30, null, 100, 400, -150, 250], ['data2', 50, 20, 10, 40, 15, 25], ['data3', -150, 120, 110, 140, 115, 125] ], type: 'scatter' }, tooltip: { grouped: true }, interaction: { enabled: true } } }) it('shows tooltip with visible data of currently hovered category', () => { moveMouse(20, 20) let tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(4) // header + data[123] expect(tooltipData[1].querySelector('.name').textContent).toBe('data2') expect(tooltipData[1].querySelector('.value').textContent).toBe('50') expect(tooltipData[2].querySelector('.name').textContent).toBe('data1') expect(tooltipData[2].querySelector('.value').textContent).toBe('30') expect(tooltipData[3].querySelector('.name').textContent).toBe('data3') expect(tooltipData[3].querySelector('.value').textContent).toBe('-150') moveMouse(350, 354) tooltipData = [...(document.querySelectorAll('.c3-tooltip tr') as any)] expect(tooltipData.length).toBe(4) // header + data[123] expect(tooltipData[1].querySelector('.name').textContent).toBe('data1') expect(tooltipData[1].querySelector('.value').textContent).toBe('400') expect(tooltipData[2].querySelector('.name').textContent).toBe('data3') expect(tooltipData[2].querySelector('.value').textContent).toBe('140') expect(tooltipData[3].querySelector('.name').textContent).toBe('data2') expect(tooltipData[3].querySelector('.value').textContent).toBe('40') }) it('shows x grid', () => { moveMouse(20, 20) expect(d3.select('.c3-xgrid-focus').style('visibility')).toBe('visible') }) }) }) describe('area chart (timeseries)', function() { describe('tooltip_grouped=true', function() { beforeAll(() => { args = { data: { x: 'x', columns: [ [ 'x', '2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04', '2018-01-05', '2018-01-06' ], ['data1', 30, 200, 200, 400, 150, 250], ['data2', 130, 100, 100, 200, 150, 50], ['data3', 230, 200, 200, 0, 250, 250] ], type: 'area', groups: [['data1', 'data2', 'data3']] }, tooltip: { grouped: true }, axis: { x: { type: 'timeseries' } }, interaction: { enabled: true } } }) it('shows tooltip with visible data of currently hovered category', () => { moveMouse(20, 20) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') const tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(4) // header + data[123] expect(tooltipData[1].querySelector('.name').textContent).toBe('data1') expect(tooltipData[2].querySelector('.name').textContent).toBe('data3') expect(tooltipData[3].querySelector('.name').textContent).toBe('data2') }) it('shows cursor:pointer only if hovering area', () => { const eventRect = d3.select('.c3-event-rect') moveMouse(1, 1) expect(eventRect.style('cursor')).toEqual('auto') moveMouse(360, 48) expect(eventRect.style('cursor')).toEqual('pointer') moveMouse(1, 1) expect(eventRect.style('cursor')).toEqual('auto') }) }) describe('tooltip_grouped=false', function() { beforeAll(() => { args = { data: { x: 'x', columns: [ [ 'x', '2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04', '2018-01-05', '2018-01-06' ], ['data1', 30, 200, 200, 400, 150, 250], ['data2', 130, 100, 100, 200, 150, 50], ['data3', 230, 200, 200, 0, 250, 250] ], type: 'area', groups: [['data1', 'data2', 'data3']] }, tooltip: { grouped: false }, axis: { x: { type: 'timeseries' }, rotated: false }, interaction: { enabled: true } } }) it('shows tooltip with only hovered data', () => { moveMouse(1, 1) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('none') moveMouse(5, 174) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('block') const tooltipData = [ ...(document.querySelectorAll('.c3-tooltip tr') as any) ] expect(tooltipData.length).toBe(2) // header + data1 expect(tooltipData[1].querySelector('.name').textContent).toBe('data1') expect(tooltipData[1].querySelector('.value').textContent).toBe('30') }) }) }) describe('disabled', function() { beforeAll(() => { args = { data: { columns: [ ['data1', 30, 200, 200, 400, 150, -250], ['data2', 130, -100, 100, 200, 150, 50], ['data3', 230, -200, 200, 0, 250, 250] ], type: 'bar', groups: [['data1', 'data2']] }, axis: { x: { type: 'category' } }, interaction: { enabled: false } } }) it('generate a single rect', () => { const eventRectList = d3.selectAll('.c3-event-rect') expect(eventRectList.size()).toBe(1) expect(eventRectList.attr('x')).toEqual('0') expect(eventRectList.attr('y')).toEqual('0') expect(eventRectList.attr('height')).toEqual('' + chart.internal.height) expect(eventRectList.attr('width')).toEqual('' + chart.internal.width) }) it('does not show tooltip when hovering data', () => { moveMouse(40, 260) expect( (document.querySelector('.c3-tooltip-container') as any).style.display ).toEqual('none') }) it('does not show cursor:pointer when hovering data', () => { moveMouse(40, 260) expect(d3.select('.c3-event-rect').style('cursor')).toEqual('auto') }) }) })