Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
@let dynamicValues = dynamicValues$();
@if(dynamicValues.length > 0) {
<ui5-select
class="input"
[value]="value()"
(input)="input.emit($event)"
(change)="change.emit($event)"
(blur)="blur.emit()"
[required]="required()"
[valueState]="valueState()"
>
@for (item of [{ value: '', key: '' }].concat(dynamicValues); track item.value) {
<ui5-option
[value]="item.value"
[selected]="item.value === value()"
>{{ item.key }}</ui5-option
>
}
</ui5-select>
class="input"
[attr.test-id]="testId()"
[value]="value()"
(input)="input.emit($event)"
(change)="change.emit($event)"
(blur)="blur.emit()"
[required]="required()"
[valueState]="valueState()"
>
@for (item of [{ value: '', key: '' }].concat(dynamicValues); track item.value) {
<ui5-option
[attr.test-id]="optionTestId(item.value)"
[value]="item.value"
[selected]="item.value === value()"
>{{ item.key }}</ui5-option>
}
</ui5-select>
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,132 @@ describe('DynamicSelectComponent', () => {
{ value: '2', key: 'Second' },
]);
});

it('should generate testId with operation name', () => {
const fieldDefinition = {
operation: 'getData',
gqlQuery: '{ someQuery }',
key: 'name',
value: 'id',
};

const context = { id: 'ctx' };

fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
fixture.componentRef.setInput('context', context);

mockResourceService.list.mockReturnValue(of([]));

fixture.detectChanges();

expect(component.testId()).toBe('dynamic-select-getData');
});

it('should generate default testId without operation name', () => {
const fieldDefinition = {
operation: '',
gqlQuery: '{ someQuery }',
key: 'name',
value: 'id',
};

const context = { id: 'ctx' };

fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
fixture.componentRef.setInput('context', context);

mockResourceService.list.mockReturnValue(of([]));

fixture.detectChanges();

expect(component.testId()).toBe('dynamic-select');
});

it('should generate optionTestId with value', () => {
const fieldDefinition = {
operation: 'getData',
gqlQuery: '{ someQuery }',
key: 'name',
value: 'id',
};

const context = { id: 'ctx' };

fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
fixture.componentRef.setInput('context', context);

mockResourceService.list.mockReturnValue(of([]));

fixture.detectChanges();

expect(component.optionTestId('option1')).toBe(
'dynamic-select-getData-option-option1',
);
});

it('should generate optionTestId for empty value', () => {
const fieldDefinition = {
operation: 'getData',
gqlQuery: '{ someQuery }',
key: 'name',
value: 'id',
};

const context = { id: 'ctx' };

fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
fixture.componentRef.setInput('context', context);

mockResourceService.list.mockReturnValue(of([]));

fixture.detectChanges();

expect(component.optionTestId('')).toBe(
'dynamic-select-getData-option-empty',
);
});

it('should generate optionTestId for null value', () => {
const fieldDefinition = {
operation: 'getData',
gqlQuery: '{ someQuery }',
key: 'name',
value: 'id',
};

const context = { id: 'ctx' };

fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
fixture.componentRef.setInput('context', context);

mockResourceService.list.mockReturnValue(of([]));

fixture.detectChanges();

expect(component.optionTestId(null)).toBe(
'dynamic-select-getData-option-empty',
);
});

