]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
feat(ui-web): set modern as default skin
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 30 Apr 2026 09:33:33 +0000 (11:33 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Thu, 30 Apr 2026 09:33:33 +0000 (11:33 +0200)
ui/web/README.md
ui/web/src/core/Constants.ts
ui/web/src/router/index.ts
ui/web/tests/unit/shared/composables/useSkin.test.ts
ui/web/tests/unit/skins/registry.test.ts

index 34fb19175720f3415d7bf044707654f00ea572b3..f63cb89398769640cc86526798b7625e2286ce8b 100644 (file)
@@ -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/<name>/`, implement a root layout component, and register it in `src/skins/registry.ts`.
+Default: `modern`. To add a skin, create `src/skins/<name>/`, implement a root layout component, and register it in `src/skins/registry.ts`.
 
 ## Getting started
 
index 854f43c26dd9d6cfaaf43320533a470e07447b1e..d9a4c7fe352763e9d357026bfa752a018e9739e1 100644 (file)
@@ -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
index bd4ff989b1d2c9f07fd6e079571f9a3ad4ae2486..43d1d58a90047af0792691f6f83e91377c070e17 100644 (file)
@@ -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 },
index ad2c8da1d6ea843b1a92724524ecd1ef2a61e28d..44ad5fdf77523ac991a4878ca8b5582cd293ef5b 100644 (file)
@@ -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<void>((_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<void>(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()
   })
 
index 154440ffadf391899ebd69ce94b25a1c88e9bbdf..b13a5a2409ae1104a47bfdec542baba9d5ff1ce1 100644 (file)
@@ -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', () => {