useCatalogEffects.test.tsx 8.4 Ko
Télécharger le fichier
/* @vitest-environment jsdom */
import {
BuildersClubFurniCountMessageEvent,
CatalogPublishedMessageEvent,
LimitedEditionSoldOutEvent,
ProductOfferEvent,
PurchaseNotAllowedMessageEvent,
PurchaseOKMessageEvent
} from '@octane/renderer';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, cleanup, renderHook, waitFor } from '@testing-library/react';
import { ReactNode } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { CatalogNode, CatalogType } from '../../api';
import { mockEventDispatcher } from '../../octane-renderer.mock';
import { INITIAL_CATALOG_UI_STATE, useCatalogStore } from './catalogStore';
import { useCatalogEffects } from './useCatalogEffects';
import { catalogIndexKey, catalogPageKey } from './useCatalogQueries';
vi.mock('../events', async (importOriginal) => {
const actual = await importOriginal<typeof import('../events')>();
return {
...actual,
useConnectionState: () => ({
phase: 'connected',
reconnectAttempt: 0,
maxReconnectAttempts: 7,
authenticated: true,
closeCode: null,
closeReason: ''
})
};
});
vi.mock('../notification', () => ({ useNotification: () => ({ simpleAlert: vi.fn(), showConfirm: vi.fn() }) }));
vi.mock('./useCatalogPlaceMultipleItems', () => ({ useCatalogPlaceMultipleItems: () => [false, vi.fn()] }));
vi.mock('./useCatalogSkipPurchaseConfirmation', () => ({ useCatalogSkipPurchaseConfirmation: () => [false, vi.fn()] }));
vi.mock('../../api', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../api')>();
return {
...actual,
DispatchUiEvent: vi.fn(),
GetProductDataForLocalization: () => ({ id: 1 }),
GetFurnitureData: () => ({ className: 'chair', id: 10 })
};
});
const makeEvent = (klass: any, parser: any) => {
const event = new klass();
event.getParser = () => parser;
return event;
};
const node = (pageId: number, pageName: string, parent: any, offerIds: number[] = []) => {
const created = new CatalogNode(
{ visible: true, 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;
};
let client: QueryClient;
const wrapper = ({ children }: { children: ReactNode }) => <QueryClientProvider client={client}>{children}</QueryClientProvider>;
beforeEach(() => {
client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
useCatalogStore.setState({ ...INITIAL_CATALOG_UI_STATE });
});
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
describe('useCatalogEffects', () => {
it('binds the query client and creates one room previewer', () => {
const { unmount } = renderHook(() => useCatalogEffects(), { wrapper });
expect(useCatalogStore.getState().roomPreviewer).not.toBeNull();
unmount();
expect(useCatalogStore.getState().roomPreviewer).toBeNull();
});
it('a catalog publish invalidates the index and the pages', () => {
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: {} as any, offersToNodes: new Map() });
client.setQueryData(catalogPageKey(CatalogType.NORMAL, 2), { page: null, frontPageItems: [], offerId: -1 });
renderHook(() => useCatalogEffects(), { wrapper });
act(() => mockEventDispatcher.dispatchEvent(makeEvent(CatalogPublishedMessageEvent, {})));
expect(client.getQueryState(catalogIndexKey(CatalogType.NORMAL)).isInvalidated).toBe(true);
expect(client.getQueryState(catalogPageKey(CatalogType.NORMAL, 2)).isInvalidated).toBe(true);
});
it('a purchase or a sold-out invalidates the current page', () => {
client.setQueryData(catalogPageKey(CatalogType.NORMAL, 2), { page: null, frontPageItems: [], offerId: -1 });
useCatalogStore.setState({ pageId: 2 });
renderHook(() => useCatalogEffects(), { wrapper });
act(() => mockEventDispatcher.dispatchEvent(makeEvent(PurchaseOKMessageEvent, { offer: {} })));
expect(client.getQueryState(catalogPageKey(CatalogType.NORMAL, 2)).isInvalidated).toBe(true);
client.setQueryData(catalogPageKey(CatalogType.NORMAL, 2), { page: null, frontPageItems: [], offerId: -1 });
act(() => mockEventDispatcher.dispatchEvent(makeEvent(LimitedEditionSoldOutEvent, {})));
expect(client.getQueryState(catalogPageKey(CatalogType.NORMAL, 2)).isInvalidated).toBe(true);
});
it('a refused purchase takes back the furni a placement purchase put in the room', () => {
const resetPlacedOfferData = vi.fn();
useCatalogStore.setState({ resetPlacedOfferData });
renderHook(() => useCatalogEffects(), { wrapper });
act(() => mockEventDispatcher.dispatchEvent(makeEvent(PurchaseNotAllowedMessageEvent, { code: 1 })));
expect(resetPlacedOfferData).toHaveBeenCalledWith(true);
});
it('a lazy product offer becomes the current offer with a page attached', () => {
const root = node(-1, 'root', null);
const chairs = node(2, 'chairs', root, [100]);
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map([[100, [chairs]]]) });
renderHook(() => useCatalogEffects(), { wrapper });
const parser = {
offer: {
offerId: 100,
localizationId: 'chair',
rent: false,
priceCredits: 1,
priceActivityPoints: 0,
priceActivityPointsType: 0,
giftable: false,
clubLevel: 0,
products: [{ productType: 's', furniClassId: 10, extraParam: '', productCount: 1, uniqueLimitedItem: false, uniqueLimitedSeriesSize: 0, uniqueLimitedItemsLeft: 0 }],
bundlePurchaseAllowed: false,
itemIds: '',
haveOffer: false
}
};
act(() => mockEventDispatcher.dispatchEvent(makeEvent(ProductOfferEvent, parser)));
const { currentOffer } = useCatalogStore.getState();
expect(currentOffer.offerId).toBe(100);
expect(currentOffer.page.pageId).toBe(2);
});
it('builders club counters land in the store', () => {
renderHook(() => useCatalogEffects(), { wrapper });
act(() => mockEventDispatcher.dispatchEvent(makeEvent(BuildersClubFurniCountMessageEvent, { furniCount: 7 })));
expect(useCatalogStore.getState().furniCount).toBe(7);
});
it('a pending request resolves once the index is in cache', async () => {
const root = node(-1, 'root', null);
const tables = node(3, 'tables', root);
useCatalogStore.setState({ isVisible: true, pendingRequest: { kind: 'id', id: 3 } });
renderHook(() => useCatalogEffects(), { wrapper });
await act(async () => {
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map() });
});
await waitFor(() => expect(useCatalogStore.getState().pendingRequest).toBeNull());
expect(useCatalogStore.getState().activeNodes.at(-1)).toBe(tables);
});
it('a deep link to an offer whose page is already open still selects the offer', async () => {
const root = node(-1, 'root', null);
const chairs = node(2, 'chairs', root, [100]);
const offer = {
offerId: 100,
isLazy: false,
product: { productType: 'floor' }
};
client.setQueryData(catalogIndexKey(CatalogType.NORMAL), { rootNode: root, offersToNodes: new Map([[100, [chairs]]]) });
client.setQueryData(catalogPageKey(CatalogType.NORMAL, 2), { page: { pageId: 2, offers: [offer] }, frontPageItems: [], offerId: -1 });
useCatalogStore.setState({ isVisible: true, pageId: 2 });
renderHook(() => useCatalogEffects(), { wrapper });
act(() => {
useCatalogStore.getState().openPageByOfferId(100);
});
await waitFor(() => expect(useCatalogStore.getState().currentOffer?.offerId).toBe(100));
});
it('a localization update bumps the version', () => {
renderHook(() => useCatalogEffects(), { wrapper });
act(() => window.dispatchEvent(new CustomEvent('octane-localization-updated')));
expect(useCatalogStore.getState().catalogLocalizationVersion).toBe(1);
});
});
