feat(admin-web): sync product context changes
Some checks failed
Publish @bytelyst/* packages / publish (push) Failing after 13s
CI — Common Platform / Build, Test & Typecheck (push) Successful in 52s

This commit is contained in:
Saravana Kumar 2026-05-30 21:04:04 +00:00
parent f77797881b
commit 20e1ac0e67
2 changed files with 98 additions and 1 deletions

View 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');
});
});

View File

@ -1,9 +1,10 @@
'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';
const STORAGE_KEY = 'admin_selected_product';
const PRODUCT_CHANGED_EVENT = 'admin:product-changed';
interface ProductContextValue {
productId: string;
@ -22,10 +23,24 @@ function getInitialProduct(): string {
export function ProductProvider({ children }: { children: ReactNode }) {
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) => {
setProductIdState(id);
if (typeof window !== 'undefined') {
localStorage.setItem(STORAGE_KEY, id);
window.dispatchEvent(new Event(PRODUCT_CHANGED_EVENT));
}
}, []);