Skip to content

Commit 635a4c9

Browse files
authored
feat: add summary section to workspace create/edit (#966)
* feat: add summary section to workspace create/edit Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> * feat: add summary section to workspace create/edit Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> * feat: add summary section to workspace create/edit Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com> --------- Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com>
1 parent efa1152 commit 635a4c9

26 files changed

+2416
-260
lines changed

workspaces/frontend/config/cspell-ignore-words.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ storageclasses
1414
pvcs
1515
rwop
1616
persistentvolumeclaims
17+
unhover
18+
unhovering

workspaces/frontend/src/__tests__/cypress/cypress/pages/workspaces/workspaceForm.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,45 @@ class WorkspaceForm {
233233
assertLabelCategoryNotExists(labelKey: string): void {
234234
cy.findByTestId(`label-category-${labelKey}`).should('not.exist');
235235
}
236+
237+
findKindLogo(kindName: string): Cypress.Chainable<JQuery<HTMLElement>> {
238+
return cy.findByTestId(`kind-logo-${kindName}`);
239+
}
240+
241+
findOptionCardHeader(cardId: string): Cypress.Chainable<JQuery<HTMLElement>> {
242+
return cy.findByTestId(`option-card-header-${cardId.replace(/ /g, '-')}`);
243+
}
244+
245+
findOptionCardDescription(cardId: string): Cypress.Chainable<JQuery<HTMLElement>> {
246+
return cy.findByTestId(`option-card-description-${cardId.replace(/ /g, '-')}`);
247+
}
248+
249+
findOptionCardIcons(cardId: string): Cypress.Chainable<JQuery<HTMLElement>> {
250+
return cy.findByTestId(`option-card-icons-${cardId.replace(/ /g, '-')}`);
251+
}
252+
253+
findFilterSidebar(): Cypress.Chainable<JQuery<HTMLElement>> {
254+
return cy.findByTestId('filter-sidebar');
255+
}
256+
257+
findRedirectSummaryIcon(step: number, suffix: string): Cypress.Chainable<JQuery<HTMLElement>> {
258+
return cy.findByTestId(`redirect-icon-${step}-${suffix}`);
259+
}
260+
261+
findRedirectPopoverContent(step: number, suffix: string): Cypress.Chainable<JQuery<HTMLElement>> {
262+
return cy.findByTestId(`redirect-popover-content-${step}-${suffix}`);
263+
}
264+
265+
assertPopoverContentVisible(
266+
step: number,
267+
suffix: string,
268+
): Cypress.Chainable<JQuery<HTMLElement>> {
269+
return this.findRedirectPopoverContent(step, suffix).should('be.visible');
270+
}
271+
272+
assertPopoverContentNotExist(step: number, suffix: string): void {
273+
cy.findByTestId(`redirect-popover-content-${step}-${suffix}`).should('not.exist');
274+
}
236275
}
237276

238277
class SecretsCreateModal {

workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/createWorkspace.cy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ describe('Create workspace', () => {
214214
createWorkspace.clickPrevious();
215215
createWorkspace.assertProgressStepVisible(STEP_NAMES.IMAGE);
216216
createWorkspace.assertExtraFilterChecked('showRedirected');
217+
createWorkspace.assertExtraFilterChecked('showHidden'); // Both filters are checked from before
218+
createWorkspace.uncheckExtraFilter('showHidden'); // Uncheck to test the filter behavior
217219
createWorkspace.findImageCard(mockImage.id).should('not.exist');
218220
createWorkspace.checkExtraFilter('showHidden');
219221
createWorkspace.findImageCard(mockImage.id).should('be.visible');
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { mockModArchResponse } from 'mod-arch-core';
2+
import { createWorkspace } from '~/__tests__/cypress/cypress/pages/workspaces/createWorkspace';
3+
import { buildMockNamespace, buildMockWorkspaceKind } from '~/shared/mock/mockBuilder';
4+
import { NOTEBOOKS_API_VERSION } from '~/__tests__/cypress/cypress/support/commands/api';
5+
import { navBar } from '~/__tests__/cypress/cypress/pages/components/navBar';
6+
import type { WorkspacekindsImageConfigValue } from '~/generated/data-contracts';
7+
import { WorkspacekindsRedirectMessageLevel } from '~/generated/data-contracts';
8+
9+
const buildMockImageConfigValue = (
10+
overrides?: Partial<WorkspacekindsImageConfigValue>,
11+
): WorkspacekindsImageConfigValue => ({
12+
id: 'default-image',
13+
displayName: 'Default Image',
14+
description: 'Default description',
15+
labels: [],
16+
hidden: false,
17+
...overrides,
18+
});
19+
20+
const DEFAULT_NAMESPACE = 'default';
21+
22+
describe('Summary Redirect Popover - Delayed Hide Behavior', () => {
23+
let mockNamespace: ReturnType<typeof buildMockNamespace>;
24+
let mockWorkspaceKind: ReturnType<typeof buildMockWorkspaceKind>;
25+
26+
beforeEach(() => {
27+
mockNamespace = buildMockNamespace({ name: DEFAULT_NAMESPACE });
28+
29+
const sourceImage = buildMockImageConfigValue({
30+
id: 'source-image',
31+
displayName: 'Source Image v1.0',
32+
description: 'Old version image',
33+
redirect: {
34+
to: 'target-image',
35+
message: {
36+
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelWarning,
37+
text: 'This image is deprecated. Please use the target image.',
38+
},
39+
},
40+
});
41+
42+
const targetImage = buildMockImageConfigValue({
43+
id: 'target-image',
44+
displayName: 'Target Image v2.0',
45+
description: 'New version image',
46+
});
47+
48+
mockWorkspaceKind = buildMockWorkspaceKind({
49+
name: 'test-kind',
50+
displayName: 'Test Workspace Kind',
51+
podTemplate: {
52+
...buildMockWorkspaceKind().podTemplate,
53+
options: {
54+
...buildMockWorkspaceKind().podTemplate.options,
55+
imageConfig: {
56+
default: 'source-image',
57+
values: [sourceImage, targetImage],
58+
},
59+
},
60+
},
61+
});
62+
63+
cy.interceptApi(
64+
'GET /api/:apiVersion/namespaces',
65+
{ path: { apiVersion: NOTEBOOKS_API_VERSION } },
66+
mockModArchResponse([mockNamespace]),
67+
).as('getNamespaces');
68+
69+
cy.interceptApi(
70+
'GET /api/:apiVersion/workspacekinds',
71+
{ path: { apiVersion: NOTEBOOKS_API_VERSION } },
72+
mockModArchResponse([mockWorkspaceKind]),
73+
).as('getWorkspaceKinds');
74+
75+
cy.visit('/workspaces/create');
76+
cy.wait('@getNamespaces');
77+
navBar.selectNamespace(mockNamespace.name);
78+
cy.wait('@getWorkspaceKinds');
79+
80+
createWorkspace.selectKind('test-kind');
81+
createWorkspace.clickNext();
82+
83+
// Need to check the showRedirected filter to see images with redirect
84+
createWorkspace.checkExtraFilter('showRedirected');
85+
86+
createWorkspace.selectImage('source-image');
87+
createWorkspace.assertImageSelected('source-image');
88+
});
89+
90+
describe('Pinning behavior', () => {
91+
it('should pin popover on icon click', () => {
92+
createWorkspace.clickNext(); // Pod config
93+
createWorkspace.clickNext(); // Properties
94+
95+
createWorkspace.findRedirectSummaryIcon(1, 'current').click();
96+
createWorkspace.assertPopoverContentVisible(1, 'current');
97+
98+
createWorkspace.findRedirectSummaryIcon(1, 'current').trigger('mouseleave');
99+
createWorkspace.assertPopoverContentVisible(1, 'current');
100+
});
101+
102+
it('should unpin popover on second click', () => {
103+
createWorkspace.clickNext(); // Pod config
104+
createWorkspace.clickNext(); // Properties
105+
106+
createWorkspace.findRedirectSummaryIcon(1, 'current').click();
107+
createWorkspace.assertPopoverContentVisible(1, 'current');
108+
109+
createWorkspace.findRedirectSummaryIcon(1, 'current').click();
110+
createWorkspace.assertPopoverContentNotExist(1, 'current');
111+
});
112+
113+
it('should not start delayed hide timer when popover is pinned', () => {
114+
createWorkspace.clickNext(); // Pod config
115+
createWorkspace.clickNext(); // Properties
116+
117+
createWorkspace.findRedirectSummaryIcon(1, 'current').click();
118+
createWorkspace.assertPopoverContentVisible(1, 'current');
119+
120+
createWorkspace.findRedirectSummaryIcon(1, 'current').trigger('mouseenter');
121+
createWorkspace.findRedirectSummaryIcon(1, 'current').trigger('mouseleave');
122+
123+
createWorkspace.assertPopoverContentVisible(1, 'current');
124+
});
125+
});
126+
127+
describe('Keyboard accessibility', () => {
128+
it('should pin popover on Enter key', () => {
129+
createWorkspace.clickNext(); // Pod config
130+
createWorkspace.clickNext(); // Properties
131+
132+
createWorkspace.findRedirectSummaryIcon(1, 'current').focus();
133+
createWorkspace.findRedirectSummaryIcon(1, 'current').type('{enter}');
134+
createWorkspace.assertPopoverContentVisible(1, 'current');
135+
});
136+
137+
it('should pin popover on Space key', () => {
138+
createWorkspace.clickNext(); // Pod config
139+
createWorkspace.clickNext(); // Properties
140+
141+
createWorkspace.findRedirectSummaryIcon(1, 'current').focus();
142+
createWorkspace.findRedirectSummaryIcon(1, 'current').type(' ');
143+
createWorkspace.assertPopoverContentVisible(1, 'current');
144+
});
145+
});
146+
});

0 commit comments

Comments
 (0)