From f86832ce8bb89c508e093d2bfdf2aa0eaf7d4f69 Mon Sep 17 00:00:00 2001 From: edalzell Date: Mon, 14 Sep 2026 15:36:31 -0700 Subject: [PATCH] Debounce video fieldtype updates --- .../components/fieldtypes/VideoFieldtype.vue | 2 +- .../fieldtypes/VideoFieldtype.test.js | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 resources/js/tests/components/fieldtypes/VideoFieldtype.test.js diff --git a/resources/js/components/fieldtypes/VideoFieldtype.vue b/resources/js/components/fieldtypes/VideoFieldtype.vue index 848a08d4fbf..5282a8b1a3a 100644 --- a/resources/js/components/fieldtypes/VideoFieldtype.vue +++ b/resources/js/components/fieldtypes/VideoFieldtype.vue @@ -7,7 +7,7 @@ :isReadOnly="isReadOnly" :placeholder="__(config.placeholder) || 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'" :aria-label="__('Video URL')" - @update:model-value="update" + @update:model-value="updateDebounced" @focus="$emit('focus')" @blur="$emit('blur')" input-class="border-s-0" diff --git a/resources/js/tests/components/fieldtypes/VideoFieldtype.test.js b/resources/js/tests/components/fieldtypes/VideoFieldtype.test.js new file mode 100644 index 00000000000..241092cdd86 --- /dev/null +++ b/resources/js/tests/components/fieldtypes/VideoFieldtype.test.js @@ -0,0 +1,62 @@ +import { mount } from '@vue/test-utils'; +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; +import VideoFieldtype from '@/components/fieldtypes/VideoFieldtype.vue'; +import { publishContextKey } from '@/components/ui'; + +window.__ = (key) => key; + +beforeEach(() => { + vi.useFakeTimers(); + + window.IntersectionObserver = class { + observe() {} + disconnect() {} + }; +}); + +afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + +const stub = (tag) => ({ + props: ['modelValue'], + emits: ['update:modelValue'], + template: `<${tag} :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />`, +}); + +const mountVideoField = (props = {}) => { + return mount(VideoFieldtype, { + props: { + handle: 'video', + config: {}, + meta: {}, + ...props, + }, + global: { + provide: { [publishContextKey]: {} }, + stubs: { + 'ui-input': stub('input'), + 'ui-input-group': { template: '
' }, + 'ui-input-group-prepend': { template: '' }, + 'ui-description': { template: '

' }, + }, + }, + }); +}; + +test('it debounces updates while typing', async () => { + const wrapper = mountVideoField({ value: null }); + const input = wrapper.find('input'); + + await input.setValue('https://www.youtube.com/watch?v=1'); + await input.setValue('https://www.youtube.com/watch?v=12'); + await input.setValue('https://www.youtube.com/watch?v=123'); + + expect(wrapper.emitted('update:value')).toBeUndefined(); + + vi.runAllTimers(); + + expect(wrapper.emitted('update:value')).toHaveLength(1); + expect(wrapper.emitted('update:value')[0]).toEqual(['https://www.youtube.com/watch?v=123']); +});