From 3d7b698397352ba3d56ab60daa87de7531de3145 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 30 Apr 2026 11:33:33 +0200 Subject: [PATCH] feat(ui-web): set modern as default skin --- ui/web/README.md | 4 +- ui/web/src/core/Constants.ts | 2 +- ui/web/src/router/index.ts | 8 +- .../unit/shared/composables/useSkin.test.ts | 92 +++++++++---------- ui/web/tests/unit/skins/registry.test.ts | 4 +- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/ui/web/README.md b/ui/web/README.md index 34fb1917..f63cb893 100644 --- a/ui/web/README.md +++ b/ui/web/README.md @@ -122,7 +122,7 @@ The `uiServer` field accepts an array to connect to multiple simulator instances | Field | Type | Required | Description | | ------------------------- | ----------------------- | -------- | ----------------------------------------- | -| `skin` | `string` | No | Skin name (default: `classic`) | +| `skin` | `string` | No | Skin name (default: `modern`) | | `theme` | `string` | No | Theme name (default: `tokyo-night-storm`) | | `host` | `string` | Yes | Simulator UI server hostname | | `port` | `number` | Yes | Simulator UI server port | @@ -161,7 +161,7 @@ Set `skin` in `config.json` to select the default UI layout. Users can switch sk | `classic` | Table-based rows with a sticky sidebar for forms. | | `modern` | Responsive card grid with modal dialogs. | -Default: `classic`. To add a skin, create `src/skins//`, implement a root layout component, and register it in `src/skins/registry.ts`. +Default: `modern`. To add a skin, create `src/skins//`, implement a root layout component, and register it in `src/skins/registry.ts`. ## Getting started diff --git a/ui/web/src/core/Constants.ts b/ui/web/src/core/Constants.ts index 854f43c2..d9a4c7fe 100644 --- a/ui/web/src/core/Constants.ts +++ b/ui/web/src/core/Constants.ts @@ -3,7 +3,7 @@ import { type SKIN_IDS } from 'ui-common' // Local UI project constants export const ASYNC_COMPONENT_DELAY_MS = 200 -export const DEFAULT_SKIN: (typeof SKIN_IDS)[number] = 'classic' +export const DEFAULT_SKIN: (typeof SKIN_IDS)[number] = 'modern' export const ASYNC_COMPONENT_TIMEOUT_MS = 10_000 export const EMPTY_VALUE_PLACEHOLDER = 'Ø' export const MAX_SKIN_ERROR_RELOADS = 2 diff --git a/ui/web/src/router/index.ts b/ui/web/src/router/index.ts index bd4ff989..43d1d58a 100644 --- a/ui/web/src/router/index.ts +++ b/ui/web/src/router/index.ts @@ -3,7 +3,7 @@ import { h } from 'vue' import { createRouter, createWebHistory, type RouteLocationNormalized } from 'vue-router' import { useToast } from 'vue-toast-notification' -import { DEFAULT_SKIN, ROUTE_NAMES } from '@/core/index.js' +import { ROUTE_NAMES } from '@/core/index.js' import { useSkin } from '@/shared/composables/useSkin.js' declare module 'vue-router' { @@ -56,7 +56,7 @@ export const router = createRouter({ components: { action: () => import('@/skins/classic/components/actions/AddChargingStations.vue'), }, - meta: { skinOnly: DEFAULT_SKIN }, + meta: { skinOnly: 'classic' }, name: ROUTE_NAMES.ADD_CHARGING_STATIONS, path: '/add-charging-stations', }, @@ -65,7 +65,7 @@ export const router = createRouter({ components: { action: () => import('@/skins/classic/components/actions/SetSupervisionUrl.vue'), }, - meta: { skinOnly: DEFAULT_SKIN }, + meta: { skinOnly: 'classic' }, name: ROUTE_NAMES.SET_SUPERVISION_URL, path: '/set-supervision-url/:hashId/:chargingStationId', props: { action: true }, @@ -75,7 +75,7 @@ export const router = createRouter({ components: { action: () => import('@/skins/classic/components/actions/StartTransaction.vue'), }, - meta: { skinOnly: DEFAULT_SKIN }, + meta: { skinOnly: 'classic' }, name: ROUTE_NAMES.START_TRANSACTION, path: '/start-transaction/:hashId/:chargingStationId/:connectorId', props: { action: true }, diff --git a/ui/web/tests/unit/shared/composables/useSkin.test.ts b/ui/web/tests/unit/shared/composables/useSkin.test.ts index ad2c8da1..44ad5fdf 100644 --- a/ui/web/tests/unit/shared/composables/useSkin.test.ts +++ b/ui/web/tests/unit/shared/composables/useSkin.test.ts @@ -29,8 +29,8 @@ describe('useSkin', () => { beforeEach(async () => { // Reset module-level singleton state to default const { activeSkinId, switchSkin } = useSkin() - if (activeSkinId.value !== 'classic') { - await switchSkin('classic') + if (activeSkinId.value !== DEFAULT_SKIN) { + await switchSkin(DEFAULT_SKIN) } localStorage.clear() }) @@ -57,41 +57,41 @@ describe('useSkin', () => { }) it('should not update activeSkinId when loadStyles rejects', async () => { - const modernSkin = skins.find(s => s.id === 'modern') - expect(modernSkin).toBeDefined() - if (modernSkin == null) return - vi.mocked(modernSkin.loadStyles).mockRejectedValueOnce(new Error('CSS not found')) + const classicSkin = skins.find(s => s.id === 'classic') + expect(classicSkin).toBeDefined() + if (classicSkin == null) return + vi.mocked(classicSkin.loadStyles).mockRejectedValueOnce(new Error('CSS not found')) const { activeSkinId, switchSkin } = useSkin() - const result = await switchSkin('modern') + const result = await switchSkin('classic') expect(result).toBe(false) - expect(activeSkinId.value).toBe('classic') + expect(activeSkinId.value).toBe('modern') expect(localStorage.getItem('ecs-ui-skin')).toBeNull() }) it('should populate lastError on skin load failure', async () => { - const modernSkin = skins.find(s => s.id === 'modern') - expect(modernSkin).toBeDefined() - if (modernSkin == null) return - vi.mocked(modernSkin.loadStyles).mockRejectedValueOnce(new Error('Network error')) + const classicSkin = skins.find(s => s.id === 'classic') + expect(classicSkin).toBeDefined() + if (classicSkin == null) return + vi.mocked(classicSkin.loadStyles).mockRejectedValueOnce(new Error('Network error')) const { lastError, switchSkin } = useSkin() - await switchSkin('modern') + await switchSkin('classic') expect(lastError.value).toBe('Network error') }) it('should set isSwitching to true during async load', async () => { - const modernSkin = skins.find(s => s.id === 'modern') - expect(modernSkin).toBeDefined() - if (modernSkin == null) return - vi.mocked(modernSkin.loadStyles).mockClear() + const classicSkin = skins.find(s => s.id === 'classic') + expect(classicSkin).toBeDefined() + if (classicSkin == null) return + vi.mocked(classicSkin.loadStyles).mockClear() let rejectLoad!: (err: Error) => void - vi.mocked(modernSkin.loadStyles).mockImplementationOnce( + vi.mocked(classicSkin.loadStyles).mockImplementationOnce( () => new Promise((_resolve, reject) => { rejectLoad = reject }) ) const { isSwitching, switchSkin } = useSkin() - const promise = switchSkin('modern') + const promise = switchSkin('classic') expect(isSwitching.value).toBe(true) rejectLoad(new Error('test cleanup')) await promise @@ -100,48 +100,48 @@ describe('useSkin', () => { it('should guard against concurrent switchSkin calls', async () => { const { activeSkinId, switchSkin } = useSkin() - const modernSkin = skins.find(s => s.id === 'modern') - expect(modernSkin).toBeDefined() - if (modernSkin == null) return - vi.mocked(modernSkin.loadStyles).mockClear() + const classicSkin = skins.find(s => s.id === 'classic') + expect(classicSkin).toBeDefined() + if (classicSkin == null) return + vi.mocked(classicSkin.loadStyles).mockClear() let resolveLoad!: () => void - vi.mocked(modernSkin.loadStyles).mockImplementationOnce( + vi.mocked(classicSkin.loadStyles).mockImplementationOnce( () => new Promise(resolve => { resolveLoad = resolve }) ) - const first = switchSkin('modern') - const second = switchSkin('modern') - expect(activeSkinId.value).toBe('classic') + const first = switchSkin('classic') + const second = switchSkin('classic') + expect(activeSkinId.value).toBe('modern') resolveLoad() await first await second - expect(activeSkinId.value).toBe('modern') - expect(modernSkin.loadStyles).toHaveBeenCalledTimes(1) + expect(activeSkinId.value).toBe('classic') + expect(classicSkin.loadStyles).toHaveBeenCalledTimes(1) }) it('should skip loadStyles when the skin is already active', async () => { - const classicSkin = skins.find(s => s.id === 'classic') - expect(classicSkin).toBeDefined() - if (classicSkin == null) return - vi.mocked(classicSkin.loadStyles).mockClear() + const modernSkin = skins.find(s => s.id === 'modern') + expect(modernSkin).toBeDefined() + if (modernSkin == null) return + vi.mocked(modernSkin.loadStyles).mockClear() const { activeSkinId, switchSkin } = useSkin() - await switchSkin('classic') - expect(classicSkin.loadStyles).not.toHaveBeenCalled() - expect(activeSkinId.value).toBe('classic') + await switchSkin('modern') + expect(modernSkin.loadStyles).not.toHaveBeenCalled() + expect(activeSkinId.value).toBe('modern') }) it('should update activeSkinId on successful skin switch', async () => { const { activeSkinId, switchSkin } = useSkin() - await switchSkin('modern') - expect(activeSkinId.value).toBe('modern') + await switchSkin('classic') + expect(activeSkinId.value).toBe('classic') }) it('should persist the active skin to localStorage', async () => { const { switchSkin } = useSkin() - await switchSkin('modern') - expect(localStorage.getItem('ecs-ui-skin')).toBe('"modern"') + await switchSkin('classic') + expect(localStorage.getItem('ecs-ui-skin')).toBe('"classic"') }) it('should ignore invalid skin id', async () => { @@ -162,15 +162,15 @@ describe('useSkin', () => { }) it('should return the singleton activeSkinId regardless of later localStorage writes', () => { - localStorage.setItem('ecs-ui-skin', '"modern"') + localStorage.setItem('ecs-ui-skin', '"classic"') const { activeSkinId } = useSkin() - expect(activeSkinId.value).toBe('classic') + expect(activeSkinId.value).toBe('modern') }) it('should set data-skin attribute on document element after switch', async () => { const { switchSkin } = useSkin() - await switchSkin('modern') - expect(document.documentElement.getAttribute('data-skin')).toBe('modern') + await switchSkin('classic') + expect(document.documentElement.getAttribute('data-skin')).toBe('classic') }) it('should handle corrupted localStorage value gracefully', () => { @@ -179,13 +179,13 @@ describe('useSkin', () => { // Re-call useSkin — the singleton already initialized, so this tests // the getFromLocalStorage fallback path const { activeSkinId } = useSkin() - expect(activeSkinId.value).toBe('classic') + expect(activeSkinId.value).toBe('modern') }) it('should remove skin-error-reload-count from sessionStorage on successful switch', async () => { sessionStorage.setItem('skin-error-reload-count', '2') const { switchSkin } = useSkin() - await switchSkin('modern') + await switchSkin('classic') expect(sessionStorage.getItem('skin-error-reload-count')).toBeNull() }) diff --git a/ui/web/tests/unit/skins/registry.test.ts b/ui/web/tests/unit/skins/registry.test.ts index 154440ff..b13a5a24 100644 --- a/ui/web/tests/unit/skins/registry.test.ts +++ b/ui/web/tests/unit/skins/registry.test.ts @@ -8,8 +8,8 @@ import { DEFAULT_SKIN } from '@/core/index.js' import { skins } from '@/skins/registry.js' describe('registry', () => { - it('should export DEFAULT_SKIN as classic', () => { - expect(DEFAULT_SKIN).toBe('classic') + it('should export DEFAULT_SKIN as modern', () => { + expect(DEFAULT_SKIN).toBe('modern') }) it('should export skins array with exactly 2 entries', () => { -- 2.43.0