diff --git a/packages/core/package.json b/packages/core/package.json
index 848f0847bd..f7e8c67fe9 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -60,7 +60,8 @@
"react-sparklines": "^1.7.0",
"react-syntax-highlighter": "^13.5.1",
"react-use": "^15.3.3",
- "remark-gfm": "^1.0.0"
+ "remark-gfm": "^1.0.0",
+ "zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.2.0",
diff --git a/packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx b/packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx
new file mode 100644
index 0000000000..3f85dab9f3
--- /dev/null
+++ b/packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx
@@ -0,0 +1,65 @@
+/*
+ * 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 { AlertDisplay } from './AlertDisplay';
+import {
+ ApiProvider,
+ ApiRegistry,
+ alertApiRef,
+ AlertApiForwarder,
+} from '@backstage/core-api';
+import Observable from 'zen-observable';
+import { renderInTestApp } from '@backstage/test-utils';
+
+const TEST_MESSAGE = 'TEST_MESSAGE';
+
+describe('', () => {
+ it('renders without exploding', async () => {
+ const apiRegistry = ApiRegistry.from([
+ [alertApiRef, new AlertApiForwarder()],
+ ]);
+
+ const { queryByText } = await renderInTestApp(
+
+
+ ,
+ );
+ expect(queryByText(TEST_MESSAGE)).not.toBeInTheDocument();
+ });
+
+ it('renders with message', async () => {
+ const apiRegistry = ApiRegistry.from([
+ [
+ alertApiRef,
+ {
+ post() {},
+ alert$() {
+ return Observable.of({ message: TEST_MESSAGE });
+ },
+ },
+ ],
+ ]);
+
+ const { queryByText } = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(queryByText(TEST_MESSAGE)).toBeInTheDocument();
+ });
+});
diff --git a/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx b/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx
new file mode 100644
index 0000000000..942f533dac
--- /dev/null
+++ b/packages/core/src/components/EmptyState/EmptyStateImage.test.tsx
@@ -0,0 +1,49 @@
+/*
+ * 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 { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
+import { EmptyStateImage } from './EmptyStateImage';
+
+describe('', () => {
+ it('render EmptyStateImage component with missing field', async () => {
+ const rendered = await renderWithEffects(
+ wrapInTestApp(),
+ );
+ expect(rendered.getByTestId('missingAnnotation')).toBeInTheDocument();
+ });
+
+ it('render EmptyStateImage component with missing info', async () => {
+ const rendered = await renderWithEffects(
+ wrapInTestApp(),
+ );
+ expect(rendered.getByTestId('noInformation')).toBeInTheDocument();
+ });
+
+ it('render EmptyStateImage component with missing content', async () => {
+ const rendered = await renderWithEffects(
+ wrapInTestApp(),
+ );
+ expect(rendered.getByTestId('createComponent')).toBeInTheDocument();
+ });
+
+ it('render EmptyStateImage component with missing data', async () => {
+ const rendered = await renderWithEffects(
+ wrapInTestApp(),
+ );
+ expect(rendered.getByTestId('noBuild')).toBeInTheDocument();
+ });
+});
diff --git a/packages/core/src/components/EmptyState/EmptyStateImage.tsx b/packages/core/src/components/EmptyState/EmptyStateImage.tsx
index d52a0f7dcf..c7a61f7190 100644
--- a/packages/core/src/components/EmptyState/EmptyStateImage.tsx
+++ b/packages/core/src/components/EmptyState/EmptyStateImage.tsx
@@ -54,6 +54,7 @@ export const EmptyStateImage = ({ missing }: Props) => {
src={noInformation}
alt="no Information"
className={classes.generalImg}
+ data-testid="noInformation"
/>
);
case 'content':
@@ -62,11 +63,17 @@ export const EmptyStateImage = ({ missing }: Props) => {
src={createComponent}
alt="create Component"
className={classes.generalImg}
+ data-testid="createComponent"
/>
);
case 'data':
return (
-
+
);
default:
return null;
diff --git a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx
new file mode 100644
index 0000000000..5c7f17ba9f
--- /dev/null
+++ b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx
@@ -0,0 +1,154 @@
+/*
+ * 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 { act, fireEvent } from '@testing-library/react';
+import { renderInTestApp } from '@backstage/test-utils';
+import { FeatureCalloutCircular } from './FeatureCalloutCircular';
+
+const INITIAL_BOUNDING_RECT: DOMRect = {
+ width: 100,
+ height: 100,
+ x: 0,
+ y: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ top: 0,
+ toJSON: () => {},
+};
+
+const UPDATED_BOUNDING_RECT: DOMRect = {
+ width: 200,
+ height: 200,
+ x: 50,
+ y: 50,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ top: 0,
+ toJSON: () => {},
+};
+
+beforeEach(() => {
+ Element.prototype.getBoundingClientRect = jest.fn(
+ () => INITIAL_BOUNDING_RECT,
+ );
+});
+
+describe('', () => {
+ it('renders without exploding', async () => {
+ const rendered = await renderInTestApp(
+ ,
+ );
+ rendered.getByText('description');
+ rendered.getByText('title');
+ });
+
+ it('renders with correct style', async () => {
+ const { getByTestId } = await renderInTestApp(
+ ,
+ );
+ const dot = await getByTestId('dot');
+ const text = await getByTestId('text');
+
+ expect(dot).toBeInTheDocument();
+ expect(text).toBeInTheDocument();
+
+ // Dot style
+ expect(dot.style.left).toBe('-800px');
+ expect(dot.style.top).toBe('-800px');
+ expect(dot.style.width).toBe('1700px');
+ expect(dot.style.height).toBe('1700px');
+
+ // Text style
+ expect(text.style.left).toBe('-400px');
+ expect(text.style.top).toBe('120px');
+ expect(text.style.width).toBe('450px');
+ });
+
+ it('update when the user scrolls', async () => {
+ const { getByTestId } = await renderInTestApp(
+ ,
+ );
+ const dot = await getByTestId('dot');
+ const text = await getByTestId('text');
+
+ act(() => {
+ Element.prototype.getBoundingClientRect = jest.fn(
+ () => UPDATED_BOUNDING_RECT,
+ );
+
+ // Trigger the window resize event.
+ fireEvent(window, new Event('resize'));
+ });
+
+ // Dot style
+ expect(dot.style.left).toBe('-750px');
+ expect(dot.style.top).toBe('-750px');
+ expect(dot.style.width).toBe('1800px');
+ expect(dot.style.height).toBe('1800px');
+
+ // Text style
+ expect(text.style.left).toBe('-300px');
+ expect(text.style.top).toBe('270px');
+ expect(text.style.width).toBe('450px');
+ });
+
+ it('update when the user resizes the window', async () => {
+ const { getByTestId } = await renderInTestApp(
+ ,
+ );
+ const dot = await getByTestId('dot');
+ const text = await getByTestId('text');
+
+ act(() => {
+ Element.prototype.getBoundingClientRect = jest.fn(
+ () => UPDATED_BOUNDING_RECT,
+ );
+
+ // Trigger the window resize event.
+ fireEvent(window, new Event('scroll'));
+ });
+
+ // Dot style
+ expect(dot.style.left).toBe('-750px');
+ expect(dot.style.top).toBe('-750px');
+ expect(dot.style.width).toBe('1800px');
+ expect(dot.style.height).toBe('1800px');
+
+ // Text style
+ expect(text.style.left).toBe('-300px');
+ expect(text.style.top).toBe('270px');
+ expect(text.style.width).toBe('450px');
+ });
+});
diff --git a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx
index 38e422dbcc..9dc0d25681 100644
--- a/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx
+++ b/packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx
@@ -160,6 +160,7 @@ export const FeatureCalloutCircular: FC = ({
<>
= ({