commit c32c593600175dfa07e5a378185ce390eeaa38b8
parent 6e641355ca8b5fd06e5639850709e80fa14bf4a5
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date: Mon, 2 Aug 2021 21:50:13 -0700
Implement IntInput component
Diffstat:
2 files changed, 234 insertions(+), 0 deletions(-)
diff --git a/src/components/IntInput.vue b/src/components/IntInput.vue
@@ -0,0 +1,128 @@
+<template>
+ <input @keydown="keydown" @keypress="keypress" v-model="stringValue">
+</template>
+
+<script>
+export default {
+ name: 'IntInput',
+ props: {
+ /**
+ * The input value
+ */
+ value: {
+ type: Number,
+ default: 0,
+ },
+
+ /**
+ * The minimum value
+ */
+ min: {
+ type: Number,
+ default: 0,
+ },
+
+ /**
+ * The maximum value
+ */
+ max: {
+ type: Number,
+ default: null,
+ },
+ },
+
+ data: function() {
+ return {
+ /**
+ * The internal value
+ */
+ intValue: this.value,
+
+ /**
+ * The value of the input
+ */
+ stringValue: this.value.toString(),
+ };
+ },
+
+ watch: {
+ /**
+ * Update the internal value from the value prop
+ */
+ value: function(newValue) {
+ this.stringValue = newValue;
+ },
+
+ /**
+ * Trigger the input event
+ */
+ intValue: function(newValue) {
+ this.$emit('input', newValue);
+ },
+
+ /**
+ * Validate the new value
+ */
+ stringValue: function(newValue, oldValue) {
+ // Parse new value
+ let parsedValue = parseInt(newValue);
+
+ // Make sure value is a number
+ if (isNaN(parsedValue)) {
+ if (newValue === '') {
+ parsedValue = 0;
+ this.stringValue = '0';
+ }
+ else {
+ parsedValue = this.intValue;
+ this.stringValue = oldValue;
+ }
+ }
+
+ // Enforce minimum and maximum
+ else if (this.min !== null && parsedValue < this.min) {
+ parsedValue = this.min;
+ this.stringValue = this.min.toString();
+ }
+ else if (this.max !== null && parsedValue > this.max) {
+ parsedValue = this.max;
+ this.stringValue = this.max.toString();
+ }
+
+ // Make sure new value is correctly formatted
+ else if (newValue !== parsedValue.toString()) {
+ this.stringValue = parsedValue.toString();
+ }
+
+ // Update intValue
+ this.intValue = parsedValue;
+ },
+ },
+
+ methods: {
+ /**
+ * Restrict input to numbers
+ */
+ keypress: function(e) {
+ if (!['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(e.key)) {
+ /* key press was not a number */
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Process up and down arrow presses
+ */
+ keydown: function(e) {
+ if (e.key === 'ArrowUp') {
+ this.stringValue = (parseInt(this.stringValue) + 1).toString();
+ e.preventDefault();
+ }
+ else if (e.key === 'ArrowDown') {
+ this.stringValue = (parseInt(this.stringValue) - 1).toString();
+ e.preventDefault();
+ }
+ }
+ },
+}
+</script>
diff --git a/tests/unit/IntInput.spec.js b/tests/unit/IntInput.spec.js
@@ -0,0 +1,106 @@
+import { expect } from 'chai';
+import { mount } from '@vue/test-utils';
+import IntInput from '@/components/IntInput.vue';
+
+describe('IntInput.vue', () => {
+ it('value should be 0 by default', () => {
+ const wrapper = mount(IntInput);
+ expect(wrapper.find('input').element.value).to.equal('0');
+ });
+
+ it('should read value prop', () => {
+ const wrapper = mount(IntInput, {
+ propsData: { value: 1 }
+ });
+ expect(wrapper.find('input').element.value).to.equal('1');
+ });
+
+ it('should update when value prop changes', () => {
+ const wrapper = mount(IntInput, {
+ propsData: { value: 1 }
+ });
+ wrapper.props().value = 2;
+ expect(wrapper.find('input').element.value).to.equal('2');
+ expect(wrapper.emitted().input).to.deep.equal([[2]]);
+ });
+
+ it('up arrow should increment value', async () => {
+ const wrapper = mount(IntInput);
+ await wrapper.trigger('keydown', { key: 'ArrowUp' });
+ expect(wrapper.find('input').element.value).to.equal('1');
+ expect(wrapper.emitted().input).to.deep.equal([[1]]);
+ });
+
+ it('down arrow should increment value', async () => {
+ const wrapper = mount(IntInput, {
+ propsData: { value: 2 }
+ });
+ await wrapper.trigger('keydown', { key: 'ArrowDown' });
+ expect(wrapper.find('input').element.value).to.equal('1');
+ expect(wrapper.emitted().input).to.deep.equal([[1]]);
+ });
+
+ it('should fire input event when value changes', async () => {
+ const wrapper = mount(IntInput);
+ await wrapper.trigger('keydown', { key: 'ArrowUp' });
+ expect(wrapper.emitted().input).to.deep.equal([[1]]);
+ });
+
+ it('should accept numerical values', async () => {
+ const wrapper = mount(IntInput);
+ wrapper.find('input').element.value = '1';
+ await wrapper.find('input').trigger('input');
+ expect(wrapper.find('input').element.value).to.equal('1');
+ expect(wrapper.emitted().input).to.deep.equal([[1]]);
+ });
+
+ it('should not accept non numerical values', async () => {
+ const wrapper = mount(IntInput, {
+ propsData: { value: 1 }
+ });
+ wrapper.find('input').element.value = 'a';
+ await wrapper.find('input').trigger('input');
+ expect(wrapper.find('input').element.value).to.equal('1');
+ expect(wrapper.emitted().input).to.be.undefined;
+ });
+
+ it('should remove leading zeros', async () => {
+ const wrapper = mount(IntInput, {
+ propsData: { value: 1 }
+ });
+ wrapper.find('input').element.value = '01';
+ await wrapper.find('input').trigger('input');
+ expect(wrapper.find('input').element.value).to.equal('1');
+ expect(wrapper.emitted().input).to.be.undefined;
+ });
+
+ it('should set empty input to 0', async () => {
+ const wrapper = mount(IntInput, {
+ propsData: { value: 1 }
+ });
+ wrapper.find('input').element.value = '';
+ await wrapper.find('input').trigger('input');
+ expect(wrapper.find('input').element.value).to.equal('0');
+ expect(wrapper.emitted().input).to.deep.equal([[0]]);
+ });
+
+ it('should not allow input to be below the minimum', async () => {
+ const wrapper = mount(IntInput, {
+ propsData: { min: 10, value: 20 }
+ });
+ wrapper.find('input').element.value = '9';
+ await wrapper.find('input').trigger('input');
+ expect(wrapper.find('input').element.value).to.equal('10');
+ expect(wrapper.emitted().input).to.deep.equal([[10]]);
+ });
+
+ it('should not allow input to be above the maximum', async () => {
+ const wrapper = mount(IntInput, {
+ propsData: { max: 10 }
+ });
+ wrapper.find('input').element.value = '11';
+ await wrapper.find('input').trigger('input');
+ expect(wrapper.find('input').element.value).to.equal('10');
+ expect(wrapper.emitted().input).to.deep.equal([[10]]);
+ });
+});