@openmfp/webcomponents 0.6.1 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/mfp-wc-dashboard.js +153 -0
  2. package/mfp-webcomponents.js +573 -0
  3. package/package.json +14 -68
  4. package/.github/workflows/pipeline.yaml +0 -41
  5. package/.storybook/main.ts +0 -34
  6. package/.storybook/preview.ts +0 -29
  7. package/.storybook/tsconfig.json +0 -11
  8. package/AGENTS.md +0 -153
  9. package/CODEOWNERS +0 -6
  10. package/CONTRIBUTING.md +0 -95
  11. package/LICENSE +0 -201
  12. package/LICENSES/Apache-2.0.txt +0 -73
  13. package/README.md +0 -91
  14. package/REUSE.toml +0 -9
  15. package/angular.json +0 -157
  16. package/docs/dashboard.md +0 -358
  17. package/docs/declarative-form.md +0 -178
  18. package/docs/declarative-table-card.md +0 -235
  19. package/docs/declarative-table.md +0 -315
  20. package/eslint.config.js +0 -41
  21. package/projects/ngx/cards/favorites/favorites.component.html +0 -12
  22. package/projects/ngx/cards/favorites/favorites.component.scss +0 -50
  23. package/projects/ngx/cards/favorites/favorites.component.ts +0 -19
  24. package/projects/ngx/cards/public-api.ts +0 -4
  25. package/projects/ngx/cards/service-status/service-status-card.component.html +0 -15
  26. package/projects/ngx/cards/service-status/service-status-card.component.scss +0 -87
  27. package/projects/ngx/cards/service-status/service-status-card.component.ts +0 -36
  28. package/projects/ngx/cards/stories/visited-service-card.stories.ts +0 -149
  29. package/projects/ngx/cards/visited-service-card/visited-service-card.component.html +0 -17
  30. package/projects/ngx/cards/visited-service-card/visited-service-card.component.scss +0 -34
  31. package/projects/ngx/cards/visited-service-card/visited-service-card.component.ts +0 -22
  32. package/projects/ngx/cards/whats-new/whats-new.component.html +0 -10
  33. package/projects/ngx/cards/whats-new/whats-new.component.scss +0 -25
  34. package/projects/ngx/cards/whats-new/whats-new.component.ts +0 -46
  35. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.html +0 -28
  36. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.scss +0 -44
  37. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.spec.ts +0 -85
  38. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.ts +0 -58
  39. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.html +0 -29
  40. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.scss +0 -63
  41. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.spec.ts +0 -255
  42. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.ts +0 -75
  43. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.spec.ts +0 -76
  44. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.ts +0 -109
  45. package/projects/ngx/declarative-ui/dashboard/card/utils/index.ts +0 -4
  46. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.spec.ts +0 -141
  47. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.ts +0 -44
  48. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.spec.ts +0 -142
  49. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.ts +0 -52
  50. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.spec.ts +0 -107
  51. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.ts +0 -22
  52. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.html +0 -134
  53. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.scss +0 -88
  54. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.spec.ts +0 -354
  55. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.ts +0 -238
  56. package/projects/ngx/declarative-ui/dashboard/dashboard/index.ts +0 -1
  57. package/projects/ngx/declarative-ui/dashboard/index.ts +0 -5
  58. package/projects/ngx/declarative-ui/dashboard/models/constants.ts +0 -2
  59. package/projects/ngx/declarative-ui/dashboard/models/dashboard.model.ts +0 -50
  60. package/projects/ngx/declarative-ui/dashboard/models/index.ts +0 -1
  61. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.html +0 -28
  62. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.scss +0 -85
  63. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.spec.ts +0 -104
  64. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.ts +0 -23
  65. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.html +0 -62
  66. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.scss +0 -12
  67. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.spec.ts +0 -301
  68. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.ts +0 -166
  69. package/projects/ngx/declarative-ui/form/declarative-form/index.ts +0 -1
  70. package/projects/ngx/declarative-ui/form/index.ts +0 -2
  71. package/projects/ngx/declarative-ui/form/models/form-field-definition.ts +0 -15
  72. package/projects/ngx/declarative-ui/form/models/index.ts +0 -1
  73. package/projects/ngx/declarative-ui/form/utils/set-property-by-path.ts +0 -30
  74. package/projects/ngx/declarative-ui/models/index.ts +0 -2
  75. package/projects/ngx/declarative-ui/models/resource.ts +0 -5
  76. package/projects/ngx/declarative-ui/models/ui-definition.ts +0 -95
  77. package/projects/ngx/declarative-ui/public-api.ts +0 -4
  78. package/projects/ngx/declarative-ui/stories/add-card-dialog.stories.ts +0 -91
  79. package/projects/ngx/declarative-ui/stories/background-lightblue.png +0 -0
  80. package/projects/ngx/declarative-ui/stories/dashboard.cards.ts +0 -107
  81. package/projects/ngx/declarative-ui/stories/dashboard.stories.ts +0 -296
  82. package/projects/ngx/declarative-ui/stories/declarative-form.stories.ts +0 -149
  83. package/projects/ngx/declarative-ui/stories/declarative-table-card.stories.ts +0 -358
  84. package/projects/ngx/declarative-ui/stories/declarative-table.stories.ts +0 -363
  85. package/projects/ngx/declarative-ui/stories/pods-table.config.ts +0 -188
  86. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +0 -138
  87. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +0 -21
  88. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +0 -345
  89. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +0 -61
  90. package/projects/ngx/declarative-ui/table/declarative-table/index.ts +0 -1
  91. package/projects/ngx/declarative-ui/table/index.ts +0 -2
  92. package/projects/ngx/declarative-ui/table/models/index.ts +0 -14
  93. package/projects/ngx/declarative-ui/table/models/table.model.ts +0 -17
  94. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.spec.ts +0 -146
  95. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.ts +0 -69
  96. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.spec.ts +0 -70
  97. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.ts +0 -13
  98. package/projects/ngx/declarative-ui/table/utils/proccess-fields.spec.ts +0 -511
  99. package/projects/ngx/declarative-ui/table/utils/proccess-fields.ts +0 -71
  100. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.spec.ts +0 -372
  101. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.ts +0 -98
  102. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-cell.constants.ts +0 -5
  103. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.html +0 -1
  104. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.scss +0 -0
  105. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.spec.ts +0 -119
  106. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.ts +0 -35
  107. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.html +0 -7
  108. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.scss +0 -0
  109. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.spec.ts +0 -114
  110. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.ts +0 -19
  111. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.html +0 -7
  112. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.scss +0 -10
  113. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.spec.ts +0 -188
  114. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.ts +0 -16
  115. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.html +0 -59
  116. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.scss +0 -33
  117. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.spec.ts +0 -316
  118. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.ts +0 -115
  119. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.html +0 -156
  120. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.scss +0 -123
  121. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.spec.ts +0 -786
  122. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +0 -286
  123. package/projects/ngx/declarative-ui/table-card/index.ts +0 -2
  124. package/projects/ngx/declarative-ui/table-card/models/configs.ts +0 -46
  125. package/projects/ngx/declarative-ui/tsconfig.lib.json +0 -11
  126. package/projects/ngx/declarative-ui/tsconfig.lib.prod.json +0 -9
  127. package/projects/ngx/declarative-ui/tsconfig.spec.json +0 -9
  128. package/projects/ngx/ng-package.json +0 -8
  129. package/projects/ngx/package.json +0 -22
  130. package/projects/ngx/public-api.ts +0 -2
  131. package/projects/ngx/tsconfig.lib.json +0 -11
  132. package/projects/ngx/tsconfig.lib.prod.json +0 -9
  133. package/projects/webcomponents/main.ts +0 -92
  134. package/projects/webcomponents/tsconfig.app.json +0 -9
  135. package/projects/webcomponents-dashboard/main.ts +0 -15
  136. package/projects/webcomponents-dashboard/tsconfig.app.json +0 -9
  137. package/renovate.json +0 -6
  138. package/scripts/bundle-wc.mjs +0 -79
  139. package/tsconfig.json +0 -37
  140. package/tsconfig.spec.json +0 -8
  141. package/tsconfig.storybook.json +0 -16
  142. package/vitest.config.ts +0 -26
