From a348b60735f945e3a593256bbd016dc6deee8e36 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 16 Mar 2018 13:02:07 -0230 Subject: Currency input defaults to empty string --- ui/app/components/currency-input.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js index 6f7862e51..940238fa5 100644 --- a/ui/app/components/currency-input.js +++ b/ui/app/components/currency-input.js @@ -8,8 +8,12 @@ inherits(CurrencyInput, Component) function CurrencyInput (props) { Component.call(this) + const sanitizedValue = sanitizeValue(props.value) + this.state = { - value: sanitizeValue(props.value), + value: sanitizedValue, + emptyState: false, + focused: false, } } @@ -58,9 +62,11 @@ CurrencyInput.prototype.handleChange = function (newValue) { if (value === '0' && newValue[newValueLastIndex] === '0') { parsedValue = parsedValue.slice(0, newValueLastIndex) } - const sanitizedValue = sanitizeValue(parsedValue) - this.setState({ value: sanitizedValue }) + this.setState({ + value: sanitizedValue, + emptyState: newValue === '' && sanitizedValue === '0', + }) onInputChange(sanitizedValue) } @@ -86,17 +92,19 @@ CurrencyInput.prototype.render = function () { readOnly, inputRef, } = this.props + const { emptyState, focused } = this.state const inputSizeMultiplier = readOnly ? 1 : 1.2 const valueToRender = this.getValueToRender() - return h('input', { className, - value: valueToRender, - placeholder, + value: emptyState ? '' : valueToRender, + placeholder: focused ? '' : placeholder, size: valueToRender.length * inputSizeMultiplier, readOnly, + onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }), + onBlur: () => this.setState({ focused: false, emptyState: false }), onChange: e => this.handleChange(e.target.value), ref: inputRef, }) -- cgit From 62a95139e6cd9de66941ddb037ec682af568a30b Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 19 Mar 2018 12:36:09 -0230 Subject: Use customized react-trigger-change in send-new-ui integration tests.. --- test/integration/lib/send-new-ui.js | 2 +- test/lib/react-trigger-change.js | 161 ++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 test/lib/react-trigger-change.js diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index faab10fdf..f0a378823 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -1,4 +1,4 @@ -const reactTriggerChange = require('react-trigger-change') +const reactTriggerChange = require('../../lib/react-trigger-change') const { timeout, queryAsync, diff --git a/test/lib/react-trigger-change.js b/test/lib/react-trigger-change.js new file mode 100644 index 000000000..a25ddff00 --- /dev/null +++ b/test/lib/react-trigger-change.js @@ -0,0 +1,161 @@ +// Trigger React's synthetic change events on input, textarea and select elements +// https://github.com/vitalyq/react-trigger-change + +/******************IMPORTANT NOTE******************/ +/* This file is a modification of the */ +/* 'react-trigger-change' library linked above. */ +/* That library breaks when 'onFocus' events are */ +/* added to components under test because it */ +/* dispatches focus events to ensure changes are */ +/* triggered in some versions of IE. */ +/* This modification removes the accomodations */ +/* 'react-trigger-change' makes for IE to ensure */ +/* our tests can pass in chrome and firefox. */ +/**************************************************/ + +'use strict'; + +// Constants and functions are declared inside the closure. +// In this way, reactTriggerChange can be passed directly to executeScript in Selenium. +module.exports = function reactTriggerChange(node) { + var supportedInputTypes = { + color: true, + date: true, + datetime: true, + 'datetime-local': true, + email: true, + month: true, + number: true, + password: true, + range: true, + search: true, + tel: true, + text: true, + time: true, + url: true, + week: true + }; + var nodeName = node.nodeName.toLowerCase(); + var type = node.type; + var event; + var descriptor; + var initialValue; + var initialChecked; + var initialCheckedRadio; + + // Do not try to delete non-configurable properties. + // Value and checked properties on DOM elements are non-configurable in PhantomJS. + function deletePropertySafe(elem, prop) { + var desc = Object.getOwnPropertyDescriptor(elem, prop); + if (desc && desc.configurable) { + delete elem[prop]; + } + } + + function getCheckedRadio(radio) { + var name = radio.name; + var radios; + var i; + if (name) { + radios = document.querySelectorAll('input[type="radio"][name="' + name + '"]'); + for (i = 0; i < radios.length; i += 1) { + if (radios[i].checked) { + return radios[i] !== radio ? radios[i] : null; + } + } + } + return null; + } + + function preventChecking(e) { + e.preventDefault(); + if (!initialChecked) { + e.target.checked = false; + } + if (initialCheckedRadio) { + initialCheckedRadio.checked = true; + } + } + + if (nodeName === 'select' || + (nodeName === 'input' && type === 'file')) { + // IE9-IE11, non-IE + // Dispatch change. + event = document.createEvent('HTMLEvents'); + event.initEvent('change', true, false); + node.dispatchEvent(event); + } else if ((nodeName === 'input' && supportedInputTypes[type]) || + nodeName === 'textarea') { + // React 16 + // Cache artificial value property descriptor. + // Property doesn't exist in React <16, descriptor is undefined. + descriptor = Object.getOwnPropertyDescriptor(node, 'value'); + + // Update inputValueTracking cached value. + // Remove artificial value property. + // Restore initial value to trigger event with it. + initialValue = node.value; + node.value = initialValue + '#'; + deletePropertySafe(node, 'value'); + node.value = initialValue; + + // React 0.14: IE10-IE11, non-IE + // React 15: non-IE + // React 16: IE10-IE11, non-IE + event = document.createEvent('HTMLEvents'); + event.initEvent('input', true, false); + node.dispatchEvent(event); + + // React 16 + // Restore artificial value property descriptor. + if (descriptor) { + Object.defineProperty(node, 'value', descriptor); + } + } else if (nodeName === 'input' && type === 'checkbox') { + // Invert inputValueTracking cached value. + node.checked = !node.checked; + + // Dispatch click. + // Click event inverts checked value. + event = document.createEvent('MouseEvents'); + event.initEvent('click', true, true); + node.dispatchEvent(event); + } else if (nodeName === 'input' && type === 'radio') { + // Cache initial checked value. + initialChecked = node.checked; + + // Find and cache initially checked radio in the group. + initialCheckedRadio = getCheckedRadio(node); + + // React 16 + // Cache property descriptor. + // Invert inputValueTracking cached value. + // Remove artificial checked property. + // Restore initial value, otherwise preventDefault will eventually revert the value. + descriptor = Object.getOwnPropertyDescriptor(node, 'checked'); + node.checked = !initialChecked; + deletePropertySafe(node, 'checked'); + node.checked = initialChecked; + + // Prevent toggling during event capturing phase. + // Set checked value to false if initialChecked is false, + // otherwise next listeners will see true. + // Restore initially checked radio in the group. + node.addEventListener('click', preventChecking, true); + + // Dispatch click. + // Click event inverts checked value. + event = document.createEvent('MouseEvents'); + event.initEvent('click', true, true); + node.dispatchEvent(event); + + // Remove listener to stop further change prevention. + node.removeEventListener('click', preventChecking, true); + + // React 16 + // Restore artificial checked property descriptor. + if (descriptor) { + Object.defineProperty(node, 'checked', descriptor); + } + } +}; -- cgit