bug: fix setTitle issues

This commit is contained in:
mathuo 2024-06-04 22:38:08 +03:00
parent ce381f8ce9
commit cd6604d28c
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
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 { 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();
}
focus(): void {
//noop
}
public init(params: GroupPanelPartInitParameters): void {
this.params = params;
this._content.textContent = params.title;
}),
addDisposableListener(this.action, 'mousedown', (ev) => {
ev.preventDefault();
}),
addDisposableListener(this.action, 'click', (ev) => {
ev.preventDefault(); //
this.params.api.close();
});
if (ev.defaultPrevented) {
return;
}
onGroupChange(_group: DockviewGroupPanel): void {
ev.preventDefault();
params.api.close();
})
);
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 ?? '';
}
}
}

View File

@ -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<DockviewPanelApi>({});
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -25,6 +30,7 @@ describe('defaultTab', () => {
test('that title is displayed', async () => {
const api = fromPartial<DockviewPanelApi>({
title: 'test_title',
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -43,8 +49,43 @@ describe('defaultTab', () => {
).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 () => {
const api = fromPartial<DockviewPanelApi>({});
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -64,6 +105,7 @@ describe('defaultTab', () => {
test('that settings closeActionOverride skips api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({
close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
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<DockviewPanelApi>({
close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
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<DockviewPanelApi>({});
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -134,6 +179,7 @@ describe('defaultTab', () => {
test('that mouseDown on close button prevents panel becoming active', async () => {
const api = fromPartial<DockviewPanelApi>({
setActive: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
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);
});
});

View File

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