addons test-utils

Co-authored-by: Camila Belo <camilaibs@gmail.com>
Co-authored-by: Eric Peterson <iamEAP@users.noreply.github.com>
Signed-off-by: Anders Näsman <andersn@spotify.com>
This commit is contained in:
Anders Näsman
2022-04-25 15:04:42 +02:00
parent aa83995917
commit 54f06e9c00
9 changed files with 421 additions and 0 deletions
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,13 @@
# techdocs-addons-test-utils
Welcome to the techdocs-addons-test-utils plugin!
_This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/techdocs-addons-test-utils](http://localhost:3000/techdocs-addons-test-utils).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
@@ -0,0 +1,48 @@
## API Report File for "@backstage/plugin-techdocs-addons-test-utils"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { default as React_2 } from 'react';
import { ReactElement } from 'react';
import { screen as screen_2 } from 'testing-library__dom';
import { TechDocsEntityMetadata } from '@backstage/plugin-techdocs-react';
import { TechDocsMetadata } from '@backstage/plugin-techdocs-react';
import { TestApiProviderProps } from '@backstage/test-utils';
// @public (undocumented)
export type Apis = TestApiProviderProps<any>['apis'];
// @public (undocumented)
export type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
};
// @public (undocumented)
export class TechDocsAddonBuilder {
constructor(addons: ReactElement[]);
// (undocumented)
atPath(path: string): this;
// (undocumented)
build(): React_2.ReactElement<
any,
string | React_2.JSXElementConstructor<any>
>;
// (undocumented)
static buildAddonsInTechDocs(addons: ReactElement[]): TechDocsAddonBuilder;
// (undocumented)
renderWithEffects(): Promise<
typeof screen_2 & {
shadowRoot: ShadowRoot | null;
}
>;
// (undocumented)
withApis(apis: Apis): this;
// (undocumented)
withDom(dom: ReactElement): this;
// (undocumented)
withEntity(entity: RecursivePartial<TechDocsEntityMetadata>): this;
// (undocumented)
withMetadata(metadata: RecursivePartial<TechDocsMetadata>): this;
}
```
@@ -0,0 +1,71 @@
{
"name": "@backstage/plugin-techdocs-addons-test-utils",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "web-library"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/techdocs-addons-test-utils"
},
"keywords": [
"backstage",
"techdocs"
],
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-components": "^0.9.3",
"@backstage/core-app-api": "^1.0.1",
"@backstage/core-plugin-api": "^1.0.1",
"@backstage/plugin-catalog": "^1.1.0",
"@backstage/plugin-search-react": "^0.1.0",
"@backstage/plugin-techdocs": "^1.1.0",
"@backstage/plugin-techdocs-react": "^0.1.0",
"@backstage/test-utils": "^1.0.1",
"@backstage/theme": "^0.2.15",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"react-use": "^17.2.4",
"react-router-dom": "6.0.0-beta.0",
"@testing-library/react": "^12.1.3",
"testing-library__dom": "^7.29.4-beta.1"
},
"peerDependencies": {
"@types/react": "^16.13.1 || ^17.0.0",
"react": "^16.13.1 || ^17.0.0",
"react-dom": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.17.0",
"@backstage/dev-utils": "^1.0.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/jest": "^26.0.7",
"@types/node": "^16.11.26",
"msw": "^0.35.0",
"cross-fetch": "^3.1.5"
},
"files": [
"dist"
]
}
@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* A package containing test utilities for TechDocs Addons.
*
* @packageDocumentation
*/
export * from './test-utils';
@@ -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.
*/
import '@testing-library/jest-dom';
import 'cross-fetch/polyfill';
@@ -0,0 +1,240 @@
/*
* 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 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 } from 'react-router-dom';
import { act, render } from '@testing-library/react';
import {
wrapInTestApp,
TestApiProvider,
TestApiProviderProps,
} from '@backstage/test-utils';
import { FlatRoutes } from '@backstage/core-app-api';
import {
TechDocsAddons,
techdocsApiRef,
TechDocsEntityMetadata,
TechDocsMetadata,
techdocsStorageApiRef,
} from '@backstage/plugin-techdocs-react';
import { TechDocsReaderPage, techdocsPlugin } from '@backstage/plugin-techdocs';
import { catalogPlugin } from '@backstage/plugin-catalog';
import { searchApiRef } from '@backstage/plugin-search-react';
/**
* @public
*/
export type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
};
const techdocsApi = {
getTechDocsMetadata: jest.fn(),
getEntityMetadata: jest.fn(),
};
const techdocsStorageApi = {
getApiOrigin: jest.fn(),
getEntityDocs: jest.fn(),
syncEntityDocs: jest.fn(),
};
const searchApi = {
query: jest.fn().mockResolvedValue({ results: [] }),
};
/**
* @public
*/
export type Apis = TestApiProviderProps<any>['apis'];
type TechDocsAddonBuilderOptions = {
dom: ReactElement;
entity: RecursivePartial<TechDocsEntityMetadata>;
metadata: RecursivePartial<TechDocsMetadata>;
componentId: string;
apis: Apis;
path: string;
};
const defaultOptions: TechDocsAddonBuilderOptions = {
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>
);
/**
* @public
*/
export class TechDocsAddonBuilder {
private options: TechDocsAddonBuilderOptions = 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 = [
[techdocsApiRef, techdocsApi],
[techdocsStorageApiRef, techdocsStorageApi],
[searchApiRef, searchApi],
...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,
};
techdocsApi.getTechDocsMetadata.mockReturnValue(
this.options.metadata || { ...defaultMetadata },
);
techdocsApi.getEntityMetadata.mockResolvedValue(
this.options.entity || { ...defaultEntity },
);
techdocsStorageApi.syncEntityDocs.mockResolvedValue('cached');
techdocsStorageApi.getApiOrigin.mockResolvedValue(
'https://backstage.example.com/api/techdocs',
);
techdocsStorageApi.getEntityDocs.mockResolvedValue(
renderToStaticMarkup(this.options.dom || defaultDom),
);
const TechDocsAddonsPage = () => {
return (
<TestApiProvider apis={apis}>
<FlatRoutes>
<Route
path="/docs/:namespace/:kind/:name/*"
element={<TechDocsReaderPage />}
>
<TechDocsAddons>
{this.addons.map((addon, index) => (
<Fragment key={index}>{addon}</Fragment>
))}
</TechDocsAddons>
</Route>
</FlatRoutes>
</TestApiProvider>
);
};
return wrapInTestApp(<TechDocsAddonsPage />, {
routeEntries: [
`/docs/${entityName.namespace}/${entityName.kind}/${entityName.name}/${this.options.path}`,
],
mountedRoutes: {
'/docs': techdocsPlugin.routes.root,
'/docs/:namespace/:kind/:name/*': techdocsPlugin.routes.docRoot,
'/catalog/:namespace/:kind/:name': catalogPlugin.routes.catalogEntity,
},
});
}
// 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<
typeof screen & { shadowRoot: ShadowRoot | null }
> {
await act(async () => {
render(this.build());
});
const shadowHost = screen.getByTestId('techdocs-native-shadowroot');
return {
...screen,
shadowRoot: shadowHost?.shadowRoot || null,
};
}
}
export default TechDocsAddonBuilder.buildAddonsInTechDocs;
+3
View File
@@ -25,6 +25,7 @@ import { createApiRef } from '@backstage/core-plugin-api';
* Utility API reference for the {@link TechDocsStorageApi}.
*
* @public
* @deprecated Import from `@backstage/plugin-techdocs-react` instead
*/
export const techdocsStorageApiRef = createApiRef<TechDocsStorageApi>({
id: 'plugin.techdocs.storageservice',
@@ -44,6 +45,7 @@ export const techdocsApiRef = createApiRef<TechDocsApi>({
* The outcome of a docs sync operation.
*
* @public
* @deprecated Import from `@backstage/plugin-techdocs-react` instead
*/
export type SyncResult = 'cached' | 'updated';
@@ -51,6 +53,7 @@ export type SyncResult = 'cached' | 'updated';
* API which talks to TechDocs storage to fetch files to render.
*
* @public
* @deprecated Import from `@backstage/plugin-techdocs-react` instead
*/
export interface TechDocsStorageApi {
getApiOrigin(): Promise<string>;
+5
View File
@@ -23940,6 +23940,11 @@ testcontainers@^8.1.2:
ssh-remote-port-forward "^1.0.4"
tar-fs "^2.1.1"
testing-library__dom@^7.29.4-beta.1:
version "7.29.4-beta.1"
resolved "https://registry.npmjs.org/testing-library__dom/-/testing-library__dom-7.29.4-beta.1.tgz#dc755f485837e923efbe12c1b7ae43b0ed326f96"
integrity sha512-vb/SMg8rXYcYYFY2eQ2n2a0p2VWNAseM4WHLfsckIyLwxRz5fYqKysUzUYpAX8SwRfruRK+tZqLuL4ND+D1s7Q==
text-extensions@^1.0.0:
version "1.9.0"
resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"