fix: allow multi-digit values in duration inputs (#15429)
## Description The shared duration input enforced its minimum after every keypress. When the minimum was 10, typing `45` changed the first digit to `10`, so the second digit produced `105`. This change lets people finish typing before the input applies its minimum and maximum. The input now normalizes the value when it loses focus or when the person presses Enter. ## Type of change - [x] Bug fix ## How has this been tested? - Added a component test that types `4`, then `5`, and confirms that the value stays `45`. - Confirmed that values below 10 and above 100 are normalized when the input loses focus. - Ran the focused Vitest file and ESLint. - Ran a local deterministic Playwright test through the delayed automation form. The test typed `45`, saved the rule, confirmed `execution_delay: 45` in the API response, confirmed `Runs after 45m` in the list, and removed the test rule. ## Checklist - [x] My code follows the style guidelines of this project. - [x] I have performed a self-review of my code. - [x] I have added tests that prove the fix works. - [x] The focused unit and browser tests pass locally.
This commit is contained in:
@@ -46,12 +46,16 @@ const transformedValue = computed({
|
||||
duration.value = null;
|
||||
return;
|
||||
}
|
||||
let minuteValue = convertToMinutes(newValue);
|
||||
|
||||
duration.value = Math.min(Math.max(minuteValue, props.min), props.max);
|
||||
duration.value = convertToMinutes(newValue);
|
||||
},
|
||||
});
|
||||
|
||||
const normalizeDuration = () => {
|
||||
if (duration.value == null) return;
|
||||
|
||||
duration.value = Math.min(Math.max(duration.value, props.min), props.max);
|
||||
};
|
||||
|
||||
// when unit is changed set the nearest value to that unit
|
||||
// so if the minute is set to 900, and the user changes the unit to "days"
|
||||
// the transformed value will show 0, but the real value will still be 900
|
||||
@@ -72,6 +76,8 @@ watch(unit, () => {
|
||||
:disabled="disabled"
|
||||
:placeholder="t('DURATION_INPUT.PLACEHOLDER')"
|
||||
class="flex-grow w-full disabled:"
|
||||
@blur="normalizeDuration"
|
||||
@keydown.enter="normalizeDuration"
|
||||
/>
|
||||
<select
|
||||
v-model="unit"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import DurationInput from '../DurationInput.vue';
|
||||
|
||||
const mountDurationInput = ({ initialValue = null } = {}) => {
|
||||
const TestHost = defineComponent({
|
||||
components: { DurationInput },
|
||||
setup() {
|
||||
const duration = ref(initialValue);
|
||||
const unit = ref('minutes');
|
||||
const submittedDuration = ref(null);
|
||||
|
||||
const handleSubmit = () => {
|
||||
submittedDuration.value = duration.value;
|
||||
};
|
||||
|
||||
return { duration, unit, submittedDuration, handleSubmit };
|
||||
},
|
||||
template: `
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<DurationInput
|
||||
v-model="duration"
|
||||
v-model:unit="unit"
|
||||
:min="10"
|
||||
:max="100"
|
||||
/>
|
||||
<output data-testid="duration">{{ duration }}</output>
|
||||
<output data-testid="submitted-duration">
|
||||
{{ submittedDuration }}
|
||||
</output>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
return mount(TestHost);
|
||||
};
|
||||
|
||||
describe('DurationInput', () => {
|
||||
it('allows a multi-digit value to be typed before enforcing the minimum', async () => {
|
||||
const wrapper = mountDurationInput();
|
||||
const input = wrapper.get('input');
|
||||
|
||||
await input.setValue('4');
|
||||
expect(input.element.value).toBe('4');
|
||||
|
||||
await input.setValue(`${input.element.value}5`);
|
||||
expect(input.element.value).toBe('45');
|
||||
expect(wrapper.get('[data-testid="duration"]').text()).toBe('45');
|
||||
});
|
||||
|
||||
it('normalizes values to the configured range on blur', async () => {
|
||||
const wrapper = mountDurationInput();
|
||||
const input = wrapper.get('input');
|
||||
|
||||
await input.setValue('4');
|
||||
await input.trigger('blur');
|
||||
expect(input.element.value).toBe('10');
|
||||
|
||||
await input.setValue('125');
|
||||
await input.trigger('blur');
|
||||
expect(input.element.value).toBe('100');
|
||||
});
|
||||
|
||||
it('normalizes the value before Enter submits the form', async () => {
|
||||
const wrapper = mountDurationInput();
|
||||
const input = wrapper.get('input');
|
||||
|
||||
await input.setValue('125');
|
||||
await input.trigger('keydown', { key: 'Enter' });
|
||||
await wrapper.get('form').trigger('submit');
|
||||
|
||||
expect(wrapper.get('[data-testid="submitted-duration"]').text()).toBe(
|
||||
'100'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user