add test-utils
Co-authored-by: Camila Belo <camilaibs@gmail.com> Co-authored-by: Emma Indal <emma.indahl@gmail.com> Signed-off-by: Anders Näsman <andersn@spotify.com>
This commit is contained in:
committed by
Emma Indal
parent
3efd08388a
commit
413024e182
@@ -25,13 +25,18 @@
|
||||
"@backstage/catalog-model": "^0.13.0",
|
||||
"@backstage/core-components": "^0.9.1",
|
||||
"@backstage/core-plugin-api": "^0.8.0",
|
||||
"@backstage/test-utils": "^0.3.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"@material-ui/styles": "^4.11.0",
|
||||
"jss": "~10.8.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
"react-use": "^17.2.4",
|
||||
"testing-library__dom": "^7.29.4-beta.1",
|
||||
"@testing-library/react": "^12.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
@@ -39,7 +44,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@backstage/test-utils": "^0.3.0"
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@types/lodash.debounce": "^4.0.6"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './test-utils';
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const useTechDocsReaderDom = jest.fn();
|
||||
export const useParams = jest.fn();
|
||||
jest.mock('@backstage/plugin-techdocs', () => ({
|
||||
...(jest.requireActual('@backstage/plugin-techdocs') as {}),
|
||||
useTechDocsReaderDom,
|
||||
withTechDocsReaderProvider: jest.fn(x => x),
|
||||
TechDocsStateIndicator: jest.fn(() => null),
|
||||
}));
|
||||
// todo(backstage/techdocs-core): Use core test-utils' `routeEntries` option.
|
||||
jest.mock('react-router', () => ({
|
||||
...(jest.requireActual('react-router') as {}),
|
||||
useParams,
|
||||
}));
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// import order matters for jest manual mocks! import this first.
|
||||
import { useTechDocsReaderDom, useParams } from './mocks';
|
||||
|
||||
import React, { ReactElement, Fragment } from 'react';
|
||||
|
||||
// Shadow DOM support for the simple and complete DOM testing utilities
|
||||
// https://github.com/testing-library/dom-testing-library/issues/742#issuecomment-674987855
|
||||
import { screen } from 'testing-library__dom';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { act, render } from '@testing-library/react';
|
||||
import { AsyncState } from 'react-use/lib/useAsyncFn';
|
||||
|
||||
import {
|
||||
wrapInTestApp,
|
||||
TestApiProvider,
|
||||
TestApiProviderProps,
|
||||
} from '@backstage/test-utils';
|
||||
|
||||
import { TechDocsEntityMetadata, TechDocsMetadata } from '../types';
|
||||
import { TechDocsReaderPage, TechDocsAddons } from '..';
|
||||
|
||||
type RecursivePartial<T> = {
|
||||
[P in keyof T]?: RecursivePartial<T[P]>;
|
||||
};
|
||||
|
||||
type Apis = TestApiProviderProps<any>['apis'];
|
||||
|
||||
export type TechDocsAddonsBuilder = {
|
||||
dom: ReactElement;
|
||||
entity: RecursivePartial<TechDocsEntityMetadata>;
|
||||
metadata: RecursivePartial<TechDocsMetadata>;
|
||||
componentId: string;
|
||||
apis: Apis;
|
||||
path: string;
|
||||
};
|
||||
|
||||
const defaultOptions: TechDocsAddonsBuilder = {
|
||||
dom: <></>,
|
||||
entity: {},
|
||||
metadata: {},
|
||||
componentId: 'docs',
|
||||
apis: [],
|
||||
path: '',
|
||||
};
|
||||
|
||||
const defaultMetadata = {
|
||||
site_name: 'Tech Docs',
|
||||
site_description: 'Tech Docs',
|
||||
};
|
||||
|
||||
const defaultEntity = {
|
||||
kind: 'Component',
|
||||
metadata: { namespace: 'default', name: 'docs' },
|
||||
};
|
||||
|
||||
const defaultDom = (
|
||||
<html lang="en">
|
||||
<head />
|
||||
<body>
|
||||
<div data-md-component="container">
|
||||
<div data-md-component="navigation" />
|
||||
<div data-md-component="toc" />
|
||||
<div data-md-component="main" />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
export class TechDocsAddonBuilder {
|
||||
private options: TechDocsAddonsBuilder = defaultOptions;
|
||||
private addons: ReactElement[];
|
||||
|
||||
static buildAddonsInTechDocs(addons: ReactElement[]) {
|
||||
return new TechDocsAddonBuilder(addons);
|
||||
}
|
||||
|
||||
constructor(addons: ReactElement[]) {
|
||||
this.addons = addons;
|
||||
}
|
||||
|
||||
withApis(apis: Apis) {
|
||||
const refs = apis.map(([ref]) => ref);
|
||||
this.options.apis = this.options.apis
|
||||
.filter(([ref]) => !refs.includes(ref))
|
||||
.concat(apis);
|
||||
return this;
|
||||
}
|
||||
|
||||
withDom(dom: ReactElement) {
|
||||
this.options.dom = dom;
|
||||
return this;
|
||||
}
|
||||
|
||||
withMetadata(metadata: RecursivePartial<TechDocsMetadata>) {
|
||||
this.options.metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
withEntity(entity: RecursivePartial<TechDocsEntityMetadata>) {
|
||||
this.options.entity = entity;
|
||||
return this;
|
||||
}
|
||||
|
||||
atPath(path: string) {
|
||||
this.options.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
const apis = [...this.options.apis];
|
||||
const entityName = {
|
||||
namespace:
|
||||
this.options.entity?.metadata?.namespace ||
|
||||
defaultEntity.metadata.namespace,
|
||||
kind: this.options.entity?.kind || defaultEntity.kind,
|
||||
name: this.options.entity?.metadata?.name || defaultEntity.metadata.name,
|
||||
};
|
||||
|
||||
const techDocsMetadata: AsyncState<TechDocsMetadata> = {
|
||||
loading: false,
|
||||
error: undefined,
|
||||
value: (this.options.metadata || {
|
||||
...defaultMetadata,
|
||||
}) as TechDocsMetadata,
|
||||
};
|
||||
|
||||
const entityMetadata: AsyncState<TechDocsEntityMetadata> = {
|
||||
loading: false,
|
||||
error: undefined,
|
||||
value: (this.options.entity || {
|
||||
...defaultEntity,
|
||||
}) as TechDocsEntityMetadata,
|
||||
};
|
||||
|
||||
const dom = document.createElement('html');
|
||||
dom.innerHTML = renderToStaticMarkup(this.options.dom || defaultDom);
|
||||
useTechDocsReaderDom.mockReturnValue(dom);
|
||||
// todo(backstage/techdocs-core): Use core test-utils' `routeEntries` option to mock
|
||||
// the current path. We use jest mocks instead for now because of a bug in
|
||||
// react-router that prevents '*' params from being mocked.
|
||||
useParams.mockReturnValue({
|
||||
...entityName,
|
||||
'*': this.options.path,
|
||||
});
|
||||
|
||||
return wrapInTestApp(
|
||||
<TestApiProvider apis={apis}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<TechDocsReaderPage
|
||||
dom={dom}
|
||||
asyncEntityMetadata={entityMetadata}
|
||||
asyncTechDocsMetadata={techDocsMetadata}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TechDocsAddons>
|
||||
{this.addons.map((addon, index) => (
|
||||
<Fragment key={index}>{addon}</Fragment>
|
||||
))}
|
||||
</TechDocsAddons>
|
||||
</Route>
|
||||
</Routes>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
render(): typeof screen & { shadowRoot: ShadowRoot | null } {
|
||||
render(this.build());
|
||||
|
||||
const shadowHost = screen.getByTestId('techdocs-native-shadowroot');
|
||||
|
||||
return {
|
||||
...screen,
|
||||
shadowRoot: shadowHost?.shadowRoot,
|
||||
};
|
||||
}
|
||||
|
||||
// Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async
|
||||
// act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit
|
||||
// cleaner, since act doesn't return the result of the evaluated function.
|
||||
// https://github.com/testing-library/react-testing-library/issues/281
|
||||
// https://github.com/facebook/react/pull/14853
|
||||
async renderWithEffects(): Promise<
|
||||
ReturnType<TechDocsAddonBuilder['render']>
|
||||
> {
|
||||
await act(async () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
const shadowHost = screen.getByTestId('techdocs-native-shadowroot');
|
||||
|
||||
return {
|
||||
...screen,
|
||||
shadowRoot: shadowHost?.shadowRoot,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default TechDocsAddonBuilder.buildAddonsInTechDocs;
|
||||
Reference in New Issue
Block a user