@openmfp/webcomponents 0.6.1

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 (140) hide show
  1. package/.github/workflows/pipeline.yaml +41 -0
  2. package/.storybook/main.ts +34 -0
  3. package/.storybook/preview.ts +29 -0
  4. package/.storybook/tsconfig.json +11 -0
  5. package/AGENTS.md +153 -0
  6. package/CODEOWNERS +6 -0
  7. package/CONTRIBUTING.md +95 -0
  8. package/LICENSE +201 -0
  9. package/LICENSES/Apache-2.0.txt +73 -0
  10. package/README.md +91 -0
  11. package/REUSE.toml +9 -0
  12. package/angular.json +157 -0
  13. package/docs/dashboard.md +358 -0
  14. package/docs/declarative-form.md +178 -0
  15. package/docs/declarative-table-card.md +235 -0
  16. package/docs/declarative-table.md +315 -0
  17. package/eslint.config.js +41 -0
  18. package/package.json +73 -0
  19. package/projects/ngx/cards/favorites/favorites.component.html +12 -0
  20. package/projects/ngx/cards/favorites/favorites.component.scss +50 -0
  21. package/projects/ngx/cards/favorites/favorites.component.ts +19 -0
  22. package/projects/ngx/cards/public-api.ts +4 -0
  23. package/projects/ngx/cards/service-status/service-status-card.component.html +15 -0
  24. package/projects/ngx/cards/service-status/service-status-card.component.scss +87 -0
  25. package/projects/ngx/cards/service-status/service-status-card.component.ts +36 -0
  26. package/projects/ngx/cards/stories/visited-service-card.stories.ts +149 -0
  27. package/projects/ngx/cards/visited-service-card/visited-service-card.component.html +17 -0
  28. package/projects/ngx/cards/visited-service-card/visited-service-card.component.scss +34 -0
  29. package/projects/ngx/cards/visited-service-card/visited-service-card.component.ts +22 -0
  30. package/projects/ngx/cards/whats-new/whats-new.component.html +10 -0
  31. package/projects/ngx/cards/whats-new/whats-new.component.scss +25 -0
  32. package/projects/ngx/cards/whats-new/whats-new.component.ts +46 -0
  33. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.html +28 -0
  34. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.scss +44 -0
  35. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.spec.ts +85 -0
  36. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.ts +58 -0
  37. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.html +29 -0
  38. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.scss +63 -0
  39. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.spec.ts +255 -0
  40. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.ts +75 -0
  41. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.spec.ts +76 -0
  42. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.ts +109 -0
  43. package/projects/ngx/declarative-ui/dashboard/card/utils/index.ts +4 -0
  44. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.spec.ts +141 -0
  45. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.ts +44 -0
  46. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.spec.ts +142 -0
  47. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.ts +52 -0
  48. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.spec.ts +107 -0
  49. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.ts +22 -0
  50. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.html +134 -0
  51. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.scss +88 -0
  52. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.spec.ts +354 -0
  53. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.ts +238 -0
  54. package/projects/ngx/declarative-ui/dashboard/dashboard/index.ts +1 -0
  55. package/projects/ngx/declarative-ui/dashboard/index.ts +5 -0
  56. package/projects/ngx/declarative-ui/dashboard/models/constants.ts +2 -0
  57. package/projects/ngx/declarative-ui/dashboard/models/dashboard.model.ts +50 -0
  58. package/projects/ngx/declarative-ui/dashboard/models/index.ts +1 -0
  59. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.html +28 -0
  60. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.scss +85 -0
  61. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.spec.ts +104 -0
  62. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.ts +23 -0
  63. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.html +62 -0
  64. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.scss +12 -0
  65. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.spec.ts +301 -0
  66. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.ts +166 -0
  67. package/projects/ngx/declarative-ui/form/declarative-form/index.ts +1 -0
  68. package/projects/ngx/declarative-ui/form/index.ts +2 -0
  69. package/projects/ngx/declarative-ui/form/models/form-field-definition.ts +15 -0
  70. package/projects/ngx/declarative-ui/form/models/index.ts +1 -0
  71. package/projects/ngx/declarative-ui/form/utils/set-property-by-path.ts +30 -0
  72. package/projects/ngx/declarative-ui/models/index.ts +2 -0
  73. package/projects/ngx/declarative-ui/models/resource.ts +5 -0
  74. package/projects/ngx/declarative-ui/models/ui-definition.ts +95 -0
  75. package/projects/ngx/declarative-ui/public-api.ts +4 -0
  76. package/projects/ngx/declarative-ui/stories/add-card-dialog.stories.ts +91 -0
  77. package/projects/ngx/declarative-ui/stories/background-lightblue.png +0 -0
  78. package/projects/ngx/declarative-ui/stories/dashboard.cards.ts +107 -0
  79. package/projects/ngx/declarative-ui/stories/dashboard.stories.ts +296 -0
  80. package/projects/ngx/declarative-ui/stories/declarative-form.stories.ts +149 -0
  81. package/projects/ngx/declarative-ui/stories/declarative-table-card.stories.ts +358 -0
  82. package/projects/ngx/declarative-ui/stories/declarative-table.stories.ts +363 -0
  83. package/projects/ngx/declarative-ui/stories/pods-table.config.ts +188 -0
  84. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +138 -0
  85. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +21 -0
  86. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +345 -0
  87. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +61 -0
  88. package/projects/ngx/declarative-ui/table/declarative-table/index.ts +1 -0
  89. package/projects/ngx/declarative-ui/table/index.ts +2 -0
  90. package/projects/ngx/declarative-ui/table/models/index.ts +14 -0
  91. package/projects/ngx/declarative-ui/table/models/table.model.ts +17 -0
  92. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.spec.ts +146 -0
  93. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.ts +69 -0
  94. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.spec.ts +70 -0
  95. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.ts +13 -0
  96. package/projects/ngx/declarative-ui/table/utils/proccess-fields.spec.ts +511 -0
  97. package/projects/ngx/declarative-ui/table/utils/proccess-fields.ts +71 -0
  98. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.spec.ts +372 -0
  99. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.ts +98 -0
  100. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-cell.constants.ts +5 -0
  101. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.html +1 -0
  102. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.scss +0 -0
  103. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.spec.ts +119 -0
  104. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.ts +35 -0
  105. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.html +7 -0
  106. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.scss +0 -0
  107. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.spec.ts +114 -0
  108. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.ts +19 -0
  109. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.html +7 -0
  110. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.scss +10 -0
  111. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.spec.ts +188 -0
  112. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.ts +16 -0
  113. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.html +59 -0
  114. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.scss +33 -0
  115. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.spec.ts +316 -0
  116. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.ts +115 -0
  117. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.html +156 -0
  118. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.scss +123 -0
  119. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.spec.ts +786 -0
  120. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +286 -0
  121. package/projects/ngx/declarative-ui/table-card/index.ts +2 -0
  122. package/projects/ngx/declarative-ui/table-card/models/configs.ts +46 -0
  123. package/projects/ngx/declarative-ui/tsconfig.lib.json +11 -0
  124. package/projects/ngx/declarative-ui/tsconfig.lib.prod.json +9 -0
  125. package/projects/ngx/declarative-ui/tsconfig.spec.json +9 -0
  126. package/projects/ngx/ng-package.json +8 -0
  127. package/projects/ngx/package.json +22 -0
  128. package/projects/ngx/public-api.ts +2 -0
  129. package/projects/ngx/tsconfig.lib.json +11 -0
  130. package/projects/ngx/tsconfig.lib.prod.json +9 -0
  131. package/projects/webcomponents/main.ts +92 -0
  132. package/projects/webcomponents/tsconfig.app.json +9 -0
  133. package/projects/webcomponents-dashboard/main.ts +15 -0
  134. package/projects/webcomponents-dashboard/tsconfig.app.json +9 -0
  135. package/renovate.json +6 -0
  136. package/scripts/bundle-wc.mjs +79 -0
  137. package/tsconfig.json +37 -0
  138. package/tsconfig.spec.json +8 -0
  139. package/tsconfig.storybook.json +16 -0
  140. package/vitest.config.ts +26 -0
