Merge pull request #248 from mathuo/247-remove-code-smells

test: remove smells and test
This commit is contained in:
mathuo 2023-04-16 22:12:56 +01:00 committed by GitHub
commit be11692114
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 265 additions and 159 deletions

View File

@ -20,6 +20,14 @@ const config: JestConfigWithTsJest = {
coverageDirectory: '<rootDir>/packages/dockview-core/coverage/', coverageDirectory: '<rootDir>/packages/dockview-core/coverage/',
testResultsProcessor: 'jest-sonar-reporter', testResultsProcessor: 'jest-sonar-reporter',
testEnvironment: 'jsdom', testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
}; };
export default config; export default config;

View File

@ -4,27 +4,39 @@ import {
} from '../../dockview/dockviewComponent'; } from '../../dockview/dockviewComponent';
import { DockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { DockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { IContentRenderer, ITabRenderer } from '../../dockview/types'; import { IContentRenderer, ITabRenderer } from '../../dockview/types';
import { GroupPanelFrameworkComponentFactory } from '../../dockview/options';
import { DefaultTab } from '../../dockview/components/tab/defaultTab';
describe('dockviewGroupPanel', () => { describe('dockviewGroupPanel', () => {
test('that dispose is called on content and tab renderers when present', () => { let contentMock: jest.Mock<IContentRenderer>;
const contentMock = jest.fn<IContentRenderer, []>(() => { let tabMock: jest.Mock<ITabRenderer>;
let accessorMock: jest.Mock<IDockviewComponent>;
beforeEach(() => {
contentMock = jest.fn<IContentRenderer, []>(() => {
const partial: Partial<IContentRenderer> = { const partial: Partial<IContentRenderer> = {
element: document.createElement('div'), element: document.createElement('div'),
dispose: jest.fn(), dispose: jest.fn(),
update: jest.fn(),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
}; };
return partial as IContentRenderer; return partial as IContentRenderer;
}); });
const tabMock = jest.fn<ITabRenderer, []>(() => { tabMock = jest.fn<ITabRenderer, []>(() => {
const partial: Partial<IContentRenderer> = { const partial: Partial<IContentRenderer> = {
element: document.createElement('div'), element: document.createElement('div'),
dispose: jest.fn(), dispose: jest.fn(),
update: jest.fn(),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
}; };
return partial as IContentRenderer; return partial as IContentRenderer;
}); });
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { accessorMock = jest.fn<DockviewComponent, []>(() => {
return { const partial: Partial<DockviewComponent> = {
options: { options: {
components: { components: {
contentComponent: contentMock, contentComponent: contentMock,
@ -34,8 +46,12 @@ describe('dockviewGroupPanel', () => {
}, },
}, },
}; };
return partial as DockviewComponent;
});
}); });
test('that dispose is called on content and tab renderers when present', () => {
const cut = new DockviewPanelModel( const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), <IDockviewComponent>new accessorMock(),
'id', 'id',
@ -50,34 +66,6 @@ describe('dockviewGroupPanel', () => {
}); });
test('that update is called on content and tab renderers when present', () => { test('that update is called on content and tab renderers when present', () => {
const contentMock = jest.fn<IContentRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
update: jest.fn(),
};
return partial as IContentRenderer;
});
const tabMock = jest.fn<ITabRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
update: jest.fn(),
};
return partial as IContentRenderer;
});
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: tabMock,
},
},
};
});
const cut = new DockviewPanelModel( const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), <IDockviewComponent>new accessorMock(),
'id', 'id',
@ -94,36 +82,6 @@ describe('dockviewGroupPanel', () => {
}); });
test('that events are fired', () => { test('that events are fired', () => {
const contentMock = jest.fn<IContentRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
const tabMock = jest.fn<ITabRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: tabMock,
},
},
};
});
const cut = new DockviewPanelModel( const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), <IDockviewComponent>new accessorMock(),
'id', 'id',
@ -168,4 +126,176 @@ describe('dockviewGroupPanel', () => {
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
}); });
test('that the default tab is created', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: jest
.fn()
.mockImplementation(() => tabMock),
},
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent',
'tabComponent'
);
expect(cut.tab).toEqual(tabMock);
});
test('that the provided default tab is chosen when no implementation is provided', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: jest
.fn()
.mockImplementation(() => tabMock),
},
defaultTabComponent: 'tabComponent',
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent'
);
expect(cut.tab).toEqual(tabMock);
});
test('that the framework tab is created when provided tab is a framework tab', () => {
const tab = jest.fn();
const tabFactory = jest.fn().mockImplementation(() => tab);
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
frameworkTabComponents: {
tabComponent: tabMock,
},
frameworkComponentFactory: (<
Partial<GroupPanelFrameworkComponentFactory>
>{
tab: { createComponent: tabFactory },
}) as GroupPanelFrameworkComponentFactory,
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent',
'tabComponent'
);
expect(tabFactory).toHaveBeenCalledWith('id', 'tabComponent', tabMock);
expect(cut.tab).toEqual(tab);
});
test('that is library default tab instance is created when no alternative exists', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent'
);
expect(cut.tab instanceof DefaultTab).toBeTruthy();
});
test('that the default content is created', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: jest.fn().mockImplementation(() => {
return contentMock;
}),
},
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent'
);
expect(cut.content).toEqual(contentMock);
});
test('that the framework content is created', () => {
const content = jest.fn();
const contentFactory = jest.fn().mockImplementation(() => content);
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
frameworkComponents: {
contentComponent: contentMock,
},
frameworkComponentFactory: (<
Partial<GroupPanelFrameworkComponentFactory>
>{
content: { createComponent: contentFactory },
}) as GroupPanelFrameworkComponentFactory,
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent'
);
expect(contentFactory).toHaveBeenCalledWith(
'id',
'contentComponent',
contentMock
);
expect(cut.content).toEqual(content);
});
}); });

