Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions apps/client/src/components/ChoiceSelector.spec.ts
Original file line number Diff line number Diff line change
@@ -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<typeof createApp>[] = []
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}`) : [],
)
})
})
15 changes: 10 additions & 5 deletions apps/client/src/components/ChoiceSelector.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" setup generic="T extends Record<string, any>, VALUE extends (Extract<keyof T, string>), LABEL extends (Extract<keyof T, string>)">
import { sortArrByObjKeyAsc } from '@cpn-console/shared'
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'

const props = withDefaults(defineProps<{
options: T[]
Expand Down Expand Up @@ -35,6 +35,10 @@ const displayed = {
selected: computed(() => options.selected.value.filter(option => option[props.labelKey].includes(search.value))),
}

function emitSelection() {
emit('update', options.selected.value, options.selected.value.map(option => option[props.valueKey]))
}

function switchSelection(event: string) {
const eventValue = event
if (!eventValue) return
Expand All @@ -44,7 +48,7 @@ function switchSelection(event: string) {
} else {
selectedValues.value.push(eventValue)
}
emit('update', options.selected.value, options.selected.value.map(option => option[props.valueKey]))
emitSelection()
}

type SwitchMultipleParam = 'notSelected' | 'notSelectedDisplayed' | 'selected' | 'selectedDisplayed'
Expand All @@ -58,11 +62,12 @@ function switchMultiple(choice: SwitchMultipleParam) {
} else if (choice === 'notSelectedDisplayed') {
displayed.notSelected.value.forEach(option => selectedValues.value.push(option[props.valueKey]))
}
emitSelection()
}

onBeforeMount(() => {
selectedValues.value = props.optionsSelected.map(option => option[props.valueKey])
})
watch(() => props.optionsSelected, (selected) => {
selectedValues.value = selected.map(option => option[props.valueKey])
}, { immediate: true })

interface Group {
tagClass: string
Expand Down
8 changes: 8 additions & 0 deletions playwright/e2e-tests/clusters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ test.describe('Clusters page', () => {
await expect(page.getByTestId(`clusterPrivacy-${clusterName}`)).toContainText(
'Dédié',
)
await page.getByTestId(`clusterLink-${clusterName}`).click()
await page.locator('#projects-select').click()
await expect(page.locator('#projects-select .fr-tag--dismiss')).toHaveCount(1)
await page.getByTestId('infosInput').fill('Description modifiée')
await page.getByTestId('updateClusterBtn').click()
await page.getByTestId(`clusterLink-${clusterName}`).click()
await page.locator('#projects-select').click()
await expect(page.locator('#projects-select .fr-tag--dismiss')).toHaveCount(1)
},
)

Expand Down
Loading