From 89df7ebf469c190022f97000718c7713640f0f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20TR=C3=89BEL=20=28Perso=29?= Date: Fri, 9 Oct 2026 16:51:55 +0200 Subject: [PATCH] fix(client): preserve dedicated cluster project selection Generated with AI assistance, reviewed by @stephanetrebel Refs #2838 --- .../src/components/ChoiceSelector.spec.ts | 74 +++++++++++++++++++ apps/client/src/components/ChoiceSelector.vue | 15 ++-- playwright/e2e-tests/clusters.spec.ts | 8 ++ 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 apps/client/src/components/ChoiceSelector.spec.ts diff --git a/apps/client/src/components/ChoiceSelector.spec.ts b/apps/client/src/components/ChoiceSelector.spec.ts new file mode 100644 index 0000000000..fd7db7d41f --- /dev/null +++ b/apps/client/src/components/ChoiceSelector.spec.ts @@ -0,0 +1,74 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { createApp, h, nextTick, reactive } from 'vue' +import ChoiceSelector from './ChoiceSelector.vue' + +const apps: ReturnType[] = [] +const containers: HTMLElement[] = [] + +afterEach(() => { + apps.forEach(app => app.unmount()) + containers.forEach(container => container.remove()) + apps.length = 0 + containers.length = 0 +}) + +function mountSelector(optionsSelected: string[] = [], initialOptions = Array.from({ length: 7 }, (_, index) => ({ id: `project-${index}`, label: `Projet ${index}` }))) { + const state = reactive({ options: initialOptions, optionsSelected }) + const onUpdate = vi.fn() + const container = document.createElement('div') + document.body.append(container) + const app = createApp({ + render: () => h(ChoiceSelector, { + id: 'projects-select', + options: state.options, + optionsSelected: state.options.filter(option => state.optionsSelected.includes(option.id)), + label: 'Projets associés', + description: '', + labelKey: 'label', + valueKey: 'id', + wrapped: false, + onUpdate, + }), + }) + app.mount(container) + apps.push(app) + containers.push(container) + return { container, onUpdate, state } +} + +function click(container: HTMLElement, testId: string) { + const element = container.querySelector(`[data-testid="${testId}"]`) + if (!(element instanceof HTMLElement)) throw new Error(`Élément introuvable : ${testId}`) + element.click() +} + +describe('choiceSelector', () => { + it('affiche les projets déjà associés quand les options arrivent après le montage', async () => { + const { container, onUpdate, state } = mountSelector([], []) + state.options = [{ id: 'project-a', label: 'Projet A' }, { id: 'project-b', label: 'Projet B' }] + state.optionsSelected = ['project-a'] + await nextTick() + + expect(container.querySelector('[data-testid="project-a-projects-select-tag"]')?.closest('.fr-tag--dismiss')).not.toBeNull() + click(container, 'project-b-projects-select-tag') + await nextTick() + expect(onUpdate.mock.calls[0][1]).toEqual(['project-a', 'project-b']) + expect(container.querySelectorAll('.fr-tag--dismiss')).toHaveLength(2) + }) + + it.each([ + ['choice-selector-add-all-projects-select', 7], + ['choice-selector-add-visible-projects-select', 7], + ['choice-selector-remove-all-projects-select', 0], + ['choice-selector-remove-visible-projects-select', 0], + ])('émet la sélection complète après %s', async (testId, expectedCount) => { + const selected = testId.includes('remove') ? Array.from({ length: 7 }, (_, index) => `project-${index}`) : [] + const { container, onUpdate } = mountSelector(selected) + click(container, testId) + await nextTick() + expect(onUpdate).toHaveBeenCalledOnce() + expect(onUpdate.mock.calls[0][1]).toEqual( + expectedCount ? Array.from({ length: 7 }, (_, index) => `project-${index}`) : [], + ) + }) +}) diff --git a/apps/client/src/components/ChoiceSelector.vue b/apps/client/src/components/ChoiceSelector.vue index b615ab6750..e460605f3c 100644 --- a/apps/client/src/components/ChoiceSelector.vue +++ b/apps/client/src/components/ChoiceSelector.vue @@ -1,6 +1,6 @@