diff --git a/packages/dockview-core/src/__tests__/dockview/components/tab/defaultTab.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/tab/defaultTab.spec.ts new file mode 100644 index 000000000..291cf4712 --- /dev/null +++ b/packages/dockview-core/src/__tests__/dockview/components/tab/defaultTab.spec.ts @@ -0,0 +1,63 @@ +import { DockviewApi } from '../../../../api/component.api'; +import { DockviewPanelApi, TitleEvent } from '../../../../api/dockviewPanelApi'; +import { DefaultTab } from '../../../../dockview/components/tab/defaultTab'; +import { fromPartial } from '@total-typescript/shoehorn'; +import { Emitter } from '../../../../events'; +import { fireEvent } from '@testing-library/dom'; + +describe('defaultTab', () => { + test('that title updates', () => { + const cut = new DefaultTab(); + + let el = cut.element.querySelector('.dv-default-tab-content'); + expect(el).toBeTruthy(); + expect(el!.textContent).toBe(''); + + const onDidTitleChange = new Emitter(); + + const api = fromPartial({ + onDidTitleChange: onDidTitleChange.event, + }); + const containerApi = fromPartial({}); + + cut.init({ + api, + containerApi, + params: {}, + title: 'title_abc', + }); + + el = cut.element.querySelector('.dv-default-tab-content'); + expect(el).toBeTruthy(); + expect(el!.textContent).toBe('title_abc'); + + onDidTitleChange.fire({ title: 'title_def' }); + + expect(el!.textContent).toBe('title_def'); + }); + + test('that click closes tab', () => { + const cut = new DefaultTab(); + + const api = fromPartial({ + onDidTitleChange: jest.fn(), + close: jest.fn(), + }); + const containerApi = fromPartial({}); + + cut.init({ + api, + containerApi, + params: {}, + title: 'title_abc', + }); + + let el = cut.element.querySelector('.dv-default-tab-action'); + + fireEvent.mouseDown(el!); + expect(api.close).toHaveBeenCalledTimes(0); + + fireEvent.click(el!); + expect(api.close).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts index 254835de9..8b927a105 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts @@ -1,16 +1,13 @@ import { CompositeDisposable } from '../../../lifecycle'; import { ITabRenderer, GroupPanelPartInitParameters } from '../../types'; import { addDisposableListener } from '../../../events'; -import { PanelUpdateEvent } from '../../../panel/types'; -import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { createCloseButton } from '../../../svg'; export class DefaultTab extends CompositeDisposable implements ITabRenderer { private _element: HTMLElement; private _content: HTMLElement; private action: HTMLElement; - // - private params: GroupPanelPartInitParameters = {} as any; + private _title: string | undefined; get element(): HTMLElement { return this._element; @@ -21,7 +18,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { this._element = document.createElement('div'); this._element.className = 'dv-default-tab'; - // + this._content = document.createElement('div'); this._content.className = 'dv-default-tab-content'; @@ -29,10 +26,9 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { this.action.className = 'dv-default-tab-action'; this.action.appendChild(createCloseButton()); - // this._element.appendChild(this._content); this._element.appendChild(this.action); - // + this.addDisposables( addDisposableListener(this.action, 'mousedown', (ev) => { ev.preventDefault(); @@ -42,40 +38,33 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { this.render(); } - public update(event: PanelUpdateEvent): void { - this.params = { ...this.params, ...event.params }; + init(params: GroupPanelPartInitParameters): void { + this._title = params.title; + + this.addDisposables( + params.api.onDidTitleChange((event) => { + this._title = event.title; + this.render(); + }), + addDisposableListener(this.action, 'mousedown', (ev) => { + ev.preventDefault(); + }), + addDisposableListener(this.action, 'click', (ev) => { + if (ev.defaultPrevented) { + return; + } + + ev.preventDefault(); + params.api.close(); + }) + ); + this.render(); } - focus(): void { - //noop - } - - public init(params: GroupPanelPartInitParameters): void { - this.params = params; - this._content.textContent = params.title; - - addDisposableListener(this.action, 'click', (ev) => { - ev.preventDefault(); // - this.params.api.close(); - }); - } - - onGroupChange(_group: DockviewGroupPanel): void { - this.render(); - } - - onPanelVisibleChange(_isPanelVisible: boolean): void { - this.render(); - } - - public layout(_width: number, _height: number): void { - // noop - } - private render(): void { - if (this._content.textContent !== this.params.title) { - this._content.textContent = this.params.title; + if (this._content.textContent !== this._title) { + this._content.textContent = this._title ?? ''; } } } diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx index 18c462e72..d5fb32c7d 100644 --- a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -2,11 +2,16 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { DockviewDefaultTab } from '../../dockview/defaultTab'; import React from 'react'; import { fromPartial } from '@total-typescript/shoehorn'; -import { DockviewApi, DockviewPanelApi } from 'dockview-core'; +import { DockviewApi, DockviewPanelApi, TitleEvent } from 'dockview-core'; +import { Emitter } from 'dockview-core/dist/cjs/events'; +import { act } from 'react-dom/test-utils'; +import { Disposable } from 'dockview-core/dist/cjs/lifecycle'; describe('defaultTab', () => { test('has close button by default', async () => { - const api = fromPartial({}); + const api = fromPartial({ + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + }); const containerApi = fromPartial({}); const params = {}; @@ -25,6 +30,7 @@ describe('defaultTab', () => { test('that title is displayed', async () => { const api = fromPartial({ title: 'test_title', + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -43,8 +49,43 @@ describe('defaultTab', () => { ).toBe('test_title'); }); + test('that title is updated', async () => { + const onDidTitleChange = new Emitter(); + + const api = fromPartial({ + title: 'test_title', + onDidTitleChange: onDidTitleChange.event, + }); + const containerApi = fromPartial({}); + const params = {}; + + render( + + ); + + let element = await screen.getByTestId('dockview-dv-default-tab'); + expect( + element.querySelector('.dv-default-tab-content')?.textContent + ).toBe('test_title'); + + act(() => { + onDidTitleChange.fire({ title: 'test_title_2' }); + }); + + element = await screen.getByTestId('dockview-dv-default-tab'); + expect( + element.querySelector('.dv-default-tab-content')?.textContent + ).toBe('test_title_2'); + }); + test('has no close button when hideClose=true', async () => { - const api = fromPartial({}); + const api = fromPartial({ + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + }); const containerApi = fromPartial({}); const params = {}; @@ -64,6 +105,7 @@ describe('defaultTab', () => { test('that settings closeActionOverride skips api.close()', async () => { const api = fromPartial({ close: jest.fn(), + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -85,13 +127,14 @@ describe('defaultTab', () => { ) as HTMLElement; fireEvent.click(btn); - expect(closeActionOverride).toBeCalledTimes(1); - expect(api.close).toBeCalledTimes(0); + expect(closeActionOverride).toHaveBeenCalledTimes(1); + expect(api.close).toHaveBeenCalledTimes(0); }); test('that clicking close calls api.close()', async () => { const api = fromPartial({ close: jest.fn(), + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -110,11 +153,13 @@ describe('defaultTab', () => { ) as HTMLElement; fireEvent.click(btn); - expect(api.close).toBeCalledTimes(1); + expect(api.close).toHaveBeenCalledTimes(1); }); test('has close button when hideClose=false', async () => { - const api = fromPartial({}); + const api = fromPartial({ + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + }); const containerApi = fromPartial({}); const params = {}; @@ -134,6 +179,7 @@ describe('defaultTab', () => { test('that mouseDown on close button prevents panel becoming active', async () => { const api = fromPartial({ setActive: jest.fn(), + onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -152,9 +198,9 @@ describe('defaultTab', () => { ) as HTMLElement; fireEvent.mouseDown(btn); - expect(api.setActive).toBeCalledTimes(0); + expect(api.setActive).toHaveBeenCalledTimes(0); fireEvent.click(element); - expect(api.setActive).toBeCalledTimes(1); + expect(api.setActive).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index 2ad153292..56c3a6793 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -1,6 +1,22 @@ import React from 'react'; import { CloseButton } from '../svg'; -import { IDockviewPanelHeaderProps } from 'dockview-core'; +import { DockviewPanelApi, IDockviewPanelHeaderProps } from 'dockview-core'; + +function useTitle(api: DockviewPanelApi): string | undefined { + const [title, setTitle] = React.useState(api.title); + + React.useEffect(() => { + const disposable = api.onDidTitleChange((event) => { + setTitle(event.title); + }); + + return () => { + disposable.dispose(); + }; + }, [api]); + + return title; +} export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps & React.DOMAttributes & { @@ -18,6 +34,8 @@ export const DockviewDefaultTab: React.FunctionComponent< closeActionOverride, ...rest }) => { + const title = useTitle(api); + const onClose = React.useCallback( (event: React.MouseEvent) => { event.preventDefault(); @@ -57,7 +75,7 @@ export const DockviewDefaultTab: React.FunctionComponent< onClick={onClick} className="dv-default-tab" > - {api.title} + {title} {!hideClose && (