fix(test): repair GlobalTaskList memo re-render test
React.memo blocks root.render() re-renders when props are unchanged, so storeState task updates were invisible to the memoized component. Switch useStore mock to a subscription model (useState + useEffect) so notifyStoreUpdate() forces a proper re-render in the expansion test.
This commit is contained in:
parent
e500d26a34
commit
75c7bf47f3
1 changed files with 38 additions and 9 deletions
|
|
@ -32,12 +32,29 @@ const taskLocalState = {
|
||||||
renameTask: vi.fn(),
|
renameTask: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const storeListeners = new Set<() => void>();
|
||||||
|
function notifyStoreUpdate(): void {
|
||||||
|
storeListeners.forEach((l) => l());
|
||||||
|
}
|
||||||
|
|
||||||
vi.mock('../../../../src/renderer/store', () => ({
|
vi.mock('../../../../src/renderer/store', () => ({
|
||||||
useStore: (selector: (state: StoreState) => unknown) => selector(storeState),
|
useStore: (selector: (state: StoreState) => unknown) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
|
const { useState, useEffect } = require('react') as typeof import('react');
|
||||||
|
const [, setVersion] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = () => setVersion((v) => v + 1);
|
||||||
|
storeListeners.add(listener);
|
||||||
|
return () => {
|
||||||
|
storeListeners.delete(listener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return selector(storeState);
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('zustand/react/shallow', () => ({
|
vi.mock('zustand/react/shallow', () => ({
|
||||||
useShallow: <T,>(selector: T) => selector,
|
useShallow: <T>(selector: T) => selector,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../src/renderer/components/common/ConfirmDialog', () => ({
|
vi.mock('../../../../src/renderer/components/common/ConfirmDialog', () => ({
|
||||||
|
|
@ -75,7 +92,8 @@ vi.mock('../../../../src/renderer/components/sidebar/TaskFiltersPopover', () =>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../src/renderer/components/ui/popover', () => ({
|
vi.mock('../../../../src/renderer/components/ui/popover', () => ({
|
||||||
Popover: ({ children }: React.PropsWithChildren) => React.createElement(React.Fragment, null, children),
|
Popover: ({ children }: React.PropsWithChildren) =>
|
||||||
|
React.createElement(React.Fragment, null, children),
|
||||||
PopoverTrigger: ({ children }: React.PropsWithChildren) =>
|
PopoverTrigger: ({ children }: React.PropsWithChildren) =>
|
||||||
React.createElement(React.Fragment, null, children),
|
React.createElement(React.Fragment, null, children),
|
||||||
PopoverContent: ({ children }: React.PropsWithChildren) =>
|
PopoverContent: ({ children }: React.PropsWithChildren) =>
|
||||||
|
|
@ -83,7 +101,8 @@ vi.mock('../../../../src/renderer/components/ui/popover', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../src/renderer/components/ui/tooltip', () => ({
|
vi.mock('../../../../src/renderer/components/ui/tooltip', () => ({
|
||||||
Tooltip: ({ children }: React.PropsWithChildren) => React.createElement(React.Fragment, null, children),
|
Tooltip: ({ children }: React.PropsWithChildren) =>
|
||||||
|
React.createElement(React.Fragment, null, children),
|
||||||
TooltipTrigger: ({ children }: React.PropsWithChildren) =>
|
TooltipTrigger: ({ children }: React.PropsWithChildren) =>
|
||||||
React.createElement(React.Fragment, null, children),
|
React.createElement(React.Fragment, null, children),
|
||||||
TooltipContent: ({ children }: React.PropsWithChildren) =>
|
TooltipContent: ({ children }: React.PropsWithChildren) =>
|
||||||
|
|
@ -113,9 +132,11 @@ function flushMicrotasks(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function findButton(host: HTMLElement, label: string): HTMLButtonElement | null {
|
function findButton(host: HTMLElement, label: string): HTMLButtonElement | null {
|
||||||
return Array.from(host.querySelectorAll('button')).find(
|
return (
|
||||||
(button) => button.textContent?.trim() === label
|
Array.from(host.querySelectorAll('button')).find(
|
||||||
) ?? null;
|
(button) => button.textContent?.trim() === label
|
||||||
|
) ?? null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function visibleSubjects(host: HTMLElement): string[] {
|
function visibleSubjects(host: HTMLElement): string[] {
|
||||||
|
|
@ -175,6 +196,7 @@ describe('GlobalTaskList project grouping', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
document.body.innerHTML = '';
|
document.body.innerHTML = '';
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
|
storeListeners.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows five tasks first, then expands and collapses with Show more and Show less', async () => {
|
it('shows five tasks first, then expands and collapses with Show more and Show less', async () => {
|
||||||
|
|
@ -199,7 +221,14 @@ describe('GlobalTaskList project grouping', () => {
|
||||||
await flushMicrotasks();
|
await flushMicrotasks();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(visibleSubjects(host)).toEqual(['Task 1', 'Task 2', 'Task 3', 'Task 4', 'Task 5', 'Task 6']);
|
expect(visibleSubjects(host)).toEqual([
|
||||||
|
'Task 1',
|
||||||
|
'Task 2',
|
||||||
|
'Task 3',
|
||||||
|
'Task 4',
|
||||||
|
'Task 5',
|
||||||
|
'Task 6',
|
||||||
|
]);
|
||||||
expect(findButton(host, 'Show less')).not.toBeNull();
|
expect(findButton(host, 'Show less')).not.toBeNull();
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|
@ -249,7 +278,7 @@ describe('GlobalTaskList project grouping', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root.render(React.createElement(GlobalTaskList));
|
notifyStoreUpdate();
|
||||||
await flushMicrotasks();
|
await flushMicrotasks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue