catalogStore.test.ts 11.2 Ko
Télécharger le fichier
/* @vitest-environment jsdom */
import { QueryClient } from '@tanstack/react-query';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { CatalogNode, CatalogPage, CatalogType, PageLocalization, ProductTypeEnum, SearchResult } from '../../api';
import { INITIAL_CATALOG_UI_STATE, useCatalogStore } from './catalogStore';
import { bindCatalogQueryClient, catalogIndexKey, catalogPageKey } from './useCatalogQueries';
const node = (pageId: number, pageName: string, parent: any, offerIds: number[] = [], visible = true) => {
const created = new CatalogNode(
{ visible, icon: 0, pageId, parentId: parent?.pageId ?? -1, pageName, localization: pageName, children: [], offerIds } as any,
parent ? parent.depth + 1 : 0,
parent
);
if (parent) parent.addChild(created);
return created;
};
const buildIndex = () => {
const root = node(-1, 'root', null);
const furni = node(1, 'furni', root);
const chairs = node(2, 'chairs', furni, [100, 101]);
const tables = node(3, 'tables', furni, [102]);
const hidden = node(4, 'hidden', root, [], false);
const offersToNodes = new Map<number, any[]>([
[100, [chairs]],
[101, [chairs]],
[102, [tables]]
]);
return { root, furni, chairs, tables, hidden, offersToNodes };
};
let client: QueryClient;
beforeEach(() => {
client = new QueryClient();
bindCatalogQueryClient(client);
useCatalogStore.setState({ ...INITIAL_CATALOG_UI_STATE });
});
afterEach(() => {
bindCatalogQueryClient(null);
vi.restoreAllMocks();
});
describe('catalogStore navigation', () => {
it('activateNode sets the active path, opens the ancestors and sets the page id', () => {
const { root, furni, chairs } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.getState().activateNode(chairs, 101);
const state = useCatalogStore.getState();
expect(state.activeNodes).toEqual([furni, chairs]);
expect(state.pageId).toBe(2);
expect(state.pendingOfferId).toBe(101);
expect(furni.isOpen).toBe(true);
expect(chairs.isActive).toBe(true);
expect(state.pageOverride).toBeNull();
expect(state.currentOffer).toBeNull();
});
it('activateNode on a root child descends to its first visible child', () => {
const { root, furni, chairs } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.getState().activateNode(furni);
expect(useCatalogStore.getState().activeNodes).toEqual([furni, chairs]);
expect(useCatalogStore.getState().pageId).toBe(2);
});
it('activating the active open node closes it', () => {
const { root, furni, chairs } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.getState().activateNode(chairs);
expect(chairs.isOpen).toBe(true);
useCatalogStore.getState().activateNode(chairs);
expect(chairs.isOpen).toBe(false);
});
it('openPageById with the index in cache activates the node', () => {
const { root, tables } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.setState({ isVisible: true });
useCatalogStore.getState().openPageById(3);
expect(useCatalogStore.getState().activeNodes.at(-1)).toBe(tables);
expect(useCatalogStore.getState().pageId).toBe(3);
});
it('openPageById while hidden stores a pending request and shows the catalog', () => {
useCatalogStore.getState().openPageById(3);
const state = useCatalogStore.getState();
expect(state.isVisible).toBe(true);
expect(state.pendingRequest).toEqual({ kind: 'id', id: 3 });
expect(state.pageId).toBe(-1);
});
it('openPageByName and openPageByOfferId resolve through the index', () => {
const { root, chairs, tables, offersToNodes } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes });
useCatalogStore.setState({ isVisible: true });
useCatalogStore.getState().openPageByName('tables');
expect(useCatalogStore.getState().activeNodes.at(-1)).toBe(tables);
useCatalogStore.getState().openPageByOfferId(100);
expect(useCatalogStore.getState().activeNodes.at(-1)).toBe(chairs);
expect(useCatalogStore.getState().pendingOfferId).toBe(100);
});
it('resolvePendingRequest applies the stored request once the index exists', () => {
const { root, tables } = buildIndex();
useCatalogStore.getState().openPageById(3);
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.getState().resolvePendingRequest();
expect(useCatalogStore.getState().pendingRequest).toBeNull();
expect(useCatalogStore.getState().activeNodes.at(-1)).toBe(tables);
});
it('resolvePendingRequest with no request and no page opens the first visible root child', () => {
const { root, chairs } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.setState({ isVisible: true });
useCatalogStore.getState().resolvePendingRequest();
expect(useCatalogStore.getState().pageId).toBe(chairs.pageId);
});
});
describe('catalogStore visibility and type', () => {
it('toggleCatalogByType hides an open catalog of the same type', () => {
useCatalogStore.setState({ isVisible: true, currentType: CatalogType.NORMAL });
useCatalogStore.getState().toggleCatalogByType(CatalogType.NORMAL);
expect(useCatalogStore.getState().isVisible).toBe(false);
});
it('switching type resets the visible state', () => {
useCatalogStore.setState({ isVisible: true, currentType: CatalogType.NORMAL, pageId: 2, activeNodes: [{} as any] });
useCatalogStore.getState().openCatalogByType(CatalogType.BUILDER);
const state = useCatalogStore.getState();
expect(state.currentType).toBe(CatalogType.BUILDER);
expect(state.pageId).toBe(-1);
expect(state.activeNodes).toEqual([]);
expect(state.isVisible).toBe(true);
});
it('an unknown type normalises to NORMAL', () => {
useCatalogStore.getState().openCatalogByType('nonsense');
expect(useCatalogStore.getState().currentType).toBe(CatalogType.NORMAL);
});
it('setIsVisible accepts an updater function', () => {
useCatalogStore.setState({ isVisible: false });
useCatalogStore.getState().setIsVisible((prev) => !prev);
expect(useCatalogStore.getState().isVisible).toBe(true);
useCatalogStore.getState().setIsVisible((prev) => !prev);
expect(useCatalogStore.getState().isVisible).toBe(false);
});
});
describe('catalogStore page override and search', () => {
const syntheticPage = () => new CatalogPage(-1, 'default_3x3', new PageLocalization([], []), [], false, 1);
it('setCurrentPage stores an override and activateNode clears it', () => {
const { root, chairs } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
const page = syntheticPage();
useCatalogStore.getState().setCurrentPage(page);
expect(useCatalogStore.getState().pageOverride).toBe(page);
useCatalogStore.getState().activateNode(chairs);
expect(useCatalogStore.getState().pageOverride).toBeNull();
});
it('clearing the search result while a search page is shown reopens the previous page', () => {
const { root, tables } = buildIndex();
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
useCatalogStore.setState({ isVisible: true, previousPageId: 3, pageOverride: syntheticPage(), searchResult: new SearchResult('x', [], []) });
useCatalogStore.getState().setSearchResult(null);
expect(useCatalogStore.getState().pageOverride).toBeNull();
expect(useCatalogStore.getState().activeNodes.at(-1)).toBe(tables);
});
it('setCurrentOffer resets the purchase options', () => {
useCatalogStore.setState({ purchaseOptions: { quantity: 4, extraData: 'x', extraParamRequired: true, previewStuffData: null } });
useCatalogStore.getState().setCurrentOffer({ offerId: 1, product: null } as any);
expect(useCatalogStore.getState().purchaseOptions).toEqual({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
});
it('selectCatalogOffer resets purchaseOptions.extraData to null for a WALL offer, matching the old reset-effect outcome', () => {
useCatalogStore.setState({ purchaseOptions: { quantity: 4, extraData: 'stale', extraParamRequired: true, previewStuffData: null } });
const wallOffer = { offerId: 5, isLazy: false, product: { productType: ProductTypeEnum.WALL, extraParam: 'some_param' } } as any;
useCatalogStore.getState().selectCatalogOffer(wallOffer);
expect(useCatalogStore.getState().purchaseOptions).toEqual({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
expect(useCatalogStore.getState().currentOffer).toBe(wallOffer);
});
});
describe('catalogStore localization refresh', () => {
it('bumpLocalizationVersion clones the offers on a pageOverride, not just currentOffer', () => {
const clonedOffer = { offerId: 1 };
const clone = vi.fn(() => clonedOffer);
const offer = { offerId: 1, clone };
const page = new CatalogPage(-1, 'default_3x3', new PageLocalization([], []), [offer as any], false, 1);
useCatalogStore.setState({ pageOverride: page as any });
useCatalogStore.getState().bumpLocalizationVersion();
expect(clone).toHaveBeenCalledOnce();
expect(useCatalogStore.getState().pageOverride).not.toBe(page);
expect(useCatalogStore.getState().catalogLocalizationVersion).toBe(1);
});
it('bumpLocalizationVersion leaves a null pageOverride untouched', () => {
useCatalogStore.setState({ pageOverride: null });
useCatalogStore.getState().bumpLocalizationVersion();
expect(useCatalogStore.getState().pageOverride).toBeNull();
});
});
describe('catalogStore refresh actions', () => {
it('refreshIndex and refreshCurrentPage invalidate the matching queries', () => {
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: {} as any, offersToNodes: new Map() });
client.setQueryData(catalogPageKey(CatalogType.NORMAL, 2), { page: null, frontPageItems: [], offerId: -1 });
useCatalogStore.setState({ pageId: 2 });
useCatalogStore.getState().refreshIndex();
useCatalogStore.getState().refreshCurrentPage();
expect(client.getQueryState(catalogIndexKey(CatalogType.NORMAL)).isInvalidated).toBe(true);
expect(client.getQueryState(catalogPageKey(CatalogType.NORMAL, 2)).isInvalidated).toBe(true);
});
});
