|
1 | 1 | /* |
2 | | - * Copyright 2025 The Backstage Authors |
| 2 | + * Copyright 2026 The Backstage Authors |
3 | 3 | * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
|
15 | 15 | */ |
16 | 16 | import { Entity } from '@backstage/catalog-model'; |
17 | 17 | import { coreExtensionData } from '@backstage/frontend-plugin-api'; |
18 | | -import { createExtensionTester } from '@backstage/frontend-test-utils'; |
| 18 | +import { |
| 19 | + createExtensionTester, |
| 20 | + renderInTestApp, |
| 21 | +} from '@backstage/frontend-test-utils'; |
| 22 | +import { EntityProvider } from '@backstage/plugin-catalog-react'; |
19 | 23 | import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; |
| 24 | +import { screen } from '@testing-library/react'; |
20 | 25 |
|
| 26 | +import { AzureContainerRegistryApiRef } from './api'; |
| 27 | +import { mockAcrTagsData } from './__fixtures__/acrTagsObject'; |
21 | 28 | import { mockEntity } from './__fixtures__/mockEntity'; |
22 | 29 | import nfsPlugin, { acrImagesEntityContent } from './alpha'; |
| 30 | +import { TagsResponse } from './types'; |
| 31 | + |
| 32 | +/** The fixture carries ISO strings where the API returns Dates. */ |
| 33 | +const tagsResponse: TagsResponse = { |
| 34 | + imageName: mockAcrTagsData.imageName, |
| 35 | + registry: mockAcrTagsData.registry, |
| 36 | + tags: mockAcrTagsData.tags.map(tag => ({ |
| 37 | + ...tag, |
| 38 | + createdTime: new Date(tag.createdTime), |
| 39 | + lastUpdateTime: new Date(tag.lastUpdateTime), |
| 40 | + })), |
| 41 | +}; |
23 | 42 |
|
24 | | -/** |
25 | | - * These assert the plugin's *wiring* under the new frontend system: the title the |
26 | | - * catalog will print on the tab, the path it mounts at, and the entities it shows |
27 | | - * for. Under the new frontend system those are declared by the extension rather |
28 | | - * than configured by the app, so an end-to-end test that clicks the tab is really |
29 | | - * checking these three facts through a browser and a deployment. |
30 | | - * |
31 | | - * Rendering is covered a layer down by AcrImagesEntityContent.test.tsx; the only |
32 | | - * render here is through the extension itself, which is the part that test cannot |
33 | | - * reach. |
34 | | - */ |
35 | 43 | describe('alpha (new frontend system)', () => { |
36 | | - it('declares the tab title the catalog will render', () => { |
| 44 | + it('declares the tab the catalog renders, and which entities get it', () => { |
37 | 45 | const tester = createExtensionTester(acrImagesEntityContent); |
38 | 46 |
|
39 | 47 | expect(tester.get(EntityContentBlueprint.dataRefs.title)).toBe( |
40 | 48 | 'ACR images', |
41 | 49 | ); |
42 | | - }); |
43 | | - |
44 | | - it('declares the path the tab mounts at', () => { |
45 | | - const tester = createExtensionTester(acrImagesEntityContent); |
46 | | - |
47 | 50 | expect(tester.get(coreExtensionData.routePath)).toBe('acr-images'); |
48 | | - }); |
49 | 51 |
|
50 | | - describe('the filter that decides which entities show the tab', () => { |
51 | | - const filter = () => { |
52 | | - const fn = createExtensionTester(acrImagesEntityContent).get( |
53 | | - EntityContentBlueprint.dataRefs.filterFunction, |
54 | | - ); |
55 | | - // The blueprint makes this output optional, and an extension without it |
56 | | - // shows the tab on every entity in the catalog — so its absence is a |
57 | | - // defect rather than a variant, and worth failing on by name. |
58 | | - if (!fn) |
59 | | - throw new Error('acrImagesEntityContent declares no entity filter'); |
60 | | - return fn; |
| 52 | + const filter = tester.get(EntityContentBlueprint.dataRefs.filterFunction); |
| 53 | + if (!filter) throw new Error('the entity content declares no filter'); |
| 54 | + const withoutAnnotation: Entity = { |
| 55 | + ...mockEntity, |
| 56 | + metadata: { ...mockEntity.metadata, annotations: {} }, |
61 | 57 | }; |
| 58 | + expect(filter(mockEntity)).toBe(true); |
| 59 | + expect(filter(withoutAnnotation)).toBe(false); |
| 60 | + }); |
62 | 61 |
|
63 | | - it('shows the tab for an entity annotated with a repository name', () => { |
64 | | - expect(filter()(mockEntity)).toBe(true); |
65 | | - }); |
66 | | - |
67 | | - it('hides the tab for an entity with no ACR annotation', () => { |
68 | | - const withoutAnnotation: Entity = { |
69 | | - ...mockEntity, |
70 | | - metadata: { ...mockEntity.metadata, annotations: {} }, |
71 | | - }; |
72 | | - |
73 | | - expect(filter()(withoutAnnotation)).toBe(false); |
74 | | - }); |
| 62 | + it('is registered by the plugin under the id the app resolves', () => { |
| 63 | + // Not covered by anything else here: createExtensionTester instantiates an |
| 64 | + // extension in isolation, so removing it from the plugin's `extensions` |
| 65 | + // leaves every other assertion in this file green while the tab disappears. |
| 66 | + expect( |
| 67 | + nfsPlugin.getExtension('entity-content:acr/acrImagesEntityContent'), |
| 68 | + ).toBeDefined(); |
| 69 | + expect(nfsPlugin.getExtension('api:acr/acrApi')).toBeDefined(); |
75 | 70 | }); |
76 | 71 |
|
77 | | - /** |
78 | | - * Not optional, and not covered by any assertion above. |
79 | | - * |
80 | | - * `createExtensionTester` instantiates an extension in isolation, so it cannot |
81 | | - * see whether the plugin actually registers it. Deleting `acrImagesEntityContent` |
82 | | - * from the plugin's `extensions` array leaves every test above green while the |
83 | | - * tab disappears from a real app — and because a plugin that contributes nothing |
84 | | - * still boots cleanly, nothing else would report it either. |
85 | | - */ |
86 | | - describe('registration', () => { |
87 | | - it('registers the entity content and the API on the plugin', () => { |
88 | | - expect( |
89 | | - nfsPlugin.getExtension('entity-content:acr/acrImagesEntityContent'), |
90 | | - ).toBeDefined(); |
91 | | - expect(nfsPlugin.getExtension('api:acr/acrApi')).toBeDefined(); |
92 | | - }); |
| 72 | + it('renders the registry through the extension', async () => { |
| 73 | + // Through the blueprint's own loader, which the component's own test cannot |
| 74 | + // reach — renaming the component leaves the assertions above green. |
| 75 | + renderInTestApp( |
| 76 | + <EntityProvider entity={mockEntity}> |
| 77 | + {createExtensionTester(acrImagesEntityContent).reactElement()} |
| 78 | + </EntityProvider>, |
| 79 | + { |
| 80 | + apis: [ |
| 81 | + [AzureContainerRegistryApiRef, { getTags: async () => tagsResponse }], |
| 82 | + ], |
| 83 | + }, |
| 84 | + ); |
93 | 85 |
|
94 | | - it('is a frontend plugin the app can install, under the expected id', () => { |
95 | | - expect(nfsPlugin.$$type).toBe('@backstage/FrontendPlugin'); |
96 | | - expect(nfsPlugin.pluginId).toBe('acr'); |
97 | | - }); |
| 86 | + expect( |
| 87 | + await screen.findByText(tagsResponse.tags[0].name), |
| 88 | + ).toBeInTheDocument(); |
98 | 89 | }); |
99 | 90 | }); |
0 commit comments