From f0ba48ea22308a07aad7db91439fde0c2d1c24d3 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Thu, 6 Mar 2025 09:17:24 -0700 Subject: [PATCH] Trim whitespace in URL form items (#2884) --- src/components/common/UrlInput.vue | 23 ++++++-- .../common/__tests__/UrlInput.test.ts | 56 ++++++++++++++++++- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/components/common/UrlInput.vue b/src/components/common/UrlInput.vue index da23c6322..7e9817b16 100644 --- a/src/components/common/UrlInput.vue +++ b/src/components/common/UrlInput.vue @@ -44,14 +44,17 @@ const emit = defineEmits<{ const validationState = ref(ValidationState.IDLE) +const cleanInput = (value: string): string => + value ? value.replace(/\s+/g, '') : '' + // Add internal value state -const internalValue = ref(props.modelValue) +const internalValue = ref(cleanInput(props.modelValue)) // Watch for external modelValue changes watch( () => props.modelValue, async (newValue: string) => { - internalValue.value = newValue + internalValue.value = cleanInput(newValue) await validateUrl(newValue) } ) @@ -67,14 +70,24 @@ onMounted(async () => { const handleInput = (value: string) => { // Update internal value without emitting - internalValue.value = value + internalValue.value = cleanInput(value) // Reset validation state when user types validationState.value = ValidationState.IDLE } const handleBlur = async () => { + const input = cleanInput(internalValue.value) + + let normalizedUrl = input + try { + const url = new URL(input) + normalizedUrl = url.toString() + } catch { + // If URL parsing fails, just use the cleaned input + } + // Emit the update only on blur - emit('update:modelValue', internalValue.value) + emit('update:modelValue', normalizedUrl) } // Default validation implementation @@ -90,7 +103,7 @@ const defaultValidateUrl = async (url: string): Promise => { const validateUrl = async (value: string) => { if (validationState.value === ValidationState.LOADING) return - const url = value.trim() + const url = cleanInput(value) // Reset state validationState.value = ValidationState.IDLE diff --git a/src/components/common/__tests__/UrlInput.test.ts b/src/components/common/__tests__/UrlInput.test.ts index 2f676192f..9bbac4b71 100644 --- a/src/components/common/__tests__/UrlInput.test.ts +++ b/src/components/common/__tests__/UrlInput.test.ts @@ -42,11 +42,11 @@ describe('UrlInput', () => { }) const input = wrapper.find('input') - await input.setValue('https://test.com') + await input.setValue('https://test.com/') await input.trigger('blur') expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([ - 'https://test.com' + 'https://test.com/' ]) }) @@ -155,4 +155,56 @@ describe('UrlInput', () => { expect(validationCount).toBe(1) // Only the initial validation should occur }) + + describe('input cleaning functionality', () => { + it('trims whitespace when user types', async () => { + const wrapper = mountComponent({ + modelValue: '', + placeholder: 'Enter URL' + }) + + const input = wrapper.find('input') + + // Test leading whitespace + await input.setValue(' https://leading-space.com') + await input.trigger('input') + await nextTick() + expect(wrapper.vm.internalValue).toBe('https://leading-space.com') + + // Test trailing whitespace + await input.setValue('https://trailing-space.com ') + await input.trigger('input') + await nextTick() + expect(wrapper.vm.internalValue).toBe('https://trailing-space.com') + + // Test both leading and trailing whitespace + await input.setValue(' https://both-spaces.com ') + await input.trigger('input') + await nextTick() + expect(wrapper.vm.internalValue).toBe('https://both-spaces.com') + + // Test whitespace in the middle of the URL + await input.setValue('https:// middle-space.com') + await input.trigger('input') + await nextTick() + expect(wrapper.vm.internalValue).toBe('https://middle-space.com') + }) + + it('trims whitespace when value set externally', async () => { + const wrapper = mountComponent({ + modelValue: ' https://initial-value.com ', + placeholder: 'Enter URL' + }) + + // Check initial value is trimmed + expect(wrapper.vm.internalValue).toBe('https://initial-value.com') + + // Update props with whitespace + await wrapper.setProps({ modelValue: ' https://updated-value.com ' }) + await nextTick() + + // Check updated value is trimmed + expect(wrapper.vm.internalValue).toBe('https://updated-value.com') + }) + }) })