From 593ea01f6a05192900b6f87ab9e834de9264576a Mon Sep 17 00:00:00 2001
From: Vincenzo Scamporlino
Date: Wed, 2 Aug 2023 23:17:08 +0200
Subject: [PATCH 1/5] catalog: fix EntitySwitch unmounting children on entity
refresh
Signed-off-by: Vincenzo Scamporlino
---
.../EntitySwitch/EntitySwitch.test.tsx | 93 ++++++++++++++++++-
.../components/EntitySwitch/EntitySwitch.tsx | 10 +-
2 files changed, 95 insertions(+), 8 deletions(-)
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
index 13e214d823..3a8795ccb7 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
@@ -20,7 +20,7 @@ import {
EntityProvider,
} from '@backstage/plugin-catalog-react';
import { render, screen } from '@testing-library/react';
-import React from 'react';
+import React, { useEffect } from 'react';
import { isKind } from './conditions';
import { EntitySwitch } from './EntitySwitch';
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
@@ -209,6 +209,97 @@ describe('EntitySwitch', () => {
expect(screen.queryByText('B')).not.toBeInTheDocument();
});
+ it('should display the children in case entity is available and loading is true', () => {
+ const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
+
+ render(
+
+
+
+ Component
}
+ />
+ System} />
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Component')).toBeInTheDocument();
+ expect(screen.queryByText('System')).not.toBeInTheDocument();
+ });
+
+ it(`shouldn't unmount the children on entity refresh`, () => {
+ const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
+
+ let mountsCount = 0;
+ function Component() {
+ useEffect(() => {
+ ++mountsCount;
+ }, []);
+
+ return Component
;
+ }
+
+ const rendered = render(
+
+
+
+ }
+ />
+ System} />
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Component')).toBeInTheDocument();
+ expect(screen.queryByText('System')).not.toBeInTheDocument();
+
+ expect(mountsCount).toBe(1);
+
+ rendered.rerender(
+
+
+
+ }
+ />
+ System} />
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Component')).toBeInTheDocument();
+ expect(screen.queryByText('System')).not.toBeInTheDocument();
+
+ expect(mountsCount).toBe(1);
+
+ rendered.rerender(
+
+
+
+ }
+ />
+ System} />
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Component')).toBeInTheDocument();
+ expect(screen.queryByText('System')).not.toBeInTheDocument();
+
+ expect(mountsCount).toBe(1);
+ });
+
it('should switch with async condition that is true', async () => {
const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
index 3cd806faa5..90de358ef9 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
@@ -64,8 +64,9 @@ export interface EntitySwitchProps {
/** @public */
export const EntitySwitch = (props: EntitySwitchProps) => {
- const { entity, loading } = useAsyncEntity();
+ const { entity } = useAsyncEntity();
const apis = useApiHolder();
+
const results = useElementFilter(
props.children,
collection =>
@@ -76,11 +77,6 @@ export const EntitySwitch = (props: EntitySwitchProps) => {
})
.getElements()
.flatMap((element: ReactElement) => {
- // Nothing is rendered while loading
- if (loading) {
- return [];
- }
-
const { if: condition, children: elementsChildren } =
element.props as EntitySwitchCase;
@@ -100,7 +96,7 @@ export const EntitySwitch = (props: EntitySwitchProps) => {
},
];
}),
- [apis, entity, loading],
+ [apis, entity],
);
const hasAsyncCases = results.some(
From 136cea792bd4ea7404a0cbfbadc3655c4284fdf2 Mon Sep 17 00:00:00 2001
From: Vincenzo Scamporlino
Date: Wed, 2 Aug 2023 23:19:15 +0200
Subject: [PATCH 2/5] changeset
Signed-off-by: Vincenzo Scamporlino
---
.changeset/twelve-ducks-swim.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/twelve-ducks-swim.md
diff --git a/.changeset/twelve-ducks-swim.md b/.changeset/twelve-ducks-swim.md
new file mode 100644
index 0000000000..1e44f09be1
--- /dev/null
+++ b/.changeset/twelve-ducks-swim.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Fixed an issue causing `EntitySwitch` to unmount its children once entity refresh was invoked
From 047e0906364366606bd626a8010e680da6d04d3d Mon Sep 17 00:00:00 2001
From: Vincenzo Scamporlino
Date: Wed, 2 Aug 2023 23:42:59 +0200
Subject: [PATCH 3/5] I think unmount should be valid
Signed-off-by: Vincenzo Scamporlino
---
.github/vale/Vocab/Backstage/accept.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt
index ed09c73c91..f85139ccbf 100644
--- a/.github/vale/Vocab/Backstage/accept.txt
+++ b/.github/vale/Vocab/Backstage/accept.txt
@@ -405,6 +405,7 @@ unbreak
Unconference
unicode
unmanaged
+unmount
unregister
unregistering
unregistration
From 959a6bc83312fd6734797122a4d5b52664645d89 Mon Sep 17 00:00:00 2001
From: Vincenzo Scamporlino
Date: Tue, 8 Aug 2023 17:03:57 +0200
Subject: [PATCH 4/5] EntitySwitch: do not render anything in case no entity is
present
Signed-off-by: Vincenzo Scamporlino
---
.../src/components/EntitySwitch/EntitySwitch.tsx | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
index 90de358ef9..b12495052e 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
@@ -77,18 +77,14 @@ export const EntitySwitch = (props: EntitySwitchProps) => {
})
.getElements()
.flatMap((element: ReactElement) => {
+ // If the entity is missing or there is an error, render nothing
+ if (!entity) {
+ return [];
+ }
+
const { if: condition, children: elementsChildren } =
element.props as EntitySwitchCase;
- // If the entity is missing or there is an error, render the default page
- if (!entity) {
- return [
- {
- if: condition === undefined,
- children: elementsChildren,
- },
- ];
- }
return [
{
if: condition?.(entity, { apis }),
From 5ad126667787a1ffd8165deedfbc7f58b2d7125a Mon Sep 17 00:00:00 2001
From: Vincenzo Scamporlino
Date: Tue, 8 Aug 2023 17:05:29 +0200
Subject: [PATCH 5/5] EntitySwitch: add missing tests and small improvements
Signed-off-by: Vincenzo Scamporlino
---
.../EntitySwitch/EntitySwitch.test.tsx | 86 +++++++++++++++++--
1 file changed, 81 insertions(+), 5 deletions(-)
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
index 3a8795ccb7..c93922e938 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
@@ -19,7 +19,7 @@ import {
AsyncEntityProvider,
EntityProvider,
} from '@backstage/plugin-catalog-react';
-import { render, screen } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
import React, { useEffect } from 'react';
import { isKind } from './conditions';
import { EntitySwitch } from './EntitySwitch';
@@ -35,6 +35,80 @@ const Wrapper = ({ children }: { children?: React.ReactNode }) => (
);
describe('EntitySwitch', () => {
+ it('should render only the first match', () => {
+ const content = (
+
+
+
+
+
+ );
+
+ render(
+
+
+ {content}
+
+ ,
+ );
+
+ expect(screen.getByText('A')).toBeInTheDocument();
+ expect(screen.queryByText('B')).not.toBeInTheDocument();
+ expect(screen.queryByText('C')).not.toBeInTheDocument();
+ });
+
+ it('should render the fallback if no cases are matching', () => {
+ const content = (
+
+
+
+
+
+ );
+
+ render(
+
+
+ {content}
+
+ ,
+ );
+
+ expect(screen.queryByText('A')).not.toBeInTheDocument();
+ expect(screen.queryByText('B')).not.toBeInTheDocument();
+ expect(screen.getByText('C')).toBeInTheDocument();
+ });
+
+ it('should render only the first fallback in case no cases are matching', () => {
+ const content = (
+
+
+
+
+
+
+ );
+
+ render(
+
+
+ {content}
+
+ ,
+ );
+
+ expect(screen.queryByText('A')).not.toBeInTheDocument();
+ expect(screen.queryByText('B')).not.toBeInTheDocument();
+ expect(screen.getByText('C')).toBeInTheDocument();
+ expect(screen.queryByText('D')).not.toBeInTheDocument();
+ });
+
it('should switch child when entity switches', () => {
const content = (
@@ -96,7 +170,7 @@ describe('EntitySwitch', () => {
expect(screen.queryByText('A')).not.toBeInTheDocument();
expect(screen.queryByText('B')).not.toBeInTheDocument();
- expect(screen.getByText('C')).toBeInTheDocument();
+ expect(screen.queryByText('C')).not.toBeInTheDocument();
});
it('should switch child when filters switch', () => {
@@ -383,10 +457,10 @@ describe('EntitySwitch', () => {
expect(screen.queryByText('A')).not.toBeInTheDocument();
});
- it('should switch with sync condition that throws', async () => {
+ it('should switch with async condition that throws', async () => {
const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
- const shouldRender = () => Promise.reject();
+ const shouldRender = jest.fn().mockRejectedValue(undefined);
render(
@@ -399,7 +473,9 @@ describe('EntitySwitch', () => {
,
);
- await expect(screen.findByText('C')).resolves.toBeInTheDocument();
+ await waitFor(() => expect(shouldRender).toHaveBeenCalled());
+
+ expect(screen.getByText('C')).toBeInTheDocument();
expect(screen.queryByText('A')).not.toBeInTheDocument();
expect(screen.queryByText('B')).not.toBeInTheDocument();
});