From 7dace2fecb7b6f47ca13dbd1eeb5926279963d77 Mon Sep 17 00:00:00 2001 From: "chihao.ou" Date: Sun, 13 Sep 2026 17:18:15 +0800 Subject: [PATCH] fix(ui): discard unapplied date ranges on dismissal --- .../src/components/common/DateRangePicker.vue | 8 ++++ .../__tests__/DateRangePicker.dismiss.spec.ts | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 frontend/src/components/common/__tests__/DateRangePicker.dismiss.spec.ts diff --git a/frontend/src/components/common/DateRangePicker.vue b/frontend/src/components/common/DateRangePicker.vue index 56861979bf..b116dff2e3 100644 --- a/frontend/src/components/common/DateRangePicker.vue +++ b/frontend/src/components/common/DateRangePicker.vue @@ -290,6 +290,14 @@ const handleEscape = (event: KeyboardEvent) => { } } +// Restore the applied range after dismissal, including parent updates from Apply. +watch(isOpen, (open) => { + if (open) return + localStartDate.value = props.startDate + localEndDate.value = props.endDate + onDateChange() +}, { flush: 'post' }) + // Sync local state with props watch( () => props.startDate, diff --git a/frontend/src/components/common/__tests__/DateRangePicker.dismiss.spec.ts b/frontend/src/components/common/__tests__/DateRangePicker.dismiss.spec.ts new file mode 100644 index 0000000000..222cd54b08 --- /dev/null +++ b/frontend/src/components/common/__tests__/DateRangePicker.dismiss.spec.ts @@ -0,0 +1,47 @@ +import { mount } from '@vue/test-utils' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { nextTick, ref } from 'vue' +import DateRangePicker from '../DateRangePicker.vue' + +vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key, locale: ref('en') }) })) +let wrapper: ReturnType> +beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date(2026, 8, 13, 12)) }) +afterEach(() => { wrapper?.unmount(); vi.useRealTimers() }) + +async function chooseDraft() { + wrapper = mount(DateRangePicker, { + props: { + startDate: '2026-09-13', endDate: '2026-09-13', + 'onUpdate:startDate': (startDate: string) => { void wrapper.setProps({ startDate }) }, + 'onUpdate:endDate': (endDate: string) => { void wrapper.setProps({ endDate }) }, + }, + }) + await wrapper.get('.date-picker-trigger').trigger('click') + await wrapper.findAll('.date-picker-preset').find(node => node.text() === 'dates.last7Days')!.trigger('click') +} + +describe('DateRangePicker unapplied changes', () => { + it.each(['outside', 'escape', 'toggle'])('discards draft dates when dismissed with %s', async (method) => { + await chooseDraft() + if (method === 'outside') document.body.click() + else if (method === 'escape') document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) + else await wrapper.get('.date-picker-trigger').trigger('click') + await nextTick() + expect(wrapper.emitted('change')).toBeUndefined() + expect(wrapper.get('.date-picker-trigger').text()).toContain('dates.today') + await wrapper.get('.date-picker-trigger').trigger('click') + expect(wrapper.findAll('input[type="date"]').map(input => input.element.value)) + .toEqual(['2026-09-13', '2026-09-13']) + }) + + it('retains a newly applied range after the parent accepts both updates', async () => { + await chooseDraft() + await wrapper.get('.date-picker-apply').trigger('click') + await nextTick() + expect(wrapper.emitted('change')).toEqual([[{ startDate: '2026-09-07', endDate: '2026-09-13', preset: '7days' }]]) + expect(wrapper.get('.date-picker-trigger').text()).toContain('dates.last7Days') + await wrapper.get('.date-picker-trigger').trigger('click') + expect(wrapper.findAll('input[type="date"]').map(input => input.element.value)) + .toEqual(['2026-09-07', '2026-09-13']) + }) +})