Skip to content

Commit 7182a8c

Browse files
Hubert-Szczepanski-SAPandreaskienleCopilot
authored
test: resources tests
Co-authored-by: Andreas Kienle <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent b3abd87 commit 7182a8c

File tree

10 files changed

+500
-20
lines changed

10 files changed

+500
-20
lines changed

src/components/ControlPlane/ActionsMenu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ export type ActionItem<T> = {
1414
export type ActionsMenuProps<T> = {
1515
item: T;
1616
actions: ActionItem<T>[];
17-
buttonIcon?: string;
1817
};
1918

20-
export function ActionsMenu<T>({ item, actions, buttonIcon = 'overflow' }: ActionsMenuProps<T>) {
19+
export function ActionsMenu<T>({ item, actions }: ActionsMenuProps<T>) {
2120
const popoverRef = useRef<MenuDomRef>(null);
2221
const [open, setOpen] = useState(false);
2322

@@ -30,10 +29,11 @@ export function ActionsMenu<T>({ item, actions, buttonIcon = 'overflow' }: Actio
3029

3130
return (
3231
<>
33-
<Button icon={buttonIcon} design="Transparent" onClick={handleOpenerClick} />
32+
<Button icon="overflow" design="Transparent" data-testid="ActionsMenu-opener" onClick={handleOpenerClick} />
3433
<Menu
3534
ref={popoverRef}
3635
open={open}
36+
data-testid="ActionsMenu"
3737
onItemClick={(event) => {
3838
const element = event.detail.item as HTMLElement & { disabled?: boolean };
3939
const actionKey = element.dataset.actionKey;

src/components/ControlPlane/GitRepositories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx';
1313
import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
1414
import { useSplitter } from '../Splitter/SplitterContext.tsx';
1515
import { YamlSidePanel } from '../Yaml/YamlSidePanel.tsx';
16-
import { useHandleResourcePatch } from '../../lib/api/types/crossplane/useHandleResourcePatch.ts';
16+
import { useHandleResourcePatch } from '../../hooks/useHandleResourcePatch.ts';
1717
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
1818
import type { GitReposResponse } from '../../lib/api/types/flux/listGitRepo';
1919
import { ActionsMenu, type ActionItem } from './ActionsMenu';

src/components/ControlPlane/Kustomizations.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx';
1313
import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
1414
import { useSplitter } from '../Splitter/SplitterContext.tsx';
1515
import { YamlSidePanel } from '../Yaml/YamlSidePanel.tsx';
16-
import { useHandleResourcePatch } from '../../lib/api/types/crossplane/useHandleResourcePatch.ts';
16+
import { useHandleResourcePatch } from '../../hooks/useHandleResourcePatch.ts';
1717
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
1818
import type { KustomizationsResponse } from '../../lib/api/types/flux/listKustomization';
1919
import { ActionsMenu, type ActionItem } from './ActionsMenu';
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { ManagedResources } from './ManagedResources.tsx';
3+
import { SplitterProvider } from '../Splitter/SplitterContext.tsx';
4+
import { ManagedResourceGroup } from '../../lib/shared/types.ts';
5+
import { MemoryRouter } from 'react-router-dom';
6+
import { useApiResourceMutation, useApiResource } from '../../lib/api/useApiResource.ts';
7+
import '@ui5/webcomponents-cypress-commands';
8+
import { useHandleResourcePatch } from '../../hooks/useHandleResourcePatch.ts';
9+
import { SplitterLayout } from '../Splitter/SplitterLayout.tsx';
10+
import { useResourcePluralNames } from '../../hooks/useResourcePluralNames';
11+
12+
describe('ManagedResources - Delete Resource', () => {
13+
let deleteCalled = false;
14+
let patchCalled = false;
15+
let triggerCallCount = 0;
16+
17+
const fakeUseApiResourceMutation: typeof useApiResourceMutation = (): any => {
18+
return {
19+
data: undefined,
20+
error: undefined,
21+
reset: () => {},
22+
trigger: async (): Promise<any> => {
23+
const currentTriggerCall = triggerCallCount++;
24+
if (currentTriggerCall === 0) {
25+
deleteCalled = true;
26+
} else {
27+
patchCalled = true;
28+
}
29+
return undefined;
30+
},
31+
isMutating: false,
32+
};
33+
};
34+
35+
const fakeUseApiResource: typeof useApiResource = (): any => {
36+
return {
37+
data: mockManagedResources,
38+
error: undefined,
39+
isLoading: false,
40+
isValidating: false,
41+
mutate: async () => undefined,
42+
};
43+
};
44+
45+
const fakeUseResourcePluralNames: typeof useResourcePluralNames = (): any => {
46+
return {
47+
getPluralKind: (kind: string) => `${kind.toLowerCase()}s`,
48+
isLoading: false,
49+
error: undefined,
50+
};
51+
};
52+
53+
const mockManagedResources: ManagedResourceGroup[] = [
54+
{
55+
items: [
56+
{
57+
apiVersion: 'account.btp.sap.crossplane.io/v1alpha1',
58+
kind: 'Subaccount',
59+
metadata: {
60+
name: 'test-subaccount',
61+
namespace: 'test-namespace',
62+
creationTimestamp: '2024-01-01T00:00:00Z',
63+
resourceVersion: '1',
64+
labels: {},
65+
},
66+
spec: {},
67+
status: {
68+
conditions: [
69+
{
70+
type: 'Ready',
71+
status: 'True',
72+
lastTransitionTime: '2024-01-01T00:00:00Z',
73+
},
74+
{
75+
type: 'Synced',
76+
status: 'True',
77+
lastTransitionTime: '2024-01-01T00:00:00Z',
78+
},
79+
],
80+
},
81+
} as any,
82+
],
83+
},
84+
];
85+
86+
beforeEach(() => {
87+
deleteCalled = false;
88+
patchCalled = false;
89+
triggerCallCount = 0;
90+
});
91+
92+
it('deletes a managed resource', () => {
93+
cy.mount(
94+
<MemoryRouter>
95+
<SplitterProvider>
96+
<ManagedResources
97+
useApiResourceMutation={fakeUseApiResourceMutation}
98+
useApiResource={fakeUseApiResource}
99+
useResourcePluralNames={fakeUseResourcePluralNames}
100+
/>
101+
</SplitterProvider>
102+
</MemoryRouter>,
103+
);
104+
105+
// Expand resource group
106+
cy.get('button[aria-label*="xpand"]').first().click({ force: true });
107+
cy.contains('test-subaccount').should('be.visible');
108+
109+
// Open actions menu and click Delete
110+
cy.get('[data-testid="ActionsMenu-opener"]').first().click({ force: true });
111+
cy.contains('Delete').click({ force: true });
112+
113+
// Type confirmation text
114+
cy.get('ui5-dialog[open]').find('ui5-input').typeIntoUi5Input('test-subaccount');
115+
116+
// Verify delete not called yet
117+
cy.then(() => cy.wrap(deleteCalled).should('equal', false));
118+
119+
// Click delete button
120+
cy.get('ui5-dialog[open]').find('ui5-button').contains('Delete').click();
121+
122+
// Verify delete was called
123+
cy.then(() => cy.wrap(deleteCalled).should('equal', true));
124+
});
125+
126+
it('force deletes a managed resource', () => {
127+
cy.mount(
128+
<MemoryRouter>
129+
<SplitterProvider>
130+
<ManagedResources
131+
useApiResourceMutation={fakeUseApiResourceMutation}
132+
useApiResource={fakeUseApiResource}
133+
useResourcePluralNames={fakeUseResourcePluralNames}
134+
/>
135+
</SplitterProvider>
136+
</MemoryRouter>,
137+
);
138+
139+
// Expand resource group
140+
cy.get('button[aria-label*="xpand"]').first().click({ force: true });
141+
cy.contains('test-subaccount').should('be.visible');
142+
143+
// Open actions menu and click Delete
144+
cy.get('[data-testid="ActionsMenu-opener"]').first().click({ force: true });
145+
cy.contains('Delete').click({ force: true });
146+
147+
// Expand Advanced section
148+
cy.contains('Advanced').click();
149+
150+
// Enable force deletion checkbox
151+
cy.contains('Force deletion').click({ force: true });
152+
153+
// Type confirmation text
154+
cy.get('ui5-dialog[open]').find('ui5-input').typeIntoUi5Input('test-subaccount');
155+
156+
// Verify neither delete nor patch called yet
157+
cy.then(() => cy.wrap(deleteCalled).should('equal', false));
158+
cy.then(() => cy.wrap(patchCalled).should('equal', false));
159+
160+
// Click delete button
161+
cy.get('ui5-dialog[open]').find('ui5-button').contains('Delete').click();
162+
163+
// Verify both delete and patch were called
164+
cy.then(() => cy.wrap(deleteCalled).should('equal', true));
165+
cy.then(() => cy.wrap(patchCalled).should('equal', true));
166+
});
167+
168+
it('keeps delete button disabled until confirmation text is entered', () => {
169+
cy.mount(
170+
<MemoryRouter>
171+
<SplitterProvider>
172+
<ManagedResources
173+
useApiResourceMutation={fakeUseApiResourceMutation}
174+
useApiResource={fakeUseApiResource}
175+
useResourcePluralNames={fakeUseResourcePluralNames}
176+
/>
177+
</SplitterProvider>
178+
</MemoryRouter>,
179+
);
180+
181+
// Expand resource group
182+
cy.get('button[aria-label*="xpand"]').first().click({ force: true });
183+
cy.contains('test-subaccount').should('be.visible');
184+
185+
// Open actions menu and click Delete
186+
cy.get('[data-testid="ActionsMenu-opener"]').first().click({ force: true });
187+
cy.contains('Delete').click({ force: true });
188+
189+
// Delete button should be disabled initially
190+
cy.get('ui5-dialog[open]').find('ui5-button').contains('Delete').should('have.attr', 'disabled');
191+
192+
// Type wrong text
193+
cy.get('ui5-dialog[open]').find('ui5-input').typeIntoUi5Input('wrong-text');
194+
195+
// Delete button should still be disabled
196+
cy.get('ui5-dialog[open]').find('ui5-button').contains('Delete').should('have.attr', 'disabled');
197+
198+
// Clear input and type correct text
199+
cy.get('ui5-dialog[open]').find('ui5-input').find('input[id*="inner"]').clear({ force: true });
200+
cy.get('ui5-dialog[open]').find('ui5-input').typeIntoUi5Input('test-subaccount');
201+
202+
// Delete button should now be enabled
203+
cy.get('ui5-dialog[open]').find('ui5-button').contains('Delete').should('not.have.attr', 'disabled');
204+
});
205+
});
206+
207+
describe('ManagedResources - Edit Resource', () => {
208+
let patchCalled = false;
209+
let patchedItem: any = null;
210+
211+
const fakeUseHandleResourcePatch: typeof useHandleResourcePatch = () => {
212+
return async (item: any) => {
213+
patchCalled = true;
214+
patchedItem = item;
215+
return true;
216+
};
217+
};
218+
219+
const fakeUseApiResource: typeof useApiResource = (): any => {
220+
return {
221+
data: mockManagedResources,
222+
error: undefined,
223+
isLoading: false,
224+
isValidating: false,
225+
mutate: async () => undefined,
226+
};
227+
};
228+
229+
const fakeUseResourcePluralNames: typeof useResourcePluralNames = (): any => {
230+
return {
231+
getPluralKind: (kind: string) => `${kind.toLowerCase()}s`,
232+
isLoading: false,
233+
error: undefined,
234+
};
235+
};
236+
237+
const mockManagedResources: ManagedResourceGroup[] = [
238+
{
239+
items: [
240+
{
241+
apiVersion: 'account.btp.sap.crossplane.io/v1alpha1',
242+
kind: 'Subaccount',
243+
metadata: {
244+
name: 'test-subaccount',
245+
namespace: 'test-namespace',
246+
creationTimestamp: '2024-01-01T00:00:00Z',
247+
resourceVersion: '1',
248+
labels: {},
249+
},
250+
spec: {},
251+
status: {
252+
conditions: [
253+
{
254+
type: 'Ready',
255+
status: 'True',
256+
lastTransitionTime: '2024-01-01T00:00:00Z',
257+
},
258+
{
259+
type: 'Synced',
260+
status: 'True',
261+
lastTransitionTime: '2024-01-01T00:00:00Z',
262+
},
263+
],
264+
},
265+
} as any,
266+
],
267+
},
268+
];
269+
270+
before(() => {
271+
cy.on('uncaught:exception', (err) => {
272+
if (err.message.includes('TextModel got disposed')) {
273+
return false;
274+
}
275+
if (err.message.includes('DiffEditorWidget')) {
276+
return false;
277+
}
278+
return true;
279+
});
280+
});
281+
282+
beforeEach(() => {
283+
patchCalled = false;
284+
patchedItem = null;
285+
});
286+
287+
it('opens edit panel and can apply changes', () => {
288+
cy.mount(
289+
<MemoryRouter>
290+
<SplitterProvider>
291+
<SplitterLayout>
292+
<ManagedResources
293+
useHandleResourcePatch={fakeUseHandleResourcePatch}
294+
useApiResource={fakeUseApiResource}
295+
useResourcePluralNames={fakeUseResourcePluralNames}
296+
/>
297+
</SplitterLayout>
298+
</SplitterProvider>
299+
</MemoryRouter>,
300+
);
301+
302+
// Expand resource group
303+
cy.get('button[aria-label*="xpand"]').first().click({ force: true });
304+
cy.contains('test-subaccount').should('be.visible');
305+
306+
// Open actions menu and click Edit
307+
cy.get('[data-testid="ActionsMenu-opener"]').first().click({ force: true });
308+
cy.contains('Edit').click({ force: true });
309+
310+
// Verify YAML panel opened
311+
cy.contains('YAML').should('be.visible');
312+
313+
// Click Apply button
314+
cy.get('[data-testid="yaml-apply-button"]').should('be.visible').click();
315+
316+
// Confirm in dialog
317+
cy.get('[data-testid="yaml-confirm-button"]', { timeout: 10000 }).should('be.visible').click({ force: true });
318+
319+
// Wait for success message
320+
cy.contains('Update submitted', { timeout: 10000 }).should('be.visible');
321+
322+
// Verify patch was called
323+
cy.then(() => cy.wrap(patchCalled).should('equal', true));
324+
cy.then(() => cy.wrap(patchedItem).should('not.be.null'));
325+
});
326+
});

0 commit comments

Comments
 (0)