it('should generate optionTestId for undefined value', () => {
const fieldDefinition = {
operation: 'getData',
gqlQuery: '{ someQuery }',
key: 'name',
value: 'id',
};

const context = { id: 'ctx' };

fixture.componentRef.setInput('dynamicValuesDefinition', fieldDefinition);
fixture.componentRef.setInput('context', context);

mockResourceService.list.mockReturnValue(of([]));

fixture.detectChanges();

expect(component.optionTestId(undefined)).toBe(
'dynamic-select-getData-option-empty',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ChangeDetectionStrategy,
Component,
DestroyRef,
computed,
effect,
inject,
input,
Expand Down Expand Up @@ -41,6 +42,11 @@ export class DynamicSelectComponent {
blur = output<void>();

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

private resourceService = inject(ResourceService);
private destroyRef = inject(DestroyRef);
Expand All @@ -55,6 +61,14 @@ export class DynamicSelectComponent {
});
}

optionTestId(value: string | null | undefined): string {
const normalizedValue = value?.toString().trim();
if (normalizedValue) {
return `${this.testId()}-option-${normalizedValue}`;
}
return `${this.testId()}-option-empty`;
}

private getDynamicValues(
dynamicValuesDefinition: NonNullable<
FieldDefinition['dynamicValuesDefinition']
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<ui5-dynamic-page>
<ui5-dynamic-page test-id="generic-detail-view">
<ui5-dynamic-page-title slot="titleArea">

<ui5-title slot="heading">
<div class="resource-title">
<div test-id="generic-detail-view-title" class="resource-title">
{{ resource()?.spec?.displayName || resourceId() }}
</div>
</ui5-title>
<ui5-text class="resource-title-subheading" slot="subheading">
<ui5-text test-id="generic-detail-view-subtitle" class="resource-title-subheading" slot="subheading">
The {{ resourceDefinition()?.singular }} for
{{ resource()?.spec?.displayName || resourceId() }}
</ui5-text>
Expand All @@ -18,6 +18,7 @@
design="Transparent"
>
<ui5-toolbar-button
test-id="generic-detail-view-download"
text="Download kubeconfig"
icon="download-from-cloud"
design="Emphasized"
Expand All @@ -30,21 +31,22 @@
<div class="resource-info">
@if (resourceDefinition()?.ui?.logoUrl) {
<img
test-id="generic-detail-view-logo"
class="resource-logo"
src="{{ resourceDefinition()?.ui?.logoUrl }}"
alt="Logo"
/>
}
<div class="resource-info-cell">
<ui5-label>Workspace Path</ui5-label>
<p>{{ workspacePath() }}</p>
<p test-id="generic-detail-view-workspace-path">{{ workspacePath() }}</p>
</div>
@if (resource(); as resource) {
@for (viewField of viewFields(); track viewField.property) {
<div class="resource-info-cell">
<div class="resource-info-cell" [attr.test-id]="'generic-detail-view-field-' + viewField.property">
@if(viewField.group) {
<ui5-label>{{ viewField.group.label ?? viewField.group.name }}</ui5-label>
<p>
<ui5-label [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-label'">{{ viewField.group.label ?? viewField.group.name }}</ui5-label>
<p [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-value'">
@for (field of viewField.group.fields; let last = $last; track field.label) {
<span [class.multiline]="viewField.group.multiline ?? true">
<span>{{ field.label }}: </span>
Expand All @@ -60,8 +62,8 @@
}
</p>
} @else {
<ui5-label>{{ viewField.label }}</ui5-label>
<p>
<ui5-label [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-label'">{{ viewField.label }}</ui5-label>
<p [attr.test-id]="'generic-detail-view-field-' + viewField.property + '-value'">
<value-cell
[fieldDefinition]="viewField"
[resource]="resource"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
<ui5-dialog #dialog>
<ui5-dialog test-id="create-resource-dialog" #dialog>
<ui5-bar slot="header" design="Header">
<ui5-title slot="startContent">
<ui5-title test-id="create-resource-dialog-title" slot="startContent">
@if (isEditMode()) {
Edit
} @else {
Create
}
</ui5-title>
</ui5-bar>
<section class="form" [formGroup]="form">
<section test-id="create-resource-dialog-form" class="form" [formGroup]="form">
@for (field of fields(); track field.property) {
@let fieldProperty = sanitizePropertyName(field.property);
<div class="inputs">
<ui5-label for="username" show-colon [required]="field.required">{{
<div class="inputs" [attr.test-id]="'create-field-container-' + fieldProperty">
<ui5-label
[attr.test-id]="'create-field-label-' + fieldProperty"
for="username"
show-colon
[required]="field.required"
>{{
field.label
}}</ui5-label>
@if (field.values?.length) {
<ui5-select
class="input"
[attr.test-id]="'create-field-' + fieldProperty"
[value]="form.controls[fieldProperty].value"
(input)="setFormControlValue($event, fieldProperty)"
(change)="setFormControlValue($event, fieldProperty)"
Expand All @@ -28,6 +34,7 @@
>
@for (value of [''].concat(field.values ?? []); track value) {
<ui5-option
[attr.test-id]="'create-field-' + fieldProperty + '-option-' + (value || 'empty')"
[value]="value"
[selected]="value === form.controls[fieldProperty].value"
>{{ value }}</ui5-option
Expand All @@ -36,6 +43,7 @@
</ui5-select>
} @else if (field.dynamicValuesDefinition) {
<dynamic-select
[attr.test-id]="'create-field-' + fieldProperty"
[context]="context()"
[dynamicValuesDefinition]="field.dynamicValuesDefinition"
[value]="form.controls[fieldProperty].value"
Expand All @@ -47,6 +55,7 @@
} @else {
<ui5-input
class="input"
[attr.test-id]="'create-field-' + fieldProperty"
[value]="form.controls[fieldProperty].value"
(blur)="onFieldBlur(fieldProperty)"
(change)="setFormControlValue($event, fieldProperty)"
Expand All @@ -67,6 +76,7 @@
</section>
<ui5-toolbar class="ui5-content-density-compact" slot="footer">
<ui5-toolbar-button
test-id="create-resource-submit"
class="dialogCloser"
design="Emphasized"
text="Submit"
Expand All @@ -75,6 +85,7 @@
>
</ui5-toolbar-button>
<ui5-toolbar-button
test-id="create-resource-cancel"
class="dialogCloser"
design="Transparent"
text="Cancel"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<ui5-dialog #dialog>
<ui5-dialog test-id="delete-resource-dialog" #dialog>
<ui5-bar slot="header" design="Header">
<ui5-title slot="startContent">
<ui5-icon name="alert" design="Critical"></ui5-icon>
<ui5-title test-id="delete-resource-dialog-title" slot="startContent">
<ui5-icon test-id="delete-resource-dialog-alert" name="alert" design="Critical"></ui5-icon>
Delete {{ innerResource()?.metadata?.name?.toLowerCase() }}
</ui5-title>
</ui5-bar>
<section class="content" [formGroup]="form">
<section test-id="delete-resource-dialog-content" class="content" [formGroup]="form">
<div class="inputs">
<p>Are you sure you want to delete {{ context()?.resourceDefinition?.singular }}
<b>{{ innerResource()?.metadata?.name?.toLowerCase() }}</b>?</p>
Expand All @@ -18,6 +18,7 @@
<ui5-input
class="input"
placeholder="Type name"
test-id="delete-resource-input"
[value]="form.controls.resource.value"
(blur)="onFieldBlur('resource')"
(change)="setFormControlValue($event, 'resource')"
Expand All @@ -29,6 +30,7 @@
</section>
<ui5-toolbar class="ui5-content-density-compact" slot="footer">
<ui5-toolbar-button
test-id="delete-resource-confirm"
class="dialogCloser"
[disabled]="form.invalid || form.controls.resource.value !== innerResource()?.metadata?.name?.toLowerCase()"
design="Emphasized"
Expand All @@ -37,6 +39,7 @@
>
</ui5-toolbar-button>
<ui5-toolbar-button
test-id="delete-resource-cancel"
class="dialogCloser"
design="Transparent"
text="Cancel"
Expand Down
Loading
Loading