Skip to content

Commit d753255

Browse files
authored
feat: add test-ids (#139)
* feat: add test-ids * feat: add tests
1 parent 0fa62c7 commit d753255

21 files changed

+378
-75
lines changed
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
@let dynamicValues = dynamicValues$();
22
@if(dynamicValues.length > 0) {
33
<ui5-select
4-
class="input"
5-
[value]="value()"
6-
(input)="input.emit($event)"
7-
(change)="change.emit($event)"
8-
(blur)="blur.emit()"
9-
[required]="required()"
10-
[valueState]="valueState()"
11-
>
12-
@for (item of [{ value: '', key: '' }].concat(dynamicValues); track item.value) {
13-
<ui5-option
14-
[value]="item.value"
15-
[selected]="item.value === value()"
16-
>{{ item.key }}</ui5-option
17-
>
18-
}
19-
</ui5-select>
4+
class="input"
5+
[attr.test-id]="testId()"
6+
[value]="value()"
7+
(input)="input.emit($event)"
8+
(change)="change.emit($event)"
9+
(blur)="blur.emit()"
10+
[required]="required()"
11+
[valueState]="valueState()"
12+
>
13+
@for (item of [{ value: '', key: '' }].concat(dynamicValues); track item.value) {
14+
<ui5-option
15+
[attr.test-id]="optionTestId(item.value)"
16+
[value]="item.value"
17+
[selected]="item.value === value()"
18+
>{{ item.key }}</ui5-option>
19+
}
20+
</ui5-select>
2021
}

projects/wc/src/app/components/dynamic-select/dynamic-select.component.spec.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,132 @@ describe('DynamicSelectComponent', () => {
6666
{ value: '2', key: 'Second' },
6767
]);
6868
});
69+
70+
it('should generate testId with operation name', () => {
71+
const fieldDefinition = {
72+
operation: 'getData',
73+
gqlQuery: '{ someQuery }',
74+
key: 'name',
75+
value: 'id',
76+
};
77+
78+
const context = { id: 'ctx' };
79+
80+
fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
81+
fixture.componentRef.setInput('context', context);
82+
83+
mockResourceService.list.mockReturnValue(of([]));
84+
85+
fixture.detectChanges();
86+
87+
expect(component.testId()).toBe('dynamic-select-getData');
88+
});
89+
90+
it('should generate default testId without operation name', () => {
91+
const fieldDefinition = {
92+
operation: '',
93+
gqlQuery: '{ someQuery }',
94+
key: 'name',
95+
value: 'id',
96+
};
97+
98+
const context = { id: 'ctx' };
99+
100+
fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
101+
fixture.componentRef.setInput('context', context);
102+
103+
mockResourceService.list.mockReturnValue(of([]));
104+
105+
fixture.detectChanges();
106+
107+
expect(component.testId()).toBe('dynamic-select');
108+
});
109+
110+
it('should generate optionTestId with value', () => {
111+
const fieldDefinition = {
112+
operation: 'getData',
113+
gqlQuery: '{ someQuery }',
114+
key: 'name',
115+
value: 'id',
116+
};
117+
118+
const context = { id: 'ctx' };
119+
120+
fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
121+
fixture.componentRef.setInput('context', context);
122+
123+
mockResourceService.list.mockReturnValue(of([]));
124+
125+
fixture.detectChanges();
126+
127+
expect(component.optionTestId('option1')).toBe(
128+
'dynamic-select-getData-option-option1',
129+
);
130+
});
131+
132+
it('should generate optionTestId for empty value', () => {
133+
const fieldDefinition = {
134+
operation: 'getData',
135+
gqlQuery: '{ someQuery }',
136+
key: 'name',
137+
value: 'id',
138+
};
139+
140+
const context = { id: 'ctx' };
141+
142+
fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
143+
fixture.componentRef.setInput('context', context);
144+
145+
mockResourceService.list.mockReturnValue(of([]));
146+
147+
fixture.detectChanges();
148+
149+
expect(component.optionTestId('')).toBe(
150+
'dynamic-select-getData-option-empty',
151+
);
152+
});
153+
154+
it('should generate optionTestId for null value', () => {
155+
const fieldDefinition = {
156+
operation: 'getData',
157+
gqlQuery: '{ someQuery }',
158+
key: 'name',
159+
value: 'id',
160+
};
161+
162+
const context = { id: 'ctx' };
163+
164+
fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
165+
fixture.componentRef.setInput('context', context);
166+
167+
mockResourceService.list.mockReturnValue(of([]));
168+
169+
fixture.detectChanges();
170+
171+
expect(component.optionTestId(null)).toBe(
172+
'dynamic-select-getData-option-empty',
173+
);
174+
});
175+
176+
it('should generate optionTestId for undefined value', () => {
177+
const fieldDefinition = {
178+
operation: 'getData',
179+
gqlQuery: '{ someQuery }',
180+
key: 'name',
181+
value: 'id',
182+
};
183+
184+
const context = { id: 'ctx' };
185+
186+
fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
187+
fixture.componentRef.setInput('context', context);
188+
189+
mockResourceService.list.mockReturnValue(of([]));
190+
191+
fixture.detectChanges();
192+
193+
expect(component.optionTestId(undefined)).toBe(
194+
'dynamic-select-getData-option-empty',
195+
);
196+
});
69197
});

