From ba9f70a9ee821b28c8e010efc209cf6ccd5158de Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Fri, 16 Oct 2026 02:23:46 -0700 Subject: [PATCH] test: share diff comment lifecycle setup (#29555) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- ...seDiffCommentDecorator.lifecycle.test.tsx} | 61 ++++++++++++++- ...fCommentDecorator.model-lifecycle.test.tsx | 75 ------------------- 2 files changed, 59 insertions(+), 77 deletions(-) rename src/renderer/src/components/diff-comments/{useDiffCommentDecorator.commentable-lines.test.tsx => useDiffCommentDecorator.lifecycle.test.tsx} (79%) delete mode 100644 src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx diff --git a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.commentable-lines.test.tsx b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.lifecycle.test.tsx similarity index 79% rename from src/renderer/src/components/diff-comments/useDiffCommentDecorator.commentable-lines.test.tsx rename to src/renderer/src/components/diff-comments/useDiffCommentDecorator.lifecycle.test.tsx index a3dbe23daae..c00b578aacf 100644 --- a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.commentable-lines.test.tsx +++ b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.lifecycle.test.tsx @@ -1,6 +1,7 @@ // @vitest-environment happy-dom -import { renderHook } from '@testing-library/react' +import { cleanup, renderHook } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { editor as MonacoEditor } from 'monaco-editor' import type { DecoratedDiffComment } from './decorated-diff-comment' import type * as ReactDomClientModule from 'react-dom/client' import type * as DiffCommentZoneCardModule from './diff-comment-zone-card' @@ -131,7 +132,9 @@ beforeEach(() => { rootCounts.unmounted = 0 }) -afterEach(() => { +afterEach(async () => { + cleanup() + await flushDeferredUnmounts() document.body.replaceChildren() try { expect(notesMenuFixture.NotesSendMenu).not.toHaveBeenCalled() @@ -292,3 +295,57 @@ describe('useDiffCommentDecorator commentable-line churn', () => { expect(rootCounts.unmounted).toBe(1) }) }) + +/** No zones exist in the overlay disposal case, so the hook never reaches these. */ +const viewZoneAccessor: MonacoEditor.IViewZoneChangeAccessor = { + addZone: () => '', + removeZone: () => undefined, + layoutZone: () => undefined +} + +describe('useDiffCommentDecorator model lifecycle', () => { + it('rebuilds model-scoped resources when a retained editor swaps models', () => { + const editorDomNode = document.createElement('div') + document.body.appendChild(editorDomNode) + const disposeMouseMove = vi.fn() + const disposeMouseLeave = vi.fn() + const disposeScroll = vi.fn() + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a partial stand-in for Monaco's ICodeEditor; useDiffCommentDecorator calls only the members defined here, and a real editor needs a laid-out DOM this suite does not build. + const editor = { + getDomNode: () => editorDomNode, + getContainerDomNode: () => editorDomNode, + getOption: () => 19, + createDecorationsCollection: () => ({ set: () => {}, clear: () => {} }), + onMouseMove: () => ({ dispose: disposeMouseMove }), + onMouseLeave: () => ({ dispose: disposeMouseLeave }), + onDidScrollChange: () => ({ dispose: disposeScroll }), + onDidDispose: () => ({ dispose: () => {} }), + changeViewZones: (callback: (accessor: MonacoEditor.IViewZoneChangeAccessor) => void) => + callback(viewZoneAccessor) + } as unknown as MonacoEditor.ICodeEditor + const hook = renderHook( + ({ monacoModelIdentity }) => + useDiffCommentDecorator({ + editor, + monacoModelIdentity, + filePath: 'notes.ts', + worktreeId: 'worktree-1', + comments: [], + onAddCommentClick: vi.fn(), + onDeleteComment: vi.fn() + }), + { initialProps: { monacoModelIdentity: 'modified-v1' } } + ) + const firstButton = editorDomNode.querySelector('.orca-diff-comment-add-btn') + expect(firstButton).not.toBeNull() + + hook.rerender({ monacoModelIdentity: 'modified-v2' }) + + const replacementButton = editorDomNode.querySelector('.orca-diff-comment-add-btn') + expect(replacementButton).not.toBeNull() + expect(replacementButton).not.toBe(firstButton) + expect(disposeMouseMove).toHaveBeenCalledOnce() + expect(disposeMouseLeave).toHaveBeenCalledOnce() + expect(disposeScroll).toHaveBeenCalledOnce() + }) +}) diff --git a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx b/src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx deleted file mode 100644 index 8139b240976..00000000000 --- a/src/renderer/src/components/diff-comments/useDiffCommentDecorator.model-lifecycle.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// @vitest-environment happy-dom -import { renderHook } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { editor as MonacoEditor } from 'monaco-editor' - -vi.mock('monaco-editor', () => import('monaco-editor/esm/vs/editor/editor.api.js')) - -const storeFixture = vi.hoisted(() => ({ - activeGroupIdByWorktree: {}, - clearDeliveredDiffComments: vi.fn() -})) - -vi.mock('@/store', () => ({ - useAppStore: (selector: (state: typeof storeFixture) => unknown) => selector(storeFixture) -})) - -import { useDiffCommentDecorator } from './useDiffCommentDecorator' - -afterEach(() => { - document.body.replaceChildren() - vi.clearAllMocks() -}) - -/** No zones exist in this suite, so the hook never reaches these. */ -const viewZoneAccessor: MonacoEditor.IViewZoneChangeAccessor = { - addZone: () => '', - removeZone: () => undefined, - layoutZone: () => undefined -} - -describe('useDiffCommentDecorator model lifecycle', () => { - it('rebuilds model-scoped resources when a retained editor swaps models', () => { - const editorDomNode = document.createElement('div') - document.body.appendChild(editorDomNode) - const disposeMouseMove = vi.fn() - const disposeMouseLeave = vi.fn() - const disposeScroll = vi.fn() - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a partial stand-in for Monaco's ICodeEditor; useDiffCommentDecorator calls only the members defined here, and a real editor needs a laid-out DOM this suite does not build. - const editor = { - getDomNode: () => editorDomNode, - getContainerDomNode: () => editorDomNode, - getOption: () => 19, - createDecorationsCollection: () => ({ set: () => {}, clear: () => {} }), - onMouseMove: () => ({ dispose: disposeMouseMove }), - onMouseLeave: () => ({ dispose: disposeMouseLeave }), - onDidScrollChange: () => ({ dispose: disposeScroll }), - onDidDispose: () => ({ dispose: () => {} }), - changeViewZones: (callback: (accessor: MonacoEditor.IViewZoneChangeAccessor) => void) => - callback(viewZoneAccessor) - } as unknown as MonacoEditor.ICodeEditor - const hook = renderHook( - ({ monacoModelIdentity }) => - useDiffCommentDecorator({ - editor, - monacoModelIdentity, - filePath: 'notes.ts', - worktreeId: 'worktree-1', - comments: [], - onAddCommentClick: vi.fn(), - onDeleteComment: vi.fn() - }), - { initialProps: { monacoModelIdentity: 'modified-v1' } } - ) - const firstButton = editorDomNode.querySelector('.orca-diff-comment-add-btn') - - hook.rerender({ monacoModelIdentity: 'modified-v2' }) - - const replacementButton = editorDomNode.querySelector('.orca-diff-comment-add-btn') - expect(replacementButton).not.toBeNull() - expect(replacementButton).not.toBe(firstButton) - expect(disposeMouseMove).toHaveBeenCalledOnce() - expect(disposeMouseLeave).toHaveBeenCalledOnce() - expect(disposeScroll).toHaveBeenCalledOnce() - }) -})