ux4g-components-web 1.4.0 → 1.5.0

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 (124) hide show
  1. package/README.md +76 -0
  2. package/dist/__tests__/css-bundle.integration.test.d.ts +11 -0
  3. package/dist/__tests__/css-bundle.integration.test.js +1102 -0
  4. package/dist/__tests__/css-bundle.phase10.property.test.d.ts +9 -0
  5. package/dist/__tests__/css-bundle.phase10.property.test.js +64 -0
  6. package/dist/__tests__/css-bundle.phase5.property.test.d.ts +9 -0
  7. package/dist/__tests__/css-bundle.phase5.property.test.js +126 -0
  8. package/dist/__tests__/css-bundle.phase6.property.test.d.ts +9 -0
  9. package/dist/__tests__/css-bundle.phase6.property.test.js +73 -0
  10. package/dist/__tests__/css-bundle.phase7.property.test.d.ts +9 -0
  11. package/dist/__tests__/css-bundle.phase7.property.test.js +76 -0
  12. package/dist/__tests__/css-bundle.phase8.property.test.d.ts +9 -0
  13. package/dist/__tests__/css-bundle.phase8.property.test.js +67 -0
  14. package/dist/__tests__/css-bundle.phase9.property.test.d.ts +9 -0
  15. package/dist/__tests__/css-bundle.phase9.property.test.js +93 -0
  16. package/dist/__tests__/css-bundle.property.test.d.ts +14 -0
  17. package/dist/__tests__/css-bundle.property.test.js +393 -0
  18. package/dist/__tests__/dom-generators.determinism.property.test.d.ts +1 -0
  19. package/dist/__tests__/dom-generators.determinism.property.test.js +71 -0
  20. package/dist/__tests__/dom-generators.id.property.test.d.ts +1 -0
  21. package/dist/__tests__/dom-generators.id.property.test.js +99 -0
  22. package/dist/__tests__/dom-generators.otp.property.test.d.ts +1 -0
  23. package/dist/__tests__/dom-generators.property.test.d.ts +1 -0
  24. package/dist/__tests__/dom-generators.property.test.js +205 -0
  25. package/dist/__tests__/dom-generators.states.property.test.d.ts +1 -0
  26. package/dist/__tests__/dom-generators.table.property.test.d.ts +1 -0
  27. package/dist/__tests__/dom-generators.tier1.property.test.d.ts +1 -0
  28. package/dist/__tests__/dom-generators.tier1.property.test.js +403 -0
  29. package/dist/__tests__/dom-generators.validation.property.test.d.ts +1 -0
  30. package/dist/__tests__/dom-generators.validation.property.test.js +327 -0
  31. package/dist/__tests__/megamenu.classbuilder.property.test.d.ts +1 -0
  32. package/dist/__tests__/megamenu.classbuilder.property.test.js +88 -0
  33. package/dist/__tests__/smoke.test.d.ts +1 -0
  34. package/dist/__tests__/smoke.test.js +65 -0
  35. package/dist/__tests__/types.phase10.property.test.d.ts +1 -0
  36. package/dist/__tests__/types.phase10.property.test.js +166 -0
  37. package/dist/__tests__/types.phase10.test.d.ts +1 -0
  38. package/dist/__tests__/types.phase10.test.js +76 -0
  39. package/dist/__tests__/types.phase3.property.test.d.ts +1 -0
  40. package/dist/__tests__/types.phase3.property.test.js +83 -0
  41. package/dist/__tests__/types.phase3.test.d.ts +1 -0
  42. package/dist/__tests__/types.phase3.test.js +76 -0
  43. package/dist/__tests__/types.phase4.property.test.d.ts +1 -0
  44. package/dist/__tests__/types.phase4.property.test.js +119 -0
  45. package/dist/__tests__/types.phase4.test.d.ts +1 -0
  46. package/dist/__tests__/types.phase4.test.js +70 -0
  47. package/dist/__tests__/types.phase5.property.test.d.ts +1 -0
  48. package/dist/__tests__/types.phase5.property.test.js +120 -0
  49. package/dist/__tests__/types.phase5.test.d.ts +1 -0
  50. package/dist/__tests__/types.phase5.test.js +64 -0
  51. package/dist/__tests__/types.phase6.property.test.d.ts +1 -0
  52. package/dist/__tests__/types.phase6.property.test.js +189 -0
  53. package/dist/__tests__/types.phase6.test.d.ts +1 -0
  54. package/dist/__tests__/types.phase6.test.js +121 -0
  55. package/dist/__tests__/types.phase7.property.test.d.ts +1 -0
  56. package/dist/__tests__/types.phase7.property.test.js +217 -0
  57. package/dist/__tests__/types.phase7.test.d.ts +1 -0
  58. package/dist/__tests__/types.phase7.test.js +106 -0
  59. package/dist/__tests__/types.phase8.property.test.d.ts +1 -0
  60. package/dist/__tests__/types.phase8.property.test.js +224 -0
  61. package/dist/__tests__/types.phase8.test.d.ts +1 -0
  62. package/dist/__tests__/types.phase8.test.js +114 -0
  63. package/dist/__tests__/types.phase9.property.test.d.ts +1 -0
  64. package/dist/__tests__/types.phase9.property.test.js +347 -0
  65. package/dist/__tests__/types.phase9.test.d.ts +1 -0
  66. package/dist/__tests__/types.phase9.test.js +226 -0
  67. package/dist/__tests__/types.restructure.property.test.d.ts +1 -0
  68. package/dist/__tests__/types.restructure.property.test.js +76 -0
  69. package/dist/__tests__/types.test.d.ts +1 -0
  70. package/dist/__tests__/types.test.js +175 -0
  71. package/dist/dom-generators/accordion.d.ts +23 -0
  72. package/dist/dom-generators/avatar.d.ts +19 -0
  73. package/dist/dom-generators/carousel.d.ts +20 -0
  74. package/dist/dom-generators/chip.d.ts +18 -0
  75. package/dist/dom-generators/combobox.d.ts +28 -0
  76. package/dist/dom-generators/date-picker.d.ts +19 -0
  77. package/dist/dom-generators/dom-generators/accordion.d.ts +21 -0
  78. package/dist/dom-generators/dom-generators/avatar.d.ts +17 -0
  79. package/dist/dom-generators/dom-generators/carousel.d.ts +19 -0
  80. package/dist/dom-generators/dom-generators/chip.d.ts +16 -0
  81. package/dist/dom-generators/dom-generators/combobox.d.ts +26 -0
  82. package/dist/dom-generators/dom-generators/date-picker.d.ts +18 -0
  83. package/dist/dom-generators/dom-generators/drawer.d.ts +17 -0
  84. package/dist/dom-generators/dom-generators/dropdown.d.ts +26 -0
  85. package/dist/dom-generators/dom-generators/file-upload.d.ts +20 -0
  86. package/dist/dom-generators/dom-generators/id-generator.d.ts +9 -0
  87. package/dist/dom-generators/dom-generators/index.d.ts +27 -0
  88. package/dist/dom-generators/dom-generators/modal.d.ts +19 -0
  89. package/dist/dom-generators/dom-generators/otp.d.ts +16 -0
  90. package/dist/dom-generators/dom-generators/popover.d.ts +17 -0
  91. package/dist/dom-generators/dom-generators/progress.d.ts +16 -0
  92. package/dist/dom-generators/dom-generators/search.d.ts +20 -0
  93. package/dist/dom-generators/dom-generators/stepper.d.ts +21 -0
  94. package/dist/dom-generators/dom-generators/table.d.ts +23 -0
  95. package/dist/dom-generators/dom-generators/tabs.d.ts +21 -0
  96. package/dist/dom-generators/dom-generators/time-picker.d.ts +18 -0
  97. package/dist/dom-generators/dom-generators/tooltip.d.ts +17 -0
  98. package/dist/dom-generators/dom-generators/types.d.ts +27 -0
  99. package/dist/dom-generators/dom-generators/validate.d.ts +20 -0
  100. package/dist/dom-generators/drawer.d.ts +19 -0
  101. package/dist/dom-generators/dropdown.d.ts +28 -0
  102. package/dist/dom-generators/file-upload.d.ts +22 -0
  103. package/dist/dom-generators/id-generator.d.ts +9 -0
  104. package/dist/dom-generators/index.bundled.d.ts +654 -0
  105. package/dist/dom-generators/index.cjs +2029 -0
  106. package/dist/dom-generators/index.d.ts +27 -0
  107. package/dist/dom-generators/index.mjs +2001 -0
  108. package/dist/dom-generators/modal.d.ts +21 -0
  109. package/dist/dom-generators/otp.d.ts +18 -0
  110. package/dist/dom-generators/popover.d.ts +19 -0
  111. package/dist/dom-generators/progress.d.ts +18 -0
  112. package/dist/dom-generators/search.d.ts +22 -0
  113. package/dist/dom-generators/stepper.d.ts +23 -0
  114. package/dist/dom-generators/table.d.ts +25 -0
  115. package/dist/dom-generators/tabs.d.ts +23 -0
  116. package/dist/dom-generators/time-picker.d.ts +19 -0
  117. package/dist/dom-generators/tooltip.d.ts +19 -0
  118. package/dist/dom-generators/types.d.ts +155 -0
  119. package/dist/dom-generators/validate.d.ts +20 -0
  120. package/dist/runtime/bootstrap.js +59 -0
  121. package/dist/runtime/index.js +55 -0
  122. package/dist/types.d.ts +155 -0
  123. package/dist/types.js +552 -0
  124. package/package.json +12 -2
