diff --git a/src/test/setup-webview.ts b/src/test/setup-webview.ts index 85d56f1e3..b2dd6fba0 100644 --- a/src/test/setup-webview.ts +++ b/src/test/setup-webview.ts @@ -99,6 +99,16 @@ if ( }; } +// Polyfill for requestAnimationFrame / cancelAnimationFrame (not implemented by JSDOM) +if (!global.requestAnimationFrame) { + global.requestAnimationFrame = (cb: FrameRequestCallback): number => { + return setTimeout(() => cb(Date.now()), 0) as unknown as number; + }; + global.cancelAnimationFrame = (id: number): void => { + clearTimeout(id); + }; +} + // Polyfill for ResizeObserver (required by @vscode-elements/elements) // JSDOM does not support ResizeObserver, so we provide a no-op implementation class ResizeObserverPolyfill { diff --git a/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx b/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx index 4c3c7feb0..9c9f8a62d 100644 --- a/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx +++ b/src/test/suite/views/data-browsing-app/monaco-viewer.test.tsx @@ -15,6 +15,13 @@ import * as vscodeApi from '../../../../views/data-browsing-app/vscode-api'; // Mock the Monaco Editor component let mockEditorValue = ''; +// Minimal Monaco instance passed as the second argument to onMount. +const mockMonacoInstance = { + editor: { EditorOption: { lineHeight: 31 } }, + KeyMod: { CtrlCmd: 2048 }, + KeyCode: { KeyC: 33 }, +}; + const mockEditorInstance = { getValue: (): string => mockEditorValue, setValue: (value: string): void => { @@ -24,16 +31,23 @@ const mockEditorInstance = { getValue: (): string => mockEditorValue, }), getContentHeight: (): number => 100, - onDidContentSizeChange: (): { dispose: () => void } => ({ + // getOption is used to read the real line height after mount. + getOption: (): number => 19, + // addAction is used to register the Ctrl+C keybinding. + addAction: (): { dispose: () => void } => ({ dispose: (): void => { /* no-op */ }, }), - getAction: (): { run: () => void } => ({ - run: (): void => { + onDidContentSizeChange: (): { dispose: () => void } => ({ + dispose: (): void => { /* no-op */ }, }), + // run() must return a Promise because the component chains .then() on it. + getAction: (): { run: () => Promise } => ({ + run: (): Promise => Promise.resolve(), + }), dispose: (): void => { /* no-op */ }, @@ -44,9 +58,11 @@ const MockEditor = ({ value, onMount }: any): JSX.Element => { React.useEffect(() => { if (onMount && value) { mockEditorValue = value; - // Simulate editor mount + // Simulate editor mount, passing both the editor instance and the monaco + // instance so the component's handleEditorMount doesn't error on the + // second argument (used for EditorOption.lineHeight, KeyMod, KeyCode). setTimeout(() => { - onMount(mockEditorInstance); + onMount(mockEditorInstance, mockMonacoInstance); }, 0); } }, [onMount, value]); @@ -489,4 +505,117 @@ describe('MonacoViewer test suite', function () { expect(deleteIcon).to.exist; }); }); + + describe('Show more / Show less', function () { + // Builds a document with `count` top-level fields named field0..fieldN. + const makeDocument = (count: number): Record => + Object.fromEntries( + Array.from({ length: count }, (_, i) => [`field${i}`, i]), + ); + + it('should not show toggle buttons for documents with ≤ 15 fields', function () { + render(); + + expect(screen.queryByText(/Show .* more field/)).to.not.exist; + expect(screen.queryByText('Show less')).to.not.exist; + }); + + it('should show "Show N more fields" button for documents with > 15 fields', function () { + // 20 fields → 20 - 15 = 5 hidden → "Show 5 more fields" + render(); + + // Button text lives inside a ; use closest('button') for the + // data-expanded attribute which is set on the )} -
- +
+
+ +
+
+ +
+ {hasMoreFields && !showAllFields && ( + + )} + + {hasMoreFields && showAllFields && ( + + )}
);