Skip to content

Commit e671e4b

Browse files
authored
fix(stateful): use provided prop instead of default value if provided. (#4057)
1 parent 82e545a commit e671e4b

File tree

12 files changed

+164
-17
lines changed

12 files changed

+164
-17
lines changed

packages/ui/src/components/va-carousel/VaCarousel.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export default defineComponent({
166166
emits: [...useStatefulEmits],
167167
168168
setup (props, { emit }) {
169-
const { valueComputed: currentSlide } = useStateful(props, emit, 'modelValue', { defaultValue: 0 })
169+
const { valueComputed: currentSlide } = useStateful(props, emit, 'modelValue')
170170
171171
const {
172172
goTo, next, prev,

packages/ui/src/components/va-form/VaForm.stories.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
VaRadio,
2424
} from '../'
2525
import { sleep } from '../../utils/sleep'
26+
import { useForm } from '../../composables'
2627

2728
export default {
2829
title: 'VaForm',
@@ -475,3 +476,66 @@ addText(
475476
'This is old demo resqued to have visual tests, but we want to rewrite it eventually.',
476477
'stale',
477478
)
479+
480+
export const FormDataInitialValue = () => ({
481+
components: {
482+
VaForm,
483+
VaInput,
484+
VaSelect,
485+
VaDateInput,
486+
VaTimeInput,
487+
VaOptionList,
488+
VaButton,
489+
},
490+
data: () => ({
491+
input: 'value',
492+
checkbox: true,
493+
date: new Date(0),
494+
time: new Date(0),
495+
options: OPTIONS,
496+
select: OPTIONS[0],
497+
validationRules: [false],
498+
}),
499+
setup () {
500+
const form = useForm('formEl')
501+
502+
return {
503+
form,
504+
}
505+
},
506+
template: `
507+
[form data]: <pre>{{ form.formData }}</pre>
508+
509+
<va-form ref="formEl" stateful>
510+
<va-checkbox
511+
v-model="checkbox"
512+
:rules="validationRules"
513+
name="checkbox"
514+
/>
515+
<va-date-input
516+
v-model="date"
517+
:rules="validationRules"
518+
name="date"
519+
/>
520+
<va-input
521+
v-model="input"
522+
:rules="validationRules"
523+
name="text"
524+
/>
525+
<va-select
526+
v-model="select"
527+
:options="options"
528+
:rules="validationRules"
529+
name="select"
530+
/>
531+
<va-time-input
532+
v-model="time"
533+
:rules="validationRules"
534+
name="time"
535+
/>
536+
</va-form>
537+
<va-button @click="$refs.form.reset()">
538+
Reset form
539+
</va-button>
540+
`,
541+
})

packages/ui/src/components/va-input/VaInput.stories.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ref } from 'vue'
12
import VaInputDemo from './VaInput.demo.vue'
23
import VaInput from './VaInput.vue'
34
import { expect } from '@storybook/jest'
@@ -19,6 +20,11 @@ export const Loading = () => ({
1920
template: '<VaInput loading />',
2021
})
2122

23+
export const Stateful = () => ({
24+
components: { VaInput },
25+
template: '<VaInput stateful />',
26+
})
27+
2228
export const Clearable = () => ({
2329
components: { VaInput },
2430
data () {

packages/ui/src/components/va-input/VaInput.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export default defineComponent({
103103
// input
104104
placeholder: { type: String, default: '' },
105105
tabindex: { type: [String, Number], default: 0 },
106-
modelValue: { type: [String, Number] },
106+
modelValue: { type: [Number, String], default: '' },
107107
type: { type: String as AnyStringPropType<'text' | 'password'>, default: 'text' },
108108
inputClass: { type: String, default: '' },
109109
pattern: { type: String },
@@ -132,7 +132,7 @@ export default defineComponent({
132132
133133
const input = shallowRef<HTMLInputElement>()
134134
135-
const { valueComputed } = useStateful(props, emit, 'modelValue', { defaultValue: '' })
135+
const { valueComputed } = useStateful(props, emit, 'modelValue')
136136
137137
const reset = () => withoutValidation(() => {
138138
emit('update:modelValue', props.clearValue)

packages/ui/src/components/va-select/VaSelect.stories.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const Validation: StoryFn = () => ({
2929
components: { VaSelect },
3030

3131
data () {
32-
return { value: '', options: ['one', 'two', 'tree'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
32+
return { value: '', options: ['one', 'two', 'three'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
3333
},
3434

3535
template: '<VaSelect v-model="value" :options="options" :rules="rules" />',
@@ -46,7 +46,7 @@ export const ImmediateValidation: StoryFn = () => ({
4646
components: { VaSelect },
4747

4848
data () {
49-
return { value: '', options: ['one', 'two', 'tree'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
49+
return { value: '', options: ['one', 'two', 'three'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
5050
},
5151

5252
template: '<VaSelect v-model="value" :options="options" :rules="rules" immediate-validation />',
@@ -63,7 +63,7 @@ export const DirtyValidation: StoryFn = () => ({
6363
components: { Component: VaSelect },
6464

6565
data () {
66-
return { value: '', dirty: false, haveError: false, options: ['one', 'two', 'tree'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
66+
return { value: '', dirty: false, haveError: false, options: ['one', 'two', 'three'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
6767
},
6868

6969
template: `
@@ -104,7 +104,7 @@ export const DirtyImmediateValidation: StoryFn = () => ({
104104
components: { Component: VaSelect },
105105

106106
data () {
107-
return { value: '', dirty: false, haveError: false, options: ['one', 'two', 'tree'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
107+
return { value: '', dirty: false, haveError: false, options: ['one', 'two', 'three'], rules: [(v: string) => (v && v === 'one') || 'Must be one'] }
108108
},
109109

110110
template: `
@@ -124,3 +124,24 @@ DirtyImmediateValidation.play = async ({ canvasElement, step }) => {
124124
expect(error).not.toBeNull()
125125
})
126126
}
127+
128+
export const Autocomplete: StoryFn = () => ({
129+
components: { VaSelect },
130+
131+
data () {
132+
// Test if initial value is correctly set
133+
return { value: 'one', options: ['one', 'two', 'three'] }
134+
},
135+
136+
template: '<VaSelect v-model="value" :options="options" autocomplete />',
137+
})
138+
139+
export const AutocompleteMultiple: StoryFn = () => ({
140+
components: { VaSelect },
141+
142+
data () {
143+
return { value: ['one', 'two'], options: ['one', 'two', 'three'] }
144+
},
145+
146+
template: '<VaSelect v-model="value" :options="options" autocomplete multiple />',
147+
})

packages/ui/src/components/va-select/hooks/useAutocomplete.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export const useAutocomplete = (
1717
) => {
1818
const getLastOptionText = (v: SelectOption[]) => v?.length ? getText(v.at(-1)!) : ''
1919

20+
if (props.autocomplete && !props.multiple) {
21+
// Set current value as autocomplete value text
22+
autocompleteValue.value = getLastOptionText(value.value)
23+
}
24+
2025
watch(value, (newValue, oldValue) => {
2126
if (!props.autocomplete) { return }
2227

packages/ui/src/components/va-stepper/VaStepper.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default defineComponent({
116116
emits: ['update:modelValue', 'finish', 'update:steps'],
117117
setup (props, { emit }) {
118118
const stepperNavigation = shallowRef<HTMLElement>()
119-
const { valueComputed: modelValue }: { valueComputed: Ref<number> } = useStateful(props, emit, 'modelValue', { defaultValue: 0 })
119+
const { valueComputed: modelValue }: { valueComputed: Ref<number> } = useStateful(props, emit, 'modelValue')
120120
121121
const focusedStep = ref({ trigger: false, stepIndex: props.navigationDisabled ? -1 : props.modelValue })
122122

packages/ui/src/composables/tests/useStateful.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,18 @@ describe('useStateful', () => {
3030
[ true, true, true ],
3131
[ false, true, undefined ],
3232
/* eslint-enable */
33-
])('stateful %s', async (stateful: boolean, valueToSet: boolean, internalValue?: true) => {
34-
const wrapper = mount(TestComponentRich, { props: { stateful } })
33+
])('stateful %s', async (stateful: boolean, valueToSet: boolean, internalValue?: boolean) => {
34+
const wrapper = mount(TestComponentRich, { props: { stateful } as any })
3535
wrapper.vm.valueComputed = valueToSet
3636
expect(wrapper.emitted()['update:modelValue']).toBeTruthy()
3737
expect(wrapper.vm.valueComputed).toBe(internalValue)
3838

3939
await wrapper.setProps({ modelValue: false })
4040
expect(wrapper.vm.valueComputed).toBe(false)
4141
})
42+
43+
it('should react to prop change', async () => {
44+
const wrapper = mount(TestComponentRich, { props: { stateful: true, modelValue: 'Hello!' } })
45+
expect(wrapper.vm.valueComputed).toBe('Hello!')
46+
})
4247
})
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { mount } from '@vue/test-utils'
2+
import { describe, it, expect } from 'vitest'
3+
import { defineComponent } from 'vue'
4+
import { NOT_PROVIDED, useUserProvidedProp } from '../useUserProvidedProp'
5+
6+
const TestComponentRich = defineComponent({
7+
template: '<p></p>',
8+
props: { modelValue: { type: String } },
9+
setup (props) {
10+
const providedProp = useUserProvidedProp('modelValue', props)
11+
12+
return {
13+
providedProp,
14+
}
15+
},
16+
emits: ['update:modelValue'],
17+
})
18+
19+
describe('useUserProvidedProp', () => {
20+
it('should react to prop change', async () => {
21+
const wrapper = mount(TestComponentRich, { props: { stateful: true } } as any)
22+
expect(wrapper.vm.providedProp).toBe(NOT_PROVIDED)
23+
24+
await wrapper.setProps({ modelValue: 'Hello' })
25+
expect(wrapper.vm.providedProp).toBe('Hello')
26+
})
27+
})

packages/ui/src/composables/useSelectable.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type SelectableProps<V = any> = StatefulProps & LoadingProps & ExtractPro
1515
indeterminateValue: V | null,
1616
disabled: boolean,
1717
readonly: boolean,
18+
modelValue: unknown
1819
}
1920

2021
export type Elements = {

0 commit comments

Comments
 (0)