Keep/keep-notes/tests/settings.spec.ts
sepehr ddb67ba9e5 fix: unify theme system - fix theme switching persistence
- Unified localStorage key to 'theme-preference' across all components
- Fixed header.tsx using wrong localStorage key ('theme' instead of 'theme-preference')
- Added localStorage hybrid persistence for instant theme changes
- Removed router.refresh() which was causing stale data revert
- Replaced Blue theme with Sepia
- Consolidated auth() calls to prevent race conditions
- Updated UserSettingsData types to include all themes
2026-01-18 22:33:41 +01:00

569 lines
21 KiB
TypeScript

import { test, expect } from '@playwright/test'
/**
* Tests complets pour les Settings UX (Story 11-2)
*
* Ce fichier teste toutes les fonctionnalités implémentées:
* - General Settings: Notifications (Email), Privacy (Analytics)
* - Profile Settings: Language, Font Size, Show Recent Notes
* - Appearance Settings: Theme Persistence (Light/Dark/Auto)
* - SettingsSearch: Functional search with filtering
*
* Prérequis:
* - Être connecté avec un compte utilisateur
* - Avoir accès aux pages de settings
* - Base de données avec les nouveaux champs (emailNotifications, anonymousAnalytics)
*/
test.describe('Settings UX - Story 11-2', () => {
// Variables pour stocker les credentials
let email: string
let password: string
test.beforeAll(async ({ browser }) => {
// Les credentials seront fournis par l'utilisateur
console.log('Credentials nécessaires pour exécuter les tests')
console.log('Veuillez fournir email et password')
})
test.beforeEach(async ({ page }) => {
// Se connecter avant chaque test
await page.goto('http://localhost:3000/login')
await page.fill('input[name="email"]', email)
await page.fill('input[name="password"]', password)
await page.click('button[type="submit"]')
// Attendre la connexion et vérifier qu'on est sur la page d'accueil
await page.waitForURL('**/main**')
await expect(page).toHaveURL(/\/main/)
})
/**
* Tests pour General Settings
*/
test.describe('General Settings', () => {
test('devrait afficher la page General Settings', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Vérifier le titre de la page
const title = await page.textContent('h1')
expect(title).toContain('General')
// Vérifier que les sections sont présentes
await expect(page.locator('#language')).toBeVisible()
await expect(page.locator('#notifications')).toBeVisible()
await expect(page.locator('#privacy')).toBeVisible()
})
test('devrait avoir le toggle Email Notifications', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Scroll vers la section notifications
await page.locator('#notifications').scrollIntoViewIfNeeded()
// Vérifier que le toggle est présent
const emailToggle = page.getByRole('switch', { name: /email notifications/i })
await expect(emailToggle).toBeVisible()
})
test('devrait pouvoir activer/désactiver Email Notifications', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Scroll vers la section notifications
await page.locator('#notifications').scrollIntoViewIfNeeded()
// Récupérer l'état initial du toggle
const emailToggle = page.getByRole('switch', { name: /email notifications/i })
const initialState = await emailToggle.getAttribute('aria-checked')
const initialEnabled = initialState === 'true'
// Cliquer sur le toggle
await emailToggle.click()
// Attendre un peu pour l'opération asynchrone
await page.waitForTimeout(500)
// Vérifier que l'état a changé
const newState = await emailToggle.getAttribute('aria-checked')
const newEnabled = newState === 'true'
expect(newEnabled).toBe(!initialEnabled)
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
})
test('devrait avoir le toggle Anonymous Analytics', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Scroll vers la section privacy
await page.locator('#privacy').scrollIntoViewIfNeeded()
// Vérifier que le toggle est présent
const analyticsToggle = page.getByRole('switch', { name: /anonymous analytics/i })
await expect(analyticsToggle).toBeVisible()
})
test('devrait pouvoir activer/désactiver Anonymous Analytics', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Scroll vers la section privacy
await page.locator('#privacy').scrollIntoViewIfNeeded()
// Récupérer l'état initial du toggle
const analyticsToggle = page.getByRole('switch', { name: /anonymous analytics/i })
const initialState = await analyticsToggle.getAttribute('aria-checked')
const initialEnabled = initialState === 'true'
// Cliquer sur le toggle
await analyticsToggle.click()
// Attendre un peu pour l'opération asynchrone
await page.waitForTimeout(500)
// Vérifier que l'état a changé
const newState = await analyticsToggle.getAttribute('aria-checked')
const newEnabled = newState === 'true'
expect(newEnabled).toBe(!initialEnabled)
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
})
test('devrait avoir le composant SettingsSearch', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Vérifier que la barre de recherche est présente
const searchInput = page.getByPlaceholder(/search/i)
await expect(searchInput).toBeVisible()
// Vérifier l'icône de recherche
await expect(page.locator('svg').filter({ hasText: '' }).first()).toBeVisible()
})
test('devrait filtrer les sections avec SettingsSearch', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
// Cliquer sur la barre de recherche
const searchInput = page.getByPlaceholder(/search/i)
await searchInput.click()
// Taper "notification"
await searchInput.fill('notification')
// Vérifier que la section notifications est visible
await expect(page.locator('#notifications')).toBeVisible()
// Vérifier que les autres sections ne sont plus visibles (ou sont filtrées)
// Note: Cela dépend de l'implémentation exacte du filtrage
})
test('devrait effacer la recherche avec le bouton X', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
const searchInput = page.getByPlaceholder(/search/i)
await searchInput.fill('test')
// Vérifier que le bouton X apparaît
const clearButton = page.getByRole('button', { name: /clear search/i })
await expect(clearButton).toBeVisible()
// Cliquer sur le bouton X
await clearButton.click()
// Vérifier que la recherche est vide
await expect(searchInput).toHaveValue('')
})
test('devrait effacer la recherche avec la touche Escape', async ({ page }) => {
await page.goto('http://localhost:3000/settings/general')
const searchInput = page.getByPlaceholder(/search/i)
await searchInput.fill('test')
// Appuyer sur Escape
await page.keyboard.press('Escape')
// Vérifier que la recherche est vide
await expect(searchInput).toHaveValue('')
})
})
/**
* Tests pour Profile Settings
*/
test.describe('Profile Settings', () => {
test('devrait afficher la page Profile Settings', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Vérifier le titre de la page
const title = await page.textContent('h1')
expect(title).toContain('Profile')
// Vérifier les sections
await expect(page.getByText(/display name/i)).toBeVisible()
await expect(page.getByText(/email/i)).toBeVisible()
await expect(page.getByText(/language preferences/i)).toBeVisible()
await expect(page.getByText(/display settings/i)).toBeVisible()
await expect(page.getByText(/change password/i)).toBeVisible()
})
test('devrait avoir le sélecteur de langue', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Scroll vers la section language
await page.getByText(/language preferences/i).scrollIntoViewIfNeeded()
// Vérifier que le sélecteur est présent
const languageSelect = page.locator('#language')
await expect(languageSelect).toBeVisible()
})
test('devrait pouvoir changer la langue', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Scroll vers la section language
await page.getByText(/language preferences/i).scrollIntoViewIfNeeded()
// Cliquer sur le sélecteur
const languageSelect = page.locator('#language')
await languageSelect.click()
// Sélectionner une langue (ex: français)
await page.getByRole('option', { name: /français/i }).click()
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
})
test('devrait avoir le sélecteur de taille de police', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Scroll vers la section display settings
await page.getByText(/display settings/i).scrollIntoViewIfNeeded()
// Vérifier que le sélecteur est présent
const fontSizeSelect = page.locator('#fontSize')
await expect(fontSizeSelect).toBeVisible()
})
test('devrait pouvoir changer la taille de police', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Scroll vers la section display settings
await page.getByText(/display settings/i).scrollIntoViewIfNeeded()
// Cliquer sur le sélecteur
const fontSizeSelect = page.locator('#fontSize')
await fontSizeSelect.click()
// Sélectionner une taille (ex: large)
await page.getByRole('option', { name: /large/i }).click()
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
// Vérifier que la taille de police a changé (vérifier la variable CSS)
const rootFontSize = await page.evaluate(() => {
return getComputedStyle(document.documentElement).getPropertyValue('--user-font-size')
})
expect(rootFontSize).toBeTruthy()
})
test('devrait avoir le toggle Show Recent Notes', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Scroll vers la section display settings
await page.getByText(/display settings/i).scrollIntoViewIfNeeded()
// Vérifier que le toggle est présent
const recentNotesToggle = page.getByRole('switch', { name: /show recent notes/i })
await expect(recentNotesToggle).toBeVisible()
})
test('devrait pouvoir activer/désactiver Show Recent Notes', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Scroll vers la section display settings
await page.getByText(/display settings/i).scrollIntoViewIfNeeded()
// Récupérer l'état initial du toggle
const recentNotesToggle = page.getByRole('switch', { name: /show recent notes/i })
const initialState = await recentNotesToggle.getAttribute('aria-checked')
const initialEnabled = initialState === 'true'
// Cliquer sur le toggle
await recentNotesToggle.click()
// Attendre un peu pour l'opération asynchrone
await page.waitForTimeout(500)
// Vérifier que l'état a changé
const newState = await recentNotesToggle.getAttribute('aria-checked')
const newEnabled = newState === 'true'
expect(newEnabled).toBe(!initialEnabled)
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
})
test('devrait pouvoir changer le nom d\'affichage', async ({ page }) => {
await page.goto('http://localhost:3000/settings/profile')
// Remplir le champ nom
const nameInput = page.getByLabel(/display name/i)
const newName = 'Test User ' + Date.now()
await nameInput.fill(newName)
// Soumettre le formulaire
await page.getByRole('button', { name: /save/i }).click()
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
// Recharger la page et vérifier que le nom a été sauvegardé
await page.reload()
await expect(nameInput).toHaveValue(newName)
})
})
/**
* Tests pour Appearance Settings
*/
test.describe('Appearance Settings', () => {
test('devrait afficher la page Appearance Settings', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
// Vérifier le titre de la page
const title = await page.textContent('h1')
expect(title).toContain('Appearance')
// Vérifier les sections
await expect(page.getByText(/theme/i)).toBeVisible()
await expect(page.getByText(/typography/i)).toBeVisible()
})
test('devrait avoir le sélecteur de thème', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
// Vérifier que le sélecteur est présent
const themeSelect = page.locator('#theme')
await expect(themeSelect).toBeVisible()
})
test('devrait pouvoir changer le thème pour Light', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
// Cliquer sur le sélecteur de thème
const themeSelect = page.locator('#theme')
await themeSelect.click()
// Sélectionner "Light"
await page.getByRole('option', { name: /light/i }).click()
// Vérifier que le thème est appliqué immédiatement
await expect(page.locator('html')).toHaveClass(/light/)
await expect(page.locator('html')).not.toHaveClass(/dark/)
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
// Vérifier que le thème est sauvegardé dans localStorage
const localStorageTheme = await page.evaluate(() => {
return localStorage.getItem('theme')
})
expect(localStorageTheme).toBe('light')
})
test('devrait pouvoir changer le thème pour Dark', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
// Cliquer sur le sélecteur de thème
const themeSelect = page.locator('#theme')
await themeSelect.click()
// Sélectionner "Dark"
await page.getByRole('option', { name: /dark/i }).click()
// Vérifier que le thème est appliqué immédiatement
await expect(page.locator('html')).toHaveClass(/dark/)
await expect(page.locator('html')).not.toHaveClass(/light/)
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
// Vérifier que le thème est sauvegardé dans localStorage
const localStorageTheme = await page.evaluate(() => {
return localStorage.getItem('theme')
})
expect(localStorageTheme).toBe('dark')
})
test('devrait pouvoir changer le thème pour Auto', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
// Cliquer sur le sélecteur de thème
const themeSelect = page.locator('#theme')
await themeSelect.click()
// Sélectionner "Auto"
await page.getByRole('option', { name: /auto/i }).click()
// Vérifier qu'un toast de succès apparaît
await expect(page.getByText(/success/i)).toBeVisible({ timeout: 3000 })
// Vérifier que le thème est sauvegardé dans localStorage
const localStorageTheme = await page.evaluate(() => {
return localStorage.getItem('theme')
})
expect(localStorageTheme).toBe('auto')
})
test('devrait charger le thème depuis localStorage', async ({ page }) => {
// Définir le thème dans localStorage avant de charger la page
await page.goto('about:blank')
await page.evaluate(() => {
localStorage.setItem('theme', 'dark')
})
// Aller sur la page Appearance Settings
await page.goto('http://localhost:3000/settings/appearance')
// Attendre que la page charge
await page.waitForLoadState('networkidle')
// Vérifier que le thème est chargé et appliqué
const themeSelect = page.locator('#theme')
await expect(themeSelect).toHaveValue('dark')
// Vérifier que le thème est appliqué au DOM
await expect(page.locator('html')).toHaveClass(/dark/)
})
test('devrait persister le thème après rechargement de page', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
// Changer le thème pour dark
const themeSelect = page.locator('#theme')
await themeSelect.click()
await page.getByRole('option', { name: /dark/i }).click()
// Attendre que le thème soit appliqué
await expect(page.locator('html')).toHaveClass(/dark/)
// Recharger la page
await page.reload()
await page.waitForLoadState('networkidle')
// Vérifier que le thème est toujours appliqué
await expect(page.locator('html')).toHaveClass(/dark/)
await expect(themeSelect).toHaveValue('dark')
})
})
/**
* Tests d'intégration cross-pages
*/
test.describe('Integration Tests', () => {
test('devrait naviguer entre les pages de settings', async ({ page }) => {
// Commencer sur General Settings
await page.goto('http://localhost:3000/settings/general')
// Cliquer sur Appearance dans la navigation
await page.getByRole('link', { name: /appearance/i }).click()
await expect(page).toHaveURL(/\/settings\/appearance/)
// Cliquer sur Profile dans la navigation
await page.getByRole('link', { name: /profile/i }).click()
await expect(page).toHaveURL(/\/settings\/profile/)
// Cliquer sur General dans la navigation
await page.getByRole('link', { name: /general/i }).click()
await expect(page).toHaveURL(/\/settings\/general/)
})
test('devrait persister les settings entre les pages', async ({ page }) => {
// Changer le thème sur Appearance Settings
await page.goto('http://localhost:3000/settings/appearance')
const themeSelect = page.locator('#theme')
await themeSelect.click()
await page.getByRole('option', { name: /dark/i }).click()
await expect(page.locator('html')).toHaveClass(/dark/)
// Aller sur General Settings et vérifier que le thème est toujours appliqué
await page.goto('http://localhost:3000/settings/general')
await expect(page.locator('html')).toHaveClass(/dark/)
// Aller sur Profile Settings et vérifier que le thème est toujours appliqué
await page.goto('http://localhost:3000/settings/profile')
await expect(page.locator('html')).toHaveClass(/dark/)
})
})
/**
* Tests de responsive design
*/
test.describe('Responsive Design', () => {
test('devrait fonctionner sur mobile', async ({ page }) => {
// Simuler un viewport mobile
await page.setViewportSize({ width: 375, height: 667 })
await page.goto('http://localhost:3000/settings/general')
// Vérifier que la page est utilisable
await expect(page.locator('h1')).toBeVisible()
await expect(page.getByPlaceholder(/search/i)).toBeVisible()
})
test('devrait fonctionner sur tablet', async ({ page }) => {
// Simuler un viewport tablet
await page.setViewportSize({ width: 768, height: 1024 })
await page.goto('http://localhost:3000/settings/general')
// Vérifier que la page est utilisable
await expect(page.locator('h1')).toBeVisible()
await expect(page.getByPlaceholder(/search/i)).toBeVisible()
})
test('devrait fonctionner sur desktop', async ({ page }) => {
// Simuler un viewport desktop
await page.setViewportSize({ width: 1280, height: 800 })
await page.goto('http://localhost:3000/settings/general')
// Vérifier que la page est utilisable
await expect(page.locator('h1')).toBeVisible()
await expect(page.getByPlaceholder(/search/i)).toBeVisible()
})
})
/**
* Tests de performance
*/
test.describe('Performance Tests', () => {
test('devrait charger rapidement General Settings', async ({ page }) => {
const startTime = Date.now()
await page.goto('http://localhost:3000/settings/general')
await page.waitForLoadState('networkidle')
const loadTime = Date.now() - startTime
// La page devrait charger en moins de 2 secondes
expect(loadTime).toBeLessThan(2000)
})
test('devrait appliquer le thème rapidement', async ({ page }) => {
await page.goto('http://localhost:3000/settings/appearance')
const themeSelect = page.locator('#theme')
const startTime = Date.now()
await themeSelect.click()
await page.getByRole('option', { name: /dark/i }).click()
await page.waitForSelector('html.dark', { timeout: 1000 })
const applyTime = Date.now() - startTime
// Le thème devrait être appliqué en moins de 500ms
expect(applyTime).toBeLessThan(500)
})
})
})