@@ -0,0 +1,114 @@
1
+ import { LinkValue } from './link-value.component';
2
+ import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
3
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
4
+
5
+ describe('LinkValueComponent', () => {
6
+ let component: LinkValue;
7
+ let fixture: ComponentFixture<LinkValue>;
8
+
9
+ const makeComponent = (urlValue: string) => {
10
+ fixture = TestBed.createComponent(LinkValue);
11
+ component = fixture.componentInstance;
12
+
13
+ fixture.componentRef.setInput('urlValue', urlValue);
14
+
15
+ fixture.detectChanges();
16
+
17
+ return { component, fixture };
18
+ };
19
+
20
+ beforeEach(() => {
21
+ TestBed.configureTestingModule({
22
+ imports: [LinkValue],
23
+ schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
24
+ });
25
+ });
26
+
27
+ it('should create', () => {
28
+ const { component } = makeComponent('https://example.com');
29
+ expect(component).toBeTruthy();
30
+ });
31
+
32
+ it('should render ui5-link', () => {
33
+ const { fixture } = makeComponent('https://example.com');
34
+ const compiled = fixture.nativeElement;
35
+ const linkElement = compiled.querySelector('ui5-link');
36
+
37
+ expect(linkElement).toBeTruthy();
38
+ expect(linkElement.textContent.trim()).toBe('Link');
39
+ });
40
+
41
+ it('should render ui5-link with different URL', () => {
42
+ const { fixture } = makeComponent('http://test.com');
43
+ const compiled = fixture.nativeElement;
44
+ const linkElement = compiled.querySelector('ui5-link');
45
+
46
+ expect(linkElement).toBeTruthy();
47
+ });
48
+
49
+ it('should call stopPropagation when link is clicked', () => {
50
+ const { component } = makeComponent('https://example.com');
51
+
52
+ const mockEvent = {
53
+ stopPropagation: vi.fn(),
54
+ } as unknown as Event;
55
+
56
+ component.stopPropagation(mockEvent);
57
+
58
+ expect(mockEvent.stopPropagation).toHaveBeenCalled();
59
+ });
60
+
61
+ it('should update when urlValue changes', () => {
62
+ const { fixture } = makeComponent('https://example.com');
63
+ const compiled = fixture.nativeElement;
64
+ let linkElement = compiled.querySelector('ui5-link');
65
+
66
+ expect(linkElement).toBeTruthy();
67
+
68
+ fixture.componentRef.setInput('urlValue', 'https://newurl.com');
69
+ fixture.detectChanges();
70
+
71
+ linkElement = compiled.querySelector('ui5-link');
72
+ expect(linkElement).toBeTruthy();
73
+ });
74
+
75
+ it('should have required urlValue input', () => {
76
+ const { component } = makeComponent('https://example.com');
77
+ expect(component.urlValue()).toBe('https://example.com');
78
+ });
79
+
80
+ it('should handle complex URLs with paths and parameters', () => {
81
+ const complexUrl = 'https://example.com/path?param=value&other=123';
82
+ const { fixture } = makeComponent(complexUrl);
83
+ const compiled = fixture.nativeElement;
84
+ const linkElement = compiled.querySelector('ui5-link');
85
+
86
+ expect(linkElement).toBeTruthy();
87
+ });
88
+
89
+ it('should handle URLs with fragments', () => {
90
+ const urlWithFragment = 'https://example.com/page#section';
91
+ const { fixture } = makeComponent(urlWithFragment);
92
+ const compiled = fixture.nativeElement;
93
+ const linkElement = compiled.querySelector('ui5-link');
94
+
95
+ expect(linkElement).toBeTruthy();
96
+ });
97
+
98
+ it('should have default testId', () => {
99
+ const { component } = makeComponent('https://example.com');
100
+ expect(component.testId()).toBe('link-value-link');
101
+ });
102
+
103
+ it('should accept custom testId', () => {
104
+ fixture = TestBed.createComponent(LinkValue);
105
+ component = fixture.componentInstance;
106
+
107
+ fixture.componentRef.setInput('urlValue', 'https://example.com');
108
+ fixture.componentRef.setInput('testId', 'custom-link-test-id');
109
+
110
+ fixture.detectChanges();
111
+
112
+ expect(component.testId()).toBe('custom-link-test-id');
113
+ });
114
+ });
@@ -0,0 +1,19 @@
1
+ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, input } from '@angular/core';
2
+ import { Link } from '@fundamental-ngx/ui5-webcomponents/link';
3
+
4
+ @Component({
5
+ selector: 'mfp-link-value',
6
+ imports: [Link],
7
+ templateUrl: './link-value.component.html',
8
+ styleUrl: './link-value.component.scss',
9
+ changeDetection: ChangeDetectionStrategy.OnPush,
10
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
11
+ })
12
+ export class LinkValue {
13
+ urlValue = input.required<string>();
14
+ testId = input<string>('link-value-link');
15
+
16
+ public stopPropagation(event: Event) {
17
+ event.stopPropagation();
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ <span class="secret-value" [attr.test-id]="testId()" [class.masked]="!isVisible()">
2
+ @if (isVisible()) {
3
+ {{ value() }}
4
+ } @else {
5
+ {{ maskedValue() }}
6
+ }
7
+ </span>
@@ -0,0 +1,10 @@
1
+ .secret-value {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 0.25rem;
5
+ }
6
+
7
+ .masked {
8
+ transform: translateY(0.2em);
9
+ display: flex;
10
+ }
@@ -0,0 +1,188 @@
1
+ import { SecretValue } from './secret-value.component';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+
4
+ describe('SecretValueComponent', () => {
5
+ let component: SecretValue;
6
+ let fixture: ComponentFixture<SecretValue>;
7
+
8
+ const makeComponent = (value: string) => {
9
+ fixture = TestBed.createComponent(SecretValue);
10
+ component = fixture.componentInstance;
11
+
12
+ fixture.componentRef.setInput('value', value);
13
+
14
+ fixture.detectChanges();
15
+
16
+ return { component, fixture };
17
+ };
18
+
19
+ beforeEach(() => {
20
+ TestBed.configureTestingModule({
21
+ imports: [SecretValue],
22
+ schemas: [],
23
+ });
24
+ });
25
+
26
+ it('should create', () => {
27
+ const { component } = makeComponent('test-secret');
28
+ expect(component).toBeTruthy();
29
+ });
30
+
31
+ it('should initialize with isVisible as false', () => {
32
+ const { component } = makeComponent('test-secret');
33
+ expect(component.isVisible()).toBe(false);
34
+ });
35
+
36
+ it('should mask value with asterisks', () => {
37
+ const { component, fixture } = makeComponent('my-secret-password');
38
+ const compiled = fixture.nativeElement;
39
+
40
+ expect(component.maskedValue()).toBe(
41
+ '*'.repeat('my-secret-password'.length),
42
+ );
43
+ expect(compiled.querySelector('.secret-value')?.textContent?.trim()).toBe(
44
+ '*'.repeat('my-secret-password'.length),
45
+ );
46
+ });
47
+
48
+ it('should mask empty string with default 8 asterisks', () => {
49
+ const { component, fixture } = makeComponent('');
50
+ const compiled = fixture.nativeElement;
51
+
52
+ expect(component.maskedValue()).toBe('*'.repeat(8));
53
+ expect(compiled.querySelector('.secret-value')?.textContent?.trim()).toBe(
54
+ '*'.repeat(8),
55
+ );
56
+ });
57
+
58
+ it('should mask short value correctly', () => {
59
+ const { component, fixture } = makeComponent('abc');
60
+ const compiled = fixture.nativeElement;
61
+
62
+ expect(component.maskedValue()).toBe('***');
63
+ expect(compiled.querySelector('.secret-value')?.textContent?.trim()).toBe(
64
+ '***',
65
+ );
66
+ });
67
+
68
+ it('should mask long value correctly', () => {
69
+ const longValue = 'a'.repeat(100);
70
+ const { component, fixture } = makeComponent(longValue);
71
+ const compiled = fixture.nativeElement;
72
+
73
+ expect(component.maskedValue()).toBe('*'.repeat(100));
74
+ expect(compiled.querySelector('.secret-value')?.textContent?.trim()).toBe(
75
+ '*'.repeat(100),
76
+ );
77
+ });
78
+
79
+ it('should display masked value by default', () => {
80
+ const { fixture } = makeComponent('secret-value');
81
+ const compiled = fixture.nativeElement;
82
+
83
+ const secretSpan = compiled.querySelector('.secret-value');
84
+
85
+ expect(secretSpan).toBeTruthy();
86
+ expect(secretSpan?.classList.contains('masked')).toBe(true);
87
+ expect(secretSpan?.textContent?.trim()).toBe(
88
+ '*'.repeat('secret-value'.length),
89
+ );
90
+ });
91
+
92
+ it('should display original value when isVisible is true', () => {
93
+ const { fixture } = makeComponent('secret-value');
94
+
95
+ fixture.componentRef.setInput('isVisible', true);
96
+ fixture.detectChanges();
97
+
98
+ const compiled = fixture.nativeElement;
99
+ const secretSpan = compiled.querySelector('.secret-value');
100
+
101
+ expect(secretSpan).toBeTruthy();
102
+ expect(secretSpan?.classList.contains('masked')).toBe(false);
103
+ expect(secretSpan?.textContent?.trim()).toBe('secret-value');
104
+ });
105
+
106
+ it('should switch from masked to original when isVisible changes', () => {
107
+ const { component, fixture } = makeComponent('secret-value');
108
+ const compiled = fixture.nativeElement;
109
+ const secretSpan = compiled.querySelector('.secret-value');
110
+
111
+ expect(component.isVisible()).toBe(false);
112
+ expect(secretSpan?.classList.contains('masked')).toBe(true);
113
+ expect(secretSpan?.textContent?.trim()).toBe(
114
+ '*'.repeat('secret-value'.length),
115
+ );
116
+
117
+ fixture.componentRef.setInput('isVisible', true);
118
+ fixture.detectChanges();
119
+
120
+ expect(component.isVisible()).toBe(true);
121
+ expect(secretSpan?.classList.contains('masked')).toBe(false);
122
+ expect(secretSpan?.textContent?.trim()).toBe('secret-value');
123
+ });
124
+
125
+ it('should switch back to masked when isVisible changes to false', () => {
126
+ const { component, fixture } = makeComponent('secret-value');
127
+
128
+ fixture.componentRef.setInput('isVisible', true);
129
+ fixture.detectChanges();
130
+ expect(component.isVisible()).toBe(true);
131
+
132
+ fixture.componentRef.setInput('isVisible', false);
133
+ fixture.detectChanges();
134
+
135
+ const compiled = fixture.nativeElement;
136
+ const secretSpan = compiled.querySelector('.secret-value');
137
+
138
+ expect(component.isVisible()).toBe(false);
139
+ expect(secretSpan?.classList.contains('masked')).toBe(true);
140
+ expect(secretSpan?.textContent?.trim()).toBe(
141
+ '*'.repeat('secret-value'.length),
142
+ );
143
+ });
144
+
145
+ it('should update masked value when input changes', () => {
146
+ const { component, fixture } = makeComponent('initial');
147
+ expect(component.maskedValue()).toBe('*'.repeat('initial'.length));
148
+
149
+ fixture.componentRef.setInput('value', 'updated-secret');
150
+ fixture.detectChanges();
151
+
152
+ expect(component.maskedValue()).toBe('*'.repeat('updated-secret'.length));
153
+ });
154
+
155
+ it('should maintain visibility state when input changes', () => {
156
+ const { component, fixture } = makeComponent('initial');
157
+
158
+ fixture.componentRef.setInput('isVisible', true);
159
+ fixture.detectChanges();
160
+
161
+ expect(component.isVisible()).toBe(true);
162
+
163
+ fixture.componentRef.setInput('value', 'updated-secret');
164
+ fixture.detectChanges();
165
+
166
+ expect(component.isVisible()).toBe(true);
167
+ const compiled = fixture.nativeElement;
168
+ const secretSpan = compiled.querySelector('.secret-value');
169
+ expect(secretSpan?.textContent?.trim()).toBe('updated-secret');
170
+ });
171
+
172
+ it('should have default testId', () => {
173
+ const { component } = makeComponent('test-secret');
174
+ expect(component.testId()).toBe('secret-value');
175
+ });
176
+
177
+ it('should accept custom testId', () => {
178
+ fixture = TestBed.createComponent(SecretValue);
179
+ component = fixture.componentInstance;
180
+
181
+ fixture.componentRef.setInput('value', 'test-secret');
182
+ fixture.componentRef.setInput('testId', 'custom-secret-test-id');
183
+
184
+ fixture.detectChanges();
185
+
186
+ expect(component.testId()).toBe('custom-secret-test-id');
187
+ });
188
+ });
@@ -0,0 +1,16 @@
1
+ import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'mfp-secret-value',
5
+ imports: [],
6
+ templateUrl: './secret-value.component.html',
7
+ styleUrl: './secret-value.component.scss',
8
+ changeDetection: ChangeDetectionStrategy.OnPush,
9
+ schemas: []
10
+ })
11
+ export class SecretValue {
12
+ value = input.required<string>();
13
+ isVisible = input<boolean>(false);
14
+ testId = input<string>('secret-value');
15
+ maskedValue = computed(() => '*'.repeat(this.value().length || 8));
16
+ }
@@ -0,0 +1,59 @@
1
+ @let displayType = displayAs();
2
+ <span [attr.test-id]="testId()" [class.label-value]="labelDisplay()" [style]="cssStyles()">
3
+ @if (displayType === 'secret') {
4
+ <span class="secret-value-container">
5
+ <mfp-secret-value [isVisible]="isVisible()" [testId]="testId() + '-secret'" [value]="value()" />
6
+ <ui5-icon
7
+ class="toggle-icon"
8
+ [attr.test-id]="testId() + '-secret-toggle'"
9
+ [name]="isVisible() ? 'hide' : 'show'"
10
+ (click)="toggleVisibility($event)"
11
+ />
12
+ </span>
13
+ } @else if (displayType === 'boolIcon' && isBoolLike()) {
14
+ <mfp-boolean-value [boolValue]="boolValue()!" [testId]="testId() + '-boolean'" />
15
+ } @else if (displayType === 'link' && isUrlValue()) {
16
+ <mfp-link-value [testId]="testId() + '-link'" [urlValue]="stringValue()!" />
17
+ } @else if (displayType === 'tooltip') {
18
+ <ui5-icon
19
+ [accessibleName]="stringValue()!"
20
+ [attr.test-id]="testId() + '-tooltip'"
21
+ [name]="tooltipIcon() ?? 'hint'"
22
+ [showTooltip]="true"
23
+ />
24
+ } @else if (displayType === 'alert') {
25
+ @if (!value()) {
26
+ <ui5-icon
27
+ design="Critical"
28
+ name="alert"
29
+ [accessibleName]="resource()?.accessibleName ?? ''"
30
+ [attr.test-id]="testId() + '-icon'"
31
+ [showTooltip]="true"
32
+ (click)="$event.stopPropagation()"
33
+ />
34
+ }
35
+ } @else if (displayType === 'img' && value()) {
36
+ <img alt="Resource image" class="image-cell" [src]="value()" />
37
+ } @else if (displayType === 'button') {
38
+ @let buttonSettings = uiSettings()?.buttonSettings;
39
+ <ui5-button
40
+ [design]="buttonSettings?.design"
41
+ [endIcon]="buttonSettings?.endIcon"
42
+ [icon]="buttonSettings?.icon"
43
+ [tooltip]="buttonSettings?.tooltip"
44
+ (click)="buttonClicked($event)"
45
+ >{{ buttonSettings?.text }}</ui5-button
46
+ >&nbsp;
47
+ } @else {
48
+ {{ value() }}
49
+ }
50
+ </span>
51
+
52
+ @if (withCopyButton()) {
53
+ <ui5-icon
54
+ class="toggle-icon"
55
+ name="copy"
56
+ [attr.test-id]="testId() + '-copy'"
57
+ (click)="copyValue($event)"
58
+ />
59
+ }
@@ -0,0 +1,33 @@
1
+ :host {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ }
5
+
6
+ .label-value {
7
+ background-color: #01B4FF;
8
+ color: #ffffff;
9
+ border-radius: 1.4rem;
10
+ padding-inline: 0.4375rem;
11
+ letter-spacing: -0.00875rem;
12
+ display: inline-block;
13
+ border: 0;
14
+ font-size: 0.875rem;
15
+ line-height: 1.4rem;
16
+ margin: 3px 3px;
17
+ }
18
+
19
+ .secret-value-container {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 0.25rem;
23
+ }
24
+
25
+ .toggle-icon {
26
+ cursor: pointer;
27
+ transition: color 0.2s ease;
28
+ padding: 0.25rem;
29
+
30
+ &:hover {
31
+ color: var(--sapButton_Hover_TextColor, #0854a0);
32
+ }
33
+ }