diff options
author | Dan J Miller <danjm.com@gmail.com> | 2018-12-05 00:32:08 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-05 00:32:08 +0800 |
commit | d1996509deccd98d7ffa4cc360dc96c5392879d3 (patch) | |
tree | c8d9dbf1a3b393573f51af2563cdc3f589b1a162 /ui/app/components/gas-customization/gas-price-chart | |
parent | 35670e926116b19e66931dace838d785adffac09 (diff) | |
parent | 8194309a9a7319bcebd6761a4596c208375adfab (diff) | |
download | tangerine-wallet-browser-d1996509deccd98d7ffa4cc360dc96c5392879d3.tar.gz tangerine-wallet-browser-d1996509deccd98d7ffa4cc360dc96c5392879d3.tar.zst tangerine-wallet-browser-d1996509deccd98d7ffa4cc360dc96c5392879d3.zip |
Merge pull request #5704 from MetaMask/new-gas-customize-feature-branch-d
Gas customization features
Diffstat (limited to 'ui/app/components/gas-customization/gas-price-chart')
5 files changed, 813 insertions, 0 deletions
diff --git a/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js new file mode 100644 index 000000000..d4c67bbde --- /dev/null +++ b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js @@ -0,0 +1,108 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import * as d3 from 'd3' +import { + generateChart, + getCoordinateData, + handleChartUpdate, + hideDataUI, + setTickPosition, + handleMouseMove, +} from './gas-price-chart.utils.js' + +export default class GasPriceChart extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + gasPrices: PropTypes.array, + estimatedTimes: PropTypes.array, + gasPricesMax: PropTypes.number, + estimatedTimesMax: PropTypes.number, + currentPrice: PropTypes.number, + updateCustomGasPrice: PropTypes.func, + } + + renderChart ({ + currentPrice, + gasPrices, + estimatedTimes, + gasPricesMax, + estimatedTimesMax, + updateCustomGasPrice, + }) { + const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax) + setTimeout(function () { + setTickPosition('y', 0, -5, 8) + setTickPosition('y', 1, -3, -5) + setTickPosition('x', 0, 3) + setTickPosition('x', 1, 3, -8) + + const { x: domainX } = getCoordinateData('.domain') + const { x: yAxisX } = getCoordinateData('.c3-axis-y-label') + const { x: tickX } = getCoordinateData('.c3-axis-x .tick') + + d3.select('.c3-axis-x .tick').attr('transform', 'translate(' + (domainX - tickX) / 2 + ', 0)') + d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)') + d3.select('.c3-axis-y-label').attr('transform', 'translate(' + (domainX - yAxisX - 12) + ', 2) rotate(-90)') + d3.select('.c3-xgrid-focus line').attr('y2', 98) + + d3.select('.c3-chart').on('mouseout', () => { + hideDataUI(chart, '#overlayed-circle') + }) + + d3.select('.c3-chart').on('click', () => { + const { x: newGasPrice } = d3.select('#overlayed-circle').datum() + updateCustomGasPrice(newGasPrice) + }) + + const { x: chartXStart, width: chartWidth } = getCoordinateData('.c3-areas-data1') + + handleChartUpdate({ + chart, + gasPrices, + newPrice: currentPrice, + cssId: '#set-circle', + }) + + d3.select('.c3-chart').on('mousemove', function () { + handleMouseMove({ + xMousePos: d3.event.clientX, + chartXStart, + chartWidth, + gasPrices, + estimatedTimes, + chart, + }) + }) + }, 0) + + this.chart = chart + } + + componentDidUpdate (prevProps) { + const { gasPrices, currentPrice: newPrice } = this.props + + if (prevProps.currentPrice !== newPrice) { + handleChartUpdate({ + chart: this.chart, + gasPrices, + newPrice, + cssId: '#set-circle', + }) + } + } + + componentDidMount () { + this.renderChart(this.props) + } + + render () { + return ( + <div className="gas-price-chart" id="container"> + <div className="gas-price-chart__root" id="chart"></div> + </div> + ) + } +} diff --git a/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js new file mode 100644 index 000000000..f19dafcc1 --- /dev/null +++ b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -0,0 +1,354 @@ +import * as d3 from 'd3' +import c3 from 'c3' +import BigNumber from 'bignumber.js' + +const newBigSigDig = n => (new BigNumber(n.toPrecision(15))) +const createOp = (a, b, op) => (newBigSigDig(a))[op](newBigSigDig(b)) +const bigNumMinus = (a = 0, b = 0) => createOp(a, b, 'minus') +const bigNumDiv = (a = 0, b = 1) => createOp(a, b, 'div') + +export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes, chart }) { + const { currentPosValue, newTimeEstimate } = getNewXandTimeEstimate({ + xMousePos, + chartXStart, + chartWidth, + gasPrices, + estimatedTimes, + }) + + if (currentPosValue === null && newTimeEstimate === null) { + hideDataUI(chart, '#overlayed-circle') + return + } + + const indexOfNewCircle = estimatedTimes.length + 1 + const dataUIObj = generateDataUIObj(currentPosValue, indexOfNewCircle, newTimeEstimate) + + chart.internal.overlayPoint(dataUIObj, indexOfNewCircle) + chart.internal.showTooltip([dataUIObj], d3.select('.c3-areas-data1')._groups[0]) + chart.internal.showXGridFocus([dataUIObj]) +} + +export function getCoordinateData (selector) { + const node = d3.select(selector).node() + return node ? node.getBoundingClientRect() : {} +} + +export function generateDataUIObj (x, index, value) { + return { + x, + value, + index, + id: 'data1', + name: 'data1', + } +} + +export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) { + const { + closestLowerValueIndex, + closestLowerValue, + closestHigherValueIndex, + closestHigherValue, + } = getAdjacentGasPrices({ gasPrices, priceToPosition: newPrice }) + + if (closestLowerValue && closestHigherValue) { + setSelectedCircle({ + chart, + newPrice, + closestLowerValueIndex, + closestLowerValue, + closestHigherValueIndex, + closestHigherValue, + }) + } else { + hideDataUI(chart, cssId) + } +} + +export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) { + const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition) + const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => e > priceToPosition) + return { + closestLowerValueIndex, + closestHigherValueIndex, + closestHigherValue: gasPrices[closestHigherValueIndex], + closestLowerValue: gasPrices[closestLowerValueIndex], + } +} + +export function extrapolateY ({ higherY = 0, lowerY = 0, higherX = 0, lowerX = 0, xForExtrapolation = 0 }) { + const slope = bigNumMinus(higherY, lowerY).div(bigNumMinus(higherX, lowerX)) + const newTimeEstimate = slope.times(bigNumMinus(higherX, xForExtrapolation)).minus(newBigSigDig(higherY)).negated() + + return newTimeEstimate.toNumber() +} + + +export function getNewXandTimeEstimate ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes }) { + const chartMouseXPos = bigNumMinus(xMousePos, chartXStart) + const posPercentile = bigNumDiv(chartMouseXPos, chartWidth) + + const currentPosValue = (bigNumMinus(gasPrices[gasPrices.length - 1], gasPrices[0])) + .times(newBigSigDig(posPercentile)) + .plus(newBigSigDig(gasPrices[0])) + .toNumber() + + const { + closestLowerValueIndex, + closestLowerValue, + closestHigherValueIndex, + closestHigherValue, + } = getAdjacentGasPrices({ gasPrices, priceToPosition: currentPosValue }) + + return !closestHigherValue || !closestLowerValue + ? { + currentPosValue: null, + newTimeEstimate: null, + } + : { + currentPosValue, + newTimeEstimate: extrapolateY({ + higherY: estimatedTimes[closestHigherValueIndex], + lowerY: estimatedTimes[closestLowerValueIndex], + higherX: closestHigherValue, + lowerX: closestLowerValue, + xForExtrapolation: currentPosValue, + }), + } +} + +export function hideDataUI (chart, dataNodeId) { + const overLayedCircle = d3.select(dataNodeId) + if (!overLayedCircle.empty()) { + overLayedCircle.remove() + } + d3.select('.c3-tooltip-container').style('display', 'none !important') + chart.internal.hideXGridFocus() +} + +export function setTickPosition (axis, n, newPosition, secondNewPosition) { + const positionToShift = axis === 'y' ? 'x' : 'y' + const secondPositionToShift = axis === 'y' ? 'y' : 'x' + d3.select('#chart') + .select(`.c3-axis-${axis}`) + .selectAll('.tick') + .filter((d, i) => i === n) + .select('text') + .attr(positionToShift, 0) + .select('tspan') + .attr(positionToShift, newPosition) + .attr(secondPositionToShift, secondNewPosition || 0) + .style('visibility', 'visible') +} + +export function appendOrUpdateCircle ({ data, itemIndex, cx, cy, cssId, appendOnly }) { + const circle = this.main + .select('.c3-selected-circles' + this.getTargetSelectorSuffix(data.id)) + .selectAll(`.c3-selected-circle-${itemIndex}`) + + if (appendOnly || circle.empty()) { + circle.data([data]) + .enter().append('circle') + .attr('class', () => this.generateClass('c3-selected-circle', itemIndex)) + .attr('id', cssId) + .attr('cx', cx) + .attr('cy', cy) + .attr('stroke', () => this.color(data)) + .attr('r', 6) + } else { + circle.data([data]) + .attr('cx', cx) + .attr('cy', cy) + } +} + +export function setSelectedCircle ({ + chart, + newPrice, + closestLowerValueIndex, + closestLowerValue, + closestHigherValueIndex, + closestHigherValue, +}) { + const numberOfValues = chart.internal.data.xs.data1.length + + const { x: lowerX, y: lowerY } = getCoordinateData(`.c3-circle-${closestLowerValueIndex}`) + let { x: higherX, y: higherY } = getCoordinateData(`.c3-circle-${closestHigherValueIndex}`) + let count = closestHigherValueIndex + 1 + + if (lowerX && higherX) { + while (lowerX === higherX) { + higherX = getCoordinateData(`.c3-circle-${count}`).x + higherY = getCoordinateData(`.c3-circle-${count}`).y + count++ + } + } + + const currentX = bigNumMinus(higherX, lowerX) + .times(bigNumMinus(newPrice, closestLowerValue)) + .div(bigNumMinus(closestHigherValue, closestLowerValue)) + .plus(newBigSigDig(lowerX)) + + const newTimeEstimate = extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX }) + + chart.internal.selectPoint( + generateDataUIObj(currentX.toNumber(), numberOfValues, newTimeEstimate), + numberOfValues + ) +} + + +export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax) { + const gasPricesMaxPadded = gasPricesMax + 1 + const chart = c3.generate({ + size: { + height: 165, + }, + transition: { + duration: 0, + }, + padding: {left: 20, right: 15, top: 6, bottom: 10}, + data: { + x: 'x', + columns: [ + ['x', ...gasPrices], + ['data1', ...estimatedTimes], + ], + types: { + data1: 'area', + }, + selection: { + enabled: false, + }, + }, + color: { + data1: '#259de5', + }, + axis: { + x: { + min: gasPrices[0], + max: gasPricesMax, + tick: { + values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)], + outer: false, + format: function (val) { return val + ' GWEI' }, + }, + padding: {left: gasPricesMax / 50, right: gasPricesMax / 50}, + label: { + text: 'Gas Price ($)', + position: 'outer-center', + }, + }, + y: { + padding: {top: 7, bottom: 7}, + tick: { + values: [Math.floor(estimatedTimesMax * 0.05), Math.ceil(estimatedTimesMax * 0.97)], + outer: false, + }, + label: { + text: 'Confirmation time (sec)', + position: 'outer-middle', + }, + min: 0, + }, + }, + legend: { + show: false, + }, + grid: { + x: {}, + lines: { + front: false, + }, + }, + point: { + focus: { + expand: { + enabled: false, + r: 3.5, + }, + }, + }, + tooltip: { + format: { + title: (v) => v.toPrecision(4), + }, + contents: function (d) { + const titleFormat = this.config.tooltip_format_title + let text + d.forEach(el => { + if (el && (el.value || el.value === 0) && !text) { + text = "<table class='" + 'custom-tooltip' + "'>" + "<tr><th colspan='2'>" + titleFormat(el.x) + '</th></tr>' + } + }) + return text + '</table>' + "<div class='tooltip-arrow'></div>" + }, + position: function (data) { + if (d3.select('#overlayed-circle').empty()) { + return { top: -100, left: -100 } + } + + const { x: circleX, y: circleY, width: circleWidth } = getCoordinateData('#overlayed-circle') + const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-chart') + + // TODO: Confirm the below constants work with all data sets and screen sizes + const flipTooltip = circleY - circleWidth < chartYStart + 5 + + d3 + .select('.tooltip-arrow') + .style('margin-top', flipTooltip ? '-16px' : '4px') + + return { + top: bigNumMinus(circleY, chartYStart).minus(19).plus(flipTooltip ? circleWidth + 38 : 0).toNumber(), + left: bigNumMinus(circleX, chartXStart).plus(newBigSigDig(circleWidth)).minus(bigNumDiv(gasPricesMaxPadded, 50)).toNumber(), + } + }, + show: true, + }, + }) + + chart.internal.selectPoint = function (data, itemIndex = (data.index || 0)) { + const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-areas-data1') + + d3.select('#set-circle').remove() + + appendOrUpdateCircle.bind(this)({ + data, + itemIndex, + cx: () => bigNumMinus(data.x, chartXStart).plus(11).toNumber(), + cy: () => bigNumMinus(data.value, chartYStart).plus(10).toNumber(), + cssId: 'set-circle', + appendOnly: true, + }) + } + + chart.internal.overlayPoint = function (data, itemIndex) { + appendOrUpdateCircle.bind(this)({ + data, + itemIndex, + cx: this.circleX.bind(this), + cy: this.circleY.bind(this), + cssId: 'overlayed-circle', + }) + } + + chart.internal.showTooltip = function (selectedData, element) { + const dataToShow = selectedData.filter((d) => d && (d.value || d.value === 0)) + + if (dataToShow.length) { + this.tooltip.html( + this.config.tooltip_contents.call(this, selectedData, this.axis.getXAxisTickFormat(), this.getYFormat(), this.color) + ).style('display', 'flex') + + // Get tooltip dimensions + const tWidth = this.tooltip.property('offsetWidth') + const tHeight = this.tooltip.property('offsetHeight') + const position = this.config.tooltip_position.call(this, dataToShow, tWidth, tHeight, element) + // Set tooltip + this.tooltip.style('top', position.top + 'px').style('left', position.left + 'px') + } + } + + return chart +} diff --git a/ui/app/components/gas-customization/gas-price-chart/index.js b/ui/app/components/gas-customization/gas-price-chart/index.js new file mode 100644 index 000000000..9895acb62 --- /dev/null +++ b/ui/app/components/gas-customization/gas-price-chart/index.js @@ -0,0 +1 @@ +export { default } from './gas-price-chart.component' diff --git a/ui/app/components/gas-customization/gas-price-chart/index.scss b/ui/app/components/gas-customization/gas-price-chart/index.scss new file mode 100644 index 000000000..097543104 --- /dev/null +++ b/ui/app/components/gas-customization/gas-price-chart/index.scss @@ -0,0 +1,132 @@ +.gas-price-chart { + display: flex; + position: relative; + justify-content: center; + + &__root { + max-height: 154px; + max-width: 391px; + position: relative; + overflow: hidden; + + @media screen and (max-width: $break-small) { + max-width: 326px; + } + } + + .tick text, .c3-axis-x-label, .c3-axis-y-label { + font-family: Roboto; + font-style: normal; + font-weight: bold; + line-height: normal; + font-size: 8px; + text-align: center; + fill: #9A9CA6 !important; + } + + .c3-tooltip-container { + display: flex; + justify-content: center !important; + align-items: flex-end !important; + } + + .custom-tooltip { + background: rgba(0, 0, 0, 1); + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 3px; + opacity: 1 !important; + height: 21px; + z-index: 1; + } + + .tooltip-arrow { + background: black; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.5); + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + opacity: 1 !important; + width: 9px; + height: 9px; + margin-top: 4px; + } + + .custom-tooltip th { + font-family: Roboto; + font-style: normal; + font-weight: 500; + line-height: normal; + font-size: 10px; + text-align: center; + padding: 3px; + color: #FFFFFF; + } + + .c3-circle { + visibility: hidden; + } + + .c3-selected-circle, .c3-circle._expanded_ { + fill: #FFFFFF !important; + stroke-width: 2.4px !important; + stroke: #2d9fd9 !important; + /* visibility: visible; */ + } + + #set-circle { + fill: #313A5E !important; + stroke: #313A5E !important; + } + + .c3-axis-x-label, .c3-axis-y-label { + font-weight: normal; + } + + .tick text tspan { + visibility: hidden; + } + + .c3-circle { + fill: #2d9fd9 !important; + } + + .c3-line-data1 { + stroke: #2d9fd9 !important; + background: rgba(0,0,0,0) !important; + color: rgba(0,0,0,0) !important; + } + + .c3 path { + fill: none; + } + + .c3 path.c3-area-data1 { + opacity: 1; + fill: #e9edf1 !important; + } + + .c3-xgrid-line line { + stroke: #B8B8B8 !important; + } + + .c3-xgrid-focus { + stroke: #aaa; + } + + .c3-axis-x .domain { + fill: none; + stroke: none; + } + + .c3-axis-y .domain { + fill: none; + stroke: #C8CCD6; + } + + .c3-event-rect { + cursor: pointer; + } +} + +#chart { + background: #F8F9FB +} diff --git a/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js new file mode 100644 index 000000000..74eddae42 --- /dev/null +++ b/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js @@ -0,0 +1,218 @@ +import React from 'react' +import assert from 'assert' +import proxyquire from 'proxyquire' +import sinon from 'sinon' +import shallow from '../../../../../lib/shallow-with-context' +import * as d3 from 'd3' + +function timeout (time) { + return new Promise((resolve, reject) => { + setTimeout(resolve, time) + }) +} + +const propsMethodSpies = { + updateCustomGasPrice: sinon.spy(), +} + +const selectReturnSpies = { + empty: sinon.spy(), + remove: sinon.spy(), + style: sinon.spy(), + select: d3.select, + attr: sinon.spy(), + on: sinon.spy(), + datum: sinon.stub().returns({ x: 'mockX' }), +} + +const mockSelectReturn = { + ...d3.select('div'), + node: () => ({ + getBoundingClientRect: () => ({ x: 123, y: 321, width: 400 }), + }), + ...selectReturnSpies, +} + +const gasPriceChartUtilsSpies = { + appendOrUpdateCircle: sinon.spy(), + generateChart: sinon.stub().returns({ mockChart: true }), + generateDataUIObj: sinon.spy(), + getAdjacentGasPrices: sinon.spy(), + getCoordinateData: sinon.stub().returns({ x: 'mockCoordinateX', width: 'mockWidth' }), + getNewXandTimeEstimate: sinon.spy(), + handleChartUpdate: sinon.spy(), + hideDataUI: sinon.spy(), + setSelectedCircle: sinon.spy(), + setTickPosition: sinon.spy(), + handleMouseMove: sinon.spy(), +} + +const testProps = { + gasPrices: [1.5, 2.5, 4, 8], + estimatedTimes: [100, 80, 40, 10], + gasPricesMax: 9, + estimatedTimesMax: '100', + currentPrice: 6, + updateCustomGasPrice: propsMethodSpies.updateCustomGasPrice, +} + +const GasPriceChart = proxyquire('../gas-price-chart.component.js', { + './gas-price-chart.utils.js': gasPriceChartUtilsSpies, + 'd3': { + ...d3, + select: function (...args) { + const result = d3.select(...args) + return result.empty() + ? mockSelectReturn + : result + }, + event: { + clientX: 'mockClientX', + }, + }, +}).default + +sinon.spy(GasPriceChart.prototype, 'renderChart') + +describe('GasPriceChart Component', function () { + let wrapper + + beforeEach(() => { + wrapper = shallow(<GasPriceChart {...testProps} />) + }) + + describe('render()', () => { + it('should render', () => { + assert(wrapper.hasClass('gas-price-chart')) + }) + + it('should render the chart div', () => { + assert(wrapper.childAt(0).hasClass('gas-price-chart__root')) + assert.equal(wrapper.childAt(0).props().id, 'chart') + }) + }) + + describe('componentDidMount', () => { + it('should call this.renderChart with the components props', () => { + assert(GasPriceChart.prototype.renderChart.callCount, 1) + wrapper.instance().componentDidMount() + assert(GasPriceChart.prototype.renderChart.callCount, 2) + assert.deepEqual(GasPriceChart.prototype.renderChart.getCall(1).args, [{...testProps}]) + }) + }) + + describe('componentDidUpdate', () => { + it('should call handleChartUpdate if props.currentPrice has changed', () => { + gasPriceChartUtilsSpies.handleChartUpdate.resetHistory() + wrapper.instance().componentDidUpdate({ currentPrice: 7 }) + assert.equal(gasPriceChartUtilsSpies.handleChartUpdate.callCount, 1) + }) + + it('should call handleChartUpdate with the correct props', () => { + gasPriceChartUtilsSpies.handleChartUpdate.resetHistory() + wrapper.instance().componentDidUpdate({ currentPrice: 7 }) + assert.deepEqual(gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, [{ + chart: { mockChart: true }, + gasPrices: [1.5, 2.5, 4, 8], + newPrice: 6, + cssId: '#set-circle', + }]) + }) + + it('should not call handleChartUpdate if props.currentPrice has not changed', () => { + gasPriceChartUtilsSpies.handleChartUpdate.resetHistory() + wrapper.instance().componentDidUpdate({ currentPrice: 6 }) + assert.equal(gasPriceChartUtilsSpies.handleChartUpdate.callCount, 0) + }) + }) + + describe('renderChart', () => { + it('should call setTickPosition 4 times, with the expected props', async () => { + await timeout(0) + gasPriceChartUtilsSpies.setTickPosition.resetHistory() + assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 0) + wrapper.instance().renderChart(testProps) + await timeout(0) + assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 4) + assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(0).args, ['y', 0, -5, 8]) + assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(1).args, ['y', 1, -3, -5]) + assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(2).args, ['x', 0, 3]) + assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(3).args, ['x', 1, 3, -8]) + }) + + it('should call handleChartUpdate with the correct props', async () => { + await timeout(0) + gasPriceChartUtilsSpies.handleChartUpdate.resetHistory() + wrapper.instance().renderChart(testProps) + await timeout(0) + assert.deepEqual(gasPriceChartUtilsSpies.handleChartUpdate.getCall(0).args, [{ + chart: { mockChart: true }, + gasPrices: [1.5, 2.5, 4, 8], + newPrice: 6, + cssId: '#set-circle', + }]) + }) + + it('should add three events to the chart', async () => { + await timeout(0) + selectReturnSpies.on.resetHistory() + assert.equal(selectReturnSpies.on.callCount, 0) + wrapper.instance().renderChart(testProps) + await timeout(0) + assert.equal(selectReturnSpies.on.callCount, 3) + + const firstOnEventArgs = selectReturnSpies.on.getCall(0).args + assert.equal(firstOnEventArgs[0], 'mouseout') + const secondOnEventArgs = selectReturnSpies.on.getCall(1).args + assert.equal(secondOnEventArgs[0], 'click') + const thirdOnEventArgs = selectReturnSpies.on.getCall(2).args + assert.equal(thirdOnEventArgs[0], 'mousemove') + }) + + it('should hide the data UI on mouseout', async () => { + await timeout(0) + selectReturnSpies.on.resetHistory() + wrapper.instance().renderChart(testProps) + gasPriceChartUtilsSpies.hideDataUI.resetHistory() + await timeout(0) + const mouseoutEventArgs = selectReturnSpies.on.getCall(0).args + assert.equal(gasPriceChartUtilsSpies.hideDataUI.callCount, 0) + mouseoutEventArgs[1]() + assert.equal(gasPriceChartUtilsSpies.hideDataUI.callCount, 1) + assert.deepEqual(gasPriceChartUtilsSpies.hideDataUI.getCall(0).args, [{ mockChart: true }, '#overlayed-circle']) + }) + + it('should updateCustomGasPrice on click', async () => { + await timeout(0) + selectReturnSpies.on.resetHistory() + wrapper.instance().renderChart(testProps) + propsMethodSpies.updateCustomGasPrice.resetHistory() + await timeout(0) + const mouseoutEventArgs = selectReturnSpies.on.getCall(1).args + assert.equal(propsMethodSpies.updateCustomGasPrice.callCount, 0) + mouseoutEventArgs[1]() + assert.equal(propsMethodSpies.updateCustomGasPrice.callCount, 1) + assert.equal(propsMethodSpies.updateCustomGasPrice.getCall(0).args[0], 'mockX') + }) + + it('should handle mousemove', async () => { + await timeout(0) + selectReturnSpies.on.resetHistory() + wrapper.instance().renderChart(testProps) + gasPriceChartUtilsSpies.handleMouseMove.resetHistory() + await timeout(0) + const mouseoutEventArgs = selectReturnSpies.on.getCall(2).args + assert.equal(gasPriceChartUtilsSpies.handleMouseMove.callCount, 0) + mouseoutEventArgs[1]() + assert.equal(gasPriceChartUtilsSpies.handleMouseMove.callCount, 1) + assert.deepEqual(gasPriceChartUtilsSpies.handleMouseMove.getCall(0).args, [{ + xMousePos: 'mockClientX', + chartXStart: 'mockCoordinateX', + chartWidth: 'mockWidth', + gasPrices: testProps.gasPrices, + estimatedTimes: testProps.estimatedTimes, + chart: { mockChart: true }, + }]) + }) + }) +}) |