diff --git a/core-web/libs/edit-content/src/lib/store/edit-content.store.ts b/core-web/libs/edit-content/src/lib/store/edit-content.store.ts index 1e3da586a88..f46ab24b3fa 100644 --- a/core-web/libs/edit-content/src/lib/store/edit-content.store.ts +++ b/core-web/libs/edit-content/src/lib/store/edit-content.store.ts @@ -31,12 +31,13 @@ export const initialRootState: EditContentRootState = { export const DotEditContentStore = signalStore( withState(initialRootState), withContent(), + withUI(), withSidebar(), withInformation(), withWorkflow(), withForm(), withLocales(), - withUI(), + withHooks({ onInit(store) { const activatedRoute = inject(ActivatedRoute); diff --git a/core-web/libs/edit-content/src/lib/store/features/ui.feature.spec.ts b/core-web/libs/edit-content/src/lib/store/features/ui.feature.spec.ts index 0d7cb4010f9..e41e93a54a8 100644 --- a/core-web/libs/edit-content/src/lib/store/features/ui.feature.spec.ts +++ b/core-web/libs/edit-content/src/lib/store/features/ui.feature.spec.ts @@ -1,3 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { signalStore, signalStoreFeature, withState } from '@ngrx/signals'; + +import { fakeAsync, tick } from '@angular/core/testing'; + +import { contentInitialState } from './content.feature'; +import { withUI } from './ui.feature'; + +import { DotContentletState } from '../../models/dot-edit-content.model'; +import { getStoredUIState, saveStoreUIState } from '../../utils/functions.util'; +import { initialRootState } from '../edit-content.store'; + jest.mock('../../utils/functions.util', () => ({ getStoredUIState: jest.fn(() => ({ activeTab: 0, @@ -7,33 +20,65 @@ jest.mock('../../utils/functions.util', () => ({ saveStoreUIState: jest.fn() })); -import { signalStore, withState } from '@ngrx/signals'; - -import { uiInitialState, withUI } from './ui.feature'; - -import { saveStoreUIState } from '../../utils/functions.util'; -import { initialRootState } from '../edit-content.store'; - describe('UI Feature', () => { + let spectator: SpectatorService; + let store: any; + + const withTest = () => + signalStoreFeature( + withState({ + ...initialRootState, + ...contentInitialState, + initialContentletState: 'edit' as DotContentletState + }) + ); + + const createStore = createServiceFactory({ + service: signalStore(withTest(), withUI()) + }); + beforeEach(() => { jest.clearAllMocks(); + spectator = createStore(); + store = spectator.service; }); - const createTestStore = () => { - const TestStore = signalStore(withState(initialRootState), withUI()); + describe('Store Initialization', () => { + it('should load initial state from localStorage', () => { + expect(getStoredUIState).toHaveBeenCalled(); + expect(store.uiState()).toEqual({ + activeTab: 0, + isSidebarOpen: true, + activeSidebarTab: 0 + }); + }); - return new TestStore(); - }; + it('should save state changes to localStorage via effect', fakeAsync(() => { + // Initial save from initialization + tick(); + expect(saveStoreUIState).toHaveBeenCalledWith(store.uiState()); - let store: ReturnType; + // Clear mock to test next state change + jest.clearAllMocks(); - beforeEach(() => { - store = createTestStore(); + // Make a state change + store.setActiveTab(2); + tick(); + + // Verify effect triggered save + expect(saveStoreUIState).toHaveBeenCalledWith({ + ...store.uiState(), + activeTab: 2 + }); + })); }); describe('Computed Properties', () => { - it('should compute activeTab', () => { + it('should compute activeTab based on initialContentletState', () => { expect(store.activeTab()).toBe(0); + + store.setActiveTab(2); + expect(store.activeTab()).toBe(2); }); it('should compute isSidebarOpen', () => { @@ -47,47 +92,31 @@ describe('UI Feature', () => { describe('Methods', () => { describe('setActiveTab', () => { - it('should update active tab and persist to localStorage', () => { + it('should update active tab in state', () => { store.setActiveTab(2); - expect(store.activeTab()).toBe(2); - expect(saveStoreUIState).toHaveBeenCalledWith({ - ...uiInitialState, - activeTab: 2 - }); }); }); describe('toggleSidebar', () => { - it('should toggle sidebar visibility and persist to localStorage', () => { + it('should toggle sidebar visibility in state', () => { const initialState = store.isSidebarOpen(); store.toggleSidebar(); - expect(store.isSidebarOpen()).toBe(!initialState); - expect(saveStoreUIState).toHaveBeenCalledWith({ - ...uiInitialState, - isSidebarOpen: !initialState - }); }); it('should toggle back to original state when called twice', () => { const initialState = store.isSidebarOpen(); store.toggleSidebar(); store.toggleSidebar(); - expect(store.isSidebarOpen()).toBe(initialState); }); }); describe('setActiveSidebarTab', () => { - it('should update active sidebar tab and persist to localStorage', () => { + it('should update active sidebar tab in state', () => { store.setActiveSidebarTab(1); - expect(store.activeSidebarTab()).toBe(1); - expect(saveStoreUIState).toHaveBeenCalledWith({ - ...uiInitialState, - activeSidebarTab: 1 - }); }); }); }); diff --git a/core-web/libs/edit-content/src/lib/store/features/ui.feature.ts b/core-web/libs/edit-content/src/lib/store/features/ui.feature.ts index cf9a2d856fe..6d1ebc8c254 100644 --- a/core-web/libs/edit-content/src/lib/store/features/ui.feature.ts +++ b/core-web/libs/edit-content/src/lib/store/features/ui.feature.ts @@ -3,11 +3,12 @@ import { signalStoreFeature, type, withComputed, + withHooks, withMethods, withState } from '@ngrx/signals'; -import { computed } from '@angular/core'; +import { computed, effect, untracked } from '@angular/core'; import { ContentState } from './content.feature'; @@ -23,7 +24,11 @@ export interface UIState { activeSidebarTab: number; } -export const uiInitialState: UIState = getStoredUIState(); +export const uiInitialState: UIState = { + activeTab: 0, + isSidebarOpen: false, + activeSidebarTab: 0 +}; /** * Feature that manages UI-related state for the content editor @@ -64,7 +69,6 @@ export function withUI() { activeTab: index }; patchState(store, { uiState: newState }); - saveStoreUIState(newState); }, /** @@ -76,7 +80,6 @@ export function withUI() { isSidebarOpen: !store.uiState().isSidebarOpen }; patchState(store, { uiState: newState }); - saveStoreUIState(newState); }, /** @@ -89,8 +92,20 @@ export function withUI() { activeSidebarTab: tab }; patchState(store, { uiState: newState }); - saveStoreUIState(newState); } - })) + })), + withHooks({ + onInit(store) { + const storedState = getStoredUIState(); + patchState(store, { uiState: storedState }); + + effect(() => { + const uiState = store.uiState(); + untracked(() => { + saveStoreUIState(uiState); + }); + }); + } + }) ); }