diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 278a4af..8ae878a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,12 @@ "Bash(mv InputSelect.story.vue selectCheckbox.story.vue select/)", "Bash(mv inputCheckbox.story.vue checkbox/)", "Bash(npx eslint *)", - "Bash(echo \"LINT EXIT: $?\")" + "Bash(echo \"LINT EXIT: $?\")", + "Bash(git commit *)", + "mcp__chrome__navigate_page", + "mcp__chrome__take_snapshot", + "mcp__chrome__click", + "mcp__chrome__evaluate_script" ] } } diff --git a/app/components/malio/date/DateTime.test.ts b/app/components/malio/date/DateTime.test.ts index ccb7409..803b861 100644 --- a/app/components/malio/date/DateTime.test.ts +++ b/app/components/malio/date/DateTime.test.ts @@ -31,7 +31,7 @@ const mountDateTime = (props: DateTimeProps = {}) => describe('MalioDateTime', () => { beforeEach(() => { vi.useFakeTimers() - vi.setSystemTime(new Date(2026, 4, 19)) // 19 mai 2026 + vi.setSystemTime(new Date(2026, 4, 19, 9, 5, 0)) // 19 mai 2026, 09:05 }) afterEach(() => vi.useRealTimers()) @@ -60,11 +60,12 @@ describe('MalioDateTime', () => { }) describe('sélection', () => { - it('émet le jour à 00:00 et garde le popover ouvert', async () => { + it('émet le jour à l\'heure actuelle (si aucune heure choisie) et garde le popover ouvert', async () => { const wrapper = mountDateTime() await wrapper.get('[data-test="date-input"]').trigger('click') await wrapper.get('[data-test="day"][data-iso="2026-05-19"]').trigger('click') - expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['2026-05-19T00:00:00']) + // heure système figée à 09:05 + expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['2026-05-19T09:05:00']) expect(wrapper.find('[data-test="popover"]').exists()).toBe(true) }) diff --git a/app/components/malio/date/DateTime.vue b/app/components/malio/date/DateTime.vue index 345fc02..fb62f46 100644 --- a/app/components/malio/date/DateTime.vue +++ b/app/components/malio/date/DateTime.vue @@ -28,10 +28,12 @@ :max="max?.slice(0, 10)" @select="onSelectDay" /> -
{
api().onKeydown(new KeyboardEvent('keydown', {key: 'ArrowUp'}))
expect(changes.at(-1)).toBe(23)
})
+
+ // Anti-boucle navigateur : un scroll programmatique déclenche une rafale d'évènements
+ // scroll (animation/snap). Ils ne doivent PAS être pris pour du scroll utilisateur,
+ // sinon settle() ré-émet en boucle et corrompt le patch DOM de Vue.
+ it('n\'émet pas en double quand un scroll programmatique déclenche une rafale de scroll', async () => {
+ vi.useFakeTimers()
+ try {
+ const changes: number[] = []
+ const {wrapper, api} = mountWheelHarness(9, (i) => changes.push(i))
+ await nextTick()
+ const el = wrapper.element as HTMLElement
+ changes.length = 0
+
+ api().scrollToIndex(12)
+
+ el.dispatchEvent(new Event('scroll'))
+ el.dispatchEvent(new Event('scroll'))
+ el.dispatchEvent(new Event('scroll'))
+
+ vi.advanceTimersByTime(300)
+
+ expect(changes).toEqual([12])
+ }
+ finally {
+ vi.useRealTimers()
+ }
+ })
})
diff --git a/app/components/malio/time/composables/useInfiniteWheel.ts b/app/components/malio/time/composables/useInfiniteWheel.ts
index ecc48bb..97849e6 100644
--- a/app/components/malio/time/composables/useInfiniteWheel.ts
+++ b/app/components/malio/time/composables/useInfiniteWheel.ts
@@ -36,8 +36,25 @@ export function useInfiniteWheel(
options: UseInfiniteWheelOptions,
) {
const centeredIndex = ref(options.initialIndex())
- let programmatic = false
let scrollEndTimer: ReturnType