Merge pull request #623 from mathuo/622-settitle-issues

bug: fix setTitle issues
This commit is contained in:
mathuo 2024-06-08 19:15:08 +03:00 committed by GitHub
commit 476b0a1d42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 164 additions and 48 deletions

View File

@ -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<TitleEvent>();
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: onDidTitleChange.event,
});
const containerApi = fromPartial<DockviewApi>({});
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<DockviewPanelApi>({
onDidTitleChange: jest.fn(),
close: jest.fn(),
});
const containerApi = fromPartial<DockviewApi>({});
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);
});
});

View File

@ -1,16 +1,13 @@
import { CompositeDisposable } from '../../../lifecycle'; import { CompositeDisposable } from '../../../lifecycle';
import { ITabRenderer, GroupPanelPartInitParameters } from '../../types'; import { ITabRenderer, GroupPanelPartInitParameters } from '../../types';
import { addDisposableListener } from '../../../events'; import { addDisposableListener } from '../../../events';
import { PanelUpdateEvent } from '../../../panel/types';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { createCloseButton } from '../../../svg'; import { createCloseButton } from '../../../svg';
export class DefaultTab extends CompositeDisposable implements ITabRenderer { export class DefaultTab extends CompositeDisposable implements ITabRenderer {
private _element: HTMLElement; private _element: HTMLElement;
private _content: HTMLElement; private _content: HTMLElement;
private action: HTMLElement; private action: HTMLElement;
// private _title: string | undefined;
private params: GroupPanelPartInitParameters = {} as any;
get element(): HTMLElement { get element(): HTMLElement {
return this._element; return this._element;
@ -21,7 +18,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'dv-default-tab'; this._element.className = 'dv-default-tab';
//
this._content = document.createElement('div'); this._content = document.createElement('div');
this._content.className = 'dv-default-tab-content'; 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.className = 'dv-default-tab-action';
this.action.appendChild(createCloseButton()); this.action.appendChild(createCloseButton());
//
this._element.appendChild(this._content); this._element.appendChild(this._content);
this._element.appendChild(this.action); this._element.appendChild(this.action);
//
this.addDisposables( this.addDisposables(
addDisposableListener(this.action, 'mousedown', (ev) => { addDisposableListener(this.action, 'mousedown', (ev) => {
ev.preventDefault(); ev.preventDefault();
@ -42,40 +38,33 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this.render(); this.render();
} }
public update(event: PanelUpdateEvent): void { init(params: GroupPanelPartInitParameters): void {
this.params = { ...this.params, ...event.params }; this._title = params.title;
this.addDisposables(
params.api.onDidTitleChange((event) => {
this._title = event.title;
this.render(); this.render();
} }),
addDisposableListener(this.action, 'mousedown', (ev) => {
focus(): void { ev.preventDefault();
//noop }),
}
public init(params: GroupPanelPartInitParameters): void {
this.params = params;
this._content.textContent = params.title;
addDisposableListener(this.action, 'click', (ev) => { addDisposableListener(this.action, 'click', (ev) => {
ev.preventDefault(); // if (ev.defaultPrevented) {
this.params.api.close(); return;
});
} }
onGroupChange(_group: DockviewGroupPanel): void { ev.preventDefault();
params.api.close();
})
);
this.render(); this.render();
} }
onPanelVisibleChange(_isPanelVisible: boolean): void {
this.render();
}
public layout(_width: number, _height: number): void {
// noop
}
private render(): void { private render(): void {
if (this._content.textContent !== this.params.title) { if (this._content.textContent !== this._title) {
this._content.textContent = this.params.title; this._content.textContent = this._title ?? '';
} }
} }
} }

View File

@ -2,11 +2,16 @@ import { fireEvent, render, screen } from '@testing-library/react';
import { DockviewDefaultTab } from '../../dockview/defaultTab'; import { DockviewDefaultTab } from '../../dockview/defaultTab';
import React from 'react'; import React from 'react';
import { fromPartial } from '@total-typescript/shoehorn'; 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', () => { describe('defaultTab', () => {
test('has close button by default', async () => { test('has close button by default', async () => {
const api = fromPartial<DockviewPanelApi>({}); const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -25,6 +30,7 @@ describe('defaultTab', () => {
test('that title is displayed', async () => { test('that title is displayed', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
title: 'test_title', title: 'test_title',
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -43,8 +49,43 @@ describe('defaultTab', () => {
).toBe('test_title'); ).toBe('test_title');
}); });
test('that title is updated', async () => {
const onDidTitleChange = new Emitter<TitleEvent>();
const api = fromPartial<DockviewPanelApi>({
title: 'test_title',
onDidTitleChange: onDidTitleChange.event,
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
/>
);
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 () => { test('has no close button when hideClose=true', async () => {
const api = fromPartial<DockviewPanelApi>({}); const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -64,6 +105,7 @@ describe('defaultTab', () => {
test('that settings closeActionOverride skips api.close()', async () => { test('that settings closeActionOverride skips api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
close: jest.fn(), close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -85,13 +127,14 @@ describe('defaultTab', () => {
) as HTMLElement; ) as HTMLElement;
fireEvent.click(btn); fireEvent.click(btn);
expect(closeActionOverride).toBeCalledTimes(1); expect(closeActionOverride).toHaveBeenCalledTimes(1);
expect(api.close).toBeCalledTimes(0); expect(api.close).toHaveBeenCalledTimes(0);
}); });
test('that clicking close calls api.close()', async () => { test('that clicking close calls api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
close: jest.fn(), close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -110,11 +153,13 @@ describe('defaultTab', () => {
) as HTMLElement; ) as HTMLElement;
fireEvent.click(btn); fireEvent.click(btn);
expect(api.close).toBeCalledTimes(1); expect(api.close).toHaveBeenCalledTimes(1);
}); });
test('has close button when hideClose=false', async () => { test('has close button when hideClose=false', async () => {
const api = fromPartial<DockviewPanelApi>({}); const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -134,6 +179,7 @@ describe('defaultTab', () => {
test('that mouseDown on close button prevents panel becoming active', async () => { test('that mouseDown on close button prevents panel becoming active', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
setActive: jest.fn(), setActive: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -152,9 +198,9 @@ describe('defaultTab', () => {
) as HTMLElement; ) as HTMLElement;
fireEvent.mouseDown(btn); fireEvent.mouseDown(btn);
expect(api.setActive).toBeCalledTimes(0); expect(api.setActive).toHaveBeenCalledTimes(0);
fireEvent.click(element); fireEvent.click(element);
expect(api.setActive).toBeCalledTimes(1); expect(api.setActive).toHaveBeenCalledTimes(1);
}); });
}); });

View File

@ -1,6 +1,22 @@
import React from 'react'; import React from 'react';
import { CloseButton } from '../svg'; 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<string | undefined>(api.title);
React.useEffect(() => {
const disposable = api.onDidTitleChange((event) => {
setTitle(event.title);
});
return () => {
disposable.dispose();
};
}, [api]);
return title;
}
export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps & export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
React.DOMAttributes<HTMLDivElement> & { React.DOMAttributes<HTMLDivElement> & {
@ -18,6 +34,8 @@ export const DockviewDefaultTab: React.FunctionComponent<
closeActionOverride, closeActionOverride,
...rest ...rest
}) => { }) => {
const title = useTitle(api);
const onClose = React.useCallback( const onClose = React.useCallback(
(event: React.MouseEvent<HTMLSpanElement>) => { (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault(); event.preventDefault();
@ -57,7 +75,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
onClick={onClick} onClick={onClick}
className="dv-default-tab" className="dv-default-tab"
> >
<span className="dv-default-tab-content">{api.title}</span> <span className="dv-default-tab-content">{title}</span>
{!hideClose && ( {!hideClose && (
<div <div
className="dv-default-tab-action" className="dv-default-tab-action"