wxt-zustand
Version:
High-performance Zustand state management for WXT web extensions with seamless cross-tab synchronization and sub-10ms React re-renders
79 lines • 3.03 kB
JavaScript
import { beforeEach, describe, expect, mock, test } from 'bun:test';
import { createStore } from 'zustand/vanilla';
// Mock @webext-core/proxy-service before importing modules that depend on it
const serviceObj = { dispatch: mock(async () => ({})) };
const mockDefineProxyService = mock((_name, _factory) => {
return [mock(() => { }), mock(() => serviceObj)];
});
// Storage.watch mock to capture unwatch calls
const unwatchMock = mock(() => { });
const watchMock = mock((_key, _cb) => {
return unwatchMock;
});
mock.module('wxt/utils/storage', () => ({
storage: {
watch: watchMock,
// Other methods unused in this test
getItem: mock(async () => undefined),
setItem: mock(async () => { }),
getItems: mock(async () => ({})),
setItems: mock(async () => { }),
},
}));
mock.module('@webext-core/proxy-service', () => ({
defineProxyService: mockDefineProxyService,
}));
const { setupBidirectionalSync } = await import('./sync');
describe('setupBidirectionalSync cleanup', () => {
beforeEach(() => {
mockDefineProxyService.mockClear();
serviceObj.dispatch.mockClear();
unwatchMock.mockClear();
watchMock.mockClear();
});
test('cleanup unsubscribes local and unwatch remote, idempotent', () => {
const base = createStore(() => ({ n: 0 }));
// Wrap subscribe to count unsubscribe calls
const realSubscribe = base.subscribe.bind(base);
let unsubscribeCount = 0;
base.subscribe = ((listener) => {
const unsub = realSubscribe(listener);
return () => {
unsubscribeCount++;
unsub();
};
});
const { cleanup } = setupBidirectionalSync('alpha', base);
expect(typeof cleanup).toBe('function');
expect(watchMock).toHaveBeenCalledTimes(1);
expect(unwatchMock).toHaveBeenCalledTimes(0);
expect(unsubscribeCount).toBe(0);
cleanup();
expect(unwatchMock).toHaveBeenCalledTimes(1);
expect(unsubscribeCount).toBe(1);
// Second cleanup does nothing (idempotent)
cleanup();
expect(unwatchMock).toHaveBeenCalledTimes(1);
expect(unsubscribeCount).toBe(1);
});
});
test('dispatch invalidation triggers reload', async () => {
const base = createStore(() => ({ n: 0 }));
// Simulate extension context invalidated on dispatch
serviceObj.dispatch = mock(async () => {
throw new Error('Extension context invalidated.');
});
// Spy reload
const originalWin = globalThis.window;
const reload = mock(() => { });
globalThis.window = { location: { reload } };
const { cleanup } = setupBidirectionalSync('beta', base);
// trigger a change
base.setState({ n: 1 });
// allow promise microtask to run
await new Promise((r) => setTimeout(r, 0));
expect(reload).toHaveBeenCalledTimes(1);
cleanup();
globalThis.window = originalWin;
});
//# sourceMappingURL=sync.test.js.map