diff --git a/src/components/common/UrlInput.vue b/src/components/common/UrlInput.vue index 7462cd0a6..ff92bfd28 100644 --- a/src/components/common/UrlInput.vue +++ b/src/components/common/UrlInput.vue @@ -12,11 +12,12 @@ :class="{ 'pi pi-spin pi-spinner text-neutral-400': validationState === UrlValidationState.LOADING, - 'pi pi-check text-green-500': + 'pi pi-check text-green-500 cursor-pointer': validationState === UrlValidationState.VALID, - 'pi pi-times text-red-500': + 'pi pi-times text-red-500 cursor-pointer': validationState === UrlValidationState.INVALID }" + @click="validateUrl(props.modelValue)" /> @@ -87,6 +88,8 @@ const defaultValidateUrl = async (url: string): Promise => { } const validateUrl = async (value: string) => { + if (validationState.value === UrlValidationState.LOADING) return + const url = value.trim() // Reset state diff --git a/src/components/common/__tests__/UrlInput.test.ts b/src/components/common/__tests__/UrlInput.test.ts index 618e766b7..2f676192f 100644 --- a/src/components/common/__tests__/UrlInput.test.ts +++ b/src/components/common/__tests__/UrlInput.test.ts @@ -106,4 +106,53 @@ describe('UrlInput', () => { expect(wrapper.find('.pi-check').exists()).toBe(true) }) + + it('triggers validation when clicking the validation icon', async () => { + let validationCount = 0 + const wrapper = mountComponent({ + modelValue: 'https://test.com', + validateUrlFn: () => { + validationCount++ + return Promise.resolve(true) + } + }) + + // Wait for initial validation + await nextTick() + await nextTick() + + // Click the validation icon + await wrapper.find('.pi-check').trigger('click') + await nextTick() + await nextTick() + + expect(validationCount).toBe(2) // Once on mount, once on click + }) + + it('prevents multiple simultaneous validations', async () => { + let validationCount = 0 + const wrapper = mountComponent({ + modelValue: '', + validateUrlFn: () => { + validationCount++ + return new Promise(() => { + // Never resolves, simulating perpetual loading state + }) + } + }) + + wrapper.setProps({ modelValue: 'https://test.com' }) + await nextTick() + await nextTick() + + // Trigger multiple validations in quick succession + wrapper.find('.pi-spinner').trigger('click') + wrapper.find('.pi-spinner').trigger('click') + wrapper.find('.pi-spinner').trigger('click') + + await nextTick() + await nextTick() + + expect(validationCount).toBe(1) // Only the initial validation should occur + }) })