projects/wc/src/app/components/dynamic-select/dynamic-select.component.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
ChangeDetectionStrategy,
33
Component,
44
DestroyRef,
5+
computed,
56
effect,
67
inject,
78
input,
@@ -41,6 +42,11 @@ export class DynamicSelectComponent {
4142
blur = output<void>();
4243

4344
dynamicValues$ = signal<{ value: string; key: string }[]>([]);
45+
testId = computed(() => {
46+
const definition = this.dynamicValuesDefinition();
47+
const operation = definition.operation?.trim();
48+
return operation ? `dynamic-select-${operation}` : 'dynamic-select';
49+
});
4450

4551
private resourceService = inject(ResourceService);
4652
private destroyRef = inject(DestroyRef);
@@ -55,6 +61,14 @@ export class DynamicSelectComponent {
5561
});
5662
}
5763

64+
optionTestId(value: string | null | undefined): string {
65+
const normalizedValue = value?.toString().trim();
66+
if (normalizedValue) {
67+
return `${this.testId()}-option-${normalizedValue}`;
68+
}
69+
return `${this.testId()}-option-empty`;
70+
}
71+
5872
private getDynamicValues(
5973
dynamicValuesDefinition: NonNullable<
6074
FieldDefinition['dynamicValuesDefinition']

projects/wc/src/app/components/generic-ui/detail-view/detail-view.component.html

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
<ui5-dynamic-page>
1+
<ui5-dynamic-page test-id="generic-detail-view">
22
<ui5-dynamic-page-title slot="titleArea">
33

44
<ui5-title slot="heading">
5-
<div class="resource-title">
5+
<div test-id="generic-detail-view-title" class="resource-title">
66
{{ resource()?.spec?.displayName || resourceId() }}
77
</div>
88
</ui5-title>
9-
<ui5-text class="resource-title-subheading" slot="subheading">
9+
<ui5-text test-id="generic-detail-view-subtitle" class="resource-title-subheading" slot="subheading">
1010
The {{ resourceDefinition()?.singular }} for
1111
{{ resource()?.spec?.displayName || resourceId() }}
1212
</ui5-text>
@@ -18,6 +18,7 @@
1818
design="Transparent"
1919
>
2020
<ui5-toolbar-button
21+
test-id="generic-detail-view-download"
2122
text="Download kubeconfig"
2223
icon="download-from-cloud"
2324
design="Emphasized"
@@ -30,21 +31,22 @@
3031
<div class="resource-info">
3132
@if (resourceDefinition()?.ui?.logoUrl) {
3233
<img
34+
test-id="generic-detail-view-logo"
3335
class="resource-logo"
3436
src="{{ resourceDefinition()?.ui?.logoUrl }}"
3537
alt="Logo"
3638
/>
3739
}
3840
<div class="resource-info-cell">
3941
<ui5-label>Workspace Path</ui5-label>
40-
<p>{{ workspacePath() }}</p>
42+
<p test-id="generic-detail-view-workspace-path">{{ workspacePath() }}</p>
4143
</div>
4244
@if (resource(); as resource) {
4345
@for (viewField of viewFields(); track viewField.property) {
44-
<div class="resource-info-cell">
46+
<div class="resource-info-cell" [attr.test-id]="'generic-detail-view-field-' + viewField.property">
4547
@if(viewField.group) {
46-
<ui5-label>{{ viewField.group.label ?? viewField.group.name }}</ui5-label>
47-
<p>
48+
<ui5-label [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-label'">{{ viewField.group.label ?? viewField.group.name }}</ui5-label>
49+
<p [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-value'">
4850
@for (field of viewField.group.fields; let last = $last; track field.label) {
4951
<span [class.multiline]="viewField.group.multiline ?? true">
5052
<span>{{ field.label }}: </span>
@@ -60,8 +62,8 @@
6062
}
6163
</p>
6264
} @else {
63-
<ui5-label>{{ viewField.label }}</ui5-label>
64-
<p>
65+
<ui5-label [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-label'">{{ viewField.label }}</ui5-label>
66+
<p [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-value'">
6567
<value-cell
6668
[fieldDefinition]="viewField"
6769
[resource]="resource"

projects/wc/src/app/components/generic-ui/list-view/create-resource-modal/create-resource-modal.component.html

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
1-
<ui5-dialog #dialog>
1+
<ui5-dialog test-id="create-resource-dialog" #dialog>
22
<ui5-bar slot="header" design="Header">
3-
<ui5-title slot="startContent">
3+
<ui5-title test-id="create-resource-dialog-title" slot="startContent">
44
@if (isEditMode()) {
55
Edit
66
} @else {
77
Create
88
}
99
</ui5-title>
1010
</ui5-bar>
11-
<section class="form" [formGroup]="form">
11+
<section test-id="create-resource-dialog-form" class="form" [formGroup]="form">
1212
@for (field of fields(); track field.property) {
1313
@let fieldProperty = sanitizePropertyName(field.property);
14-
<div class="inputs">
15-
<ui5-label for="username" show-colon [required]="field.required">{{
14+
<div class="inputs" [attr.test-id]="'create-field-container-' + fieldProperty">
15+
<ui5-label
16+
[attr.test-id]="'create-field-label-' + fieldProperty"
17+
for="username"
18+
show-colon
19+
[required]="field.required"
20+
>{{
1621
field.label
1722
}}</ui5-label>
1823
@if (field.values?.length) {
1924
<ui5-select
2025
class="input"
26+
[attr.test-id]="'create-field-' + fieldProperty"
2127
[value]="form.controls[fieldProperty].value"
2228
(input)="setFormControlValue($event, fieldProperty)"
2329
(change)="setFormControlValue($event, fieldProperty)"
@@ -28,6 +34,7 @@
2834
>
2935
@for (value of [''].concat(field.values ?? []); track value) {
3036
<ui5-option
37+
[attr.test-id]="'create-field-' + fieldProperty + '-option-' + (value || 'empty')"
3138
[value]="value"
3239
[selected]="value === form.controls[fieldProperty].value"
3340
>{{ value }}</ui5-option
@@ -36,6 +43,7 @@
3643
</ui5-select>
3744
} @else if (field.dynamicValuesDefinition) {
3845
<dynamic-select
46+
[attr.test-id]="'create-field-' + fieldProperty"
3947
[context]="context()"
4048
[dynamicValuesDefinition]="field.dynamicValuesDefinition"
4149
[value]="form.controls[fieldProperty].value"
@@ -47,6 +55,7 @@
4755
} @else {
4856
<ui5-input
4957
class="input"
58+
[attr.test-id]="'create-field-' + fieldProperty"
5059
[value]="form.controls[fieldProperty].value"
5160
(blur)="onFieldBlur(fieldProperty)"
5261
(change)="setFormControlValue($event, fieldProperty)"
@@ -67,6 +76,7 @@
6776
</section>
6877
<ui5-toolbar class="ui5-content-density-compact" slot="footer">
6978
<ui5-toolbar-button
79+
test-id="create-resource-submit"
7080
class="dialogCloser"
7181
design="Emphasized"
7282
text="Submit"
@@ -75,6 +85,7 @@
7585
>
7686
</ui5-toolbar-button>
7787
<ui5-toolbar-button
88+
test-id="create-resource-cancel"
7889
class="dialogCloser"
7990
design="Transparent"
8091
text="Cancel"

projects/wc/src/app/components/generic-ui/list-view/delete-resource-confirmation-modal/delete-resource-modal.component.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
<ui5-dialog #dialog>
1+
<ui5-dialog test-id="delete-resource-dialog" #dialog>
22
<ui5-bar slot="header" design="Header">
3-
<ui5-title slot="startContent">
4-
<ui5-icon name="alert" design="Critical"></ui5-icon>
3+
<ui5-title test-id="delete-resource-dialog-title" slot="startContent">
4+
<ui5-icon test-id="delete-resource-dialog-alert" name="alert" design="Critical"></ui5-icon>
55
Delete {{ innerResource()?.metadata?.name?.toLowerCase() }}
66
</ui5-title>
77
</ui5-bar>
8-
<section class="content" [formGroup]="form">
8+
<section test-id="delete-resource-dialog-content" class="content" [formGroup]="form">
99
<div class="inputs">
1010
<p>Are you sure you want to delete {{ context()?.resourceDefinition?.singular }}
1111
<b>{{ innerResource()?.metadata?.name?.toLowerCase() }}</b>?</p>
@@ -18,6 +18,7 @@
1818
<ui5-input
1919
class="input"
2020
placeholder="Type name"
21+
test-id="delete-resource-input"
2122
[value]="form.controls.resource.value"
2223
(blur)="onFieldBlur('resource')"
2324
(change)="setFormControlValue($event, 'resource')"
@@ -29,6 +30,7 @@
2930
</section>
3031
<ui5-toolbar class="ui5-content-density-compact" slot="footer">
3132
<ui5-toolbar-button
33+
test-id="delete-resource-confirm"
3234
class="dialogCloser"
3335
[disabled]="form.invalid || form.controls.resource.value !== innerResource()?.metadata?.name?.toLowerCase()"
3436
design="Emphasized"
@@ -37,6 +39,7 @@
3739
>
3840
</ui5-toolbar-button>
3941
<ui5-toolbar-button
42+
test-id="delete-resource-cancel"
4043
class="dialogCloser"
4144
design="Transparent"
4245
text="Cancel"

0 commit comments

Comments
 (0)