mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-01 21:27:58 +00:00
fix(editor): dispose compose DiffEditor models after widget reset (#1828)
Closing the compose or git diff dialog threw a Monaco console error because the library disposed TextModels while DiffEditorWidget still held them.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import type { DiffEditorProps } from '@monaco-editor/react';
|
||||
import type { editor } from 'monaco-editor';
|
||||
import { DiffEditor } from './monacoLoader';
|
||||
|
||||
/**
|
||||
* DiffEditor wrapper that owns model disposal. keepCurrentOriginalModel and
|
||||
* keepCurrentModifiedModel stop @monaco-editor/react from disposing first.
|
||||
* On unmount this resets the widget (setModel(null)) then disposes the models.
|
||||
* Monaco 0.56 throws "TextModel got disposed before DiffEditorWidget model
|
||||
* got reset" if the library disposes models while the widget still holds them.
|
||||
*/
|
||||
export function SafeDiffEditor({ onMount, ...props }: DiffEditorProps) {
|
||||
const editorRef = useRef<editor.IStandaloneDiffEditor | null>(null);
|
||||
const modelsRef = useRef<editor.IDiffEditorModel | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const diffEditor = editorRef.current;
|
||||
const models = modelsRef.current;
|
||||
modelsRef.current = null;
|
||||
try {
|
||||
diffEditor?.setModel(null);
|
||||
diffEditor?.dispose();
|
||||
} catch {
|
||||
// Widget already torn down by monaco-react.
|
||||
}
|
||||
try {
|
||||
models?.original.dispose();
|
||||
models?.modified.dispose();
|
||||
} catch {
|
||||
// Models already disposed.
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DiffEditor
|
||||
{...props}
|
||||
keepCurrentOriginalModel
|
||||
keepCurrentModifiedModel
|
||||
onMount={(ed, monaco) => {
|
||||
editorRef.current = ed;
|
||||
modelsRef.current = ed.getModel();
|
||||
onMount?.(ed, monaco);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import type { DiffEditorProps } from '@monaco-editor/react';
|
||||
|
||||
let lastDiffProps: DiffEditorProps | null = null;
|
||||
let disposeCalls: string[] = [];
|
||||
|
||||
vi.mock('@/lib/monacoLoader', () => ({
|
||||
DiffEditor: (props: DiffEditorProps) => {
|
||||
lastDiffProps = props;
|
||||
if (props.onMount) {
|
||||
const original = { dispose: () => disposeCalls.push('original.dispose') };
|
||||
const modified = { dispose: () => disposeCalls.push('modified.dispose') };
|
||||
let attached: { original: typeof original; modified: typeof modified } | null = {
|
||||
original,
|
||||
modified,
|
||||
};
|
||||
props.onMount({
|
||||
getModel: () => attached,
|
||||
setModel: (next: typeof attached) => {
|
||||
disposeCalls.push('setModel');
|
||||
attached = next;
|
||||
},
|
||||
dispose: () => {
|
||||
if (attached) {
|
||||
throw new Error('TextModel got disposed before DiffEditorWidget model got reset');
|
||||
}
|
||||
disposeCalls.push('editor.dispose');
|
||||
},
|
||||
} as never, {} as never);
|
||||
}
|
||||
return <div data-testid="diff-editor" />;
|
||||
},
|
||||
}));
|
||||
|
||||
import { SafeDiffEditor } from '../SafeDiffEditor';
|
||||
|
||||
describe('SafeDiffEditor', () => {
|
||||
it('keeps current models and resets the widget before disposing them', () => {
|
||||
disposeCalls = [];
|
||||
const { unmount } = render(
|
||||
<SafeDiffEditor
|
||||
height="100%"
|
||||
language="yaml"
|
||||
original="a"
|
||||
modified="b"
|
||||
/>,
|
||||
);
|
||||
expect(lastDiffProps?.keepCurrentOriginalModel).toBe(true);
|
||||
expect(lastDiffProps?.keepCurrentModifiedModel).toBe(true);
|
||||
unmount();
|
||||
expect(disposeCalls.indexOf('setModel')).toBeGreaterThanOrEqual(0);
|
||||
expect(disposeCalls.indexOf('setModel')).toBeLessThan(disposeCalls.indexOf('original.dispose'));
|
||||
expect(disposeCalls.indexOf('setModel')).toBeLessThan(disposeCalls.indexOf('modified.dispose'));
|
||||
expect(disposeCalls.indexOf('editor.dispose')).toBeGreaterThan(disposeCalls.indexOf('setModel'));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user