@@ -1,50 +0,0 @@
1
- import { ButtonSettings } from '../../models/ui-definition';
2
-
3
-
4
-
5
-
6
- export const CARD_TYPES = {
7
- WC: 'wc',
8
- ANGULAR: 'angular',
9
- SAP_UI: 'sap-ui',
10
- } as const;
11
-
12
- export type CardsType = (typeof CARD_TYPES)[keyof typeof CARD_TYPES];
13
-
14
- export interface CardConfig {
15
- id: string;
16
- w?: number;
17
- h?: number;
18
- x?: number;
19
- y?: number;
20
- maxH?: number;
21
- maxW?: number;
22
- minH?: number;
23
- minW?: number;
24
- sectionId?: string;
25
- component: string;
26
- type?: CardsType;
27
- componentInputs?: Record<string, unknown>;
28
- label?: string;
29
- }
30
-
31
- export interface SectionConfig {
32
- id: string;
33
- w?: number;
34
- title?: string;
35
- editable?: boolean;
36
- }
37
-
38
- export interface DashboardButtonsSettings {
39
- editViewButton?: Partial<ButtonSettings>;
40
- addCardButton?: Partial<ButtonSettings>;
41
- }
42
-
43
- export interface DashboardConfig {
44
- title: string;
45
- description?: string;
46
- backgroundImageUrl?: string;
47
- buttonsSettings?: DashboardButtonsSettings;
48
- customActions?: ButtonSettings[];
49
- editable?: boolean;
50
- }
@@ -1 +0,0 @@
1
- export * from './dashboard.model';
@@ -1,28 +0,0 @@
1
- <div
2
- class="section"
3
- [class.section--edit]="editMode() && section().editable !== false"
4
- >
5
- @if (editMode() && section().editable !== false) {
6
- <ui5-button
7
- class="section__remove"
8
- design="Default"
9
- icon="decline"
10
- (click)="removeSection.emit()"
11
- >
12
- </ui5-button>
13
- }
14
- @if (section().title) {
15
- <div class="section__header">
16
- <span class="section__title">{{ section().title }}</span>
17
- </div>
18
- }
19
- <div class="section__grid" [style.--cols]="columns()">
20
- @for (card of cards(); track card.id) {
21
- <mfp-dashboard-card
22
- [card]="card"
23
- [editMode]="editMode() && section().editable !== false"
24
- (removeCard)="removeCard.emit($any(card.id))"
25
- />
26
- }
27
- </div>
28
- </div>
@@ -1,85 +0,0 @@
1
- :host {
2
- display: block;
3
- min-width: 0;
4
- }
5
-
6
- .section {
7
- position: relative;
8
- display: flex;
9
- flex-direction: column;
10
- border: 1px solid transparent;
11
- border-top: none;
12
- border-radius: 0 0 0.5rem 0.5rem;
13
- padding: 1.25rem 0.75rem 0.75rem;
14
-
15
- &--edit {
16
- border-color: var(--sapGroup_ContentBorderColor, #d9d9d9);
17
- }
18
-
19
- &__remove {
20
- position: absolute;
21
- top: -0.75rem;
22
- right: -0.75rem;
23
- border-radius: 50% !important;
24
- width: 1.25rem !important;
25
- height: 1.25rem !important;
26
- min-width: unset !important;
27
- padding: 0 !important;
28
- font-size: 0.625rem !important;
29
- }
30
-
31
- &__header {
32
- position: absolute;
33
- top: 0;
34
- left: 0;
35
- right: 0;
36
- transform: translateY(-50%);
37
- display: flex;
38
- align-items: center;
39
- pointer-events: none;
40
-
41
- &::before,
42
- &::after {
43
- content: '';
44
- flex: 1;
45
- height: 1px;
46
- background: transparent;
47
- }
48
-
49
- &::before {
50
- max-width: 0.75rem;
51
- flex: 0 0 0.75rem;
52
- }
53
- }
54
-
55
- &--edit &__header {
56
- &::before,
57
- &::after {
58
- background: var(--sapGroup_ContentBorderColor, #d9d9d9);
59
- }
60
- }
61
-
62
- &__title {
63
- font-size: 1rem;
64
- font-weight: 600;
65
- color: var(--sapTextColor, #fff);
66
- padding: 0 0.5rem;
67
- white-space: nowrap;
68
- pointer-events: all;
69
- }
70
-
71
- &__grid {
72
- display: grid;
73
- grid-template-columns: repeat(var(--cols, 12), 1fr);
74
- grid-auto-rows: var(--row-height, 10px);
75
- gap: var(--row-gap, 0px);
76
-
77
- @media (max-width: 599px) {
78
- grid-template-columns: 1fr;
79
- }
80
-
81
- @media (min-width: 600px) and (max-width: 1023px) {
82
- grid-template-columns: repeat(8, 1fr);
83
- }
84
- }
85
- }
@@ -1,104 +0,0 @@
1
- import { DashboardCard } from '../card/dashboard-card.component';
2
- import { DashboardSection } from './dashboard-section.component';
3
- import { ComponentFixture, TestBed } from '@angular/core/testing';
4
- import { By } from '@angular/platform-browser';
5
-
6
- type Fixture = ComponentFixture<DashboardSection>;
7
-
8
- function setup(): { fixture: Fixture; component: DashboardSection } {
9
- const fixture = TestBed.createComponent(DashboardSection);
10
- const component = fixture.componentInstance;
11
- return { fixture, component };
12
- }
13
-
14
- function root(fixture: Fixture): ShadowRoot | HTMLElement {
15
- return fixture.nativeElement.shadowRoot ?? fixture.nativeElement;
16
- }
17
-
18
- describe('DashboardSection', () => {
19
- beforeEach(async () => {
20
- await TestBed.configureTestingModule({
21
- imports: [DashboardSection],
22
- }).compileComponents();
23
- });
24
-
25
- it('uses w for grid placement and lets content define the height', () => {
26
- const { fixture } = setup();
27
-
28
- fixture.componentRef.setInput('section', {
29
- id: 'section-1',
30
- title: 'Favorites',
31
- w: 8,
32
- });
33
- fixture.componentRef.setInput('cards', []);
34
- fixture.detectChanges();
35
-
36
- expect(fixture.nativeElement.style.gridColumn).toBe('span 8');
37
- expect(fixture.nativeElement.style.gridRow).toBe('');
38
- expect(root(fixture).textContent).toContain('Favorites');
39
- });
40
-
41
- it('shows a remove button in editable edit mode and emits when clicked', () => {
42
- const { fixture, component } = setup();
43
- let emitted = 0;
44
-
45
- component.removeSection.subscribe(() => emitted++);
46
- fixture.componentRef.setInput('section', {
47
- id: 'section-1',
48
- title: 'Favorites',
49
- editable: true,
50
- });
51
- fixture.componentRef.setInput('cards', []);
52
- fixture.componentRef.setInput('editMode', true);
53
- fixture.detectChanges();
54
-
55
- const button = root(fixture).querySelector('.section__remove');
56
- button?.dispatchEvent(new Event('click'));
57
-
58
- expect(button).not.toBeNull();
59
- expect(emitted).toBe(1);
60
- });
61
-
62
- it('disables section edit affordances when the section is marked non-editable', () => {
63
- const { fixture } = setup();
64
-
65
- fixture.componentRef.setInput('section', {
66
- id: 'section-1',
67
- title: 'Favorites',
68
- editable: false,
69
- });
70
- fixture.componentRef.setInput('cards', [
71
- { id: 'card-1', component: 'demo-widget' },
72
- ]);
73
- fixture.componentRef.setInput('editMode', true);
74
- fixture.detectChanges();
75
-
76
- const card = fixture.debugElement.query(By.directive(DashboardCard))
77
- .componentInstance as DashboardCard;
78
-
79
- expect(root(fixture).querySelector('.section__remove')).toBeNull();
80
- expect(card.editMode()).toBe(false);
81
- });
82
-
83
- it('emits the card id when a nested card requests removal', () => {
84
- const { fixture, component } = setup();
85
- const emitted: string[] = [];
86
-
87
- component.removeCard.subscribe((id) => emitted.push(id));
88
- fixture.componentRef.setInput('section', {
89
- id: 'section-1',
90
- editable: true,
91
- });
92
- fixture.componentRef.setInput('cards', [
93
- { id: 'card-1', component: 'demo-widget' },
94
- ]);
95
- fixture.componentRef.setInput('editMode', true);
96
- fixture.detectChanges();
97
-
98
- const card = fixture.debugElement.query(By.directive(DashboardCard))
99
- .componentInstance as DashboardCard;
100
- card.removeCard.emit();
101
-
102
- expect(emitted).toEqual(['card-1']);
103
- });
104
- });
@@ -1,23 +0,0 @@
1
- import { DashboardCard } from '../card/dashboard-card.component';
2
- import { CardConfig, SectionConfig } from '../models';
3
- import { Component, ViewEncapsulation, input, output } from '@angular/core';
4
- import { Button } from '@fundamental-ngx/ui5-webcomponents/button';
5
-
6
- @Component({
7
- selector: 'mfp-dashboard-section',
8
- imports: [DashboardCard, Button],
9
- templateUrl: './dashboard-section.component.html',
10
- styleUrl: './dashboard-section.component.scss',
11
- encapsulation: ViewEncapsulation.Emulated,
12
- host: {
13
- '[style.grid-column]': '"span " + (section().w ?? 12)',
14
- },
15
- })
16
- export class DashboardSection {
17
- section = input.required<SectionConfig>();
18
- cards = input<CardConfig[]>([]);
19
- columns = input<number>(12);
20
- editMode = input<boolean>(false);
21
- readonly removeSection = output<void>();
22
- readonly removeCard = output<string>();
23
- }
@@ -1,62 +0,0 @@
1
- <section test-id="generic-form" class="form" [formGroup]="form">
2
- @for (field of fields(); track field.name) {
3
- @let control = form.controls[field.name];
4
- <div
5
- class="inputs"
6
- [attr.test-id]="'generic-form-field-container-' + field.name"
7
- >
8
- <ui5-label
9
- [attr.test-id]="'generic-form-field-label-' + field.name"
10
- show-colon
11
- [required]="field.required"
12
- >{{ field.label }}</ui5-label
13
- >
14
- @if (field.values?.length) {
15
- <ui5-select
16
- class="input"
17
- [attr.test-id]="'generic-form-field-' + field.name"
18
- [value]="control.value"
19
- (input)="setFormControlValue($event, field)"
20
- (change)="setFormControlValue($event, field)"
21
- (blur)="onFieldBlur(field)"
22
- [required]="field.required"
23
- [valueState]="getValueState(field.name)"
24
- [disabled]="field.disabled"
25
- >
26
- @if (getError(field.name); as error) {
27
- <div slot="valueStateMessage">{{ error }}</div>
28
- }
29
- @for (value of [''].concat(field.values ?? []); track value) {
30
- <ui5-option
31
- [attr.test-id]="
32
- 'generic-form-field-' +
33
- field.name +
34
- '-option-' +
35
- (value || 'empty')
36
- "
37
- [value]="value"
38
- [selected]="value === control.value"
39
- >{{ value }}</ui5-option
40
- >
41
- }
42
- </ui5-select>
43
- } @else {
44
- <ui5-input
45
- class="input"
46
- [attr.test-id]="'generic-form-field-' + field.name"
47
- [value]="control.value"
48
- (blur)="onFieldBlur(field)"
49
- (change)="setFormControlValue($event, field)"
50
- (input)="setFormControlValue($event, field)"
51
- [required]="field.required"
52
- [valueState]="getValueState(field.name)"
53
- [disabled]="field.disabled"
54
- >
55
- @if (getError(field.name); as error) {
56
- <div slot="valueStateMessage">{{ error }}</div>
57
- }
58
- </ui5-input>
59
- }
60
- </div>
61
- }
62
- </section>
@@ -1,12 +0,0 @@
1
- .form > div {
2
- display: flex;
3
- flex-direction: column;
4
- justify-content: space-evenly;
5
- align-items: flex-start;
6
- margin-bottom: 0.5rem;
7
- width: 100%;
8
- }
9
-
10
- .input {
11
- width: 100%;
12
- }
@@ -1,301 +0,0 @@
1
- import { FormFieldChangeEvent, FormFieldDefinition } from '../models';
2
- import { DeclarativeForm } from './declarative-form.component';
3
- import { ComponentFixture, TestBed } from '@angular/core/testing';
4
- import { ReactiveFormsModule } from '@angular/forms';
5
-
6
- describe('DeclarativeForm', () => {
7
- let component: DeclarativeForm;
8
- let fixture: ComponentFixture<DeclarativeForm>;
9
-
10
- const testFields: FormFieldDefinition[] = [
11
- { name: 'metadata.name', label: 'Name', required: true, validation: 'onChange' },
12
- { name: 'metadata.namespace', label: 'Namespace', required: false, validation: 'onBlur' },
13
- { name: 'metadata.labels', label: 'Labels' },
14
- ];
15
-
16
- beforeEach(async () => {
17
- await TestBed.configureTestingModule({
18
- imports: [ReactiveFormsModule, DeclarativeForm],
19
- }).compileComponents();
20
-
21
- fixture = TestBed.createComponent(DeclarativeForm);
22
- component = fixture.componentInstance;
23
- fixture.componentRef.setInput('fields', testFields);
24
- fixture.detectChanges();
25
- });
26
-
27
- it('should create', () => {
28
- expect(component).toBeTruthy();
29
- });
30
-
31
- describe('form initialization', () => {
32
- it('should create controls for each field', () => {
33
- expect(component.form.controls['metadata.name']).toBeDefined();
34
- expect(component.form.controls['metadata.namespace']).toBeDefined();
35
- expect(component.form.controls['metadata.labels']).toBeDefined();
36
- });
37
-
38
- it('should not register validators for required fields', () => {
39
- const ctrl = component.form.controls['metadata.name'];
40
-
41
- ctrl.setValue('');
42
-
43
- expect(ctrl.validator).toBeNull();
44
- expect(ctrl.valid).toBe(true);
45
- });
46
-
47
- it('should pre-populate controls from initialValues without marking dirty', () => {
48
- fixture.componentRef.setInput('initialValues', {
49
- 'metadata.name': 'my-resource',
50
- 'metadata.namespace': 'default',
51
- });
52
-
53
- fixture.detectChanges();
54
-
55
- expect(component.form.controls['metadata.name'].value).toBe(
56
- 'my-resource',
57
- );
58
- expect(component.form.controls['metadata.namespace'].value).toBe(
59
- 'default',
60
- );
61
- expect(component.form.controls['metadata.name'].dirty).toBe(false);
62
- expect(component.getValueState('metadata.name')).toBe('None');
63
- });
64
-
65
- it('should default missing initialValues keys to empty string', () => {
66
- fixture.componentRef.setInput('initialValues', {});
67
- expect(component.form.controls['metadata.name'].value).toBe('');
68
- });
69
-
70
- it('should render a select when field.values are provided', () => {
71
- fixture.componentRef.setInput('fields', [
72
- {
73
- name: 'metadata.namespace',
74
- label: 'Namespace',
75
- values: ['default', 'kube-system'],
76
- },
77
- ]);
78
-
79
- fixture.detectChanges();
80
-
81
- const shadowRoot = fixture.nativeElement.shadowRoot as ShadowRoot;
82
- const fieldElement = shadowRoot.querySelector(
83
- '[test-id="generic-form-field-metadata.namespace"]',
84
- );
85
-
86
- expect(fieldElement?.tagName.toLowerCase()).toBe('ui5-select');
87
- });
88
-
89
- it('should render an input when field.values are not provided', () => {
90
- fixture.componentRef.setInput('fields', [
91
- {
92
- name: 'metadata.namespace',
93
- label: 'Namespace',
94
- },
95
- ]);
96
-
97
- fixture.detectChanges();
98
-
99
- const shadowRoot = fixture.nativeElement.shadowRoot as ShadowRoot;
100
- const fieldElement = shadowRoot.querySelector(
101
- '[test-id="generic-form-field-metadata.namespace"]',
102
- );
103
-
104
- expect(fieldElement?.tagName.toLowerCase()).toBe('ui5-input');
105
- });
106
- });
107
-
108
- describe('fieldChange output', () => {
109
- it('should emit on value change for validation: onChange field', () => {
110
- const emitted: FormFieldChangeEvent[] = [];
111
- component.fieldChange.subscribe((event) => emitted.push(event));
112
-
113
- const onChangeField = testFields.find((f) => f.name === 'metadata.name')!;
114
- component.setFormControlValue(
115
- { target: { value: 'new-value' } } as unknown as Event,
116
- onChangeField,
117
- );
118
-
119
- expect(emitted).toEqual([
120
- { fieldProperty: 'metadata.name', value: 'new-value' },
121
- ]);
122
- });
123
-
124
- it('should not emit on blur for validation: onChange field', () => {
125
- const emitted: FormFieldChangeEvent[] = [];
126
- component.fieldChange.subscribe((event) => emitted.push(event));
127
-
128
- const onChangeField = testFields.find((f) => f.name === 'metadata.name')!;
129
- component.onFieldBlur(onChangeField);
130
-
131
- expect(emitted).toEqual([]);
132
- });
133
-
134
- it('should emit on blur for validation: onBlur field', () => {
135
- const emitted: FormFieldChangeEvent[] = [];
136
- component.fieldChange.subscribe((event) => emitted.push(event));
137
-
138
- const onBlurField = testFields.find((f) => f.name === 'metadata.namespace')!;
139
- component.setFormControlValue(
140
- { target: { value: 'kube-system' } } as unknown as Event,
141
- onBlurField,
142
- );
143
-
144
- expect(emitted).toEqual([]);
145
-
146
- component.onFieldBlur(onBlurField);
147
-
148
- expect(emitted).toEqual([
149
- { fieldProperty: 'metadata.namespace', value: 'kube-system' },
150
- ]);
151
- });
152
-
153
- it('should not emit on value change for validation: onBlur field', () => {
154
- const emitted: FormFieldChangeEvent[] = [];
155
- component.fieldChange.subscribe((event) => emitted.push(event));
156
-
157
- const onBlurField = testFields.find((f) => f.name === 'metadata.namespace')!;
158
- component.setFormControlValue(
159
- { target: { value: 'kube-system' } } as unknown as Event,
160
- onBlurField,
161
- );
162
-
163
- expect(emitted).toEqual([]);
164
- });
165
-
166
- it('should not emit on change or blur for field without validation', () => {
167
- const emitted: FormFieldChangeEvent[] = [];
168
- component.fieldChange.subscribe((event) => emitted.push(event));
169
-
170
- const noValidationField = testFields.find((f) => f.name === 'metadata.labels')!;
171
- component.setFormControlValue(
172
- { target: { value: 'some-label' } } as unknown as Event,
173
- noValidationField,
174
- );
175
- component.onFieldBlur(noValidationField);
176
-
177
- expect(emitted).toEqual([]);
178
- });
179
-
180
- it('should emit fieldChange for validated fields when initialValues change', () => {
181
- const emitted: FormFieldChangeEvent[] = [];
182
- component.fieldChange.subscribe((event) => emitted.push(event));
183
-
184
- fixture.componentRef.setInput('initialValues', {
185
- 'metadata.name': 'my-resource',
186
- 'metadata.namespace': 'ns-value',
187
- });
188
- fixture.detectChanges();
189
-
190
- expect(emitted).toEqual(
191
- expect.arrayContaining([
192
- { fieldProperty: 'metadata.name', value: 'my-resource' },
193
- { fieldProperty: 'metadata.namespace', value: 'ns-value' },
194
- ]),
195
- );
196
- expect(
197
- emitted.find((e) => e.fieldProperty === 'metadata.labels'),
198
- ).toBeUndefined();
199
- });
200
- });
201
-
202
- describe('initial emission', () => {
203
- it('should emit fieldChange for fields with validation on init', () => {
204
- const localFixture = TestBed.createComponent(DeclarativeForm);
205
- const localComponent = localFixture.componentInstance;
206
-
207
- const emitted: FormFieldChangeEvent[] = [];
208
- localComponent.fieldChange.subscribe((event) => emitted.push(event));
209
-
210
- localFixture.componentRef.setInput('fields', testFields);
211
- localFixture.detectChanges();
212
-
213
- expect(emitted).toEqual(
214
- expect.arrayContaining([
215
- { fieldProperty: 'metadata.name', value: '' },
216
- { fieldProperty: 'metadata.namespace', value: '' },
217
- ]),
218
- );
219
- });
220
-
221
- it('should not emit fieldChange for fields without validation on init', () => {
222
- const localFixture = TestBed.createComponent(DeclarativeForm);
223
- const localComponent = localFixture.componentInstance;
224
-
225
- const emitted: FormFieldChangeEvent[] = [];
226
- localComponent.fieldChange.subscribe((event) => emitted.push(event));
227
-
228
- localFixture.componentRef.setInput('fields', testFields);
229
- localFixture.detectChanges();
230
-
231
- expect(
232
- emitted.find((e) => e.fieldProperty === 'metadata.labels'),
233
- ).toBeUndefined();
234
- });
235
- });
236
-
237
- describe('field errors', () => {
238
- it('should show no error for a pristine field', () => {
239
- fixture.componentRef.setInput('fieldErrors', {
240
- 'metadata.name': 'Name is required',
241
- });
242
- fixture.detectChanges();
243
-
244
- expect(component.getValueState('metadata.name')).toBe('None');
245
- });
246
-
247
- it('should show error for a dirty field with an error string', () => {
248
- fixture.componentRef.setInput('fieldErrors', {
249
- 'metadata.name': 'Name is required',
250
- });
251
-
252
- const onChangeField = testFields.find((f) => f.name === 'metadata.name')!;
253
- component.setFormControlValue(
254
- { target: { value: '' } } as unknown as Event,
255
- onChangeField,
256
- );
257
- fixture.detectChanges();
258
-
259
- const shadowRoot = fixture.nativeElement.shadowRoot as ShadowRoot;
260
- const message = shadowRoot.querySelector('[slot="valueStateMessage"]');
261
-
262
- expect(component.getValueState('metadata.name')).toBe('Negative');
263
- expect(message?.textContent?.trim()).toBe('Name is required');
264
- });
265
-
266
- it('should show error for a touched field', () => {
267
- fixture.componentRef.setInput('fieldErrors', {
268
- 'metadata.name': 'Name is required',
269
- });
270
- fixture.detectChanges();
271
-
272
- const control = component.form.controls['metadata.name'];
273
- control.markAsTouched();
274
-
275
- expect(component.getValueState('metadata.name')).toBe('Negative');
276
- });
277
- });
278
-
279
- describe('submit output', () => {
280
- it('should emit nested object built from dotted field names', () => {
281
- const emitted: Record<string, unknown>[] = [];
282
- component.formSubmit.subscribe((value) => emitted.push(value));
283
-
284
- component.form.controls['metadata.name'].setValue('my-app');
285
- component.form.controls['metadata.namespace'].setValue('default');
286
- component.form.controls['metadata.labels'].setValue('app=my-app');
287
-
288
- component.submit();
289
-
290
- expect(emitted).toEqual([
291
- {
292
- metadata: {
293
- name: 'my-app',
294
- namespace: 'default',
295
- labels: 'app=my-app',
296
- },
297
- },
298
- ]);
299
- });
300
- });
301
- });