test: ajout 17 tests JS app.js, tests entités/handlers complémentaires
Tests JavaScript (17 tests vitest, tests/js/app.test.js) : - Member/Admin checkboxes (3 tests) : member checked déselectionne les autres, admin checked sélectionne tout et déselectionne member, admin unchecked ne fait rien - Stats period selector (2 tests) : custom affiche le range, current le cache - data-confirm forms (2 tests) : confirm annulé empêche soumission, confirm accepté autorise la soumission (window.confirm mocké via vi.fn) - Sidebar dropdown (1 test) : vérifie la structure bouton/menu/arrow - Mobile sidebar (2 tests) : toggle ouvre la sidebar, overlay la ferme - Mobile menu public (1 test) : toggle affiche/cache le menu et bascule les icônes - Cookie banner (4 tests) : affichage sans consent, masqué si déjà accepté, accepter stocke 'accepted' et cache, refuser stocke 'refused' et cache - Tarif tabs (1 test) : clic sur onglet bascule les contenus - Search setup (1 test) : pas d'erreur sans éléments DOM Tests entités complémentaires : - AttestationTest : ajout setEmailTracking avec EmailTracking et null - CustomerTest : ajout vérification getUpdatedAt après setState - ServiceTest : ajout testSetStatusSameStatus (même statut, pas d'historique ajouté) - UserExtendedTest : ajout testAvatarFile avec File réel et null Tests MessageHandlers : - AppLogMessageHandlerTest (2 tests) : avec userId (find user), sans userId (null) - MeilisearchSyncMessageHandlerTest (12 tests) : remove customer/revendeur/price/unknown, index customer/revendeur/price trouvé et non trouvé, index unknown type Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -207,7 +207,7 @@ hadolint_report: ## Lance Hadolint sur le Dockerfile prod et genere le rapport J
|
||||
audit: ## Lance l'audit de securite Composer
|
||||
docker compose -f docker-compose-dev.yml exec php composer audit
|
||||
|
||||
reports: phpstan_report eslint_report test_coverage hadolint_report phpmetrics ## Genere tous les rapports pour SonarQube
|
||||
reports: phpstan_report eslint_report run_test_coverage_js test_coverage hadolint_report phpmetrics ## Genere tous les rapports pour SonarQube
|
||||
|
||||
## —— SonarQube ————————————————————————————————————
|
||||
sonar: reports ## Genere les rapports puis lance le scan SonarQube
|
||||
|
||||
254
tests/js/app.test.js
Normal file
254
tests/js/app.test.js
Normal file
@@ -0,0 +1,254 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
|
||||
describe('app.js DOMContentLoaded', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
const loadApp = async () => {
|
||||
// Reset module cache and re-import
|
||||
vi.resetModules()
|
||||
await import('../../assets/app.js')
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'))
|
||||
}
|
||||
|
||||
describe('Member/Admin checkboxes', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<input type="checkbox" name="groups[]" value="siteconseil_member" checked>
|
||||
<input type="checkbox" name="groups[]" value="siteconseil_admin">
|
||||
<input type="checkbox" name="groups[]" value="esy-web">
|
||||
<input type="checkbox" name="groups[]" value="esy-mail">
|
||||
`
|
||||
})
|
||||
|
||||
it('unchecks other groups when member is checked', async () => {
|
||||
await loadApp()
|
||||
|
||||
const member = document.querySelector('[value="siteconseil_member"]')
|
||||
const admin = document.querySelector('[value="siteconseil_admin"]')
|
||||
const esyWeb = document.querySelector('[value="esy-web"]')
|
||||
|
||||
admin.checked = true
|
||||
esyWeb.checked = true
|
||||
|
||||
member.checked = true
|
||||
member.dispatchEvent(new Event('change'))
|
||||
|
||||
expect(admin.checked).toBe(false)
|
||||
expect(esyWeb.checked).toBe(false)
|
||||
})
|
||||
|
||||
it('checks all groups and unchecks member when admin is checked', async () => {
|
||||
await loadApp()
|
||||
|
||||
const member = document.querySelector('[value="siteconseil_member"]')
|
||||
const admin = document.querySelector('[value="siteconseil_admin"]')
|
||||
const esyWeb = document.querySelector('[value="esy-web"]')
|
||||
|
||||
admin.checked = true
|
||||
admin.dispatchEvent(new Event('change'))
|
||||
|
||||
expect(member.checked).toBe(false)
|
||||
expect(esyWeb.checked).toBe(true)
|
||||
})
|
||||
|
||||
it('does nothing when admin is unchecked', async () => {
|
||||
await loadApp()
|
||||
|
||||
const member = document.querySelector('[value="siteconseil_member"]')
|
||||
const admin = document.querySelector('[value="siteconseil_admin"]')
|
||||
|
||||
member.checked = true
|
||||
admin.checked = false
|
||||
admin.dispatchEvent(new Event('change'))
|
||||
|
||||
expect(member.checked).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Stats period selector', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<select id="stats-period-select">
|
||||
<option value="current">Mois en cours</option>
|
||||
<option value="custom">Personnalise</option>
|
||||
</select>
|
||||
<div id="stats-custom-range" class="hidden"></div>
|
||||
`
|
||||
})
|
||||
|
||||
it('shows custom range when custom is selected', async () => {
|
||||
await loadApp()
|
||||
const select = document.getElementById('stats-period-select')
|
||||
const range = document.getElementById('stats-custom-range')
|
||||
|
||||
select.value = 'custom'
|
||||
select.dispatchEvent(new Event('change'))
|
||||
|
||||
expect(range.classList.contains('hidden')).toBe(false)
|
||||
})
|
||||
|
||||
it('hides custom range when current is selected', async () => {
|
||||
await loadApp()
|
||||
const select = document.getElementById('stats-period-select')
|
||||
const range = document.getElementById('stats-custom-range')
|
||||
|
||||
select.value = 'current'
|
||||
select.dispatchEvent(new Event('change'))
|
||||
|
||||
expect(range.classList.contains('hidden')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-confirm forms', () => {
|
||||
it('prevents submission when confirm is cancelled', async () => {
|
||||
document.body.innerHTML = '<form data-confirm="Etes-vous sur ?"><button type="submit">Submit</button></form>'
|
||||
window.confirm = vi.fn(() => false)
|
||||
|
||||
await loadApp()
|
||||
const form = document.querySelector('form')
|
||||
const event = new Event('submit', { cancelable: true })
|
||||
form.dispatchEvent(event)
|
||||
|
||||
expect(event.defaultPrevented).toBe(true)
|
||||
})
|
||||
|
||||
it('allows submission when confirm is accepted', async () => {
|
||||
document.body.innerHTML = '<form data-confirm="Etes-vous sur ?"><button type="submit">Submit</button></form>'
|
||||
window.confirm = vi.fn(() => true)
|
||||
|
||||
await loadApp()
|
||||
const form = document.querySelector('form')
|
||||
const event = new Event('submit', { cancelable: true })
|
||||
form.dispatchEvent(event)
|
||||
|
||||
expect(event.defaultPrevented).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sidebar dropdown', () => {
|
||||
it('registers click handlers on dropdown buttons', async () => {
|
||||
document.body.innerHTML = `<div><button class="sidebar-dropdown-btn"><span class="sidebar-dropdown-arrow"></span></button><ul class="hidden">Menu</ul></div>`
|
||||
await loadApp()
|
||||
|
||||
const btn = document.querySelector('.sidebar-dropdown-btn')
|
||||
// Verify the button exists and has the expected structure
|
||||
expect(btn).not.toBeNull()
|
||||
expect(btn.querySelector('.sidebar-dropdown-arrow')).not.toBeNull()
|
||||
expect(btn.nextElementSibling).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Mobile sidebar', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<button id="admin-sidebar-toggle"></button>
|
||||
<div id="admin-sidebar"></div>
|
||||
<div id="admin-overlay"></div>
|
||||
`
|
||||
})
|
||||
|
||||
it('opens sidebar on toggle click', async () => {
|
||||
await loadApp()
|
||||
document.getElementById('admin-sidebar-toggle').click()
|
||||
expect(document.getElementById('admin-sidebar').classList.contains('open')).toBe(true)
|
||||
})
|
||||
|
||||
it('closes sidebar on overlay click', async () => {
|
||||
await loadApp()
|
||||
const sidebar = document.getElementById('admin-sidebar')
|
||||
sidebar.classList.add('open')
|
||||
|
||||
document.getElementById('admin-overlay').click()
|
||||
expect(sidebar.classList.contains('open')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Mobile menu (public)', () => {
|
||||
it('toggles mobile menu and icons', async () => {
|
||||
document.body.innerHTML = `
|
||||
<button id="mobile-menu-btn"></button>
|
||||
<div id="mobile-menu" class="hidden"></div>
|
||||
<span id="menu-icon-open"></span>
|
||||
<span id="menu-icon-close" class="hidden"></span>
|
||||
`
|
||||
await loadApp()
|
||||
|
||||
document.getElementById('mobile-menu-btn').click()
|
||||
|
||||
expect(document.getElementById('mobile-menu').classList.contains('hidden')).toBe(false)
|
||||
expect(document.getElementById('menu-icon-open').classList.contains('hidden')).toBe(true)
|
||||
expect(document.getElementById('menu-icon-close').classList.contains('hidden')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Cookie banner', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<div id="cookie-banner" class="hidden"></div>
|
||||
<button id="cookie-accept"></button>
|
||||
<button id="cookie-refuse"></button>
|
||||
`
|
||||
})
|
||||
|
||||
it('shows banner when no consent', async () => {
|
||||
await loadApp()
|
||||
expect(document.getElementById('cookie-banner').classList.contains('hidden')).toBe(false)
|
||||
})
|
||||
|
||||
it('hides banner when already accepted', async () => {
|
||||
localStorage.setItem('cookie_consent', 'accepted')
|
||||
await loadApp()
|
||||
expect(document.getElementById('cookie-banner').classList.contains('hidden')).toBe(true)
|
||||
})
|
||||
|
||||
it('hides banner and stores accepted on accept click', async () => {
|
||||
await loadApp()
|
||||
document.getElementById('cookie-accept').click()
|
||||
|
||||
expect(document.getElementById('cookie-banner').classList.contains('hidden')).toBe(true)
|
||||
expect(localStorage.getItem('cookie_consent')).toBe('accepted')
|
||||
})
|
||||
|
||||
it('hides banner and stores refused on refuse click', async () => {
|
||||
await loadApp()
|
||||
document.getElementById('cookie-refuse').click()
|
||||
|
||||
expect(document.getElementById('cookie-banner').classList.contains('hidden')).toBe(true)
|
||||
expect(localStorage.getItem('cookie_consent')).toBe('refused')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tarif tabs', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<div id="tarif-tabs">
|
||||
<button data-tab="ndd">NDD</button>
|
||||
<button data-tab="mail">Mail</button>
|
||||
</div>
|
||||
<div id="content-ndd">NDD content</div>
|
||||
<div id="content-mail" class="hidden">Mail content</div>
|
||||
`
|
||||
})
|
||||
|
||||
it('switches tabs on click', async () => {
|
||||
await loadApp()
|
||||
|
||||
document.querySelector('[data-tab="mail"]').click()
|
||||
|
||||
expect(document.getElementById('content-ndd').classList.contains('hidden')).toBe(true)
|
||||
expect(document.getElementById('content-mail').classList.contains('hidden')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderHit and performSearch', () => {
|
||||
it('search setup does nothing without elements', async () => {
|
||||
document.body.innerHTML = ''
|
||||
await loadApp()
|
||||
// No error thrown
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user