@@ -0,0 +1,347 @@
1
+ // src/__tests__/types.phase9.property.test.ts
2
+ // Property-based tests for all Phase 9 build functions
3
+ // Requirements: 38.1-38.45
4
+ import * as fc from 'fast-check';
5
+ import { buildDateTimePickerClasses, buildStatusPipelineClasses, buildJourneyTimelineClasses, buildFormFieldGroupClasses, buildOtpInputClasses, buildFileUploadClasses, buildProgressIndicatorClasses, buildFeedbackClasses, buildDraftStatusBannerClasses, buildSlaProgressIndicatorClasses, buildCarouselClasses, buildEmptyStateClasses, buildChipGroupClasses, buildNavbarClasses, buildSocialLinksClasses, } from '../../src/types';
6
+ // Helper: split result into tokens
7
+ const tokens = (s) => s.split(' ');
8
+ const hasDuplicates = (arr) => new Set(arr).size !== arr.length;
9
+ // ── Property 1: DateTimePicker ────────────────────────────────────────────────
10
+ // **Validates: Requirements 38.1-38.3**
11
+ describe('Property 1: DateTimePicker class is exactly the correct base class for each mode', () => {
12
+ it('mode determines class exactly, mutually exclusive, no duplicates', () => {
13
+ fc.assert(fc.property(fc.constantFrom('date', 'time'), (mode) => {
14
+ const result = buildDateTimePickerClasses(mode);
15
+ const toks = tokens(result);
16
+ if (mode === 'date') {
17
+ expect(result).toBe('ux4g-date-picker-container');
18
+ }
19
+ else {
20
+ expect(result).toBe('ux4g-time-picker-container');
21
+ }
22
+ // never contains both
23
+ expect(toks).not.toContain(mode === 'date' ? 'ux4g-time-picker-container' : 'ux4g-date-picker-container');
24
+ // no duplicates
25
+ expect(hasDuplicates(toks)).toBe(false);
26
+ }), { numRuns: 100 });
27
+ });
28
+ });
29
+ // ── Property 2: StatusPipeline ────────────────────────────────────────────────
30
+ // **Validates: Requirements 38.4-38.12**
31
+ describe('Property 2: StatusPipeline class string contains required tokens and correctly applies all modifiers', () => {
32
+ it('base token always present, orientation/alignment/variant/size toggle correctly, alignment ignored when vertical, no duplicates', () => {
33
+ fc.assert(fc.property(fc.constantFrom('horizontal', 'vertical'), fc.constantFrom('default', 'center', 'left'), fc.constantFrom('default', 'bottom-line', 'bottom-line-fill', 'mobile', 'progress'), fc.constantFrom('default', 's'), (orientation, alignment, variant, size) => {
34
+ const result = buildStatusPipelineClasses(orientation, alignment, variant, size);
35
+ const toks = tokens(result);
36
+ // always contains base
37
+ expect(toks).toContain('ux4g-status-pipeline-stepper');
38
+ if (orientation === 'vertical') {
39
+ expect(toks).toContain('ux4g-status-pipeline-vertical');
40
+ // alignment ignored — no horizontal, center, or left classes
41
+ expect(toks).not.toContain('ux4g-status-pipeline-horizontal');
42
+ expect(toks).not.toContain('ux4g-status-pipeline-center');
43
+ expect(toks).not.toContain('ux4g-status-pipeline-left');
44
+ }
45
+ else {
46
+ // horizontal
47
+ expect(toks).not.toContain('ux4g-status-pipeline-vertical');
48
+ if (alignment === 'center') {
49
+ expect(toks).toContain('ux4g-status-pipeline-horizontal');
50
+ expect(toks).toContain('ux4g-status-pipeline-center');
51
+ }
52
+ else if (alignment === 'left') {
53
+ expect(toks).toContain('ux4g-status-pipeline-horizontal');
54
+ expect(toks).toContain('ux4g-status-pipeline-left');
55
+ }
56
+ }
57
+ if (variant === 'bottom-line-fill') {
58
+ expect(toks).toContain('ux4g-status-pipeline-bottom-line');
59
+ expect(toks).toContain('ux4g-status-pipeline-bottom-line-fill');
60
+ }
61
+ else if (variant === 'bottom-line') {
62
+ expect(toks).toContain('ux4g-status-pipeline-bottom-line');
63
+ expect(toks).not.toContain('ux4g-status-pipeline-bottom-line-fill');
64
+ }
65
+ else if (variant === 'mobile') {
66
+ expect(toks).toContain('ux4g-status-pipeline-mobile');
67
+ }
68
+ else if (variant === 'progress') {
69
+ expect(toks).toContain('ux4g-status-pipeline-progress');
70
+ }
71
+ if (size === 's') {
72
+ expect(toks).toContain('ux4g-status-pipeline-s');
73
+ }
74
+ // no duplicates
75
+ expect(hasDuplicates(toks)).toBe(false);
76
+ }), { numRuns: 100 });
77
+ });
78
+ });
79
+ // ── Property 3: JourneyTimeline ───────────────────────────────────────────────
80
+ // **Validates: Requirements 38.13-38.15**
81
+ describe('Property 3: JourneyTimeline class string always contains base token and exactly one orientation modifier', () => {
82
+ it('base always present, exactly one orientation modifier, mutually exclusive, no duplicates', () => {
83
+ fc.assert(fc.property(fc.constantFrom('vertical', 'horizontal'), (orientation) => {
84
+ const result = buildJourneyTimelineClasses(orientation);
85
+ const toks = tokens(result);
86
+ expect(toks).toContain('ux4g-journey-timeline');
87
+ if (orientation === 'vertical') {
88
+ expect(toks).toContain('ux4g-journey-timeline--vertical');
89
+ expect(toks).not.toContain('ux4g-journey-timeline--horizontal');
90
+ }
91
+ else {
92
+ expect(toks).toContain('ux4g-journey-timeline--horizontal');
93
+ expect(toks).not.toContain('ux4g-journey-timeline--vertical');
94
+ }
95
+ // no duplicates
96
+ expect(hasDuplicates(toks)).toBe(false);
97
+ }), { numRuns: 100 });
98
+ });
99
+ });
100
+ // ── Property 4: Single-class wrappers ─────────────────────────────────────────
101
+ // **Validates: Requirements 38.16-38.17, 38.28-38.29, 38.34-38.35, 38.39**
102
+ describe('Property 4: Single-class wrappers always return exactly their base class', () => {
103
+ it('each single-class wrapper returns exactly its base class, extra appended, no duplicates', () => {
104
+ fc.assert(fc.property(fc.option(fc.string({ minLength: 1 }).filter(s => !s.includes(' '))), (extra) => {
105
+ const extraArg = extra ?? undefined;
106
+ // buildFormFieldGroupClasses
107
+ const formResult = buildFormFieldGroupClasses(extraArg);
108
+ const formToks = tokens(formResult);
109
+ expect(formToks).toContain('ux4g-form-group');
110
+ if (extraArg)
111
+ expect(formToks).toContain(extraArg);
112
+ expect(hasDuplicates(formToks)).toBe(false);
113
+ // buildFeedbackClasses
114
+ const feedbackResult = buildFeedbackClasses(extraArg);
115
+ const feedbackToks = tokens(feedbackResult);
116
+ expect(feedbackToks).toContain('ux4g-feedback');
117
+ if (extraArg)
118
+ expect(feedbackToks).toContain(extraArg);
119
+ expect(hasDuplicates(feedbackToks)).toBe(false);
120
+ // buildCarouselClasses
121
+ const carouselResult = buildCarouselClasses(extraArg);
122
+ const carouselToks = tokens(carouselResult);
123
+ expect(carouselToks).toContain('ux4g-carousel');
124
+ if (extraArg)
125
+ expect(carouselToks).toContain(extraArg);
126
+ expect(hasDuplicates(carouselToks)).toBe(false);
127
+ // buildEmptyStateClasses
128
+ const emptyResult = buildEmptyStateClasses(extraArg);
129
+ const emptyToks = tokens(emptyResult);
130
+ expect(emptyToks).toContain('ux4g-empty-state');
131
+ if (extraArg)
132
+ expect(emptyToks).toContain(extraArg);
133
+ expect(hasDuplicates(emptyToks)).toBe(false);
134
+ // buildNavbarClasses
135
+ const navbarResult = buildNavbarClasses(extraArg);
136
+ const navbarToks = tokens(navbarResult);
137
+ expect(navbarToks).toContain('ux4g-navbar');
138
+ if (extraArg)
139
+ expect(navbarToks).toContain(extraArg);
140
+ expect(hasDuplicates(navbarToks)).toBe(false);
141
+ }), { numRuns: 100 });
142
+ });
143
+ });
144
+ // ── Property 5: OTP Input ─────────────────────────────────────────────────────
145
+ // **Validates: Requirements 38.18-38.22**
146
+ describe('Property 5: OTP Input class string contains base token and correctly toggles state modifiers', () => {
147
+ it('base always present, state modifiers mutually exclusive, no duplicates', () => {
148
+ fc.assert(fc.property(fc.constantFrom('default', 'success', 'error', 'locked'), (state) => {
149
+ const result = buildOtpInputClasses(state);
150
+ const toks = tokens(result);
151
+ expect(toks).toContain('ux4g-otp');
152
+ if (state === 'default') {
153
+ expect(toks).not.toContain('ux4g-otp-success');
154
+ expect(toks).not.toContain('ux4g-otp-error');
155
+ expect(toks).not.toContain('ux4g-otp-locked');
156
+ }
157
+ else if (state === 'success') {
158
+ expect(toks).toContain('ux4g-otp-success');
159
+ expect(toks).not.toContain('ux4g-otp-error');
160
+ expect(toks).not.toContain('ux4g-otp-locked');
161
+ }
162
+ else if (state === 'error') {
163
+ expect(toks).toContain('ux4g-otp-error');
164
+ expect(toks).not.toContain('ux4g-otp-success');
165
+ expect(toks).not.toContain('ux4g-otp-locked');
166
+ }
167
+ else if (state === 'locked') {
168
+ expect(toks).toContain('ux4g-otp-locked');
169
+ expect(toks).not.toContain('ux4g-otp-success');
170
+ expect(toks).not.toContain('ux4g-otp-error');
171
+ }
172
+ expect(hasDuplicates(toks)).toBe(false);
173
+ }), { numRuns: 100 });
174
+ });
175
+ });
176
+ // ── Property 6: File Upload ───────────────────────────────────────────────────
177
+ // **Validates: Requirements 38.23-38.25**
178
+ describe('Property 6: File Upload class string contains base token and correctly appends state modifier', () => {
179
+ it('base always present, state modifier appended when provided, no duplicates', () => {
180
+ fc.assert(fc.property(fc.option(fc.constantFrom('default', 'default-vle', 'selecting', 'scanning', 'uploaded', 'uploaded-vle', 'error')), (state) => {
181
+ const stateArg = state ?? undefined;
182
+ const result = buildFileUploadClasses(stateArg);
183
+ const toks = tokens(result);
184
+ expect(toks).toContain('ux4g-upload');
185
+ if (stateArg) {
186
+ expect(toks).toContain(`ux4g-upload-state-${stateArg}`);
187
+ }
188
+ else {
189
+ expect(result).toBe('ux4g-upload');
190
+ }
191
+ expect(hasDuplicates(toks)).toBe(false);
192
+ }), { numRuns: 100 });
193
+ });
194
+ });
195
+ // ── Property 7: Progress Indicator ───────────────────────────────────────────
196
+ // **Validates: Requirements 38.26-38.27**
197
+ describe('Property 7: Progress Indicator class is exactly the correct base class for each type', () => {
198
+ it('type determines class exactly, mutually exclusive, no duplicates', () => {
199
+ fc.assert(fc.property(fc.constantFrom('bar', 'circle'), (type) => {
200
+ const result = buildProgressIndicatorClasses(type);
201
+ const toks = tokens(result);
202
+ if (type === 'bar') {
203
+ expect(result).toBe('ux4g-progress-bar');
204
+ expect(toks).not.toContain('ux4g-progress-circle');
205
+ }
206
+ else {
207
+ expect(result).toBe('ux4g-progress-circle');
208
+ expect(toks).not.toContain('ux4g-progress-bar');
209
+ }
210
+ expect(hasDuplicates(toks)).toBe(false);
211
+ }), { numRuns: 100 });
212
+ });
213
+ });
214
+ // ── Property 8: Draft Status Banner ──────────────────────────────────────────
215
+ // **Validates: Requirements 38.30-38.32**
216
+ describe('Property 8: Draft Status Banner class is exactly the correct variant class with vendor typo preserved', () => {
217
+ it('variant determines class exactly, vendor typo preserved, mutually exclusive, no duplicates', () => {
218
+ fc.assert(fc.property(fc.constantFrom('default', 'auto', 'success'), (variant) => {
219
+ const result = buildDraftStatusBannerClasses(variant);
220
+ const toks = tokens(result);
221
+ if (variant === 'default') {
222
+ expect(result).toBe('ux4g-daft-staus-bar');
223
+ }
224
+ else if (variant === 'auto') {
225
+ expect(result).toBe('ux4g-auto-daft-staus-bar');
226
+ }
227
+ else {
228
+ expect(result).toBe('ux4g-success-daft-staus-bar');
229
+ }
230
+ // never contains more than one variant class
231
+ const variantClasses = toks.filter(t => t === 'ux4g-daft-staus-bar' ||
232
+ t === 'ux4g-auto-daft-staus-bar' ||
233
+ t === 'ux4g-success-daft-staus-bar');
234
+ expect(variantClasses.length).toBe(1);
235
+ expect(hasDuplicates(toks)).toBe(false);
236
+ }), { numRuns: 100 });
237
+ });
238
+ });
239
+ // ── Property 9: SLA Progress Indicator ───────────────────────────────────────
240
+ // **Validates: Requirements 38.33**
241
+ describe('Property 9: SLA Progress Indicator class is exactly ux4g-sla-{type}', () => {
242
+ it('result is exactly ux4g-sla-{type}, mutually exclusive, no duplicates', () => {
243
+ fc.assert(fc.property(fc.constantFrom('circle', 'linear', 'badge'), (type) => {
244
+ const result = buildSlaProgressIndicatorClasses(type);
245
+ const toks = tokens(result);
246
+ expect(result).toBe(`ux4g-sla-${type}`);
247
+ // never contains more than one type class
248
+ const typeClasses = toks.filter(t => t === 'ux4g-sla-circle' || t === 'ux4g-sla-linear' || t === 'ux4g-sla-badge');
249
+ expect(typeClasses.length).toBe(1);
250
+ expect(hasDuplicates(toks)).toBe(false);
251
+ }), { numRuns: 100 });
252
+ });
253
+ });
254
+ // ── Property 10: Chip Group ───────────────────────────────────────────────────
255
+ // **Validates: Requirements 38.36-38.38**
256
+ describe('Property 10: Chip Group class string contains correct variant class and correctly toggles active', () => {
257
+ it('variant classes mutually exclusive, active toggles correctly, no duplicates', () => {
258
+ fc.assert(fc.property(fc.constantFrom('filter', 'choice'), fc.boolean(), (variant, active) => {
259
+ const result = buildChipGroupClasses(variant, active);
260
+ const toks = tokens(result);
261
+ if (variant === 'filter') {
262
+ expect(toks).toContain('ux4g-filter-chip-group');
263
+ expect(toks).not.toContain('ux4g-choice-chip-group');
264
+ }
265
+ else {
266
+ expect(toks).toContain('ux4g-choice-chip-group');
267
+ expect(toks).not.toContain('ux4g-filter-chip-group');
268
+ }
269
+ if (active) {
270
+ expect(toks).toContain('active');
271
+ }
272
+ else {
273
+ expect(toks).not.toContain('active');
274
+ }
275
+ expect(hasDuplicates(toks)).toBe(false);
276
+ }), { numRuns: 100 });
277
+ });
278
+ });
279
+ // ── Property 11: Social Links ─────────────────────────────────────────────────
280
+ // **Validates: Requirements 38.40-38.43**
281
+ describe('Property 11: Social Links class always contains ux4g-d-flex and exactly one gap class', () => {
282
+ it('ux4g-d-flex always present, exactly one gap class, mutually exclusive, no duplicates', () => {
283
+ fc.assert(fc.property(fc.constantFrom('sm', 'md', 'lg'), (size) => {
284
+ const result = buildSocialLinksClasses(size);
285
+ const toks = tokens(result);
286
+ expect(toks).toContain('ux4g-d-flex');
287
+ if (size === 'sm') {
288
+ expect(toks).toContain('ux4g-gap-xs');
289
+ expect(toks).not.toContain('ux4g-gap-s');
290
+ expect(toks).not.toContain('ux4g-gap-m');
291
+ }
292
+ else if (size === 'md') {
293
+ expect(toks).toContain('ux4g-gap-s');
294
+ expect(toks).not.toContain('ux4g-gap-xs');
295
+ expect(toks).not.toContain('ux4g-gap-m');
296
+ }
297
+ else {
298
+ expect(toks).toContain('ux4g-gap-m');
299
+ expect(toks).not.toContain('ux4g-gap-xs');
300
+ expect(toks).not.toContain('ux4g-gap-s');
301
+ }
302
+ // exactly two tokens when no extra
303
+ expect(toks.length).toBe(2);
304
+ expect(hasDuplicates(toks)).toBe(false);
305
+ }), { numRuns: 100 });
306
+ });
307
+ });
308
+ // ── Property 12: Extra param ──────────────────────────────────────────────────
309
+ // **Validates: Requirements 38.44-38.45**
310
+ describe('Property 12: Extra param is always appended as a space-separated token and never duplicates existing tokens for all Phase 9 build functions', () => {
311
+ it('extra token appended exactly once, no duplicates, across all 15 build functions', () => {
312
+ fc.assert(fc.property(fc.string({ minLength: 1 }).filter(s => !s.includes(' ')), (extra) => {
313
+ const cases = [
314
+ buildDateTimePickerClasses('date', extra),
315
+ buildDateTimePickerClasses('time', extra),
316
+ buildStatusPipelineClasses('horizontal', 'default', 'default', 'default', extra),
317
+ buildStatusPipelineClasses('vertical', 'default', 'default', 'default', extra),
318
+ buildJourneyTimelineClasses('vertical', extra),
319
+ buildJourneyTimelineClasses('horizontal', extra),
320
+ buildFormFieldGroupClasses(extra),
321
+ buildOtpInputClasses('success', extra),
322
+ buildFileUploadClasses('error', extra),
323
+ buildProgressIndicatorClasses('bar', extra),
324
+ buildProgressIndicatorClasses('circle', extra),
325
+ buildFeedbackClasses(extra),
326
+ buildDraftStatusBannerClasses('default', extra),
327
+ buildDraftStatusBannerClasses('auto', extra),
328
+ buildDraftStatusBannerClasses('success', extra),
329
+ buildSlaProgressIndicatorClasses('circle', extra),
330
+ buildCarouselClasses(extra),
331
+ buildEmptyStateClasses(extra),
332
+ buildChipGroupClasses('filter', false, extra),
333
+ buildNavbarClasses(extra),
334
+ buildSocialLinksClasses('md', extra),
335
+ ];
336
+ for (const result of cases) {
337
+ const toks = tokens(result);
338
+ // extra token is present
339
+ expect(toks).toContain(extra);
340
+ // extra token appears exactly once
341
+ expect(toks.filter(t => t === extra).length).toBe(1);
342
+ // no duplicates overall
343
+ expect(hasDuplicates(toks)).toBe(false);
344
+ }
345
+ }), { numRuns: 100 });
346
+ });
347
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,226 @@
1
+ // src/__tests__/types.phase9.test.ts
2
+ // Unit tests for all Phase 9 build functions
3
+ // Requirements: 18.1-18.17
4
+ import { buildDateTimePickerClasses, buildStatusPipelineClasses, buildJourneyTimelineClasses, buildFormFieldGroupClasses, buildOtpInputClasses, buildFileUploadClasses, buildProgressIndicatorClasses, buildFeedbackClasses, buildDraftStatusBannerClasses, buildSlaProgressIndicatorClasses, buildCarouselClasses, buildEmptyStateClasses, buildChipGroupClasses, buildNavbarClasses, buildSocialLinksClasses, } from '../../src/types';
5
+ // ── DateTimePicker ────────────────────────────────────────────────────────────
6
+ describe('buildDateTimePickerClasses', () => {
7
+ it("mode='date' -> 'ux4g-date-picker-container'", () => {
8
+ expect(buildDateTimePickerClasses('date')).toBe('ux4g-date-picker-container');
9
+ });
10
+ it("mode='time' -> 'ux4g-time-picker-container'", () => {
11
+ expect(buildDateTimePickerClasses('time')).toBe('ux4g-time-picker-container');
12
+ });
13
+ it("mode='date' with extra -> 'ux4g-date-picker-container my'", () => {
14
+ expect(buildDateTimePickerClasses('date', 'my')).toBe('ux4g-date-picker-container my');
15
+ });
16
+ });
17
+ // ── StatusPipeline ────────────────────────────────────────────────────────────
18
+ describe('buildStatusPipelineClasses', () => {
19
+ it('no args -> base only', () => {
20
+ expect(buildStatusPipelineClasses()).toBe('ux4g-status-pipeline-stepper');
21
+ });
22
+ it("orientation='horizontal' -> base only", () => {
23
+ expect(buildStatusPipelineClasses('horizontal')).toBe('ux4g-status-pipeline-stepper');
24
+ });
25
+ it("orientation='horizontal', alignment='center'", () => {
26
+ expect(buildStatusPipelineClasses('horizontal', 'center')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-horizontal ux4g-status-pipeline-center');
27
+ });
28
+ it("orientation='horizontal', alignment='left'", () => {
29
+ expect(buildStatusPipelineClasses('horizontal', 'left')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-horizontal ux4g-status-pipeline-left');
30
+ });
31
+ it("orientation='vertical' -> vertical class only", () => {
32
+ expect(buildStatusPipelineClasses('vertical')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-vertical');
33
+ });
34
+ it("orientation='vertical', alignment='center' -> alignment ignored", () => {
35
+ expect(buildStatusPipelineClasses('vertical', 'center')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-vertical');
36
+ });
37
+ it("variant='bottom-line'", () => {
38
+ expect(buildStatusPipelineClasses('horizontal', 'default', 'bottom-line')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-bottom-line');
39
+ });
40
+ it("variant='bottom-line-fill' adds both bottom-line and bottom-line-fill", () => {
41
+ expect(buildStatusPipelineClasses('horizontal', 'default', 'bottom-line-fill')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-bottom-line ux4g-status-pipeline-bottom-line-fill');
42
+ });
43
+ it("variant='mobile'", () => {
44
+ expect(buildStatusPipelineClasses('horizontal', 'default', 'mobile')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-mobile');
45
+ });
46
+ it("variant='progress'", () => {
47
+ expect(buildStatusPipelineClasses('horizontal', 'default', 'progress')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-progress');
48
+ });
49
+ it("size='s'", () => {
50
+ expect(buildStatusPipelineClasses('horizontal', 'default', 'default', 's')).toBe('ux4g-status-pipeline-stepper ux4g-status-pipeline-s');
51
+ });
52
+ });
53
+ // ── JourneyTimeline ───────────────────────────────────────────────────────────
54
+ describe('buildJourneyTimelineClasses', () => {
55
+ it('no args -> vertical default', () => {
56
+ expect(buildJourneyTimelineClasses()).toBe('ux4g-journey-timeline ux4g-journey-timeline--vertical');
57
+ });
58
+ it("orientation='vertical'", () => {
59
+ expect(buildJourneyTimelineClasses('vertical')).toBe('ux4g-journey-timeline ux4g-journey-timeline--vertical');
60
+ });
61
+ it("orientation='horizontal'", () => {
62
+ expect(buildJourneyTimelineClasses('horizontal')).toBe('ux4g-journey-timeline ux4g-journey-timeline--horizontal');
63
+ });
64
+ it("orientation='vertical' with extra", () => {
65
+ expect(buildJourneyTimelineClasses('vertical', 'my')).toBe('ux4g-journey-timeline ux4g-journey-timeline--vertical my');
66
+ });
67
+ });
68
+ // ── FormFieldGroup ────────────────────────────────────────────────────────────
69
+ describe('buildFormFieldGroupClasses', () => {
70
+ it('no args -> ux4g-form-group', () => {
71
+ expect(buildFormFieldGroupClasses()).toBe('ux4g-form-group');
72
+ });
73
+ it('with extra', () => {
74
+ expect(buildFormFieldGroupClasses('my')).toBe('ux4g-form-group my');
75
+ });
76
+ });
77
+ // ── OtpInput ─────────────────────────────────────────────────────────────────
78
+ describe('buildOtpInputClasses', () => {
79
+ it('no args -> ux4g-otp', () => {
80
+ expect(buildOtpInputClasses()).toBe('ux4g-otp');
81
+ });
82
+ it("state='default' -> no modifier", () => {
83
+ expect(buildOtpInputClasses('default')).toBe('ux4g-otp');
84
+ });
85
+ it("state='success'", () => {
86
+ expect(buildOtpInputClasses('success')).toBe('ux4g-otp ux4g-otp-success');
87
+ });
88
+ it("state='error'", () => {
89
+ expect(buildOtpInputClasses('error')).toBe('ux4g-otp ux4g-otp-error');
90
+ });
91
+ it("state='locked'", () => {
92
+ expect(buildOtpInputClasses('locked')).toBe('ux4g-otp ux4g-otp-locked');
93
+ });
94
+ });
95
+ // ── FileUpload ────────────────────────────────────────────────────────────────
96
+ describe('buildFileUploadClasses', () => {
97
+ it('no args -> ux4g-upload', () => {
98
+ expect(buildFileUploadClasses()).toBe('ux4g-upload');
99
+ });
100
+ it("state='default'", () => {
101
+ expect(buildFileUploadClasses('default')).toBe('ux4g-upload ux4g-upload-state-default');
102
+ });
103
+ it("state='selecting'", () => {
104
+ expect(buildFileUploadClasses('selecting')).toBe('ux4g-upload ux4g-upload-state-selecting');
105
+ });
106
+ it("state='error'", () => {
107
+ expect(buildFileUploadClasses('error')).toBe('ux4g-upload ux4g-upload-state-error');
108
+ });
109
+ it("state='uploaded-vle' with extra", () => {
110
+ expect(buildFileUploadClasses('uploaded-vle', 'my')).toBe('ux4g-upload ux4g-upload-state-uploaded-vle my');
111
+ });
112
+ });
113
+ // ── ProgressIndicator ─────────────────────────────────────────────────────────
114
+ describe('buildProgressIndicatorClasses', () => {
115
+ it("type='bar' -> 'ux4g-progress-bar'", () => {
116
+ expect(buildProgressIndicatorClasses('bar')).toBe('ux4g-progress-bar');
117
+ });
118
+ it("type='circle' -> 'ux4g-progress-circle'", () => {
119
+ expect(buildProgressIndicatorClasses('circle')).toBe('ux4g-progress-circle');
120
+ });
121
+ it("type='bar' with extra", () => {
122
+ expect(buildProgressIndicatorClasses('bar', 'my')).toBe('ux4g-progress-bar my');
123
+ });
124
+ });
125
+ // ── Feedback ─────────────────────────────────────────────────────────────────
126
+ describe('buildFeedbackClasses', () => {
127
+ it('no args -> ux4g-feedback', () => {
128
+ expect(buildFeedbackClasses()).toBe('ux4g-feedback');
129
+ });
130
+ it('with extra', () => {
131
+ expect(buildFeedbackClasses('my')).toBe('ux4g-feedback my');
132
+ });
133
+ });
134
+ // ── DraftStatusBanner ─────────────────────────────────────────────────────────
135
+ describe('buildDraftStatusBannerClasses', () => {
136
+ it("no args -> 'ux4g-daft-staus-bar' (vendor typo preserved)", () => {
137
+ expect(buildDraftStatusBannerClasses()).toBe('ux4g-daft-staus-bar');
138
+ });
139
+ it("variant='default' -> 'ux4g-daft-staus-bar'", () => {
140
+ expect(buildDraftStatusBannerClasses('default')).toBe('ux4g-daft-staus-bar');
141
+ });
142
+ it("variant='auto' -> 'ux4g-auto-daft-staus-bar'", () => {
143
+ expect(buildDraftStatusBannerClasses('auto')).toBe('ux4g-auto-daft-staus-bar');
144
+ });
145
+ it("variant='success' -> 'ux4g-success-daft-staus-bar'", () => {
146
+ expect(buildDraftStatusBannerClasses('success')).toBe('ux4g-success-daft-staus-bar');
147
+ });
148
+ });
149
+ // ── SlaProgressIndicator ──────────────────────────────────────────────────────
150
+ describe('buildSlaProgressIndicatorClasses', () => {
151
+ it("type='circle' -> 'ux4g-sla-circle'", () => {
152
+ expect(buildSlaProgressIndicatorClasses('circle')).toBe('ux4g-sla-circle');
153
+ });
154
+ it("type='linear' -> 'ux4g-sla-linear'", () => {
155
+ expect(buildSlaProgressIndicatorClasses('linear')).toBe('ux4g-sla-linear');
156
+ });
157
+ it("type='badge' -> 'ux4g-sla-badge'", () => {
158
+ expect(buildSlaProgressIndicatorClasses('badge')).toBe('ux4g-sla-badge');
159
+ });
160
+ });
161
+ // ── Carousel ─────────────────────────────────────────────────────────────────
162
+ describe('buildCarouselClasses', () => {
163
+ it('no args -> ux4g-carousel', () => {
164
+ expect(buildCarouselClasses()).toBe('ux4g-carousel');
165
+ });
166
+ it('with extra', () => {
167
+ expect(buildCarouselClasses('my')).toBe('ux4g-carousel my');
168
+ });
169
+ });
170
+ // ── EmptyState ────────────────────────────────────────────────────────────────
171
+ describe('buildEmptyStateClasses', () => {
172
+ it('no args -> ux4g-empty-state', () => {
173
+ expect(buildEmptyStateClasses()).toBe('ux4g-empty-state');
174
+ });
175
+ it('with extra', () => {
176
+ expect(buildEmptyStateClasses('my')).toBe('ux4g-empty-state my');
177
+ });
178
+ });
179
+ // ── ChipGroup ─────────────────────────────────────────────────────────────────
180
+ describe('buildChipGroupClasses', () => {
181
+ it('no args -> ux4g-filter-chip-group (default)', () => {
182
+ expect(buildChipGroupClasses()).toBe('ux4g-filter-chip-group');
183
+ });
184
+ it("variant='filter'", () => {
185
+ expect(buildChipGroupClasses('filter')).toBe('ux4g-filter-chip-group');
186
+ });
187
+ it("variant='choice'", () => {
188
+ expect(buildChipGroupClasses('choice')).toBe('ux4g-choice-chip-group');
189
+ });
190
+ it("variant='filter', active=true", () => {
191
+ expect(buildChipGroupClasses('filter', true)).toBe('ux4g-filter-chip-group active');
192
+ });
193
+ it("variant='choice', active=false", () => {
194
+ expect(buildChipGroupClasses('choice', false)).toBe('ux4g-choice-chip-group');
195
+ });
196
+ it("variant='filter', active=true, extra='my'", () => {
197
+ expect(buildChipGroupClasses('filter', true, 'my')).toBe('ux4g-filter-chip-group active my');
198
+ });
199
+ });
200
+ // ── Navbar ────────────────────────────────────────────────────────────────────
201
+ describe('buildNavbarClasses', () => {
202
+ it('no args -> ux4g-navbar', () => {
203
+ expect(buildNavbarClasses()).toBe('ux4g-navbar');
204
+ });
205
+ it('with extra', () => {
206
+ expect(buildNavbarClasses('my')).toBe('ux4g-navbar my');
207
+ });
208
+ });
209
+ // ── SocialLinks ───────────────────────────────────────────────────────────────
210
+ describe('buildSocialLinksClasses', () => {
211
+ it('no args -> ux4g-d-flex ux4g-gap-s (md default)', () => {
212
+ expect(buildSocialLinksClasses()).toBe('ux4g-d-flex ux4g-gap-s');
213
+ });
214
+ it("size='sm' -> ux4g-d-flex ux4g-gap-xs", () => {
215
+ expect(buildSocialLinksClasses('sm')).toBe('ux4g-d-flex ux4g-gap-xs');
216
+ });
217
+ it("size='md' -> ux4g-d-flex ux4g-gap-s", () => {
218
+ expect(buildSocialLinksClasses('md')).toBe('ux4g-d-flex ux4g-gap-s');
219
+ });
220
+ it("size='lg' -> ux4g-d-flex ux4g-gap-m", () => {
221
+ expect(buildSocialLinksClasses('lg')).toBe('ux4g-d-flex ux4g-gap-m');
222
+ });
223
+ it("size='md' with extra", () => {
224
+ expect(buildSocialLinksClasses('md', 'my')).toBe('ux4g-d-flex ux4g-gap-s my');
225
+ });
226
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Property-based tests for UX4G Package Restructure
3
+ * Properties 1-2
4
+ * Tag: Feature: ux4g-package-restructure
5
+ *
6
+ * Validates: Requirements 7.1, 7.2, 7.3
7
+ */
8
+ import * as fc from 'fast-check';
9
+ import { buildButtonClasses, VARIANT_CLASS_MAP, SIZE_CLASS_MAP, } from '../../src/types';
10
+ const allButtonVariants = [
11
+ 'primary',
12
+ 'outline-primary',
13
+ 'text-primary',
14
+ 'tonal-primary',
15
+ 'danger',
16
+ 'outline-danger',
17
+ 'text-danger',
18
+ 'tonal-danger',
19
+ ];
20
+ const allButtonSizes = ['xl', 'lg', 'md', 'sm', 'xs'];
21
+ const allButtonShapes = ['rectangle', 'pill'];
22
+ /**
23
+ * Property 1: Class_Builder output is preserved after restructure
24
+ *
25
+ * For any valid input combination to buildButtonClasses(), the output from
26
+ * ux4g-components-web/types SHALL be identical to the expected pre-restructure output.
27
+ * The returned class string must be non-empty and contain the expected variant/size
28
+ * class substrings.
29
+ *
30
+ * Tag: Feature: ux4g-package-restructure, Property 1
31
+ * Validates: Requirements 7.1, 7.2
32
+ */
33
+ describe('Property 1: Class_Builder output is preserved after restructure', () => {
34
+ it('Feature: ux4g-package-restructure, Property 1 — buildButtonClasses output is non-empty and contains expected variant and size substrings', () => {
35
+ fc.assert(fc.property(fc.constantFrom(...allButtonVariants), fc.constantFrom(...allButtonSizes), fc.boolean(), fc.boolean(), fc.option(fc.constantFrom(...allButtonShapes), { nil: undefined }), fc.option(fc.string(), { nil: undefined }), (variant, size, disabled, loading, shape, extra) => {
36
+ const result = buildButtonClasses(variant, size, disabled, loading, shape, extra);
37
+ // Output must be non-empty
38
+ if (!result || result.trim().length === 0)
39
+ return false;
40
+ // Must contain the expected variant class
41
+ const expectedVariantClass = VARIANT_CLASS_MAP[variant];
42
+ if (!result.includes(expectedVariantClass))
43
+ return false;
44
+ // Must contain the expected size class
45
+ const expectedSizeClass = SIZE_CLASS_MAP[size];
46
+ if (!expectedSizeClass)
47
+ return false;
48
+ if (!result.includes(expectedSizeClass))
49
+ return false;
50
+ return true;
51
+ }), { numRuns: 100 });
52
+ });
53
+ });
54
+ /**
55
+ * Property 2: SIZE_CLASS_MAP maps every size to a non-null, non-empty class string
56
+ *
57
+ * For any size value in the Button SIZE_CLASS_MAP (xl, lg, md, sm, xs), the mapped
58
+ * CSS class string SHALL be non-null and non-empty.
59
+ *
60
+ * Tag: Feature: ux4g-package-restructure, Property 2
61
+ * Validates: Requirements 7.3
62
+ */
63
+ describe('Property 2: SIZE_CLASS_MAP maps every size to a non-null, non-empty class string', () => {
64
+ it('Feature: ux4g-package-restructure, Property 2 — every size maps to a non-null, non-empty string', () => {
65
+ fc.assert(fc.property(fc.constantFrom('xl', 'lg', 'md', 'sm', 'xs'), (size) => {
66
+ const mapped = SIZE_CLASS_MAP[size];
67
+ // Must be non-null
68
+ if (mapped === null || mapped === undefined)
69
+ return false;
70
+ // Must be non-empty
71
+ if (mapped.trim().length === 0)
72
+ return false;
73
+ return true;
74
+ }), { numRuns: 100 });
75
+ });
76
+ });