-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5017 from manuelmeister/bugfix/number-input
Create number field
- Loading branch information
Showing
18 changed files
with
761 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<!-- | ||
Displays a field as a e-number-field + write access via API wrapper | ||
--> | ||
|
||
<template> | ||
<api-wrapper v-slot="wrapper" v-bind="$props" v-on="$listeners"> | ||
<e-number-field | ||
ref="textField" | ||
:value="wrapper.localValue" | ||
v-bind="$attrs" | ||
:path="path" | ||
:readonly="wrapper.readonly" | ||
:disabled="disabled" | ||
:error-messages="wrapper.errorMessages" | ||
:loading="wrapper.isSaving || wrapper.isLoading ? 'secondary' : false" | ||
:outlined="outlined" | ||
:filled="filled" | ||
:dense="dense" | ||
@input="wrapper.on.input" | ||
@blur="wrapper.on.blur" | ||
> | ||
<template #append> | ||
<api-wrapper-append :wrapper="wrapper" /> | ||
</template> | ||
</e-number-field> | ||
</api-wrapper> | ||
</template> | ||
|
||
<script> | ||
import { apiPropsMixin } from '@/mixins/apiPropsMixin.js' | ||
import ApiWrapper from './ApiWrapper.vue' | ||
import ApiWrapperAppend from './ApiWrapperAppend.vue' | ||
export default { | ||
name: 'ApiNumberField', | ||
components: { ApiWrapper, ApiWrapperAppend }, | ||
mixins: [apiPropsMixin], | ||
props: { | ||
outlined: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
}, | ||
data() { | ||
return {} | ||
}, | ||
methods: { | ||
focus() { | ||
this.$refs.textField.focus() | ||
}, | ||
}, | ||
} | ||
</script> | ||
|
||
<style scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
frontend/src/components/form/api/__tests__/ApiNumberField.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import ApiNumberField from '../ApiNumberField.vue' | ||
import ApiWrapper from '@/components/form/api/ApiWrapper.vue' | ||
import Vue from 'vue' | ||
import Vuetify from 'vuetify' | ||
import flushPromises from 'flush-promises' | ||
import formBaseComponents from '@/plugins/formBaseComponents' | ||
import merge from 'lodash/merge' | ||
import { ApiMock } from '@/components/form/api/__tests__/ApiMock' | ||
import { i18n } from '@/plugins' | ||
import { mount as mountComponent } from '@vue/test-utils' | ||
import { waitForDebounce } from '@/test/util' | ||
|
||
Vue.use(Vuetify) | ||
Vue.use(formBaseComponents) | ||
|
||
describe('An ApiNumberField', () => { | ||
let vuetify | ||
let wrapper | ||
let apiMock | ||
|
||
const path = 'test-field/123' | ||
const NUMBER_1 = 1.2 | ||
const NUMBER_1_string = '1.2' | ||
|
||
beforeEach(() => { | ||
vuetify = new Vuetify() | ||
apiMock = ApiMock.create() | ||
}) | ||
|
||
afterEach(() => { | ||
jest.restoreAllMocks() | ||
wrapper.destroy() | ||
}) | ||
|
||
const mount = (options) => { | ||
const app = Vue.component('App', { | ||
components: { ApiNumberField }, | ||
props: { | ||
path: { type: String, default: path }, | ||
}, | ||
template: `<div data-app> | ||
<api-number-field | ||
:auto-save="false" | ||
:path="path" | ||
uri="test-field/123" | ||
label="Test field" | ||
required="true" | ||
/> | ||
</div>`, | ||
}) | ||
apiMock.get().thenReturn(ApiMock.success(NUMBER_1).forPath(path)) | ||
const defaultOptions = { | ||
mocks: { | ||
$tc: () => {}, | ||
api: apiMock.getMocks(), | ||
}, | ||
} | ||
return mountComponent(app, { | ||
vuetify, | ||
i18n, | ||
attachTo: document.body, | ||
...merge(defaultOptions, options), | ||
}) | ||
} | ||
|
||
test('triggers api.patch and status update if input changes', async () => { | ||
apiMock.patch().thenReturn(ApiMock.success(NUMBER_1)) | ||
wrapper = mount() | ||
|
||
await flushPromises() | ||
|
||
const input = wrapper.find('input') | ||
await input.setValue(NUMBER_1) | ||
await input.trigger('submit') | ||
|
||
await waitForDebounce() | ||
await flushPromises() | ||
|
||
expect(apiMock.getMocks().patch).toBeCalledTimes(1) | ||
expect(wrapper.findComponent(ApiWrapper).vm.parsedLocalValue).toBe(NUMBER_1) | ||
}) | ||
|
||
test('updates state if value in store is refreshed and has new value', async () => { | ||
wrapper = mount() | ||
apiMock.get().thenReturn(ApiMock.success(NUMBER_1).forPath(path)) | ||
|
||
wrapper.findComponent(ApiWrapper).vm.reload() | ||
|
||
await waitForDebounce() | ||
await flushPromises() | ||
|
||
expect(wrapper.findComponent(ApiWrapper).vm.parsedLocalValue).toBe(NUMBER_1) | ||
expect(wrapper.find('input[type=text]').element.value).toBe(NUMBER_1_string) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<template> | ||
<EParseField | ||
ref="input" | ||
:value="value" | ||
:format="format" | ||
:parse="parse" | ||
:input-filter="inputFilter" | ||
:required="required" | ||
:vee-id="veeId" | ||
:vee-rules="veeRules" | ||
reset-on-blur | ||
v-bind="$attrs" | ||
@input="$emit('input', $event)" | ||
> | ||
<!-- passing through all slots --> | ||
<slot v-for="(_, name) in $slots" :slot="name" :name="name" /> | ||
<template #scoped="{ scopedSlots }"> | ||
<template v-for="(_, name) in scopedSlots" :slot="name" slot-scope="slotData"> | ||
<slot :name="name" v-bind="slotData" /> | ||
</template> | ||
</template> | ||
</EParseField> | ||
</template> | ||
|
||
<script> | ||
import { formComponentMixin } from '@/mixins/formComponentMixin.js' | ||
export default { | ||
name: 'ENumberField', | ||
mixins: [formComponentMixin], | ||
props: { | ||
value: { type: [String, Number], required: false, default: null }, | ||
}, | ||
emits: ['input'], | ||
methods: { | ||
format(value) { | ||
switch (value) { | ||
case null: | ||
return '' | ||
default: | ||
return value + '' | ||
} | ||
}, | ||
inputFilter(value) { | ||
if (/\d/.test(value) && value.match(/^[^,]*,[^,.]+$/g)) { | ||
value = value.replace(/\./g, '').replace(/,/g, '.') | ||
} | ||
// Remove all dots except the first one | ||
let firstDotFound = false | ||
value = value.replace(/\./g, (match) => | ||
firstDotFound ? '' : (firstDotFound = match) | ||
) | ||
// Remove everything except numbers, dots and the first minus sign | ||
const negative = value.startsWith('-') | ||
value = value.replace(/[^0-9.]/g, '') | ||
value = negative ? '-' + value : value | ||
return value | ||
}, | ||
/** | ||
* @param {string} value | ||
*/ | ||
parse(value) { | ||
return isNaN(parseFloat(value)) || /^\.0*$/.test(value) ? null : parseFloat(value) | ||
}, | ||
focus() { | ||
this.$refs.input.focus() | ||
}, | ||
}, | ||
} | ||
</script> |
Oops, something went wrong.