Skip to content

Commit

Permalink
Backport PR #15843 on branch 4.1.x (Fix saving of item positions in r…
Browse files Browse the repository at this point in the history
…eactive toolbar) (#15906)

* Backport PR #15843: Fix saving of item positions in reactive toolbar

* Restore the galata function to resize sidebar in documentation/utils (instead of sidebar's helper) and add a copy of it in jupyterlab/utils

---------

Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com>
Co-authored-by: Nicolas Brichet <nicolas.brichet@quantstack.net>
  • Loading branch information
3 people committed Mar 4, 2024
1 parent d274f84 commit 90427f8
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 23 deletions.
179 changes: 179 additions & 0 deletions galata/test/jupyterlab/notebook-toolbar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Distributed under the terms of the Modified BSD License.

import { expect, IJupyterLabPageFixture, test } from '@jupyterlab/galata';
import { setSidebarWidth } from './utils';

const fileName = 'notebook.ipynb';

Expand All @@ -14,6 +15,60 @@ async function populateNotebook(page: IJupyterLabPageFixture) {
await page.notebook.addCell('code', '2 ** 3');
}

/**
* Adds an item in notebook toolbar using the extension system.
*/
async function addWidgetsInNotebookToolbar(
page: IJupyterLabPageFixture,
notebook: string,
content: string,
afterElem: string
) {
await page.notebook.activate(notebook);
await page.evaluate(
options => {
const { content, afterElem } = options;

// A minimal widget class, with required field for adding an item in toolbar.
class MinimalWidget {
constructor(node) {
this.node = node;
}
addClass() {}
node = null;
processMessage() {}
hasClass(name) {
return false;
}
_parent = null;
get parent() {
return this._parent;
}
set parent(p) {
this._parent?.layout.removeWidget(this);
this._parent = p;
}
}

const plugin = {
id: 'my-test-plugin',
activate: app => {
const toolbar = app.shell.activeWidget.toolbar;
const node = document.createElement('div');
node.classList.add('jp-CommandToolbarButton');
node.classList.add('jp-Toolbar-item');
node.textContent = content;
const widget = new MinimalWidget(node);
toolbar.insertAfter(afterElem, content, widget);
}
};
jupyterapp.registerPlugin(plugin);

Check warning on line 65 in galata/test/jupyterlab/notebook-toolbar.test.ts

View workflow job for this annotation

GitHub Actions / Linux (lint, 3.11)

'jupyterapp' is not defined
jupyterapp.activatePlugin('my-test-plugin');

Check warning on line 66 in galata/test/jupyterlab/notebook-toolbar.test.ts

View workflow job for this annotation

GitHub Actions / Linux (lint, 3.11)

'jupyterapp' is not defined
},
{ content, afterElem }
);
}

test.describe('Notebook Toolbar', () => {
test.beforeEach(async ({ page }) => {
await page.notebook.createNew(fileName);
Expand Down Expand Up @@ -141,3 +196,127 @@ test('Toolbar items act on owner widget', async ({ page }) => {
expect(classlistEnd.split(' ')).toContain('jp-mod-current');
expect(await page.notebook.getCellCount()).toEqual(2);
});

test.describe('Reactive toolbar', () => {
test.beforeEach(async ({ page }) => {
await page.notebook.createNew(fileName);
});

test('Reducing toolbar width should display opener item', async ({
page
}) => {
const toolbar = page.locator('.jp-NotebookPanel-toolbar');
await expect(toolbar.locator('.jp-Toolbar-item:visible')).toHaveCount(14);
await expect(
toolbar.locator('.jp-Toolbar-responsive-opener')
).not.toBeVisible();

await setSidebarWidth(page, 520);

await expect(
toolbar.locator('.jp-Toolbar-responsive-opener')
).toBeVisible();

await expect(toolbar.locator('.jp-Toolbar-item:visible')).toHaveCount(12);
});

test('Items in popup toolbar should have the same order', async ({
page
}) => {
const toolbar = page.locator('.jp-NotebookPanel-toolbar');

await setSidebarWidth(page, 520);

await toolbar.locator('.jp-Toolbar-responsive-opener').click();

// A 'visible' selector is added because there is another response popup element
// when running in playwright (don't know where it come from, it is an empty
// toolbar).
const popupToolbar = page.locator(
'body > .jp-Toolbar-responsive-popup:visible'
);
const popupToolbarItems = popupToolbar.locator('.jp-Toolbar-item:visible');
await expect(popupToolbarItems).toHaveCount(3);

const itemChildClasses = [
'.jp-DebuggerBugButton',
'.jp-Toolbar-kernelName',
'.jp-Notebook-ExecutionIndicator'
];

for (let i = 0; i < (await popupToolbarItems.count()); i++) {
await expect(
popupToolbarItems.nth(i).locator(itemChildClasses[i])
).toHaveCount(1);
}
});

test('Item added from extension should be correctly placed', async ({
page
}) => {
const toolbar = page.locator('.jp-NotebookPanel-toolbar');
await addWidgetsInNotebookToolbar(
page,
'notebook.ipynb',
'new item 1',
'cellType'
);

const toolbarItems = toolbar.locator('.jp-Toolbar-item:visible');
await expect(toolbarItems.nth(10)).toHaveText('new item 1');
});

test('Item should be correctly placed after resize', async ({ page }) => {
const toolbar = page.locator('.jp-NotebookPanel-toolbar');
await addWidgetsInNotebookToolbar(
page,
'notebook.ipynb',
'new item 1',
'cellType'
);

await setSidebarWidth(page, 600);
await toolbar.locator('.jp-Toolbar-responsive-opener').click();

// A 'visible' selector is added because there is another response popup element
// when running in playwright (don't know where it come from, it is an empty
// toolbar).
const popupToolbar = page.locator(
'body > .jp-Toolbar-responsive-popup:visible'
);
const popupToolbarItems = popupToolbar.locator('.jp-Toolbar-item:visible');

await expect(popupToolbarItems.nth(1)).toHaveText('new item 1');

await setSidebarWidth(page);
const toolbarItems = toolbar.locator('.jp-Toolbar-item:visible');
await expect(toolbarItems.nth(10)).toHaveText('new item 1');
});

test('Item added from extension should be correctly placed in popup toolbar', async ({
page
}) => {
const toolbar = page.locator('.jp-NotebookPanel-toolbar');

await setSidebarWidth(page, 600);

await addWidgetsInNotebookToolbar(
page,
'notebook.ipynb',
'new item 1',
'cellType'
);

await toolbar.locator('.jp-Toolbar-responsive-opener').click();

// A 'visible' selector is added because there is another response popup element
// when running in playwright (don't know where it come from, it is an empty
// toolbar).
const popupToolbar = page.locator(
'body > .jp-Toolbar-responsive-popup:visible'
);
const popupToolbarItems = popupToolbar.locator('.jp-Toolbar-item:visible');

await expect(popupToolbarItems.nth(1)).toHaveText('new item 1');
});
});
33 changes: 33 additions & 0 deletions galata/test/jupyterlab/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,36 @@ export async function dragCellTo(
await page.waitForCondition(options.stopCondition);
await page.mouse.up();
}

/**
* Set the sidebar width
*
* @param page Page object
* @param width Sidebar width in pixels
* @param side Which sidebar to set: 'left' or 'right'
*/
export async function setSidebarWidth(
page: IJupyterLabPageFixture,
width = 251,
side: 'left' | 'right' = 'left'
): Promise<void> {
const handles = page.locator(
'#jp-main-split-panel > .lm-SplitPanel-handle:not(.lm-mod-hidden)'
);
const splitHandle =
side === 'left'
? await handles.first().elementHandle()
: await handles.last().elementHandle();
const handleBBox = await splitHandle.boundingBox();

await page.mouse.move(
handleBBox.x + 0.5 * handleBBox.width,
handleBBox.y + 0.5 * handleBBox.height
);
await page.mouse.down();
await page.mouse.move(
side === 'left' ? 33 + width : page.viewportSize().width - 33 - width,
handleBBox.y + 0.5 * handleBBox.height
);
await page.mouse.up();
}

0 comments on commit 90427f8

Please sign in to comment.