From 4faf199f7281c8f76b09ea0616c610df961ba200 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Fri, 24 Apr 2020 14:42:19 +0200 Subject: [PATCH] added tests for RadarPage --- packages/app/src/apis.ts | 8 + plugins/tech-radar/package.json | 1 + plugins/tech-radar/src/api.ts | 78 +++++++++ .../src/components/RadarPage.test.tsx | 155 ++++++++++++++++++ .../tech-radar/src/components/RadarPage.tsx | 99 +++++++++++ .../src/example/ExampleRadar.test.tsx | 43 ----- .../tech-radar/src/example/ExampleRadar.tsx | 45 ----- plugins/tech-radar/src/example/data.ts | 99 ----------- plugins/tech-radar/src/index.ts | 14 ++ plugins/tech-radar/src/plugin.ts | 4 +- plugins/tech-radar/src/sampleData.ts | 107 ++++++++++++ yarn.lock | 4 + 12 files changed, 468 insertions(+), 189 deletions(-) create mode 100644 plugins/tech-radar/src/api.ts create mode 100644 plugins/tech-radar/src/components/RadarPage.test.tsx create mode 100644 plugins/tech-radar/src/components/RadarPage.tsx delete mode 100644 plugins/tech-radar/src/example/ExampleRadar.test.tsx delete mode 100644 plugins/tech-radar/src/example/ExampleRadar.tsx delete mode 100644 plugins/tech-radar/src/example/data.ts create mode 100644 plugins/tech-radar/src/sampleData.ts diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index bfa049acfb..a24a25e205 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -24,11 +24,18 @@ import { featureFlagsApiRef, FeatureFlags, } from '@backstage/core'; + import { lighthouseApiRef, LighthouseRestApi, } from '@backstage/plugin-lighthouse'; +import { + techRadarApiRef, + TechRadar, + loadSampleData, +} from '@backstage/plugin-tech-radar'; + const builder = ApiRegistry.builder(); export const alertApiForwarder = new AlertApiForwarder(); @@ -40,5 +47,6 @@ builder.add(errorApiRef, errorApiForwarder); builder.add(featureFlagsApiRef, new FeatureFlags()); builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003')); +builder.add(techRadarApiRef, new TechRadar(1800, 800, loadSampleData)); export default builder.build() as ApiHolder; diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index faffd0e4bb..dd1747b02d 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@backstage/core": "^0.1.1-alpha.4", + "@backstage/test-utils-core": "^0.1.1-alpha.4", "@backstage/theme": "^0.1.1-alpha.4", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts new file mode 100644 index 0000000000..19fdb2b895 --- /dev/null +++ b/plugins/tech-radar/src/api.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { ApiRef } from '@backstage/core'; + +export interface RadarRing { + id: string; + name: string; + color: string; +} + +export interface RadarQuadrant { + id: string; + name: string; +} + +export interface RadarEntry { + key: string; // react key + id: string; + moved: number; + quadrant: string; + ring: string; + title: string; + url: string; +} + +export interface TechRadarLoaderResponse { + quadrants: RadarQuadrant[]; + rings: RadarRing[]; + entries: RadarEntry[]; +} + +export interface TechRadarAdditionalOptions { + title?: string; + subtitle?: string; + svgProps?: object; +} + +export type TechRadarApi = { + width: number; + height: number; + load: () => Promise; + additionalOpts: TechRadarAdditionalOptions; +}; + +export const techRadarApiRef = new ApiRef({ + id: 'plugin.techradar', + description: 'Used by the Tech Radar to render the diagram', +}); + +export class TechRadar implements TechRadarApi { + private defaultAdditionalOpts: object = { + title: 'Tech Radar', + subtitle: 'Welcome to the Tech Radar!', + }; + + constructor( + public width: number, + public height: number, + public load: () => Promise, + public additionalOpts: TechRadarAdditionalOptions = {}, + ) { + this.additionalOpts = { ...this.defaultAdditionalOpts, ...additionalOpts }; + } +} diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx new file mode 100644 index 0000000000..9913895949 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarPage.test.tsx @@ -0,0 +1,155 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 from 'react'; +import { render, waitForElement } from '@testing-library/react'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import { ApiRegistry, ApiProvider, errorApiRef } from '@backstage/core'; +import { withLogCollector } from '@backstage/test-utils-core'; + +import GetBBoxPolyfill from '../utils/polyfills/getBBox'; +import { techRadarApiRef, TechRadar, loadSampleData } from '../index'; +import RadarPage from './RadarPage'; + +describe('RadarPage', () => { + beforeAll(() => { + GetBBoxPolyfill.create(0, 0, 1000, 500); + }); + + afterAll(() => { + GetBBoxPolyfill.remove(); + }); + + it('should render a progress bar', async () => { + const errorApi = { post: () => {} }; + const techRadarApi = new TechRadar(1200, 800, loadSampleData, { + svgProps: { 'data-testid': 'tech-radar-svg' }, + }); + + const { getByTestId, queryByTestId } = render( + + + + + , + ); + + expect(getByTestId('progress')).toBeInTheDocument(); + + await waitForElement(() => queryByTestId('tech-radar-svg')); + }); + + it('should render a header with a svg', async () => { + const errorApi = { post: () => {} }; + const techRadarApi = new TechRadar(1200, 800, loadSampleData, { + svgProps: { 'data-testid': 'tech-radar-svg' }, + }); + + const { getByText, getByTestId } = render( + + + + + , + ); + + await waitForElement(() => getByTestId('tech-radar-svg')); + + expect(getByText('Welcome to the Tech Radar!')).toBeInTheDocument(); + expect(getByTestId('tech-radar-svg')).toBeInTheDocument(); + }); + + it('should call the errorApi if load fails', async () => { + const errorApi = { post: jest.fn() }; + const techRadarLoadFail = () => + Promise.reject(new Error('404 Page Not Found')); + const techRadarApi = new TechRadar(1200, 800, techRadarLoadFail, { + svgProps: { 'data-testid': 'tech-radar-svg' }, + }); + + const { queryByTestId } = render( + + + + + , + ); + + await waitForElement(() => !queryByTestId('progress')); + + expect(errorApi.post).toHaveBeenCalledTimes(1); + expect(errorApi.post).toHaveBeenCalledWith(new Error('404 Page Not Found')); + expect(queryByTestId('tech-radar-svg')).not.toBeInTheDocument(); + }); + + it('should not render without errorApiRef', () => { + const techRadarApi = new TechRadar(1200, 800, loadSampleData); + + expect( + withLogCollector(['error'], () => { + expect(() => { + render( + + + + + , + ); + }).toThrow(); + }).error[0], + ).toMatch( + /^Error: Uncaught \[Error: No implementation available for apiRef{core.error}\]/, + ); + }); + + it('should not render without techRadarApiRef', () => { + const errorApi = { post: () => {} }; + + expect( + withLogCollector(['error'], () => { + expect(() => { + render( + + + + + , + ); + }).toThrow(); + }).error[0], + ).toMatch( + /^Error: Uncaught \[Error: No implementation available for apiRef{plugin.techradar}\]/, + ); + }); +}); diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx new file mode 100644 index 0000000000..cef69011a2 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarPage.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { useEffect, useState, FC } from 'react'; +import { Grid } from '@material-ui/core'; +import { + Progress, + Page, + Header, + Content, + pageTheme, + useApi, + errorApiRef, + ErrorApi, +} from '@backstage/core'; +import Radar from '../components/Radar'; +import { techRadarApiRef, TechRadar, TechRadarLoaderResponse } from '../api'; + +const useTechRadarLoader = (techRadarApi: TechRadar) => { + const [state, setState] = useState<{ + loading: boolean; + error?: Error; + data?: TechRadarLoaderResponse; + }>({ + loading: true, + error: undefined, + data: undefined, + }); + + useEffect(() => { + techRadarApi + .load() + .then((payload: TechRadarLoaderResponse) => { + setState({ loading: false, error: undefined, data: payload }); + }) + .catch((err: Error) => { + setState({ + loading: false, + error: err, + data: undefined, + }); + }); + }, []); + + return state; +}; + +const RadarPage: FC<{}> = () => { + const errorApi = useApi(errorApiRef); + const techRadarApi = useApi(techRadarApiRef); + const { loading, error, data } = useTechRadarLoader(techRadarApi); + + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error && error.message]); + + return ( + +
+ + + + {loading && } + {!loading && !error && ( + + )} + + + + + ); +}; + +export default RadarPage; diff --git a/plugins/tech-radar/src/example/ExampleRadar.test.tsx b/plugins/tech-radar/src/example/ExampleRadar.test.tsx deleted file mode 100644 index c5eb0655a0..0000000000 --- a/plugins/tech-radar/src/example/ExampleRadar.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 from 'react'; -import { render } from '@testing-library/react'; -import { ThemeProvider } from '@material-ui/core'; -import { lightTheme } from '@backstage/theme'; -import GetBBoxPolyfill from '../utils/polyfills/getBBox'; - -import ExampleRadar from './ExampleRadar'; - -describe('ExampleRadar', () => { - beforeAll(() => { - GetBBoxPolyfill.create(0, 0, 1000, 500); - }); - - afterAll(() => { - GetBBoxPolyfill.remove(); - }); - - it('should render', () => { - const { getByText } = render( - - - , - ); - - expect(getByText('Welcome to the Tech Radar!')).toBeInTheDocument(); - }); -}); diff --git a/plugins/tech-radar/src/example/ExampleRadar.tsx b/plugins/tech-radar/src/example/ExampleRadar.tsx deleted file mode 100644 index 0d9a762ad7..0000000000 --- a/plugins/tech-radar/src/example/ExampleRadar.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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, { FC } from 'react'; -import { Grid } from '@material-ui/core'; -import { Page, Header, Content, pageTheme } from '@backstage/core'; -import Radar from '../components/Radar'; -import mockData from './data'; - -const ExampleRadar: FC<{}> = () => { - return ( - -
- - - - - - - - - ); -}; - -export default ExampleRadar; diff --git a/plugins/tech-radar/src/example/data.ts b/plugins/tech-radar/src/example/data.ts deleted file mode 100644 index 8071195977..0000000000 --- a/plugins/tech-radar/src/example/data.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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. - */ - -interface Ring { - id: string; - name: string; - color: string; -} - -const rings = new Array(); -rings.push({ id: 'use', name: 'USE', color: '#93c47d' }); -rings.push({ id: 'trial', name: 'TRIAL', color: '#93d2c2' }); -rings.push({ id: 'assess', name: 'ASSESS', color: '#fbdb84' }); -rings.push({ id: 'hold', name: 'HOLD', color: '#efafa9' }); - -interface Quadrant { - id: string; - name: string; -} - -const quadrants = new Array(); -quadrants.push({ id: 'infrastructure', name: 'Infrastructure' }); -quadrants.push({ id: 'frameworks', name: 'Frameworks' }); -quadrants.push({ id: 'languages', name: 'Languages' }); -quadrants.push({ id: 'process', name: 'Process' }); - -interface Entry { - key: string; // react key - id: string; - moved: number; - quadrant: string; - ring: string; - title: string; - url: string; -} - -const entries = new Array(); -const createEntry = (overrides: Partial) => - ({ - moved: 0, - ring: 'use', - url: '#', - key: overrides.id, - ...overrides, - } as Entry); - -entries.push( - createEntry({ id: 'javascript', title: 'JavaScript', quadrant: 'languages' }), -); -entries.push( - createEntry({ id: 'typescript', title: 'TypeScript', quadrant: 'languages' }), -); -entries.push( - createEntry({ id: 'webpack', title: 'Webpack', quadrant: 'frameworks' }), -); -entries.push( - createEntry({ id: 'react', title: 'React', quadrant: 'frameworks' }), -); -entries.push( - createEntry({ - id: 'code-reviews', - title: 'Code Reviews', - quadrant: 'process', - }), -); -entries.push( - createEntry({ - id: 'mob-programming', - title: 'Mob Programming', - quadrant: 'process', - ring: 'assess', - }), -); -entries.push( - createEntry({ - id: 'github-actions', - title: 'GitHub Actions', - quadrant: 'infrastructure', - }), -); - -export default { - rings, - quadrants, - entries, -}; diff --git a/plugins/tech-radar/src/index.ts b/plugins/tech-radar/src/index.ts index 3a0a0fe2d3..926a7b410f 100644 --- a/plugins/tech-radar/src/index.ts +++ b/plugins/tech-radar/src/index.ts @@ -15,3 +15,17 @@ */ export { plugin } from './plugin'; + +/** + * The API for configuring the Tech Radar in a Backstage deployment. + */ +export * from './api'; + +/** + * Load sample data for Backstage users to get setup quickly. + * + * @example + * import { techRadarApiRef, TechRadar, loadSampleData } from '@backstage/plugin-tech-radar'; + * builder.add(techRadarApiRef, new TechRadar(800, 500, loadSampleData)); + */ +export { default as loadSampleData } from './sampleData'; diff --git a/plugins/tech-radar/src/plugin.ts b/plugins/tech-radar/src/plugin.ts index f337aac56f..3497a66f5d 100644 --- a/plugins/tech-radar/src/plugin.ts +++ b/plugins/tech-radar/src/plugin.ts @@ -15,11 +15,11 @@ */ import { createPlugin } from '@backstage/core'; -import ExampleRadar from './example/ExampleRadar'; +import RadarPage from './components/RadarPage'; export const plugin = createPlugin({ id: 'tech-radar', register({ router }) { - router.registerRoute('/tech-radar', ExampleRadar); + router.registerRoute('/tech-radar', RadarPage); }, }); diff --git a/plugins/tech-radar/src/sampleData.ts b/plugins/tech-radar/src/sampleData.ts new file mode 100644 index 0000000000..dc8c67106e --- /dev/null +++ b/plugins/tech-radar/src/sampleData.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { + RadarRing, + RadarQuadrant, + RadarEntry, + TechRadarLoaderResponse, +} from './api'; + +const rings = new Array(); +rings.push({ id: 'use', name: 'USE', color: '#93c47d' }); +rings.push({ id: 'trial', name: 'TRIAL', color: '#93d2c2' }); +rings.push({ id: 'assess', name: 'ASSESS', color: '#fbdb84' }); +rings.push({ id: 'hold', name: 'HOLD', color: '#efafa9' }); + +const quadrants = new Array(); +quadrants.push({ id: 'infrastructure', name: 'Infrastructure' }); +quadrants.push({ id: 'frameworks', name: 'Frameworks' }); +quadrants.push({ id: 'languages', name: 'Languages' }); +quadrants.push({ id: 'process', name: 'Process' }); + +const entries = new Array(); +entries.push({ + moved: 0, + ring: 'use', + url: '#', + key: 'javascript', + id: 'javascript', + title: 'JavaScript', + quadrant: 'languages', +}); +entries.push({ + moved: 0, + ring: 'use', + url: '#', + key: 'typescript', + id: 'typescript', + title: 'TypeScript', + quadrant: 'languages', +}); +entries.push({ + moved: 0, + ring: 'use', + url: '#', + key: 'webpack', + id: 'webpack', + title: 'Webpack', + quadrant: 'frameworks', +}); +entries.push({ + moved: 0, + ring: 'use', + url: '#', + key: 'react', + id: 'react', + title: 'React', + quadrant: 'frameworks', +}); +entries.push({ + moved: 0, + ring: 'use', + url: '#', + key: 'code-reviews', + id: 'code-reviews', + title: 'Code Reviews', + quadrant: 'process', +}); +entries.push({ + moved: 0, + url: '#', + key: 'mob-programming', + id: 'mob-programming', + title: 'Mob Programming', + quadrant: 'process', + ring: 'assess', +}); +entries.push({ + moved: 0, + ring: 'use', + url: '#', + key: 'github-actions', + id: 'github-actions', + title: 'GitHub Actions', + quadrant: 'infrastructure', +}); + +export default function loadSampleData(): Promise { + return Promise.resolve({ + rings, + quadrants, + entries, + }); +} diff --git a/yarn.lock b/yarn.lock index 64b7865ae0..aebee80013 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7821,6 +7821,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +<<<<<<< HEAD <<<<<<< HEAD cypress@*, cypress@^4.2.0: <<<<<<< HEAD @@ -7831,6 +7832,9 @@ cypress@*, cypress@^4.2.0: ======= cypress@*, cypress@4.2.0, cypress@^4.2.0: >>>>>>> 91e3670... re-ran yarn install +======= +cypress@*, cypress@^4.2.0: +>>>>>>> 630c24d... added tests for RadarPage version "4.2.0" resolved "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz#45673fb648b1a77b9a78d73e58b89ed05212d243" integrity sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==