diff --git a/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.css b/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.css index db15c2779dcb..b69456e1f9ac 100644 --- a/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.css +++ b/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.css @@ -16,15 +16,22 @@ } .horizontal-splitter -.sizer { +.sash { top: -2px; height: 5px; z-index: 27; width: 100%; position: relative; + touch-action: none; } .horizontal-splitter -.sizer.resizing { - background-color: var(--vscode-focusBorder); +.sash.hovering { + transition: background-color 0.1s ease-out; + background-color: var(--vscode-sash-hoverBorder); +} + +.horizontal-splitter +.sash.resizing { + background-color: var(--vscode-sash-hoverBorder); } diff --git a/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.tsx b/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.tsx index ca87ed22cb39..24501e95a93c 100644 --- a/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.tsx +++ b/src/vs/base/browser/ui/positronComponents/splitters/horizontalSplitter.tsx @@ -7,13 +7,17 @@ import './horizontalSplitter.css'; // React. -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; // Other dependencies. import * as DOM from '../../../dom.js'; +import { Delayer } from '../../../../common/async.js'; import { isMacintosh } from '../../../../common/platform.js'; +import { DisposableStore } from '../../../../common/lifecycle.js'; import { positronClassNames } from '../../../../common/positronUtilities.js'; import { createStyleSheet } from '../../../domStylesheets.js'; +import { usePositronReactServicesContext } from '../../../positronReactRendererContext.js'; +import { getHoverDelay, isPointInsideElement } from './verticalSplitter.js'; /** * HorizontalSplitterResizeParams interface. This defines the parameters of a resize operation. When @@ -36,9 +40,57 @@ export const HorizontalSplitter = (props: { showResizeIndicator?: boolean; onBeginResize: () => HorizontalSplitterResizeParams; onResize: (height: number) => void; + onDoubleClick?: () => void; }) => { + // Context hooks. + const services = usePositronReactServicesContext(); + + // Reference hooks. + const hoverDelayerRef = useRef>(undefined); + // State hooks. const [resizing, setResizing] = useState(false); + const [hovering, setHovering] = useState(false); + + // Main useEffect. + useEffect(() => { + // Create the disposable store for cleanup. + const disposables = new DisposableStore(); + + // Set the hover delayer. + const hoverDelay = getHoverDelay(services.configurationService); + hoverDelayerRef.current = disposables.add(new Delayer(hoverDelay)); + + // Add the onDidChangeConfiguration event handler. + disposables.add( + services.configurationService.onDidChangeConfiguration(e => { + // Track changes to workbench.sash.hoverDelay. + if (e.affectedKeys.has('workbench.sash.hoverDelay') && hoverDelayerRef.current) { + hoverDelayerRef.current.defaultDelay = getHoverDelay(services.configurationService); + } + }) + ); + + // Return the cleanup function that will dispose of the disposables. + return () => disposables.dispose(); + }, [services.configurationService]); + + /** + * onPointerEnter handler. + */ + const pointerEnterHandler = () => { + hoverDelayerRef.current?.trigger(() => setHovering(true)); + }; + + /** + * onPointerLeave handler. + */ + const pointerLeaveHandler = () => { + if (!resizing) { + hoverDelayerRef.current?.cancel(); + setHovering(false); + } + }; /** * onPointerDown handler. @@ -56,15 +108,23 @@ export const HorizontalSplitter = (props: { // Setup the resize state. const resizeParams = props.onBeginResize(); - const target = DOM.getWindow(e.currentTarget).document.body; + const sash = e.currentTarget; + const body = DOM.getWindow(sash).document.body; const clientY = e.clientY; - const styleSheet = createStyleSheet(target); + const styleSheet = createStyleSheet(body); + + // Track whether any meaningful drag occurred, so we can distinguish + // a click (or double-click) from a drag on pointer release. + let didDrag = false; /** * pointermove event handler. * @param e A PointerEvent that describes a user interaction with the pointer. */ const pointerMoveHandler = (e: PointerEvent) => { + // The pointer moved, mark as dragging. + didDrag = true; + // Consume the event. e.preventDefault(); e.stopPropagation(); @@ -84,9 +144,10 @@ export const HorizontalSplitter = (props: { cursor = isMacintosh ? 'row-resize' : 'ns-resize'; } - // Update the style sheet's text content with the desired cursor. This is a clever + // Update the style sheet's text content with the desired cursor and + // disable text selection during the resize operation. This is a clever // technique adopted from src/vs/base/browser/ui/sash/sash.ts. - styleSheet.textContent = `* { cursor: ${cursor} !important; }`; + styleSheet.textContent = `* { cursor: ${cursor} !important; user-select: none !important; }`; // Call the onResize callback. props.onResize(newHeight); @@ -97,28 +158,24 @@ export const HorizontalSplitter = (props: { * @param e A PointerEvent that describes a user interaction with the pointer. */ const lostPointerCaptureHandler = (e: PointerEvent) => { - // Clear the dragging flag. - setResizing(false); + // Only commit the final height if the user actually dragged. + // This avoids interfering with click and double-click interactions. + if (didDrag) { + // Handle the last possible move change. + pointerMoveHandler(e); + } // Remove our pointer event handlers. - target.removeEventListener('pointermove', pointerMoveHandler); - target.removeEventListener('lostpointercapture', lostPointerCaptureHandler); - - // Calculate the new height. - let newHeight = calculateNewHeight(e); - - // Adjust the new height to be within limits. - if (newHeight < resizeParams.minimumHeight) { - newHeight = resizeParams.minimumHeight; - } else if (newHeight > resizeParams.maximumHeight) { - newHeight = resizeParams.maximumHeight; - } + sash.removeEventListener('pointermove', pointerMoveHandler); + sash.removeEventListener('lostpointercapture', lostPointerCaptureHandler); // Remove the style sheet. - target.removeChild(styleSheet); + body.removeChild(styleSheet); - // Call the onEndResize callback. - props.onResize(newHeight); + // Clear the resizing flag. + setResizing(false); + hoverDelayerRef.current?.cancel(); + setHovering(isPointInsideElement(e.clientX, e.clientY, sash)); }; /** @@ -136,25 +193,40 @@ export const HorizontalSplitter = (props: { resizeParams.startingHeight - delta; }; - // Set the dragging flag. + // Set the dragging flag and show hover indicator immediately. setResizing(true); + hoverDelayerRef.current?.cancel(); + setHovering(true); - // Set the capture target of future pointer events to be the current target and add our - // pointer event handlers. - target.setPointerCapture(e.pointerId); - target.addEventListener('pointermove', pointerMoveHandler); - target.addEventListener('lostpointercapture', lostPointerCaptureHandler); + // Set pointer capture on the sizer element and add our pointer event handlers. + sash.setPointerCapture(e.pointerId); + sash.addEventListener('pointermove', pointerMoveHandler); + sash.addEventListener('lostpointercapture', lostPointerCaptureHandler); + }; + + /** + * onDoubleClick handler. + */ + const doubleClickHandler = () => { + props.onDoubleClick?.(); + hoverDelayerRef.current?.cancel(); + setHovering(false); }; // Render. return (
+ {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
); diff --git a/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.css b/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.css index 6ea6b3e9bb4c..63274e3c8dc7 100644 --- a/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.css +++ b/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.css @@ -29,11 +29,11 @@ .vertical-splitter .sash-hovering { transition: background-color 0.1s ease-out; - background-color: var(--vscode-focusBorder); + background-color: var(--vscode-sash-hoverBorder); } .vertical-splitter .sash-resizing { - background-color: var(--vscode-focusBorder); + background-color: var(--vscode-sash-hoverBorder); } .vertical-splitter .sash .sash-indicator { @@ -43,11 +43,11 @@ .vertical-splitter .sash .sash-indicator.hovering { transition: background-color 0.1s ease-out; - background-color: var(--vscode-focusBorder); + background-color: var(--vscode-sash-hoverBorder); } .vertical-splitter .sash .sash-indicator.resizing { - background-color: var(--vscode-focusBorder); + background-color: var(--vscode-sash-hoverBorder); } .vertical-splitter .expand-collapse-button { diff --git a/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.tsx b/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.tsx index a91133f0f3f1..2d62a2ccc319 100644 --- a/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.tsx +++ b/src/vs/base/browser/ui/positronComponents/splitters/verticalSplitter.tsx @@ -7,7 +7,7 @@ import './verticalSplitter.css'; // React. -import React, { PointerEvent, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; // Other dependencies. import * as DOM from '../../../dom.js'; @@ -71,7 +71,7 @@ export interface VerticalSplitterResizeParams { * @param configurationService The configuration service. * @returns The sash size. */ -const getSashSize = (configurationService: IConfigurationService) => +export const getSashSize = (configurationService: IConfigurationService) => configurationService.getValue('workbench.sash.size'); /** @@ -79,7 +79,7 @@ const getSashSize = (configurationService: IConfigurationService) => * @param configurationService The configuration service. * @returns The hover delay. */ -const getHoverDelay = (configurationService: IConfigurationService) => +export const getHoverDelay = (configurationService: IConfigurationService) => configurationService.getValue('workbench.sash.hoverDelay'); @@ -90,7 +90,7 @@ const getHoverDelay = (configurationService: IConfigurationService) => * @param element The element. * @returns true, if the point is inside the specified element; otherwise, false. */ -const isPointInsideElement = (x: number, y: number, element?: HTMLElement) => { +export const isPointInsideElement = (x: number, y: number, element?: HTMLElement) => { if (!element) { return false; } @@ -148,8 +148,8 @@ export const VerticalSplitter = ({ const services = usePositronReactServicesContext(); // Reference hooks. - const sashRef = useRef(undefined!); const expandCollapseButtonRef = useRef(undefined!); + const hoverDelayerRef = useRef>(undefined); // State hooks. const [splitterWidth, setSplitterWidth] = useState( @@ -159,10 +159,8 @@ export const VerticalSplitter = ({ calculateSashWidth(services.configurationService, collapsible) ); const [sashIndicatorWidth, setSashIndicatorWidth] = useState(getSashSize(services.configurationService)); - const [hoverDelay, setHoverDelay] = useState(getHoverDelay(services.configurationService)); const [hovering, setHovering] = useState(false); const [highlightExpandCollapse, setHighlightExpandCollapse] = useState(false); - const [hoveringDelayer, setHoveringDelayer] = useState>(undefined!); const [collapsed, setCollapsed, collapsedRef] = useStateRef(isCollapsed); const [resizing, setResizing] = useState(false); @@ -171,6 +169,10 @@ export const VerticalSplitter = ({ // Create the disposable store for cleanup. const disposableStore = new DisposableStore(); + // Set the hover delayer. + const hoverDelay = getHoverDelay(services.configurationService); + hoverDelayerRef.current = disposableStore.add(new Delayer(hoverDelay)); + // Add the onDidChangeConfiguration event handler. disposableStore.add( services.configurationService.onDidChangeConfiguration(configurationChangeEvent => { @@ -184,16 +186,13 @@ export const VerticalSplitter = ({ } // Track changes to workbench.sash.hoverDelay. - if (configurationChangeEvent.affectedKeys.has('workbench.sash.hoverDelay')) { - setHoverDelay(getHoverDelay(services.configurationService)); + if (configurationChangeEvent.affectedKeys.has('workbench.sash.hoverDelay') && hoverDelayerRef.current) { + hoverDelayerRef.current.defaultDelay = getHoverDelay(services.configurationService); } } }) ); - // Set the hover delayer. - setHoveringDelayer(disposableStore.add(new Delayer(0))); - // Return the cleanup function that will dispose of the disposables. return () => disposableStore.dispose(); }, [collapsible, services.configurationService]); @@ -208,14 +207,15 @@ export const VerticalSplitter = ({ * @param e A PointerEvent that describes a user interaction with the pointer. */ const sashPointerEnterHandler = (e: React.PointerEvent) => { - hoveringDelayer.trigger(() => { + const sash = e.currentTarget; + hoverDelayerRef.current?.trigger(() => { setHovering(true); - const rect = sashRef.current.getBoundingClientRect(); + const rect = sash.getBoundingClientRect(); if (e.clientY >= rect.top + EXPAND_COLLAPSE_BUTTON_TOP && e.clientY <= rect.top + EXPAND_COLLAPSE_BUTTON_TOP + EXPAND_COLLAPSE_BUTTON_SIZE) { setHighlightExpandCollapse(true); } - }, hoverDelay); + }); }; /** @@ -223,9 +223,10 @@ export const VerticalSplitter = ({ * @param e A PointerEvent that describes a user interaction with the pointer. */ const sashPointerLeaveHandler = (e: React.PointerEvent) => { - // When not resizing, trigger the delayer. + // When not resizing, unset hover. if (!resizing) { - hoveringDelayer.trigger(() => setHovering(false), hoverDelay); + hoverDelayerRef.current?.cancel(); + setHovering(false); } }; @@ -234,7 +235,7 @@ export const VerticalSplitter = ({ * @param e A PointerEvent that describes a user interaction with the pointer. */ const expandCollapseButtonPointerEnterHandler = (e: React.PointerEvent) => { - hoveringDelayer.cancel(); + hoverDelayerRef.current?.cancel(); setHovering(true); setHighlightExpandCollapse(true); }; @@ -244,7 +245,8 @@ export const VerticalSplitter = ({ * @param e A PointerEvent that describes a user interaction with the pointer. */ const expandCollapseButtonPointerLeaveHandler = (e: React.PointerEvent) => { - hoveringDelayer.trigger(() => setHovering(false), hoverDelay); + hoverDelayerRef.current?.cancel(); + setHovering(false); setHighlightExpandCollapse(false); }; @@ -261,7 +263,7 @@ export const VerticalSplitter = ({ onCollapsedChanged?.(false); } - hoveringDelayer.cancel(); + hoverDelayerRef.current?.cancel(); setHovering(false); setHighlightExpandCollapse(false); }; @@ -290,9 +292,10 @@ export const VerticalSplitter = ({ // Setup the resize state. const resizeParams = onBeginResize(); const startingWidth = collapsed ? sashWidth : resizeParams.startingWidth; - const target = DOM.getWindow(e.currentTarget).document.body; + const sash = e.currentTarget; + const body = DOM.getWindow(e.currentTarget).document.body; const clientX = e.clientX; - const styleSheet = createStyleSheet(target); + const styleSheet = createStyleSheet(body); /** * pointermove event handler. @@ -355,18 +358,16 @@ export const VerticalSplitter = ({ pointerMoveHandler(e); // Remove our pointer event handlers. - // @ts-ignore - target.removeEventListener('pointermove', pointerMoveHandler); - // @ts-ignore - target.removeEventListener('lostpointercapture', lostPointerCaptureHandler); + sash.removeEventListener('pointermove', pointerMoveHandler); + sash.removeEventListener('lostpointercapture', lostPointerCaptureHandler); // Remove the style sheet. - target.removeChild(styleSheet); + body.removeChild(styleSheet); // Clear the resizing flag. setResizing(false); - hoveringDelayer.cancel(); - setHovering(isPointInsideElement(e.clientX, e.clientY, sashRef.current)); + hoverDelayerRef.current?.cancel(); + setHovering(isPointInsideElement(e.clientX, e.clientY, sash)); }; // Set the dragging flag @@ -374,11 +375,9 @@ export const VerticalSplitter = ({ // Set the capture target of future pointer events to be the current target and add our // pointer event handlers. - target.setPointerCapture(e.pointerId); - // @ts-ignore - target.addEventListener('pointermove', pointerMoveHandler); - // @ts-ignore - target.addEventListener('lostpointercapture', lostPointerCaptureHandler); + sash.setPointerCapture(e.pointerId); + sash.addEventListener('pointermove', pointerMoveHandler); + sash.addEventListener('lostpointercapture', lostPointerCaptureHandler); }; // Render. @@ -393,7 +392,6 @@ export const VerticalSplitter = ({ }} >
{ + const el = outputsInnerRef.current; + if (el) { + el.style.height = ''; + el.style.maxHeight = ''; + el.classList.remove('height-override'); + } + }, []); + + // Reset height override when outputs change (new execution) or scrolling mode toggles. + useEffect(() => { + clearHeightOverride(); + }, [outputs, outputScrolling, clearHeightOverride]); + + const onBeginResize = useCallback((): HorizontalSplitterResizeParams => { + const el = outputsInnerRef.current; + return { + startingHeight: el?.offsetHeight ?? 0, + minimumHeight: MINIMUM_SCROLLABLE_OUTPUT_HEIGHT, + // Cap the max height to the output content. + maximumHeight: Math.max(el?.scrollHeight ?? 0, MINIMUM_SCROLLABLE_OUTPUT_HEIGHT), + }; + }, []); + + const onResize = useCallback((height: number) => { + const el = outputsInnerRef.current; + if (el) { + el.style.height = height + 'px'; + el.style.maxHeight = height + 'px'; + el.classList.add('height-override'); + } + }, []); + // Update the output overflow context key. React.useEffect(() => { if (!outputOverflowsKey) { return; } @@ -181,8 +218,9 @@ const CellOutputsSection = React.memo(function CellOutputsSection({ cell, output 'positron-notebook-code-cell-outputs-inner', 'positron-notebook-scrollable', 'positron-notebook-scrollable-fade', - { 'output-scrolling': outputScrolling } + { 'output-scrolling': outputScrolling }, )}> + {isCollapsed ? : outputs?.map((output) => ( @@ -201,6 +239,14 @@ const CellOutputsSection = React.memo(function CellOutputsSection({ cell, output )) }
+ {outputScrolling && !isCollapsed && hasOutputs && + + }
); diff --git a/test/e2e/pages/notebooksPositron.ts b/test/e2e/pages/notebooksPositron.ts index 3c7c778603a9..82741efe7f8a 100644 --- a/test/e2e/pages/notebooksPositron.ts +++ b/test/e2e/pages/notebooksPositron.ts @@ -68,6 +68,7 @@ export class PositronNotebooks extends Notebooks { // Cell outputs cellOutput = (index: number) => this.cell.nth(index).getByTestId('cell-output'); + cellOutputSash = (index: number) => this.cellOutput(index).locator('.horizontal-splitter .sash'); private outputActionBar = (index: number) => this.cell.nth(index).locator('.cell-output-action-bar'); outputCollapsedLabel = (index: number) => this.cellOutput(index).getByText('Output collapsed'); outputTruncationMessage = (index: number) => this.cellOutput(index).getByText(/\.\.\. Show [\d,.\s\u00A0]+ more lines/); @@ -637,6 +638,48 @@ export class PositronNotebooks extends Notebooks { }); } + /** + * Action: Drag the output resize sash for a cell by a given distance. + * @param cellIndex - The index of the cell whose output sash to drag. + * @param distance - The vertical distance in pixels to drag (positive = down). + */ + async dragCellOutputSash(cellIndex: number, distance: number) { + const page = this.code.driver.page; + const sash = this.cellOutputSash(cellIndex); + + // Reveal the sash for debugging. + await sash.scrollIntoViewIfNeeded(); + + // Get the sash's starting position. + const box = await sash.boundingBox(); + expect(box).toBeTruthy(); + const startX = box!.x + box!.width / 2; + const startY = box!.y + box!.height / 2; + + // Drag the sash down to grow the output area. + await page.mouse.move(startX, startY); + await page.mouse.down(); + await page.mouse.move(startX, startY + distance, { steps: 10 }); + await page.mouse.up(); + + // Reveal the final sash position for debugging. + await sash.scrollIntoViewIfNeeded(); + } + + /** + * Get the height of a cell's output area. + * @param cellIndex - The index of the cell to measure + * @returns The height of the cell's output area in pixels + */ + async getCellOutputHeight(cellIndex: number): Promise { + const output = this.cellOutput(cellIndex); + const box = await output.boundingBox(); + if (!box) { + throw new Error(`Could not get bounding box for cell output at index ${cellIndex}`); + } + return box.height; + } + /** * Action: Create a new code cell at the END of the notebook. */ @@ -1394,6 +1437,29 @@ export class PositronNotebooks extends Notebooks { }); } + /** + * Verify: the height of the cell's output area matches expected height. + * @param cellIndex - The index of the cell to check. + * @param height - The expected height of the cell's output area in pixels. + * @param options - Options to control expectation: + * tolerance: Optional pixel tolerance for height comparison (default: 0, meaning exact match). + */ + async expectCellOutputHeight( + cellIndex: number, + height: number, + { tolerance = 0 }: { tolerance?: number } = {} + ): Promise { + await test.step(`Verify cell output height at index ${cellIndex} is ${height}px (±${tolerance}px)`, async () => { + const actual = await this.getCellOutputHeight(cellIndex); + if (tolerance === 0) { + expect(actual).toBe(height); + } else { + expect(actual).toBeGreaterThanOrEqual(height - tolerance); + expect(actual).toBeLessThanOrEqual(height + tolerance); + } + }); + } + /** * Verify: the cell at the specified index is fully visible within the * notebook scroll container. For cells taller than the viewport, checks diff --git a/test/e2e/tests/notebooks-positron/notebook-output-resize.test.ts b/test/e2e/tests/notebooks-positron/notebook-output-resize.test.ts new file mode 100644 index 000000000000..fd73d9f1eb88 --- /dev/null +++ b/test/e2e/tests/notebooks-positron/notebook-output-resize.test.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2026 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import { expect, tags } from '../_test.setup'; +import { test } from './_test.setup.js'; + +test.use({ + suiteId: __filename +}); + +test.describe('Positron Notebooks: Output Resize', { + tag: [tags.WIN, tags.WEB, tags.POSITRON_NOTEBOOKS] +}, () => { + + test('Drag sash resizes scrollable cell output', async function ({ app, settings }) { + const { notebooks, notebooksPositron } = app.workbench; + + await test.step('Setup: Create notebook with scrollable output', async () => { + // Enable output scrolling explicitly - the default differs in dev vs release builds. + await settings.set({ 'notebook.output.scrolling': true }, { reload: 'web' }); + + await notebooks.createNewNotebook(); + await notebooksPositron.expectCellCountToBe(1); + await notebooksPositron.kernel.select('Python'); + + // Generate output that exceeds the scrollable area so the resize + // sash appears. + await notebooksPositron.addCodeToCell(0, 'for i in range(100): print(f"line {i}")', { run: true }); + await notebooksPositron.expectOutputAtIndex(0, ['line 0']); + }); + + const sash = notebooksPositron.cellOutputSash(0); + + await test.step('Resize sash is visible for scrollable output', async () => { + await expect(sash).toBeVisible(); + }); + + const initialHeight = await notebooksPositron.getCellOutputHeight(0); + + await test.step('Dragging the sash changes the output height', async () => { + const dragDistance = 150; + await notebooksPositron.dragCellOutputSash(0, dragDistance); + await notebooksPositron.expectCellOutputHeight(0, initialHeight + dragDistance, { tolerance: 5 }); + }); + + await test.step('Double-clicking the sash resets to default height', async () => { + await sash.click({ clickCount: 2 }); + await notebooksPositron.expectCellOutputHeight(0, initialHeight, { tolerance: 5 }); + }); + + await test.step('Sash is hidden when output is collapsed', async () => { + await notebooksPositron.outputCollapseToggle(0).scrollIntoViewIfNeeded(); + await notebooksPositron.outputCollapseToggle(0).click(); + await expect(sash).toBeHidden(); + + // Expand again + await notebooksPositron.outputCollapseToggle(0).click(); + await expect(sash).toBeVisible(); + }); + + await test.step('Re-running cell resets height', async () => { + await notebooksPositron.dragCellOutputSash(0, 100); + await notebooksPositron.runCodeAtIndex(0); + await notebooksPositron.expectCellOutputHeight(0, initialHeight, { tolerance: 5 }); + }); + }); +});