@openmfp/webcomponents 0.6.1 → 0.6.8

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,345 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { By } from '@angular/platform-browser';
3
- import { DeclarativeTable } from './declarative-table.component';
4
- import { GenericResource, TableFieldDefinition, ValueCellButtonClickEvent } from '../models';
5
- import { ValueCell } from '../value-cell/value-cell.component';
6
-
7
- type Fixture = ComponentFixture<DeclarativeTable<GenericResource>>;
8
- type Comp = DeclarativeTable<GenericResource>;
9
-
10
- function setup(opts: {
11
- columns: TableFieldDefinition[];
12
- resources?: GenericResource[];
13
- trackByPath?: string;
14
- totalItemsCount?: number;
15
- paginationLimit?: number;
16
- hasMore?: boolean;
17
- }): { fixture: Fixture; component: Comp } {
18
- const fixture: Fixture = TestBed.createComponent(
19
- DeclarativeTable as unknown as typeof DeclarativeTable<GenericResource>,
20
- );
21
- const component = fixture.componentInstance;
22
- fixture.componentRef.setInput('columns', opts.columns);
23
- fixture.componentRef.setInput('resources', opts.resources ?? []);
24
- if (opts.trackByPath !== undefined) fixture.componentRef.setInput('trackByPath', opts.trackByPath);
25
- if (opts.totalItemsCount !== undefined) fixture.componentRef.setInput('totalItemsCount', opts.totalItemsCount);
26
- if (opts.paginationLimit !== undefined) fixture.componentRef.setInput('paginationLimit', opts.paginationLimit);
27
- if (opts.hasMore !== undefined) fixture.componentRef.setInput('hasMore', opts.hasMore);
28
- fixture.detectChanges();
29
- return { fixture, component };
30
- }
31
-
32
- function root(fixture: Fixture): ShadowRoot | HTMLElement {
33
- return fixture.nativeElement.shadowRoot ?? fixture.nativeElement;
34
- }
35
-
36
- function el(fixture: Fixture, testId: string): Element | null {
37
- return root(fixture).querySelector(`[test-id="${testId}"]`);
38
- }
39
-
40
- describe('DeclarativeTable', () => {
41
- beforeEach(async () => {
42
- vi.useFakeTimers();
43
- await TestBed.configureTestingModule({
44
- imports: [DeclarativeTable as unknown as typeof DeclarativeTable<GenericResource>],
45
- }).compileComponents();
46
- });
47
-
48
- afterEach(() => {
49
- vi.useRealTimers();
50
- });
51
-
52
- describe('column headers', () => {
53
- it('renders a header cell for each column', () => {
54
- const { fixture } = setup({
55
- columns: [
56
- { property: 'name', label: 'Name' },
57
- { property: 'status', label: 'Status' },
58
- ],
59
- });
60
- expect(el(fixture, 'generic-table-header-name')).not.toBeNull();
61
- expect(el(fixture, 'generic-table-header-status')).not.toBeNull();
62
- });
63
-
64
- it('renders group header with group label and test-id from first field property', () => {
65
- const { fixture } = setup({
66
- columns: [
67
- { property: 'city', label: 'City', group: { name: 'location', label: 'Location' } },
68
- { property: 'country', label: 'Country', group: { name: 'location', label: 'Location' } },
69
- ],
70
- });
71
- // processGroupFields preserves the first field's property as the column property
72
- const header = el(fixture, 'generic-table-header-city');
73
- expect(header).not.toBeNull();
74
- expect(header?.textContent?.trim()).toBe('Location');
75
- });
76
-
77
- it('renders group header with group name when no label', () => {
78
- const { fixture } = setup({
79
- columns: [
80
- { property: 'city', group: { name: 'location' } },
81
- ],
82
- });
83
- const header = el(fixture, 'generic-table-header-city');
84
- expect(header?.textContent?.trim()).toBe('location');
85
- });
86
-
87
- it('renders column header with label text', () => {
88
- const { fixture } = setup({
89
- columns: [{ property: 'name', label: 'Full Name' }],
90
- });
91
- const header = el(fixture, 'generic-table-header-name');
92
- expect(header?.textContent?.trim()).toBe('Full Name');
93
- });
94
- });
95
-
96
- describe('no-data state', () => {
97
- it('renders no-data illustrated message when resources is empty', () => {
98
- const { fixture } = setup({ columns: [{ property: 'name' }], resources: [] });
99
- expect(el(fixture, 'generic-table-view-nodata')).not.toBeNull();
100
- });
101
-
102
- it('does not render no-data message when resources exist', () => {
103
- const { fixture } = setup({
104
- columns: [{ property: 'name' }],
105
- resources: [{ id: '1', name: 'Alice' }],
106
- });
107
- expect(el(fixture, 'generic-table-view-nodata')).toBeNull();
108
- });
109
- });
110
-
111
- describe('row rendering', () => {
112
- it('renders a row for each resource', () => {
113
- const { fixture } = setup({
114
- columns: [{ property: 'name' }],
115
- resources: [{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }],
116
- });
117
- expect(el(fixture, 'generic-table-row-0')).not.toBeNull();
118
- expect(el(fixture, 'generic-table-row-1')).not.toBeNull();
119
- });
120
-
121
- it('renders cells with correct test-ids', () => {
122
- const { fixture } = setup({
123
- columns: [{ property: 'name' }, { property: 'status' }],
124
- resources: [{ id: '1', name: 'Alice', status: 'Active' }],
125
- });
126
- expect(el(fixture, 'generic-table-cell-0-name')).not.toBeNull();
127
- expect(el(fixture, 'generic-table-cell-0-status')).not.toBeNull();
128
- });
129
-
130
- it('marks row as disabled when isAvailable is false', () => {
131
- const { fixture } = setup({
132
- columns: [{ property: 'name' }],
133
- resources: [{ id: '1', name: 'Alice', isAvailable: false }],
134
- });
135
- const row = el(fixture, 'generic-table-row-0');
136
- expect(row?.classList.contains('disabled')).toBe(true);
137
- });
138
-
139
- it('does not mark row as disabled when isAvailable is true', () => {
140
- const { fixture } = setup({
141
- columns: [{ property: 'name' }],
142
- resources: [{ id: '1', name: 'Alice', isAvailable: true }],
143
- });
144
- const row = el(fixture, 'generic-table-row-0');
145
- expect(row?.classList.contains('disabled')).toBe(false);
146
- });
147
- });
148
-
149
- describe('group columns', () => {
150
- it('renders group cell with sub-field test-ids using first-field property', () => {
151
- // processGroupFields: grouped column keeps first field's property ('city')
152
- // cell test-id: 'generic-table-cell-{i}-{column.property}-{field.property}'
153
- // => 'generic-table-cell-0-city-city' and 'generic-table-cell-0-city-country'
154
- const { fixture } = setup({
155
- columns: [
156
- { property: 'city', group: { name: 'location' } },
157
- { property: 'country', group: { name: 'location' } },
158
- ],
159
- resources: [{ id: '1', city: 'Berlin', country: 'Germany' }],
160
- });
161
- expect(el(fixture, 'generic-table-cell-0-city-city')).not.toBeNull();
162
- expect(el(fixture, 'generic-table-cell-0-city-country')).not.toBeNull();
163
- });
164
-
165
- it('renders field label inside group cell when label is set', () => {
166
- const { fixture } = setup({
167
- columns: [
168
- { property: 'city', label: 'City', group: { name: 'location' } },
169
- ],
170
- resources: [{ id: '1', city: 'Berlin' }],
171
- });
172
- const cell = el(fixture, 'generic-table-cell-0-city-city');
173
- expect(cell?.textContent).toContain('City:');
174
- });
175
- });
176
-
177
- describe('tableRowClicked output', () => {
178
- it('emits tableRowClicked with resource on row click', () => {
179
- const resource = { id: '1', name: 'Alice' };
180
- const { fixture, component } = setup({
181
- columns: [{ property: 'name' }],
182
- resources: [resource],
183
- });
184
-
185
- const emitted: unknown[] = [];
186
- component.tableRowClicked.subscribe((e) => emitted.push(e));
187
-
188
- const row = el(fixture, 'generic-table-row-0') as HTMLElement;
189
- row?.click();
190
- fixture.detectChanges();
191
-
192
- expect(emitted).toHaveLength(1);
193
- expect(emitted[0]).toEqual(resource);
194
- });
195
- });
196
-
197
- describe('buttonClick output', () => {
198
- it('bubbles buttonClick from value-cell', () => {
199
- const field: TableFieldDefinition = {
200
- property: 'action',
201
- uiSettings: { displayAs: 'button', buttonSettings: { text: 'Go', action: 'navigate' } },
202
- };
203
- const resource = { id: '1', action: 'go' };
204
- const { fixture, component } = setup({
205
- columns: [field],
206
- resources: [resource],
207
- });
208
-
209
- const emitted: ValueCellButtonClickEvent<GenericResource>[] = [];
210
- component.buttonClick.subscribe((e) => emitted.push(e));
211
-
212
- // The button lives inside value-cell's shadow root, unreachable via DOM
213
- // querySelector in jsdom. Get the ValueCell instance directly
214
- // and invoke its buttonClicked method to test the event chain.
215
- const valueCellDe = fixture.debugElement.query(By.directive(ValueCell));
216
- const valueCellComp: ValueCell<GenericResource, TableFieldDefinition> = valueCellDe.componentInstance;
217
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accessing protected method for testing
218
- (valueCellComp as any).buttonClicked(new MouseEvent('click'));
219
- fixture.detectChanges();
220
-
221
- expect(emitted).toHaveLength(1);
222
- expect(emitted[0].field).toEqual(field);
223
- expect(emitted[0].resource).toEqual(resource);
224
- });
225
- });
226
-
227
- describe('hasMore / table-growing', () => {
228
- it('renders ui5-table-growing when hasMore is true', () => {
229
- const { fixture } = setup({
230
- columns: [{ property: 'name' }],
231
- resources: [{ id: '1', name: 'Alice' }],
232
- hasMore: true,
233
- });
234
- expect(root(fixture).querySelector('ui5-table-growing')).not.toBeNull();
235
- });
236
-
237
- it('does not render ui5-table-growing when hasMore is false', () => {
238
- const { fixture } = setup({
239
- columns: [{ property: 'name' }],
240
- resources: [{ id: '1', name: 'Alice' }],
241
- hasMore: false,
242
- });
243
- expect(root(fixture).querySelector('ui5-table-growing')).toBeNull();
244
- });
245
-
246
- it('exposes loadMoreResources as output', () => {
247
- const { component } = setup({
248
- columns: [{ property: 'name' }],
249
- resources: [],
250
- hasMore: true,
251
- });
252
- expect(typeof component.loadMoreResources.emit).toBe('function');
253
- expect(typeof component.loadMoreResources.subscribe).toBe('function');
254
- });
255
- });
256
-
257
- describe('pagination footer', () => {
258
- it('displays loaded count vs total', () => {
259
- const { fixture } = setup({
260
- columns: [{ property: 'name' }],
261
- resources: [{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }],
262
- totalItemsCount: 10,
263
- });
264
- const text = root(fixture).textContent;
265
- expect(text).toContain('2 / 10');
266
- });
267
-
268
- it('emits paginationLimitChanged when select fires change event with value 50', () => {
269
- const { fixture, component } = setup({
270
- columns: [{ property: 'name' }],
271
- resources: [],
272
- paginationLimit: 5,
273
- });
274
-
275
- const emitted: number[] = [];
276
- component.paginationLimitChanged.subscribe((v) => emitted.push(v));
277
-
278
- const select = root(fixture).querySelector('ui5-select') as HTMLElement & { value: string };
279
- if (select) {
280
- Object.defineProperty(select, 'value', { value: '50', configurable: true });
281
- select.dispatchEvent(new Event('change'));
282
- fixture.detectChanges();
283
- }
284
-
285
- expect(emitted).toHaveLength(1);
286
- expect(emitted[0]).toBe(50);
287
- });
288
- });
289
-
290
- describe('viewColumns computed', () => {
291
- it('collapses grouped fields into single column entry', () => {
292
- const { component } = setup({
293
- columns: [
294
- { property: 'city', group: { name: 'location' } },
295
- { property: 'country', group: { name: 'location' } },
296
- { property: 'name' },
297
- ],
298
- });
299
- const cols = component.viewColumns();
300
- expect(cols).toHaveLength(2);
301
- expect(cols[0].group?.fields).toHaveLength(2);
302
- expect(cols[1].property).toBe('name');
303
- });
304
- });
305
-
306
- describe('rowTrackBy', () => {
307
- it('returns id field value by default', () => {
308
- const { component } = setup({
309
- columns: [{ property: 'name' }],
310
- resources: [{ id: 'abc', name: 'Alice' }],
311
- });
312
- const result = component.rowTrackBy(0, { id: 'abc', name: 'Alice' });
313
- expect(result).toBe('abc');
314
- });
315
-
316
- it('returns value from custom trackByPath', () => {
317
- const { component } = setup({
318
- columns: [{ property: 'name' }],
319
- resources: [{ id: '1', metadata: { name: 'pod-1' } }],
320
- trackByPath: 'metadata.name',
321
- });
322
- const result = component.rowTrackBy(0, { id: '1', metadata: { name: 'pod-1' } });
323
- expect(result).toBe('pod-1');
324
- });
325
-
326
- it('falls back to index when trackByPath resolves to undefined', () => {
327
- const { component } = setup({
328
- columns: [{ property: 'name' }],
329
- resources: [{ name: 'Alice' }],
330
- trackByPath: 'nonexistent',
331
- });
332
- const result = component.rowTrackBy(3, { name: 'Alice' });
333
- expect(result).toBe(3);
334
- });
335
-
336
- it('works for resources without an id field', () => {
337
- const { component } = setup({
338
- columns: [{ property: 'name' }],
339
- resources: [{ name: 'Alice' }],
340
- });
341
- const result = component.rowTrackBy(0, { name: 'Alice' });
342
- expect(result).toBe(0);
343
- });
344
- });
345
- });
@@ -1,61 +0,0 @@
1
- import { GenericResource } from '../../models';
2
- import { TableFieldDefinition, ValueCellButtonClickEvent } from '../models';
3
- import { processGroupFields } from '../utils/proccess-fields';
4
- import { getResourceValueByJsonPath } from '../utils/resource-field-by-path';
5
- import { ValueCell } from '../value-cell/value-cell.component';
6
- import {
7
- Component,
8
- ViewEncapsulation,
9
- computed,
10
- input,
11
- output,
12
- } from '@angular/core';
13
- import { IllustratedMessage } from '@fundamental-ngx/ui5-webcomponents-fiori';
14
- import { Option } from '@fundamental-ngx/ui5-webcomponents/option';
15
- import { Select } from '@fundamental-ngx/ui5-webcomponents/select';
16
- import { Table } from '@fundamental-ngx/ui5-webcomponents/table';
17
- import { TableCell } from '@fundamental-ngx/ui5-webcomponents/table-cell';
18
- import { TableGrowing } from '@fundamental-ngx/ui5-webcomponents/table-growing';
19
- import { TableHeaderCell } from '@fundamental-ngx/ui5-webcomponents/table-header-cell';
20
- import { TableHeaderRow } from '@fundamental-ngx/ui5-webcomponents/table-header-row';
21
- import { TableRow } from '@fundamental-ngx/ui5-webcomponents/table-row';
22
- import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
23
-
24
- @Component({
25
- selector: 'mfp-declarative-table',
26
- imports: [
27
- IllustratedMessage,
28
- Table,
29
- TableCell,
30
- TableHeaderCell,
31
- TableHeaderRow,
32
- TableRow,
33
- ValueCell,
34
- Select,
35
- Option,
36
- TableGrowing,
37
- ],
38
- templateUrl: './declarative-table.component.html',
39
- styleUrl: './declarative-table.component.scss',
40
- encapsulation: ViewEncapsulation.ShadowDom,
41
- })
42
- export class DeclarativeTable<T extends GenericResource> {
43
- columns = input.required<TableFieldDefinition[]>();
44
- resources = input.required<T[]>();
45
- trackByPath = input<string>('id');
46
-
47
- totalItemsCount = input<number>();
48
- paginationLimit = input<number>(5);
49
- hasMore = input<boolean>(false);
50
-
51
- readonly buttonClick = output<ValueCellButtonClickEvent<T>>();
52
- readonly tableRowClicked = output<T>();
53
- readonly loadMoreResources = output<void>();
54
- readonly paginationLimitChanged = output<number>();
55
-
56
- columnTrackBy = (column: TableFieldDefinition, index: number) =>
57
- column.property ?? column.value ?? index;
58
- rowTrackBy = (_index: number, item: T): unknown =>
59
- getResourceValueByJsonPath(item, { property: this.trackByPath() }) ?? _index;
60
- viewColumns = computed(() => processGroupFields(this.columns()));
61
- }
@@ -1 +0,0 @@
1
- export * from './declarative-table.component';
@@ -1,2 +0,0 @@
1
- export * from './declarative-table';
2
- export * from './models';
@@ -1,14 +0,0 @@
1
- export * from './table.model';
2
- export type { GenericResource } from '../../models/resource';
3
- export type {
4
- ButtonSettings,
5
- ModalSettings,
6
- UiSettings,
7
- CssRule,
8
- CssRuleCondition,
9
- FieldDefinition,
10
- TableFieldDefinition,
11
- ValueCellButtonClickEvent,
12
- PropertyField,
13
- TransformType,
14
- } from '../../models/ui-definition';
@@ -1,17 +0,0 @@
1
- import { GenericResource } from '../../models/resource';
2
- import { FieldDefinition } from '../../models/ui-definition';
3
-
4
- export interface ValueCellButtonClickEvent<T extends GenericResource> {
5
- event: MouseEvent;
6
- field: TableFieldDefinition;
7
- resource: T | undefined;
8
- }
9
-
10
- export interface TableFieldDefinition extends FieldDefinition {
11
- group?: {
12
- name: string;
13
- label?: string;
14
- delimiter?: string;
15
- multiline?: boolean;
16
- };
17
- }
@@ -1,146 +0,0 @@
1
- import { CssRule, CssRuleCondition } from '../../models';
2
- import {
3
- cssRuleResolver,
4
- evaluateCssRules,
5
- parseStringValue,
6
- } from './cssRules.engine';
7
-
8
- describe('cssRules.engine', () => {
9
- describe('parseStringValue', () => {
10
- it('returns boolean for true and false strings', () => {
11
- expect(parseStringValue('true')).toBe(true);
12
- expect(parseStringValue('false')).toBe(false);
13
- });
14
-
15
- it('returns number for numeric strings', () => {
16
- expect(parseStringValue('42')).toBe(42);
17
- });
18
-
19
- it('returns original string when not boolean or number', () => {
20
- expect(parseStringValue('text')).toBe('text');
21
- });
22
- });
23
-
24
- describe('cssRuleResolver', () => {
25
- it('handles equality and inequality comparisons', () => {
26
- const equalsRule: CssRule = {
27
- if: { condition: 'equals', value: '10' },
28
- styles: {},
29
- };
30
- const notEqualsRule: CssRule = {
31
- if: { condition: 'notEquals', value: 'off' },
32
- styles: {},
33
- };
34
-
35
- expect(cssRuleResolver(equalsRule, '10')).toBe(true);
36
- expect(cssRuleResolver(notEqualsRule, 'on')).toBe(true);
37
- expect(cssRuleResolver(notEqualsRule, 'off')).toBe(false);
38
- });
39
-
40
- it('handles numeric comparisons', () => {
41
- const greaterThanRule: CssRule = {
42
- if: { condition: 'greaterThan', value: '3' },
43
- styles: {},
44
- };
45
- const greaterThanOrEqualRule: CssRule = {
46
- if: { condition: 'greaterThanOrEqual', value: '3' },
47
- styles: {},
48
- };
49
- const lessThanRule: CssRule = {
50
- if: { condition: 'lessThan', value: '3' },
51
- styles: {},
52
- };
53
- const lessThanOrEqualRule: CssRule = {
54
- if: { condition: 'lessThanOrEqual', value: '3' },
55
- styles: {},
56
- };
57
-
58
- expect(cssRuleResolver(greaterThanRule, '5')).toBe(true);
59
- expect(cssRuleResolver(greaterThanOrEqualRule, '3')).toBe(true);
60
- expect(cssRuleResolver(lessThanRule, '2')).toBe(true);
61
- expect(cssRuleResolver(lessThanOrEqualRule, '3')).toBe(true);
62
- });
63
-
64
- it('checks containment for strings and arrays', () => {
65
- const stringContainsRule: CssRule = {
66
- if: { condition: 'contains', value: 'world' },
67
- styles: {},
68
- };
69
- const arrayContainsRule: CssRule = {
70
- if: { condition: 'contains', value: 'green' },
71
- styles: {},
72
- };
73
-
74
- expect(cssRuleResolver(stringContainsRule, 'hello world')).toBe(true);
75
- expect(
76
- cssRuleResolver(arrayContainsRule, [
77
- 'blue',
78
- 'green',
79
- ] as unknown as string),
80
- ).toBe(true);
81
- expect(cssRuleResolver(stringContainsRule, 'hello')).toBe(false);
82
- });
83
-
84
- it('returns false for contains when value is not string or array', () => {
85
- const rule: CssRule = {
86
- if: { condition: 'contains', value: '2' },
87
- styles: {},
88
- };
89
-
90
- expect(cssRuleResolver(rule, '123')).toBe(false);
91
- });
92
-
93
- it('returns false for unsupported conditions', () => {
94
- const rule: CssRule = {
95
- if: { condition: 'unknown' as CssRuleCondition, value: 'x' },
96
- styles: {},
97
- };
98
-
99
- expect(cssRuleResolver(rule, 'value')).toBe(false);
100
- });
101
- });
102
-
103
- describe('evaluateCssRules', () => {
104
- it('returns empty object when rules are missing', () => {
105
- expect(evaluateCssRules('value', undefined)).toEqual({});
106
- });
107
-
108
- it('applies styles for matching rules only', () => {
109
- const rules: CssRule[] = [
110
- {
111
- if: { condition: 'equals', value: 'active' },
112
- styles: { color: 'green' },
113
- },
114
- {
115
- if: { condition: 'notEquals', value: 'active' },
116
- styles: { color: 'red', fontWeight: '700' },
117
- },
118
- ];
119
-
120
- expect(evaluateCssRules('active', rules)).toEqual({ color: 'green' });
121
- });
122
-
123
- it('merges styles from multiple matching rules', () => {
124
- const rules: CssRule[] = [
125
- {
126
- if: { condition: 'equals', value: 'ok' },
127
- styles: { color: 'blue' },
128
- },
129
- {
130
- if: { condition: 'contains', value: 'o' },
131
- styles: { backgroundColor: 'yellow' },
132
- },
133
- {
134
- if: { condition: 'notEquals', value: 'fail' },
135
- styles: { borderColor: 'black' },
136
- },
137
- ];
138
-
139
- expect(evaluateCssRules('ok', rules)).toEqual({
140
- color: 'blue',
141
- backgroundColor: 'yellow',
142
- borderColor: 'black',
143
- });
144
- });
145
- });
146
- });
@@ -1,69 +0,0 @@
1
- import { CssRule } from '../../models';
2
-
3
- export const parseStringValue = (value: string) => {
4
- if (value === 'true') {
5
- return true;
6
- }
7
-
8
- if (value === 'false') {
9
- return false;
10
- }
11
-
12
- if (!isNaN(Number(value))) {
13
- return Number(value);
14
- }
15
-
16
- return value;
17
- };
18
-
19
- export const cssRuleResolver = (
20
- rule: CssRule,
21
- resourceValue: string,
22
- ): boolean => {
23
- const parsedResouceValue = parseStringValue(resourceValue);
24
- const parsedConditionValue = parseStringValue(rule.if.value);
25
-
26
- switch (rule.if.condition) {
27
- case 'equals':
28
- return parsedResouceValue === parsedConditionValue;
29
- case 'notEquals':
30
- return parsedResouceValue !== parsedConditionValue;
31
- case 'greaterThan':
32
- return parsedResouceValue > parsedConditionValue;
33
- case 'greaterThanOrEqual':
34
- return parsedResouceValue >= parsedConditionValue;
35
- case 'lessThan':
36
- return parsedResouceValue < parsedConditionValue;
37
- case 'lessThanOrEqual':
38
- return parsedResouceValue <= parsedConditionValue;
39
- case 'contains':
40
- if (Array.isArray(parsedResouceValue)) {
41
- return parsedResouceValue.includes(parsedConditionValue);
42
- } else if (
43
- typeof parsedResouceValue === 'string' &&
44
- typeof parsedConditionValue === 'string'
45
- ) {
46
- return parsedResouceValue.includes(parsedConditionValue);
47
- }
48
-
49
- return false;
50
- }
51
- return false;
52
- };
53
-
54
- export const evaluateCssRules = (
55
- value: string,
56
- rules?: CssRule[],
57
- ): Partial<CSSStyleDeclaration> => {
58
- if (!rules) {
59
- return {};
60
- }
61
-
62
- return rules.reduce((acc, rule) => {
63
- const result = cssRuleResolver(rule, value);
64
- if (result) {
65
- return { ...acc, ...rule.styles };
66
- }
67
- return acc;
68
- }, {});
69
- };