feat(admin-web): sync product context changes
This commit is contained in:
parent
f77797881b
commit
20e1ac0e67
82
dashboards/admin-web/src/__tests__/product-context.test.tsx
Normal file
82
dashboards/admin-web/src/__tests__/product-context.test.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// @vitest-environment happy-dom
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
import { act } from 'react';
|
||||||
|
import { createRoot, type Root } from 'react-dom/client';
|
||||||
|
|
||||||
|
import { ProductProvider, useProduct } from '@/lib/product-context';
|
||||||
|
|
||||||
|
function ProductProbe() {
|
||||||
|
const { productId, productName, setProductId } = useProduct();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<output data-testid="product-id">{productId}</output>
|
||||||
|
<output data-testid="product-name">{productName}</output>
|
||||||
|
<button type="button" onClick={() => setProductId('nomgap')}>
|
||||||
|
Switch to NomGap
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ProductProvider', () => {
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
let root: Root;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
||||||
|
true;
|
||||||
|
localStorage.clear();
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
root = createRoot(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
act(() => root.unmount());
|
||||||
|
container.remove();
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('persists changes and emits a same-tab product-changed event', () => {
|
||||||
|
let eventCount = 0;
|
||||||
|
window.addEventListener('admin:product-changed', () => {
|
||||||
|
eventCount += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.render(
|
||||||
|
<ProductProvider>
|
||||||
|
<ProductProbe />
|
||||||
|
</ProductProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
container.querySelector('button')!.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector('[data-testid="product-id"]')?.textContent).toBe('nomgap');
|
||||||
|
expect(container.querySelector('[data-testid="product-name"]')?.textContent).toBe('NomGap');
|
||||||
|
expect(localStorage.getItem('admin_selected_product')).toBe('nomgap');
|
||||||
|
expect(eventCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('syncs provider state when another admin component updates localStorage', () => {
|
||||||
|
act(() => {
|
||||||
|
root.render(
|
||||||
|
<ProductProvider>
|
||||||
|
<ProductProbe />
|
||||||
|
</ProductProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
localStorage.setItem('admin_selected_product', 'mindlyst');
|
||||||
|
window.dispatchEvent(new Event('admin:product-changed'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector('[data-testid="product-id"]')?.textContent).toBe('mindlyst');
|
||||||
|
expect(container.querySelector('[data-testid="product-name"]')?.textContent).toBe('MindLyst');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,9 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react';
|
||||||
import { KNOWN_PRODUCTS, PRODUCT_ID } from '@/lib/product-constants';
|
import { KNOWN_PRODUCTS, PRODUCT_ID } from '@/lib/product-constants';
|
||||||
|
|
||||||
const STORAGE_KEY = 'admin_selected_product';
|
const STORAGE_KEY = 'admin_selected_product';
|
||||||
|
const PRODUCT_CHANGED_EVENT = 'admin:product-changed';
|
||||||
|
|
||||||
interface ProductContextValue {
|
interface ProductContextValue {
|
||||||
productId: string;
|
productId: string;
|
||||||
@ -22,10 +23,24 @@ function getInitialProduct(): string {
|
|||||||
export function ProductProvider({ children }: { children: ReactNode }) {
|
export function ProductProvider({ children }: { children: ReactNode }) {
|
||||||
const [productId, setProductIdState] = useState<string>(getInitialProduct);
|
const [productId, setProductIdState] = useState<string>(getInitialProduct);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function syncSelectedProduct() {
|
||||||
|
setProductIdState(getInitialProduct());
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener(PRODUCT_CHANGED_EVENT, syncSelectedProduct);
|
||||||
|
window.addEventListener('storage', syncSelectedProduct);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(PRODUCT_CHANGED_EVENT, syncSelectedProduct);
|
||||||
|
window.removeEventListener('storage', syncSelectedProduct);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const setProductId = useCallback((id: string) => {
|
const setProductId = useCallback((id: string) => {
|
||||||
setProductIdState(id);
|
setProductIdState(id);
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
localStorage.setItem(STORAGE_KEY, id);
|
localStorage.setItem(STORAGE_KEY, id);
|
||||||
|
window.dispatchEvent(new Event(PRODUCT_CHANGED_EVENT));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user