View File

@ -114,7 +114,7 @@ describe('baseComponentGridview', () => {
proportionalLayout: true, proportionalLayout: true,
}); });
const events: TestPanel[] = []; const events: (TestPanel | undefined)[] = [];
const disposable = new CompositeDisposable( const disposable = new CompositeDisposable(
cut.onDidAddGroup((event) => { cut.onDidAddGroup((event) => {

View File

@ -18,11 +18,9 @@ import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
import { CompositeDisposable } from '../../lifecycle'; import { CompositeDisposable } from '../../lifecycle';
import { DockviewPanelApi } from '../../api/dockviewPanelApi'; import { DockviewPanelApi } from '../../api/dockviewPanelApi';
import { IDockviewPanel } from '../../dockview/dockviewPanel'; import { IDockviewPanel } from '../../dockview/dockviewPanel';
import { import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
IDockviewPanelModel,
DockviewPanelModel,
} from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { WatermarkRendererInitParameters } from '../../dockview/types';
enum GroupChangeKind2 { enum GroupChangeKind2 {
ADD_PANEL, ADD_PANEL,
@ -76,7 +74,7 @@ class Watermark implements IWatermarkRenderer {
return 'watermark-id'; return 'watermark-id';
} }
init(params: GroupPanelPartInitParameters) { init(params: WatermarkRendererInitParameters) {
// //
} }
@ -170,7 +168,7 @@ class TestHeaderPart implements ITabRenderer {
export class TestPanel implements IDockviewPanel { export class TestPanel implements IDockviewPanel {
private _group: DockviewGroupPanel | undefined; private _group: DockviewGroupPanel | undefined;
private _params: IGroupPanelInitParameters; private _params: IGroupPanelInitParameters | undefined;
readonly view: IDockviewPanelModel; readonly view: IDockviewPanelModel;
get title() { get title() {
@ -516,7 +514,7 @@ describe('groupview', () => {
dockviewComponent, dockviewComponent,
'id', 'id',
{}, {},
null null as any
); );
expect(cut.toJSON()).toEqual({ expect(cut.toJSON()).toEqual({
@ -539,7 +537,7 @@ describe('groupview', () => {
dockviewComponent, dockviewComponent,
'id', 'id',
{}, {},
null null as any
); );
cut.locked = true; cut.locked = true;
@ -568,25 +566,25 @@ describe('groupview', () => {
dockviewComponent, dockviewComponent,
'id', 'id',
{}, {},
null null as any
); );
const contentContainer = groupviewContainer const contentContainer = groupviewContainer
.getElementsByClassName('content-container') .getElementsByClassName('content-container')
.item(0)!.childNodes; .item(0)!.childNodes;
const panel1 = new TestPanel('id_1', null); const panel1 = new TestPanel('id_1', null as any);
cut.openPanel(panel1); cut.openPanel(panel1);
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel1.view.content.element); expect(contentContainer.item(0)).toBe(panel1.view.content.element);
const panel2 = new TestPanel('id_2', null); const panel2 = new TestPanel('id_2', null as any);
cut.openPanel(panel2); cut.openPanel(panel2);
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element); expect(contentContainer.item(0)).toBe(panel2.view.content.element);
const panel3 = new TestPanel('id_2', null); const panel3 = new TestPanel('id_2', null as any);
cut.openPanel(panel3, { skipSetPanelActive: true }); cut.openPanel(panel3, { skipSetPanelActive: true });
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);

View File

@ -1,9 +1,7 @@
import { GroupviewPanelState, ITabRenderer } from './types'; import { GroupviewPanelState } from './types';
import { DockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewGroupPanel } from './dockviewGroupPanel';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { IDockviewComponent } from './dockviewComponent'; import { IDockviewComponent } from './dockviewComponent';
import { createComponent } from '../panel/componentFactory';
import { DefaultTab } from './components/tab/defaultTab';
import { DockviewPanelModel } from './dockviewPanelModel'; import { DockviewPanelModel } from './dockviewPanelModel';
import { DockviewApi } from '../api/component.api'; import { DockviewApi } from '../api/component.api';
@ -14,7 +12,7 @@ export interface IPanelDeserializer {
): IDockviewPanel; ): IDockviewPanel;
} }
// depreciated // @depreciated
interface LegacyState extends GroupviewPanelState { interface LegacyState extends GroupviewPanelState {
view?: { view?: {
tab?: { id: string }; tab?: { id: string };
@ -42,30 +40,6 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
? viewData.tab?.id ? viewData.tab?.id
: panelData.tabComponent; : panelData.tabComponent;
let tab: ITabRenderer;
if (tabComponent) {
tab = createComponent(
panelId,
tabComponent,
this.layout.options.tabComponents,
this.layout.options.frameworkTabComponents,
this.layout.options.frameworkComponentFactory?.tab,
() => new DefaultTab()
);
} else if (this.layout.options.defaultTabComponent) {
tab = createComponent(
panelId,
this.layout.options.defaultTabComponent,
this.layout.options.tabComponents,
this.layout.options.frameworkTabComponents,
this.layout.options.frameworkComponentFactory?.tab,
() => new DefaultTab()
);
} else {
tab = new DefaultTab();
}
const view = new DockviewPanelModel( const view = new DockviewPanelModel(
this.layout, this.layout,
panelId, panelId,

View File

@ -43,8 +43,7 @@ export class DockviewPanelModel implements IDockviewPanelModel {
readonly tabComponent?: string readonly tabComponent?: string
) { ) {
this._content = this.createContentComponent(this.id, contentComponent); this._content = this.createContentComponent(this.id, contentComponent);
this._tab = this._tab = this.createTabComponent(this.id, tabComponent);
this.createTabComponent(this.id, tabComponent) ?? new DefaultTab();
} }
init(params: GroupPanelPartInitParameters): void { init(params: GroupPanelPartInitParameters): void {
@ -108,13 +107,26 @@ export class DockviewPanelModel implements IDockviewPanelModel {
id: string, id: string,
componentName?: string componentName?: string
): ITabRenderer { ): ITabRenderer {
if (componentName) {
return createComponent( return createComponent(
id, id,
componentName, componentName,
this.accessor.options.tabComponents || {}, this.accessor.options.tabComponents,
this.accessor.options.frameworkTabComponents, this.accessor.options.frameworkTabComponents,
this.accessor.options.frameworkComponentFactory?.tab, this.accessor.options.frameworkComponentFactory?.tab,
() => new DefaultTab() () => new DefaultTab()
); );
} else if (this.accessor.options.defaultTabComponent) {
return createComponent(
id,
this.accessor.options.defaultTabComponent,
this.accessor.options.tabComponents,
this.accessor.options.frameworkTabComponents,
this.accessor.options.frameworkComponentFactory?.tab,
() => new DefaultTab()
);
} else {
return new DefaultTab();
}
} }
} }

View File

@ -27,7 +27,7 @@ const config: JestConfigWithTsJest = {
'^.+\\.tsx?$': [ '^.+\\.tsx?$': [
'ts-jest', 'ts-jest',
{ {
tsconfig, tsconfig: '<rootDir>/tsconfig.test.json',
}, },
], ],
}, },

View File

@ -1,5 +1,3 @@
import * as React from 'react';
export function setMockRefElement(node: Partial<HTMLElement>): void { export function setMockRefElement(node: Partial<HTMLElement>): void {
const mockRef = { const mockRef = {
get current() { get current() {
@ -10,5 +8,5 @@ export function setMockRefElement(node: Partial<HTMLElement>): void {
}, },
}; };
jest.spyOn(React, 'useRef').mockReturnValueOnce(mockRef); jest.spyOn(require('react'), 'useRef').mockReturnValueOnce(mockRef);
} }

View File

@ -48,7 +48,7 @@ describe('gridview react', () => {
render(<DockviewReact components={components} onReady={onReady} />); render(<DockviewReact components={components} onReady={onReady} />);
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -59,7 +59,7 @@ describe('gridview react', () => {
/> />
); );
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -46,7 +46,7 @@ describe('gridview react', () => {
render(<PaneviewReact components={components} onReady={onReady} />); render(<PaneviewReact components={components} onReady={onReady} />);
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -18,7 +18,7 @@ describe('react', () => {
render(<TestWrapper onReady={onReady} component={Component} />); render(<TestWrapper onReady={onReady} component={Component} />);
expect(api).toBeTruthy(); expect(api!).toBeTruthy();
expect(screen.getByTestId('valueA').textContent).toBe('stringA'); expect(screen.getByTestId('valueA').textContent).toBe('stringA');
expect(screen.getByTestId('valueB').textContent).toBe('42'); expect(screen.getByTestId('valueB').textContent).toBe('42');
@ -60,7 +60,7 @@ const TestWrapper = (props: {
React.useEffect(() => { React.useEffect(() => {
const cut = new ReactPart<TestInterface>( const cut = new ReactPart<TestInterface>(
ref.current, ref.current!,
{ {
addPortal: (portal: React.ReactPortal) => { addPortal: (portal: React.ReactPortal) => {
setPortal((_) => [..._, portal]); setPortal((_) => [..._, portal]);

View File

@ -47,7 +47,7 @@ describe('dockview', () => {
render(<DockviewReact components={components} onReady={onReady} />); render(<DockviewReact components={components} onReady={onReady} />);
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -58,7 +58,7 @@ describe('gridview react', () => {
/> />
); );
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -46,7 +46,7 @@ describe('gridview react', () => {
render(<PaneviewReact components={components} onReady={onReady} />); render(<PaneviewReact components={components} onReady={onReady} />);
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -18,7 +18,7 @@ describe('react', () => {
render(<TestWrapper onReady={onReady} component={Component} />); render(<TestWrapper onReady={onReady} component={Component} />);
expect(api).toBeTruthy(); expect(api!).toBeTruthy();
expect(screen.getByTestId('valueA').textContent).toBe('stringA'); expect(screen.getByTestId('valueA').textContent).toBe('stringA');
expect(screen.getByTestId('valueB').textContent).toBe('42'); expect(screen.getByTestId('valueB').textContent).toBe('42');
@ -60,7 +60,7 @@ const TestWrapper = (props: {
React.useEffect(() => { React.useEffect(() => {
const cut = new ReactPart<TestInterface>( const cut = new ReactPart<TestInterface>(
ref.current, ref.current!,
{ {
addPortal: (portal: React.ReactPortal) => { addPortal: (portal: React.ReactPortal) => {
setPortal((_) => [..._, portal]); setPortal((_) => [..._, portal]);

View File

@ -58,7 +58,7 @@ describe('splitview react', () => {
/> />
); );
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -58,7 +58,7 @@ describe('splitview react', () => {
/> />
); );
expect(api.width).toBe(650); expect(api!.width).toBe(650);
expect(api.height).toBe(450); expect(api!.height).toBe(450);
}); });
}); });

View File

@ -21,5 +21,6 @@
"ES2019", "ES2019",
"DOM" "DOM"
] ]
} },
"exclude": ["**/*.spec.ts"]
} }

View File

@ -1,22 +1,7 @@
{ {
"extends": "./tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "jsx": "react-jsx"
"target": "es2019",
"sourceMap": true,
"declaration": true,
"jsx": "react",
"noImplicitReturns": true,
"noImplicitAny": true,
"allowUnreachableCode": false,
"forceConsistentCasingInFileNames": true,
// "strict": true,
"strictBindCallApply": true, // pass
"alwaysStrict": true, // pass
"noImplicitThis": true, // pass
"strictFunctionTypes": true, // pass
"strictNullChecks": false,
"strictPropertyInitialization": false,
}, },
"exclude": ["node_modules", "dist"] "include": ["**/*.spec.ts"]
} }