perf(renderer): skip unchanged kanban task cards
This commit is contained in:
parent
a491cd6c1c
commit
c02565b07d
2 changed files with 82 additions and 3 deletions
|
|
@ -10,6 +10,7 @@ const unreadBadgeMock = vi.hoisted(() => ({
|
|||
|
||||
const unreadCommentCountMock = vi.hoisted(() => ({
|
||||
value: 0,
|
||||
calls: 0,
|
||||
}));
|
||||
|
||||
vi.mock('@renderer/components/team/MemberBadge', () => ({
|
||||
|
|
@ -71,7 +72,10 @@ vi.mock('@renderer/hooks/useTheme', () => ({
|
|||
}));
|
||||
|
||||
vi.mock('@renderer/hooks/useUnreadCommentCount', () => ({
|
||||
useUnreadCommentCount: () => unreadCommentCountMock.value,
|
||||
useUnreadCommentCount: () => {
|
||||
unreadCommentCountMock.calls += 1;
|
||||
return unreadCommentCountMock.value;
|
||||
},
|
||||
}));
|
||||
|
||||
/* eslint-enable @typescript-eslint/naming-convention -- Re-enable after component mocks. */
|
||||
|
|
@ -188,6 +192,7 @@ async function rerenderStrictTaskCard(
|
|||
afterEach(() => {
|
||||
unreadBadgeMock.props.length = 0;
|
||||
unreadCommentCountMock.value = 0;
|
||||
unreadCommentCountMock.calls = 0;
|
||||
});
|
||||
|
||||
async function renderTaskCard(
|
||||
|
|
@ -211,6 +216,56 @@ describe('KanbanTaskCard comment badge pulse', () => {
|
|||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
it('skips rerender when refreshed task objects keep the same snapshot', async () => {
|
||||
const taskMap = new Map();
|
||||
const memberColorMap = new Map([['alice', 'blue']]);
|
||||
const { root } = await renderTaskCard({
|
||||
task: { ...baseTask, comments: [] },
|
||||
taskMap,
|
||||
memberColorMap,
|
||||
});
|
||||
|
||||
expect(unreadCommentCountMock.calls).toBeGreaterThan(0);
|
||||
unreadCommentCountMock.calls = 0;
|
||||
|
||||
await rerenderTaskCard(root, {
|
||||
task: { ...baseTask, comments: [] },
|
||||
taskMap,
|
||||
memberColorMap,
|
||||
});
|
||||
|
||||
expect(unreadCommentCountMock.calls).toBe(0);
|
||||
|
||||
await act(async () => {
|
||||
root.unmount();
|
||||
await flushReact();
|
||||
});
|
||||
});
|
||||
|
||||
it('rerenders when a hidden task field changes so click handlers stay current', async () => {
|
||||
const taskMap = new Map();
|
||||
const memberColorMap = new Map([['alice', 'blue']]);
|
||||
const { root } = await renderTaskCard({
|
||||
task: { ...baseTask, comments: [] },
|
||||
taskMap,
|
||||
memberColorMap,
|
||||
});
|
||||
|
||||
unreadCommentCountMock.calls = 0;
|
||||
await rerenderTaskCard(root, {
|
||||
task: { ...baseTask, comments: [], description: 'Updated hidden details' },
|
||||
taskMap,
|
||||
memberColorMap,
|
||||
});
|
||||
|
||||
expect(unreadCommentCountMock.calls).toBeGreaterThan(0);
|
||||
|
||||
await act(async () => {
|
||||
root.unmount();
|
||||
await flushReact();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not pulse on initial render with existing comments', async () => {
|
||||
const { host, root } = await renderTaskCard({
|
||||
task: { ...baseTask, comments: [createComment('comment-1')] },
|
||||
|
|
|
|||
|
|
@ -83,6 +83,30 @@ interface CommentPulseSyncAction {
|
|||
}
|
||||
|
||||
const EMPTY_TASK_COMMENTS: readonly TaskComment[] = [];
|
||||
const taskCardSignatureCache = new WeakMap<TeamTaskWithKanban, string>();
|
||||
|
||||
function getTaskCardSignature(task: TeamTaskWithKanban): string {
|
||||
const cached = taskCardSignatureCache.get(task);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
const signature = JSON.stringify(task);
|
||||
taskCardSignatureCache.set(task, signature);
|
||||
return signature;
|
||||
}
|
||||
|
||||
function areKanbanTaskStatesEqual(
|
||||
prev: KanbanTaskState | undefined,
|
||||
next: KanbanTaskState | undefined
|
||||
): boolean {
|
||||
if (prev === next) return true;
|
||||
if (!prev || !next) return !prev && !next;
|
||||
return (
|
||||
prev.column === next.column &&
|
||||
prev.reviewer === next.reviewer &&
|
||||
prev.errorDescription === next.errorDescription &&
|
||||
prev.movedAt === next.movedAt
|
||||
);
|
||||
}
|
||||
|
||||
function createCommentPulseState(
|
||||
taskKey: string,
|
||||
|
|
@ -652,10 +676,10 @@ export const KanbanTaskCard = memo(
|
|||
);
|
||||
},
|
||||
(prev, next) =>
|
||||
prev.task === next.task &&
|
||||
getTaskCardSignature(prev.task) === getTaskCardSignature(next.task) &&
|
||||
prev.teamName === next.teamName &&
|
||||
prev.columnId === next.columnId &&
|
||||
prev.kanbanTaskState === next.kanbanTaskState &&
|
||||
areKanbanTaskStatesEqual(prev.kanbanTaskState, next.kanbanTaskState) &&
|
||||
prev.hasReviewers === next.hasReviewers &&
|
||||
prev.compact === next.compact &&
|
||||
prev.taskMap === next.taskMap &&
|
||||
|
|
|
|||
Loading…
Reference in a new issue