Path: blob/main/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Event } from '../../../../../base/common/event.js';7import { DisposableStore } from '../../../../../base/common/lifecycle.js';8import { mock } from '../../../../../base/test/common/mock.js';9import { assertSnapshot } from '../../../../../base/test/common/snapshot.js';10import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';11import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';12import { LanguageFeaturesService } from '../../../../../editor/common/services/languageFeaturesService.js';13import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';14import { IEditorPaneSelectionChangeEvent } from '../../../../common/editor.js';15import { NotebookCellOutline } from '../../browser/contrib/outline/notebookOutline.js';16import { INotebookEditor, INotebookEditorPane } from '../../browser/notebookBrowser.js';17import { INotebookCellList } from '../../browser/view/notebookRenderingCommon.js';18import { OutlineEntry } from '../../browser/viewModel/OutlineEntry.js';19import { NotebookStickyLine, computeContent } from '../../browser/viewParts/notebookEditorStickyScroll.js';20import { CellKind } from '../../common/notebookCommon.js';21import { createNotebookCellList, setupInstantiationService, withTestNotebook } from './testNotebookEditor.js';22import { OutlineTarget } from '../../../../services/outline/browser/outline.js';2324suite('NotebookEditorStickyScroll', () => {25let disposables: DisposableStore;26let instantiationService: TestInstantiationService;2728const domNode: HTMLElement = document.createElement('div');2930teardown(() => {31disposables.dispose();32});3334const store = ensureNoDisposablesAreLeakedInTestSuite();3536setup(() => {37disposables = new DisposableStore();38instantiationService = setupInstantiationService(disposables);39instantiationService.set(ILanguageFeaturesService, new LanguageFeaturesService());40});4142function getOutline(editor: any) {43if (!editor.hasModel()) {44assert.ok(false, 'MUST have active text editor');45}46const outline = store.add(instantiationService.createInstance(NotebookCellOutline, new class extends mock<INotebookEditorPane>() {47override getControl() {48return editor;49}50override onDidChangeModel: Event<void> = Event.None;51override onDidChangeSelection: Event<IEditorPaneSelectionChangeEvent> = Event.None;52}, OutlineTarget.QuickPick));53return outline;54}5556function nbStickyTestHelper(domNode: HTMLElement, notebookEditor: INotebookEditor, notebookCellList: INotebookCellList, notebookOutlineEntries: OutlineEntry[], disposables: Pick<DisposableStore, 'add'>) {57const output = computeContent(notebookEditor, notebookCellList, notebookOutlineEntries, 0);58for (const stickyLine of output.values()) {59disposables.add(stickyLine.line);60}61return createStickyTestElement(output.values());62}6364function createStickyTestElement(stickyLines: IterableIterator<{ line: NotebookStickyLine; rendered: boolean }>) {65const outputElements = [];66for (const stickyLine of stickyLines) {67if (stickyLine.rendered) {68outputElements.unshift(stickyLine.line.element.innerText);69}70}71return outputElements;72}7374test('test0: should render empty, scrollTop at 0', async function () {75await withTestNotebook(76[77['# header a', 'markdown', CellKind.Markup, [], {}],78['## header aa', 'markdown', CellKind.Markup, [], {}],79['var b = 1;', 'javascript', CellKind.Code, [], {}],80['var b = 1;', 'javascript', CellKind.Code, [], {}],81['var b = 1;', 'javascript', CellKind.Code, [], {}],82['var b = 1;', 'javascript', CellKind.Code, [], {}],83['# header b', 'markdown', CellKind.Markup, [], {}],84['var c = 2;', 'javascript', CellKind.Code, [], {}]85],86async (editor, viewModel) => {87viewModel.restoreEditorViewState({88editingCells: Array.from({ length: 8 }, () => false),89editorViewStates: Array.from({ length: 8 }, () => null),90cellTotalHeights: Array.from({ length: 8 }, () => 50),91cellLineNumberStates: {},92collapsedInputCells: {},93collapsedOutputCells: {},94});9596const cellList = disposables.add(createNotebookCellList(instantiationService, disposables));97cellList.attachViewModel(viewModel);98cellList.layout(400, 100);99100editor.setScrollTop(0);101editor.visibleRanges = [{ start: 0, end: 8 }];102103const outline = getOutline(editor);104const notebookOutlineEntries = outline.entries;105const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables);106await assertSnapshot(resultingMap);107outline.dispose();108});109});110111test('test1: should render 0->1, visible range 3->8', async function () {112await withTestNotebook(113[114['# header a', 'markdown', CellKind.Markup, [], {}], // 0115['## header aa', 'markdown', CellKind.Markup, [], {}], // 50116['var b = 1;', 'javascript', CellKind.Code, [], {}], // 100117['var b = 1;', 'javascript', CellKind.Code, [], {}], // 150118['var b = 1;', 'javascript', CellKind.Code, [], {}], // 200119['var b = 1;', 'javascript', CellKind.Code, [], {}], // 250120['# header b', 'markdown', CellKind.Markup, [], {}], // 300121['var c = 2;', 'javascript', CellKind.Code, [], {}] // 350122],123async (editor, viewModel, ds) => {124viewModel.restoreEditorViewState({125editingCells: Array.from({ length: 8 }, () => false),126editorViewStates: Array.from({ length: 8 }, () => null),127cellTotalHeights: Array.from({ length: 8 }, () => 50),128cellLineNumberStates: {},129collapsedInputCells: {},130collapsedOutputCells: {},131});132133const cellList = ds.add(createNotebookCellList(instantiationService, ds));134cellList.attachViewModel(viewModel);135cellList.layout(400, 100);136137editor.setScrollTop(175);138editor.visibleRanges = [{ start: 3, end: 8 }];139140const outline = getOutline(editor);141const notebookOutlineEntries = outline.entries;142const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);143144await assertSnapshot(resultingMap);145outline.dispose();146});147});148149test('test2: should render 0, visible range 6->9 so collapsing next 2 against following section', async function () {150await withTestNotebook(151[152['# header a', 'markdown', CellKind.Markup, [], {}], // 0153['## header aa', 'markdown', CellKind.Markup, [], {}], // 50154['### header aaa', 'markdown', CellKind.Markup, [], {}],// 100155['var b = 1;', 'javascript', CellKind.Code, [], {}], // 150156['var b = 1;', 'javascript', CellKind.Code, [], {}], // 200157['var b = 1;', 'javascript', CellKind.Code, [], {}], // 250158['var b = 1;', 'javascript', CellKind.Code, [], {}], // 300159['# header b', 'markdown', CellKind.Markup, [], {}], // 350160['var c = 2;', 'javascript', CellKind.Code, [], {}] // 400161],162async (editor, viewModel, ds) => {163viewModel.restoreEditorViewState({164editingCells: Array.from({ length: 9 }, () => false),165editorViewStates: Array.from({ length: 9 }, () => null),166cellTotalHeights: Array.from({ length: 9 }, () => 50),167cellLineNumberStates: {},168collapsedInputCells: {},169collapsedOutputCells: {},170});171172const cellList = ds.add(createNotebookCellList(instantiationService, ds));173cellList.attachViewModel(viewModel);174cellList.layout(400, 100);175176editor.setScrollTop(325); // room for a single header177editor.visibleRanges = [{ start: 6, end: 9 }];178179const outline = getOutline(editor);180const notebookOutlineEntries = outline.entries;181const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);182183await assertSnapshot(resultingMap);184outline.dispose();185});186});187188test('test3: should render 0->2, collapsing against equivalent level header', async function () {189await withTestNotebook(190[191['# header a', 'markdown', CellKind.Markup, [], {}], // 0192['## header aa', 'markdown', CellKind.Markup, [], {}], // 50193['### header aaa', 'markdown', CellKind.Markup, [], {}],// 100194['var b = 1;', 'javascript', CellKind.Code, [], {}], // 150195['### header aab', 'markdown', CellKind.Markup, [], {}],// 200196['var b = 1;', 'javascript', CellKind.Code, [], {}], // 250197['var b = 1;', 'javascript', CellKind.Code, [], {}], // 300198['var b = 1;', 'javascript', CellKind.Code, [], {}], // 350199['# header b', 'markdown', CellKind.Markup, [], {}], // 400200['var c = 2;', 'javascript', CellKind.Code, [], {}] // 450201],202async (editor, viewModel, ds) => {203viewModel.restoreEditorViewState({204editingCells: Array.from({ length: 10 }, () => false),205editorViewStates: Array.from({ length: 10 }, () => null),206cellTotalHeights: Array.from({ length: 10 }, () => 50),207cellLineNumberStates: {},208collapsedInputCells: {},209collapsedOutputCells: {},210});211212const cellList = ds.add(createNotebookCellList(instantiationService, ds));213cellList.attachViewModel(viewModel);214cellList.layout(400, 100);215216editor.setScrollTop(175); // room for a single header217editor.visibleRanges = [{ start: 3, end: 10 }];218219const outline = getOutline(editor);220const notebookOutlineEntries = outline.entries;221const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);222223await assertSnapshot(resultingMap);224outline.dispose();225});226});227228// outdated/improper behavior229test('test4: should render 0, scrolltop halfway through cell 0', async function () {230await withTestNotebook(231[232['# header a', 'markdown', CellKind.Markup, [], {}],233['## header aa', 'markdown', CellKind.Markup, [], {}],234['var b = 1;', 'javascript', CellKind.Code, [], {}],235['var b = 1;', 'javascript', CellKind.Code, [], {}],236['var b = 1;', 'javascript', CellKind.Code, [], {}],237['var b = 1;', 'javascript', CellKind.Code, [], {}],238['# header b', 'markdown', CellKind.Markup, [], {}],239['var c = 2;', 'javascript', CellKind.Code, [], {}]240],241async (editor, viewModel, ds) => {242viewModel.restoreEditorViewState({243editingCells: Array.from({ length: 8 }, () => false),244editorViewStates: Array.from({ length: 8 }, () => null),245cellTotalHeights: Array.from({ length: 8 }, () => 50),246cellLineNumberStates: {},247collapsedInputCells: {},248collapsedOutputCells: {},249});250251const cellList = ds.add(createNotebookCellList(instantiationService, ds));252cellList.attachViewModel(viewModel);253cellList.layout(400, 100);254255editor.setScrollTop(50);256editor.visibleRanges = [{ start: 0, end: 8 }];257258const outline = getOutline(editor);259const notebookOutlineEntries = outline.entries;260const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);261262await assertSnapshot(resultingMap);263outline.dispose();264});265});266267test('test5: should render 0->2, scrolltop halfway through cell 2', async function () {268await withTestNotebook(269[270['# header a', 'markdown', CellKind.Markup, [], {}],271['## header aa', 'markdown', CellKind.Markup, [], {}],272['### header aaa', 'markdown', CellKind.Markup, [], {}],273['#### header aaaa', 'markdown', CellKind.Markup, [], {}],274['var b = 1;', 'javascript', CellKind.Code, [], {}],275['var b = 1;', 'javascript', CellKind.Code, [], {}],276['var b = 1;', 'javascript', CellKind.Code, [], {}],277['var b = 1;', 'javascript', CellKind.Code, [], {}],278['# header b', 'markdown', CellKind.Markup, [], {}],279['var c = 2;', 'javascript', CellKind.Code, [], {}]280],281async (editor, viewModel, ds) => {282viewModel.restoreEditorViewState({283editingCells: Array.from({ length: 10 }, () => false),284editorViewStates: Array.from({ length: 10 }, () => null),285cellTotalHeights: Array.from({ length: 10 }, () => 50),286cellLineNumberStates: {},287collapsedInputCells: {},288collapsedOutputCells: {},289});290291const cellList = ds.add(createNotebookCellList(instantiationService, ds));292cellList.attachViewModel(viewModel);293cellList.layout(400, 100);294295editor.setScrollTop(125);296editor.visibleRanges = [{ start: 2, end: 10 }];297298const outline = getOutline(editor);299const notebookOutlineEntries = outline.entries;300const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);301302await assertSnapshot(resultingMap);303outline.dispose();304});305});306307test('test6: should render 6->7, scrolltop halfway through cell 7', async function () {308await withTestNotebook(309[310['# header a', 'markdown', CellKind.Markup, [], {}],311['## header aa', 'markdown', CellKind.Markup, [], {}],312['var b = 1;', 'javascript', CellKind.Code, [], {}],313['var b = 1;', 'javascript', CellKind.Code, [], {}],314['var b = 1;', 'javascript', CellKind.Code, [], {}],315['var b = 1;', 'javascript', CellKind.Code, [], {}],316['# header b', 'markdown', CellKind.Markup, [], {}],317['## header bb', 'markdown', CellKind.Markup, [], {}],318['### header bbb', 'markdown', CellKind.Markup, [], {}],319['var c = 2;', 'javascript', CellKind.Code, [], {}]320],321async (editor, viewModel, ds) => {322viewModel.restoreEditorViewState({323editingCells: Array.from({ length: 10 }, () => false),324editorViewStates: Array.from({ length: 10 }, () => null),325cellTotalHeights: Array.from({ length: 10 }, () => 50),326cellLineNumberStates: {},327collapsedInputCells: {},328collapsedOutputCells: {},329});330331const cellList = ds.add(createNotebookCellList(instantiationService, ds));332cellList.attachViewModel(viewModel);333cellList.layout(400, 100);334335editor.setScrollTop(375);336editor.visibleRanges = [{ start: 7, end: 10 }];337338const outline = getOutline(editor);339const notebookOutlineEntries = outline.entries;340const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);341342await assertSnapshot(resultingMap);343outline.dispose();344});345});346347test('test7: should render 0->1, collapsing against next section', async function () {348await withTestNotebook(349[350['# header a', 'markdown', CellKind.Markup, [], {}], //0351['## header aa', 'markdown', CellKind.Markup, [], {}], //50352['### header aaa', 'markdown', CellKind.Markup, [], {}], //100353['#### header aaaa', 'markdown', CellKind.Markup, [], {}], //150354['var b = 1;', 'javascript', CellKind.Code, [], {}], //200355['var b = 1;', 'javascript', CellKind.Code, [], {}], //250356['var b = 1;', 'javascript', CellKind.Code, [], {}], //300357['var b = 1;', 'javascript', CellKind.Code, [], {}], //350358['# header b', 'markdown', CellKind.Markup, [], {}], //400359['## header bb', 'markdown', CellKind.Markup, [], {}], //450360['### header bbb', 'markdown', CellKind.Markup, [], {}],361['var c = 2;', 'javascript', CellKind.Code, [], {}]362],363async (editor, viewModel, ds) => {364viewModel.restoreEditorViewState({365editingCells: Array.from({ length: 12 }, () => false),366editorViewStates: Array.from({ length: 12 }, () => null),367cellTotalHeights: Array.from({ length: 12 }, () => 50),368cellLineNumberStates: {},369collapsedInputCells: {},370collapsedOutputCells: {},371});372373const cellList = ds.add(createNotebookCellList(instantiationService, ds));374cellList.attachViewModel(viewModel);375cellList.layout(400, 100);376377editor.setScrollTop(350);378editor.visibleRanges = [{ start: 7, end: 12 }];379380const outline = getOutline(editor);381const notebookOutlineEntries = outline.entries;382const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds);383384await assertSnapshot(resultingMap);385outline.dispose();386});387});388});389390391