123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- // Copyright (c) Jupyter Development Team.
- import 'jest';
- import {
- Toolbar,
- ToolbarButton,
- CommandToolbarButton,
- SessionContext
- } from '@jupyterlab/apputils';
- import { toArray } from '@lumino/algorithm';
- import { CommandRegistry } from '@lumino/commands';
- import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
- import { Widget } from '@lumino/widgets';
- import { simulate } from 'simulate-event';
- import {
- createSessionContext,
- framePromise,
- JupyterServer
- } from '@jupyterlab/testutils';
- const server = new JupyterServer();
- beforeAll(async () => {
- await server.start();
- });
- afterAll(async () => {
- await server.shutdown();
- });
- describe('@jupyterlab/apputils', () => {
- let widget: Toolbar<Widget>;
- beforeEach(async () => {
- jest.setTimeout(20000);
- widget = new Toolbar();
- });
- afterEach(async () => {
- widget.dispose();
- });
- describe('Toolbar', () => {
- describe('#constructor()', () => {
- it('should construct a new toolbar widget', () => {
- const widget = new Toolbar();
- expect(widget).toBeInstanceOf(Toolbar);
- });
- it('should add the `jp-Toolbar` class', () => {
- const widget = new Toolbar();
- expect(widget.hasClass('jp-Toolbar')).toBe(true);
- });
- });
- describe('#names()', () => {
- it('should get an ordered list the toolbar item names', () => {
- widget.addItem('foo', new Widget());
- widget.addItem('bar', new Widget());
- widget.addItem('baz', new Widget());
- expect(toArray(widget.names())).toEqual(['foo', 'bar', 'baz']);
- });
- });
- describe('#addItem()', () => {
- it('should add an item to the toolbar', () => {
- const item = new Widget();
- expect(widget.addItem('test', item)).toBe(true);
- expect(toArray(widget.names())).toContain('test');
- });
- it('should add the `jp-Toolbar-item` class to the widget', () => {
- const item = new Widget();
- widget.addItem('test', item);
- expect(item.hasClass('jp-Toolbar-item')).toBe(true);
- });
- it('should return false if the name is already used', () => {
- widget.addItem('test', new Widget());
- expect(widget.addItem('test', new Widget())).toBe(false);
- });
- });
- describe('#insertItem()', () => {
- it('should insert the item into the toolbar', () => {
- widget.addItem('a', new Widget());
- widget.addItem('b', new Widget());
- widget.insertItem(1, 'c', new Widget());
- expect(toArray(widget.names())).toEqual(['a', 'c', 'b']);
- });
- it('should clamp the bounds', () => {
- widget.addItem('a', new Widget());
- widget.addItem('b', new Widget());
- widget.insertItem(10, 'c', new Widget());
- expect(toArray(widget.names())).toEqual(['a', 'b', 'c']);
- });
- });
- describe('#insertAfter()', () => {
- it('should insert an item into the toolbar after `c`', () => {
- widget.addItem('a', new Widget());
- widget.addItem('b', new Widget());
- widget.insertItem(1, 'c', new Widget());
- widget.insertAfter('c', 'd', new Widget());
- expect(toArray(widget.names())).toEqual(['a', 'c', 'd', 'b']);
- });
- it('should return false if the target item does not exist', () => {
- widget.addItem('a', new Widget());
- widget.addItem('b', new Widget());
- const value = widget.insertAfter('c', 'd', new Widget());
- expect(value).toBe(false);
- });
- });
- describe('#insertBefore()', () => {
- it('should insert an item into the toolbar before `c`', () => {
- widget.addItem('a', new Widget());
- widget.addItem('b', new Widget());
- widget.insertItem(1, 'c', new Widget());
- widget.insertBefore('c', 'd', new Widget());
- expect(toArray(widget.names())).toEqual(['a', 'd', 'c', 'b']);
- });
- it('should return false if the target item does not exist', () => {
- widget.addItem('a', new Widget());
- widget.addItem('b', new Widget());
- const value = widget.insertBefore('c', 'd', new Widget());
- expect(value).toBe(false);
- });
- });
- describe('.createFromCommand', () => {
- const commands = new CommandRegistry();
- const testLogCommandId = 'test:toolbar-log';
- const logArgs: ReadonlyPartialJSONObject[] = [];
- let enabled = false;
- let toggled = true;
- let visible = false;
- commands.addCommand(testLogCommandId, {
- execute: args => {
- logArgs.push(args);
- },
- label: 'Test log command label',
- caption: 'Test log command caption',
- usage: 'Test log command usage',
- iconClass: 'test-icon-class',
- className: 'test-log-class',
- isEnabled: () => enabled,
- isToggled: () => toggled,
- isVisible: () => visible
- });
- async function render(button: CommandToolbarButton) {
- button.update();
- await framePromise();
- expect(button.renderPromise).toBeDefined();
- await button.renderPromise;
- }
- it('should create a button', () => {
- const button = new CommandToolbarButton({
- commands,
- id: testLogCommandId
- });
- expect(button).toBeInstanceOf(CommandToolbarButton);
- button.dispose();
- });
- it('should add main class', async () => {
- const button = new CommandToolbarButton({
- commands,
- id: testLogCommandId
- });
- await render(button);
- const buttonNode = button.node.firstChild as HTMLButtonElement;
- expect(buttonNode.classList.contains('test-log-class')).toBe(true);
- button.dispose();
- });
- it('should add an icon with icon class and label', async () => {
- const button = new CommandToolbarButton({
- commands,
- id: testLogCommandId
- });
- await render(button);
- const buttonNode = button.node.firstChild as HTMLButtonElement;
- expect(buttonNode.title).toBe('Test log command caption');
- const wrapperNode = buttonNode.firstChild as HTMLElement;
- const iconNode = wrapperNode.firstChild as HTMLElement;
- expect(iconNode.classList.contains('test-icon-class')).toBe(true);
- button.dispose();
- });
- it('should apply state classes', async () => {
- enabled = false;
- toggled = true;
- visible = false;
- const button = new CommandToolbarButton({
- commands,
- id: testLogCommandId
- });
- await render(button);
- const buttonNode = button.node.firstChild as HTMLButtonElement;
- expect(buttonNode.disabled).toBe(true);
- expect(buttonNode.classList.contains('lm-mod-toggled')).toBe(true);
- expect(buttonNode.classList.contains('lm-mod-hidden')).toBe(true);
- button.dispose();
- });
- it('should update state classes', async () => {
- enabled = false;
- toggled = true;
- visible = false;
- const button = new CommandToolbarButton({
- commands,
- id: testLogCommandId
- });
- await render(button);
- const buttonNode = button.node.firstChild as HTMLButtonElement;
- expect(buttonNode.disabled).toBe(true);
- expect(buttonNode.classList.contains('lm-mod-toggled')).toBe(true);
- expect(buttonNode.classList.contains('lm-mod-hidden')).toBe(true);
- enabled = true;
- visible = true;
- commands.notifyCommandChanged(testLogCommandId);
- expect(buttonNode.disabled).toBe(false);
- expect(buttonNode.classList.contains('lm-mod-toggled')).toBe(true);
- expect(buttonNode.classList.contains('lm-mod-hidden')).toBe(false);
- enabled = false;
- visible = false;
- button.dispose();
- });
- it('should use the command label if no icon class/label', async () => {
- const id = 'to-be-removed';
- const cmd = commands.addCommand(id, {
- execute: () => {
- return;
- },
- label: 'Label-only button'
- });
- const button = new CommandToolbarButton({
- commands,
- id
- });
- await render(button);
- const buttonNode = button.node.firstChild as HTMLButtonElement;
- expect(buttonNode.textContent).toBe('Label-only button');
- cmd.dispose();
- });
- it('should update the node content on command change event', async () => {
- const id = 'to-be-removed';
- let iconClassValue: string = '';
- const cmd = commands.addCommand(id, {
- execute: () => {
- /* no op */
- },
- label: 'Label-only button',
- iconClass: () => iconClassValue ?? ''
- });
- const button = new CommandToolbarButton({
- commands,
- id
- });
- await render(button);
- const buttonNode = button.node.firstChild as HTMLButtonElement;
- expect(buttonNode.textContent).toBe('Label-only button');
- expect(buttonNode.classList.contains(iconClassValue)).toBe(false);
- iconClassValue = 'updated-icon-class';
- commands.notifyCommandChanged(id);
- await render(button);
- const wrapperNode = buttonNode.firstChild as HTMLElement;
- const iconNode = wrapperNode.firstChild as HTMLElement;
- expect(iconNode.classList.contains(iconClassValue)).toBe(true);
- cmd.dispose();
- });
- });
- describe('Kernel buttons', () => {
- let sessionContext: SessionContext;
- beforeEach(async () => {
- sessionContext = await createSessionContext();
- });
- afterEach(async () => {
- await sessionContext.shutdown();
- sessionContext.dispose();
- });
- describe('.createInterruptButton()', () => {
- it("should add an inline svg node with the 'stop' icon", async () => {
- const button = Toolbar.createInterruptButton(sessionContext);
- Widget.attach(button, document.body);
- await framePromise();
- expect(
- button.node.querySelector("[data-icon$='stop']")
- ).toBeDefined();
- });
- });
- describe('.createRestartButton()', () => {
- it("should add an inline svg node with the 'refresh' icon", async () => {
- const button = Toolbar.createRestartButton(sessionContext);
- Widget.attach(button, document.body);
- await framePromise();
- expect(
- button.node.querySelector("[data-icon$='refresh']")
- ).toBeDefined();
- });
- });
- describe('.createKernelNameItem()', () => {
- it("should display the `'display_name'` of the kernel", async () => {
- const item = Toolbar.createKernelNameItem(sessionContext);
- await sessionContext.initialize();
- Widget.attach(item, document.body);
- await framePromise();
- const node = item.node.querySelector(
- '.jp-ToolbarButtonComponent-label'
- )!;
- expect(node.textContent).toBe(sessionContext.kernelDisplayName);
- });
- });
- describe('.createKernelStatusItem()', () => {
- beforeEach(async () => {
- await sessionContext.initialize();
- await sessionContext.session?.kernel?.info;
- });
- it('should display a busy status if the kernel status is busy', async () => {
- const item = Toolbar.createKernelStatusItem(sessionContext);
- let called = false;
- sessionContext.statusChanged.connect((_, status) => {
- if (status === 'busy') {
- expect(
- item.node.querySelector("[data-icon$='circle']")
- ).toBeDefined();
- called = true;
- }
- });
- const future = sessionContext.session?.kernel?.requestExecute({
- code: 'a = 109\na'
- })!;
- await future.done;
- expect(called).toBe(true);
- });
- it('should show the current status in the node title', async () => {
- const item = Toolbar.createKernelStatusItem(sessionContext);
- const status = sessionContext.session?.kernel?.status;
- expect(item.node.title.toLowerCase()).toContain(status);
- let called = false;
- const future = sessionContext.session?.kernel?.requestExecute({
- code: 'a = 1'
- })!;
- future.onIOPub = msg => {
- if (sessionContext.session?.kernel?.status === 'busy') {
- expect(item.node.title.toLowerCase()).toContain('busy');
- called = true;
- }
- };
- await future.done;
- expect(called).toBe(true);
- });
- it('should handle a starting session', async () => {
- await sessionContext.session?.kernel?.info;
- await sessionContext.shutdown();
- sessionContext = await createSessionContext();
- await sessionContext.initialize();
- const item = Toolbar.createKernelStatusItem(sessionContext);
- expect(item.node.title).toBe('Kernel Connecting');
- expect(
- item.node.querySelector("[data-icon$='circle-empty']")
- ).toBeDefined();
- await sessionContext.initialize();
- await sessionContext.session?.kernel?.info;
- });
- });
- });
- });
- describe('ToolbarButton', () => {
- describe('#constructor()', () => {
- it('should accept no arguments', () => {
- const widget = new ToolbarButton();
- expect(widget).toBeInstanceOf(ToolbarButton);
- });
- it('should accept options', async () => {
- const widget = new ToolbarButton({
- className: 'foo',
- iconClass: 'iconFoo',
- onClick: () => {
- return void 0;
- },
- tooltip: 'bar'
- });
- Widget.attach(widget, document.body);
- await framePromise();
- const button = widget.node.firstChild as HTMLElement;
- expect(button.classList.contains('foo')).toBe(true);
- expect(button.querySelector('.iconFoo')).toBeDefined();
- expect(button.title).toBe('bar');
- });
- });
- describe('#dispose()', () => {
- it('should dispose of the resources used by the widget', () => {
- const button = new ToolbarButton();
- button.dispose();
- expect(button.isDisposed).toBe(true);
- });
- it('should be safe to call more than once', () => {
- const button = new ToolbarButton();
- button.dispose();
- button.dispose();
- expect(button.isDisposed).toBe(true);
- });
- });
- describe('#handleEvent()', () => {
- describe('click', () => {
- it('should activate the callback', async () => {
- let called = false;
- const button = new ToolbarButton({
- onClick: () => {
- called = true;
- }
- });
- Widget.attach(button, document.body);
- await framePromise();
- simulate(button.node.firstChild as HTMLElement, 'mousedown');
- expect(called).toBe(true);
- button.dispose();
- });
- });
- describe('keydown', () => {
- it('Enter should activate the callback', async () => {
- let called = false;
- const button = new ToolbarButton({
- onClick: () => {
- called = true;
- }
- });
- Widget.attach(button, document.body);
- await framePromise();
- simulate(button.node.firstChild as HTMLElement, 'keydown', {
- key: 'Enter'
- });
- expect(called).toBe(true);
- button.dispose();
- });
- it('Space should activate the callback', async () => {
- let called = false;
- const button = new ToolbarButton({
- onClick: () => {
- called = true;
- }
- });
- Widget.attach(button, document.body);
- await framePromise();
- simulate(button.node.firstChild as HTMLElement, 'keydown', {
- key: ' '
- });
- expect(called).toBe(true);
- button.dispose();
- });
- });
- });
- // describe('#onAfterAttach()', () => {
- // it('should add event listeners to the node', () => {
- // const button = new LogToolbarButton();
- // Widget.attach(button, document.body);
- // expect(button.methods).to.contain('onAfterAttach');
- // simulate(button.node, 'click');
- // expect(button.events).to.contain('click');
- // button.dispose();
- // });
- // });
- // describe('#onBeforeDetach()', () => {
- // it('should remove event listeners from the node', async () => {
- // const button = new LogToolbarButton();
- // Widget.attach(button, document.body);
- // await framePromise();
- // Widget.detach(button);
- // expect(button.methods).to.contain('onBeforeDetach');
- // simulate(button.node, 'click');
- // expect(button.events).to.not.contain('click');
- // button.dispose();
- // });
- // });
- });
- });
|