Keep existing EntitySwitch behaviour, add tests

Keep the existing behavior for non async statements

Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com>
Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Johan Haals
2021-10-05 14:41:24 +02:00
parent bb0f6b8a0f
commit 4e3755151c
2 changed files with 106 additions and 23 deletions
@@ -112,4 +112,63 @@ describe('EntitySwitch', () => {
expect(rendered.queryByText('A')).not.toBeInTheDocument();
expect(rendered.queryByText('B')).toBeInTheDocument();
});
it('should switch with async condition that is true', async () => {
const entity = { kind: 'component' } as Entity;
const shouldRender = () => Promise.resolve(true);
const rendered = render(
<Wrapper>
<EntityProvider entity={entity}>
<EntitySwitch>
<EntitySwitch.Case if={shouldRender} children="A" />
<EntitySwitch.Case children="B" />
</EntitySwitch>
</EntityProvider>
</Wrapper>,
);
await expect(rendered.findByText('A')).resolves.toBeInTheDocument();
expect(rendered.queryByText('B')).not.toBeInTheDocument();
});
it('should switch with sync condition that is false', async () => {
const entity = { kind: 'component' } as Entity;
const shouldRender = () => Promise.resolve(false);
const rendered = render(
<Wrapper>
<EntityProvider entity={entity}>
<EntitySwitch>
<EntitySwitch.Case if={shouldRender} children="A" />
<EntitySwitch.Case if={() => true} children="B" />
</EntitySwitch>
</EntityProvider>
</Wrapper>,
);
await expect(rendered.findByText('B')).resolves.toBeInTheDocument();
expect(rendered.queryByText('A')).not.toBeInTheDocument();
});
it('should switch with sync condition that throws', async () => {
const entity = { kind: 'component' } as Entity;
const shouldRender = () => Promise.reject();
const rendered = render(
<Wrapper>
<EntityProvider entity={entity}>
<EntitySwitch>
<EntitySwitch.Case if={shouldRender} children="A" />
<EntitySwitch.Case if={() => false} children="B" />
<EntitySwitch.Case children="C" />
</EntitySwitch>
</EntityProvider>
</Wrapper>,
);
await expect(rendered.findByText('C')).resolves.toBeInTheDocument();
expect(rendered.queryByText('A')).not.toBeInTheDocument();
expect(rendered.queryByText('B')).not.toBeInTheDocument();
});
});
@@ -16,7 +16,7 @@
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { PropsWithChildren, ReactNode } from 'react';
import React, { PropsWithChildren, ReactNode } from 'react';
import {
attachComponentData,
useApiHolder,
@@ -45,45 +45,69 @@ type SwitchCase = {
children: JSX.Element;
};
type SwitchCaseResult = {
if: boolean | Promise<boolean>;
children: JSX.Element;
};
export const EntitySwitch = ({ children }: PropsWithChildren<{}>) => {
const { entity } = useEntity();
const apis = useApiHolder();
const switchCases = useElementFilter(children, collection =>
collection
.selectByComponentData({
key: ENTITY_SWITCH_KEY,
withStrictError: 'Child of EntitySwitch is not an EntitySwitch.Case',
})
.getElements()
.flatMap<SwitchCase>((element: React.ReactElement) => {
const { if: condition, children: elementsChildren } = element.props;
return [{ if: condition, children: elementsChildren }];
}),
const results = useElementFilter(
children,
collection =>
collection
.selectByComponentData({
key: ENTITY_SWITCH_KEY,
withStrictError: 'Child of EntitySwitch is not an EntitySwitch.Case',
})
.getElements()
.flatMap<SwitchCaseResult>((element: React.ReactElement) => {
const { if: condition, children: elementsChildren } =
element.props as SwitchCase;
return [
{
if: condition?.(entity, { apis }) ?? true,
children: elementsChildren,
},
];
}),
[apis, entity],
);
const hasAsyncCases = results.some(
r => typeof r.if === 'object' && 'then' in r.if,
);
if (hasAsyncCases) {
return <AsyncEntitySwitch results={results} />;
}
return results.find(r => r.if)?.children ?? null;
};
function AsyncEntitySwitch({ results }: { results: SwitchCaseResult[] }) {
const { loading, value } = useAsync(async () => {
const promises = switchCases.map(
const promises = results.map(
async ({ if: condition, children: output }) => {
if (!condition) {
return output;
try {
if (await condition) {
return output;
}
} catch {
/* ignored */
}
try {
const matches = await condition(entity, { apis });
return matches ? output : null;
} catch {
return null;
}
return null;
},
);
return (await Promise.all(promises)).find(Boolean) ?? null;
}, [switchCases]);
}, [results]);
if (loading || !value) {
return null;
}
return value;
};
}
EntitySwitch.Case = EntitySwitchCase;