@fgv/ts-res-ui-components 5.0.0-10

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 (231) hide show
  1. package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1111 -0
  7. package/README.md +18 -0
  8. package/REFACTORING_PLAN.md +171 -0
  9. package/config/jest.config.json +16 -0
  10. package/config/jest.setup.js +64 -0
  11. package/config/rig.json +16 -0
  12. package/lib/components/common/QualifierContextControl.d.ts +14 -0
  13. package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
  14. package/lib/components/common/QualifierContextControl.js +78 -0
  15. package/lib/components/common/QualifierContextControl.js.map +1 -0
  16. package/lib/components/common/ResourceListView.d.ts +11 -0
  17. package/lib/components/common/ResourceListView.d.ts.map +1 -0
  18. package/lib/components/common/ResourceListView.js +20 -0
  19. package/lib/components/common/ResourceListView.js.map +1 -0
  20. package/lib/components/common/ResourceTreeView.d.ts +12 -0
  21. package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
  22. package/lib/components/common/ResourceTreeView.js +162 -0
  23. package/lib/components/common/ResourceTreeView.js.map +1 -0
  24. package/lib/components/forms/HierarchyEditor.d.ts +10 -0
  25. package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
  26. package/lib/components/forms/HierarchyEditor.js +106 -0
  27. package/lib/components/forms/HierarchyEditor.js.map +1 -0
  28. package/lib/components/forms/QualifierEditForm.d.ts +11 -0
  29. package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
  30. package/lib/components/forms/QualifierEditForm.js +181 -0
  31. package/lib/components/forms/QualifierEditForm.js.map +1 -0
  32. package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
  33. package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
  34. package/lib/components/forms/QualifierTypeEditForm.js +172 -0
  35. package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
  36. package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
  37. package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
  38. package/lib/components/forms/ResourceTypeEditForm.js +188 -0
  39. package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
  40. package/lib/components/forms/index.d.ts +9 -0
  41. package/lib/components/forms/index.d.ts.map +1 -0
  42. package/lib/components/forms/index.js +5 -0
  43. package/lib/components/forms/index.js.map +1 -0
  44. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
  45. package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
  46. package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
  47. package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
  48. package/lib/components/views/CompiledView/index.d.ts +5 -0
  49. package/lib/components/views/CompiledView/index.d.ts.map +1 -0
  50. package/lib/components/views/CompiledView/index.js +595 -0
  51. package/lib/components/views/CompiledView/index.js.map +1 -0
  52. package/lib/components/views/ConfigurationView/index.d.ts +5 -0
  53. package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
  54. package/lib/components/views/ConfigurationView/index.js +363 -0
  55. package/lib/components/views/ConfigurationView/index.js.map +1 -0
  56. package/lib/components/views/FilterView/index.d.ts +5 -0
  57. package/lib/components/views/FilterView/index.d.ts.map +1 -0
  58. package/lib/components/views/FilterView/index.js +463 -0
  59. package/lib/components/views/FilterView/index.js.map +1 -0
  60. package/lib/components/views/ImportView/index.d.ts +5 -0
  61. package/lib/components/views/ImportView/index.d.ts.map +1 -0
  62. package/lib/components/views/ImportView/index.js +514 -0
  63. package/lib/components/views/ImportView/index.js.map +1 -0
  64. package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
  65. package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
  66. package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
  67. package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
  68. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
  69. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
  70. package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
  71. package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
  72. package/lib/components/views/ResolutionView/index.d.ts +5 -0
  73. package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
  74. package/lib/components/views/ResolutionView/index.js +255 -0
  75. package/lib/components/views/ResolutionView/index.js.map +1 -0
  76. package/lib/components/views/SourceView/index.d.ts +5 -0
  77. package/lib/components/views/SourceView/index.d.ts.map +1 -0
  78. package/lib/components/views/SourceView/index.js +316 -0
  79. package/lib/components/views/SourceView/index.js.map +1 -0
  80. package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
  81. package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
  82. package/lib/components/views/ZipLoaderView/index.js +313 -0
  83. package/lib/components/views/ZipLoaderView/index.js.map +1 -0
  84. package/lib/hooks/useConfigurationState.d.ts +46 -0
  85. package/lib/hooks/useConfigurationState.d.ts.map +1 -0
  86. package/lib/hooks/useConfigurationState.js +239 -0
  87. package/lib/hooks/useConfigurationState.js.map +1 -0
  88. package/lib/hooks/useFilterState.d.ts +7 -0
  89. package/lib/hooks/useFilterState.d.ts.map +1 -0
  90. package/lib/hooks/useFilterState.js +80 -0
  91. package/lib/hooks/useFilterState.js.map +1 -0
  92. package/lib/hooks/useResolutionState.d.ts +8 -0
  93. package/lib/hooks/useResolutionState.d.ts.map +1 -0
  94. package/lib/hooks/useResolutionState.js +253 -0
  95. package/lib/hooks/useResolutionState.js.map +1 -0
  96. package/lib/hooks/useResourceData.d.ts +19 -0
  97. package/lib/hooks/useResourceData.d.ts.map +1 -0
  98. package/lib/hooks/useResourceData.js +368 -0
  99. package/lib/hooks/useResourceData.js.map +1 -0
  100. package/lib/hooks/useViewState.d.ts +10 -0
  101. package/lib/hooks/useViewState.d.ts.map +1 -0
  102. package/lib/hooks/useViewState.js +29 -0
  103. package/lib/hooks/useViewState.js.map +1 -0
  104. package/lib/index.d.ts +27 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +34 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/test/helpers/testDataLoader.d.ts +37 -0
  109. package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
  110. package/lib/test/helpers/testDataLoader.js +171 -0
  111. package/lib/test/helpers/testDataLoader.js.map +1 -0
  112. package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
  113. package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
  114. package/lib/test/unit/utils/configurationUtils.test.js +497 -0
  115. package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
  116. package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
  117. package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
  118. package/lib/test/unit/utils/fileProcessing.test.js +321 -0
  119. package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
  120. package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
  121. package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
  122. package/lib/test/unit/utils/filterResources.test.js +403 -0
  123. package/lib/test/unit/utils/filterResources.test.js.map +1 -0
  124. package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
  125. package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
  126. package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
  127. package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
  128. package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
  129. package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
  130. package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
  131. package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
  132. package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
  133. package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
  134. package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
  135. package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
  136. package/lib/types/index.d.ts +251 -0
  137. package/lib/types/index.d.ts.map +1 -0
  138. package/lib/types/index.js +2 -0
  139. package/lib/types/index.js.map +1 -0
  140. package/lib/utils/configurationUtils.d.ts +74 -0
  141. package/lib/utils/configurationUtils.d.ts.map +1 -0
  142. package/lib/utils/configurationUtils.js +359 -0
  143. package/lib/utils/configurationUtils.js.map +1 -0
  144. package/lib/utils/fileProcessing.d.ts +18 -0
  145. package/lib/utils/fileProcessing.d.ts.map +1 -0
  146. package/lib/utils/fileProcessing.js +142 -0
  147. package/lib/utils/fileProcessing.js.map +1 -0
  148. package/lib/utils/filterResources.d.ts +38 -0
  149. package/lib/utils/filterResources.d.ts.map +1 -0
  150. package/lib/utils/filterResources.js +153 -0
  151. package/lib/utils/filterResources.js.map +1 -0
  152. package/lib/utils/resolutionEditing.d.ts +58 -0
  153. package/lib/utils/resolutionEditing.d.ts.map +1 -0
  154. package/lib/utils/resolutionEditing.js +246 -0
  155. package/lib/utils/resolutionEditing.js.map +1 -0
  156. package/lib/utils/resolutionUtils.d.ts +28 -0
  157. package/lib/utils/resolutionUtils.d.ts.map +1 -0
  158. package/lib/utils/resolutionUtils.js +216 -0
  159. package/lib/utils/resolutionUtils.js.map +1 -0
  160. package/lib/utils/tsResIntegration.d.ts +71 -0
  161. package/lib/utils/tsResIntegration.d.ts.map +1 -0
  162. package/lib/utils/tsResIntegration.js +294 -0
  163. package/lib/utils/tsResIntegration.js.map +1 -0
  164. package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
  165. package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
  166. package/lib/utils/zipLoader/browserZipLoader.js +247 -0
  167. package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
  168. package/lib/utils/zipLoader/index.d.ts +8 -0
  169. package/lib/utils/zipLoader/index.d.ts.map +1 -0
  170. package/lib/utils/zipLoader/index.js +13 -0
  171. package/lib/utils/zipLoader/index.js.map +1 -0
  172. package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
  173. package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
  174. package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
  175. package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
  176. package/lib/utils/zipLoader/types.d.ts +139 -0
  177. package/lib/utils/zipLoader/types.d.ts.map +1 -0
  178. package/lib/utils/zipLoader/types.js +2 -0
  179. package/lib/utils/zipLoader/types.js.map +1 -0
  180. package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
  181. package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
  182. package/lib/utils/zipLoader/zipUtils.js +229 -0
  183. package/lib/utils/zipLoader/zipUtils.js.map +1 -0
  184. package/package.json +69 -0
  185. package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
  186. package/rush-logs/ts-res-ui-components.build.log +9 -0
  187. package/src/components/common/QualifierContextControl.tsx +151 -0
  188. package/src/components/common/ResourceListView.tsx +63 -0
  189. package/src/components/common/ResourceTreeView.tsx +271 -0
  190. package/src/components/forms/HierarchyEditor.tsx +204 -0
  191. package/src/components/forms/QualifierEditForm.tsx +355 -0
  192. package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
  193. package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
  194. package/src/components/forms/index.ts +11 -0
  195. package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
  196. package/src/components/views/CompiledView/index.tsx +922 -0
  197. package/src/components/views/ConfigurationView/index.tsx +800 -0
  198. package/src/components/views/FilterView/index.tsx +825 -0
  199. package/src/components/views/ImportView/index.tsx +717 -0
  200. package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
  201. package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
  202. package/src/components/views/ResolutionView/index.tsx +591 -0
  203. package/src/components/views/SourceView/index.tsx +536 -0
  204. package/src/components/views/ZipLoaderView/index.tsx +485 -0
  205. package/src/hooks/useConfigurationState.ts +374 -0
  206. package/src/hooks/useFilterState.ts +97 -0
  207. package/src/hooks/useResolutionState.ts +355 -0
  208. package/src/hooks/useResourceData.ts +467 -0
  209. package/src/hooks/useViewState.ts +44 -0
  210. package/src/index.ts +45 -0
  211. package/src/test/helpers/testDataLoader.ts +195 -0
  212. package/src/test/unit/utils/configurationUtils.test.ts +630 -0
  213. package/src/test/unit/utils/fileProcessing.test.ts +391 -0
  214. package/src/test/unit/utils/filterResources.test.ts +574 -0
  215. package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
  216. package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
  217. package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
  218. package/src/types/index.ts +322 -0
  219. package/src/utils/configurationUtils.ts +424 -0
  220. package/src/utils/fileProcessing.ts +160 -0
  221. package/src/utils/filterResources.ts +206 -0
  222. package/src/utils/resolutionEditing.ts +319 -0
  223. package/src/utils/resolutionUtils.ts +289 -0
  224. package/src/utils/tsResIntegration.ts +440 -0
  225. package/src/utils/zipLoader/browserZipLoader.ts +319 -0
  226. package/src/utils/zipLoader/index.ts +26 -0
  227. package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
  228. package/src/utils/zipLoader/types.ts +175 -0
  229. package/src/utils/zipLoader/zipUtils.ts +266 -0
  230. package/temp/build/typescript/ts_gZid87Hu.json +1 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,521 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+ import '@fgv/ts-utils-jest';
24
+ import { succeed, fail } from '@fgv/ts-utils';
25
+ import {
26
+ createResolverWithContext,
27
+ resolveResourceDetailed,
28
+ getAvailableQualifiers,
29
+ hasPendingContextChanges,
30
+ evaluateConditionsForCandidate,
31
+ type ResolutionOptions
32
+ } from '../../../utils/resolutionUtils';
33
+ import { ProcessedResources, ResolutionResult } from '../../../types';
34
+
35
+ describe('resolutionUtils', () => {
36
+ let mockProcessedResources: ProcessedResources;
37
+ let mockResolver: any;
38
+
39
+ beforeEach(() => {
40
+ mockProcessedResources = {
41
+ system: {
42
+ resourceManager: {
43
+ getBuiltResource: jest.fn()
44
+ } as any,
45
+ qualifiers: {
46
+ validating: {
47
+ get: jest.fn()
48
+ }
49
+ } as any,
50
+ qualifierTypes: {} as any,
51
+ resourceTypes: {} as any,
52
+ importManager: {} as any,
53
+ contextQualifierProvider: {} as any
54
+ },
55
+ compiledCollection: {
56
+ resources: [
57
+ {
58
+ id: 'resource1' as unknown as any,
59
+ decision: 0 as unknown as any,
60
+ type: {} as any,
61
+ candidates: []
62
+ },
63
+ {
64
+ id: 'resource2' as unknown as any,
65
+ decision: 1 as unknown as any,
66
+ type: {} as any,
67
+ candidates: []
68
+ }
69
+ ],
70
+ qualifiers: [
71
+ { name: 'language' as unknown as any, type: {} as any, defaultPriority: 100 as unknown as any },
72
+ { name: 'territory' as unknown as any, type: {} as any, defaultPriority: 90 as unknown as any }
73
+ ],
74
+ qualifierTypes: [],
75
+ resourceTypes: [],
76
+ conditions: [
77
+ {
78
+ qualifierIndex: 0 as unknown as any,
79
+ value: 'en',
80
+ operator: 'matches',
81
+ priority: 100 as unknown as any
82
+ }
83
+ ],
84
+ conditionSets: [{ conditions: [0 as unknown as any] }],
85
+ decisions: [{ conditionSets: [0 as unknown as any] }]
86
+ },
87
+ resolver: {} as any,
88
+ resourceCount: 2,
89
+ summary: {
90
+ totalResources: 2,
91
+ resourceIds: ['resource1', 'resource2'],
92
+ errorCount: 0,
93
+ warnings: []
94
+ }
95
+ };
96
+
97
+ mockResolver = {
98
+ contextQualifierProvider: {
99
+ get: jest.fn().mockReturnValue(succeed('en'))
100
+ },
101
+ resolveResource: jest.fn(),
102
+ resolveAllResourceCandidates: jest.fn(),
103
+ resolveComposedResourceValue: jest.fn(),
104
+ resolveDecision: jest.fn(),
105
+ conditionCache: {
106
+ 0: { score: 1.0, matchType: 'match' }
107
+ }
108
+ };
109
+ });
110
+
111
+ describe('getAvailableQualifiers', () => {
112
+ test('returns qualifier names from compiled collection', () => {
113
+ const result = getAvailableQualifiers(mockProcessedResources);
114
+
115
+ expect(result).toEqual(['language', 'territory']);
116
+ });
117
+
118
+ test('returns empty array when no qualifiers', () => {
119
+ const resourcesWithoutQualifiers = {
120
+ ...mockProcessedResources,
121
+ compiledCollection: {
122
+ ...mockProcessedResources.compiledCollection,
123
+ qualifiers: undefined as any
124
+ }
125
+ };
126
+
127
+ const result = getAvailableQualifiers(resourcesWithoutQualifiers);
128
+
129
+ expect(result).toEqual([]);
130
+ });
131
+
132
+ test('handles empty qualifiers array', () => {
133
+ const resourcesWithEmptyQualifiers = {
134
+ ...mockProcessedResources,
135
+ compiledCollection: {
136
+ ...mockProcessedResources.compiledCollection,
137
+ qualifiers: []
138
+ }
139
+ };
140
+
141
+ const result = getAvailableQualifiers(resourcesWithEmptyQualifiers);
142
+
143
+ expect(result).toEqual([]);
144
+ });
145
+
146
+ test('extracts names from qualifier objects', () => {
147
+ const resourcesWithComplexQualifiers = {
148
+ ...mockProcessedResources,
149
+ compiledCollection: {
150
+ ...mockProcessedResources.compiledCollection,
151
+ qualifiers: [
152
+ {
153
+ name: 'language' as unknown as any,
154
+ type: {} as any,
155
+ typeName: 'language',
156
+ defaultPriority: 100 as unknown as any
157
+ },
158
+ {
159
+ name: 'territory' as unknown as any,
160
+ type: {} as any,
161
+ typeName: 'territory',
162
+ defaultPriority: 90 as unknown as any
163
+ },
164
+ {
165
+ name: 'platform' as unknown as any,
166
+ type: {} as any,
167
+ typeName: 'literal',
168
+ defaultPriority: 80 as unknown as any
169
+ }
170
+ ]
171
+ }
172
+ };
173
+
174
+ const result = getAvailableQualifiers(resourcesWithComplexQualifiers);
175
+
176
+ expect(result).toEqual(['language', 'territory', 'platform']);
177
+ });
178
+ });
179
+
180
+ describe('hasPendingContextChanges', () => {
181
+ test('returns false when contexts are identical', () => {
182
+ const context = { language: 'en', territory: 'US' };
183
+ const pendingContext = { language: 'en', territory: 'US' };
184
+
185
+ expect(hasPendingContextChanges(context, pendingContext)).toBe(false);
186
+ });
187
+
188
+ test('returns true when contexts differ', () => {
189
+ const context = { language: 'en', territory: 'US' };
190
+ const pendingContext = { language: 'fr', territory: 'US' };
191
+
192
+ expect(hasPendingContextChanges(context, pendingContext)).toBe(true);
193
+ });
194
+
195
+ test('returns true when pending has additional values', () => {
196
+ const context = { language: 'en' };
197
+ const pendingContext = { language: 'en', territory: 'US' };
198
+
199
+ expect(hasPendingContextChanges(context, pendingContext)).toBe(true);
200
+ });
201
+
202
+ test('returns true when pending has fewer values', () => {
203
+ const context = { language: 'en', territory: 'US' };
204
+ const pendingContext = { language: 'en' };
205
+
206
+ expect(hasPendingContextChanges(context, pendingContext)).toBe(true);
207
+ });
208
+
209
+ test('handles undefined values', () => {
210
+ const context = { language: 'en', territory: undefined };
211
+ const pendingContext = { language: 'en', territory: undefined };
212
+
213
+ expect(hasPendingContextChanges(context, pendingContext)).toBe(false);
214
+ });
215
+
216
+ test('detects undefined to string changes', () => {
217
+ const context = { language: 'en', territory: undefined };
218
+ const pendingContext = { language: 'en', territory: 'US' };
219
+
220
+ expect(hasPendingContextChanges(context, pendingContext)).toBe(true);
221
+ });
222
+
223
+ test('handles empty objects', () => {
224
+ expect(hasPendingContextChanges({}, {})).toBe(false);
225
+ });
226
+
227
+ test('detects changes in empty vs non-empty', () => {
228
+ expect(hasPendingContextChanges({}, { language: 'en' })).toBe(true);
229
+ expect(hasPendingContextChanges({ language: 'en' }, {})).toBe(true);
230
+ });
231
+ });
232
+
233
+ describe('evaluateConditionsForCandidate', () => {
234
+ test('returns empty array when decision is missing', () => {
235
+ const invalidCompiled = { decision: 999 }; // Non-existent decision
236
+
237
+ const result = evaluateConditionsForCandidate(
238
+ mockResolver,
239
+ 0,
240
+ invalidCompiled,
241
+ mockProcessedResources.compiledCollection
242
+ );
243
+
244
+ expect(result).toEqual([]);
245
+ });
246
+
247
+ test('returns empty array when candidate index is out of range', () => {
248
+ const compiled = { decision: 0 };
249
+
250
+ const result = evaluateConditionsForCandidate(
251
+ mockResolver,
252
+ 999, // Out of range
253
+ compiled,
254
+ mockProcessedResources.compiledCollection
255
+ );
256
+
257
+ expect(result).toEqual([]);
258
+ });
259
+
260
+ test('evaluates conditions successfully', () => {
261
+ const compiled = { decision: 0 };
262
+
263
+ const result = evaluateConditionsForCandidate(
264
+ mockResolver,
265
+ 0,
266
+ compiled,
267
+ mockProcessedResources.compiledCollection
268
+ );
269
+
270
+ expect(result).toHaveLength(1);
271
+ expect(result[0]).toEqual({
272
+ qualifierName: 'language',
273
+ qualifierValue: 'en',
274
+ conditionValue: 'en',
275
+ operator: 'matches',
276
+ score: 1.0,
277
+ matched: true,
278
+ matchType: 'match',
279
+ scoreAsDefault: undefined,
280
+ conditionIndex: 0
281
+ });
282
+ });
283
+
284
+ test('handles missing condition sets', () => {
285
+ const compiledCollectionWithoutConditionSets = {
286
+ ...mockProcessedResources.compiledCollection,
287
+ decisions: [{ conditionSets: undefined }]
288
+ };
289
+ const compiled = { decision: 0 };
290
+
291
+ const result = evaluateConditionsForCandidate(
292
+ mockResolver,
293
+ 0,
294
+ compiled,
295
+ compiledCollectionWithoutConditionSets
296
+ );
297
+
298
+ expect(result).toEqual([]);
299
+ });
300
+
301
+ test('handles resolver errors gracefully', () => {
302
+ const errorResolver = {
303
+ ...mockResolver,
304
+ contextQualifierProvider: {
305
+ get: jest.fn().mockImplementation(() => {
306
+ throw new Error('Context error');
307
+ })
308
+ }
309
+ };
310
+ const compiled = { decision: 0 };
311
+
312
+ const result = evaluateConditionsForCandidate(
313
+ errorResolver,
314
+ 0,
315
+ compiled,
316
+ mockProcessedResources.compiledCollection
317
+ );
318
+
319
+ expect(result).toEqual([]);
320
+ });
321
+
322
+ test('handles missing cache entries', () => {
323
+ const resolverWithoutCache = {
324
+ ...mockResolver,
325
+ conditionCache: {}
326
+ };
327
+ const compiled = { decision: 0 };
328
+
329
+ const result = evaluateConditionsForCandidate(
330
+ resolverWithoutCache,
331
+ 0,
332
+ compiled,
333
+ mockProcessedResources.compiledCollection
334
+ );
335
+
336
+ expect(result).toHaveLength(1);
337
+ expect(result[0].score).toBe(0);
338
+ expect(result[0].matchType).toBe('noMatch');
339
+ expect(result[0].matched).toBe(false);
340
+ });
341
+ });
342
+
343
+ describe('resolveResourceDetailed', () => {
344
+ beforeEach(() => {
345
+ const mockResource = {
346
+ id: 'resource1',
347
+ candidates: [{ value: 'candidate1' }, { value: 'candidate2' }],
348
+ decision: {
349
+ baseDecision: {}
350
+ }
351
+ };
352
+
353
+ mockProcessedResources.system.resourceManager.getBuiltResource = jest
354
+ .fn()
355
+ .mockReturnValue(succeed(mockResource));
356
+
357
+ mockResolver.resolveResource = jest.fn().mockReturnValue(succeed(mockResource.candidates[0]));
358
+ mockResolver.resolveAllResourceCandidates = jest
359
+ .fn()
360
+ .mockReturnValue(succeed([mockResource.candidates[0]]));
361
+ mockResolver.resolveComposedResourceValue = jest.fn().mockReturnValue(succeed('composed value'));
362
+ mockResolver.resolveDecision = jest.fn().mockReturnValue(
363
+ succeed({
364
+ success: true,
365
+ instanceIndices: [0],
366
+ defaultInstanceIndices: []
367
+ })
368
+ );
369
+ });
370
+
371
+ test('resolves resource successfully', () => {
372
+ const result = resolveResourceDetailed(mockResolver, 'resource1', mockProcessedResources);
373
+
374
+ expect(result).toSucceedAndSatisfy((resolution: ResolutionResult) => {
375
+ expect(resolution.success).toBe(true);
376
+ expect(resolution.resourceId).toBe('resource1');
377
+ expect(resolution.resource).toBeDefined();
378
+ expect(resolution.bestCandidate).toBeDefined();
379
+ expect(resolution.allCandidates).toBeDefined();
380
+ expect(resolution.candidateDetails).toBeInstanceOf(Array);
381
+ expect(resolution.candidateDetails).toBeDefined();
382
+ expect(resolution.composedValue).toBe('composed value');
383
+ });
384
+ });
385
+
386
+ test('handles resource not found', () => {
387
+ mockProcessedResources.system.resourceManager.getBuiltResource = jest
388
+ .fn()
389
+ .mockReturnValue(fail('Resource not found'));
390
+
391
+ const result = resolveResourceDetailed(mockResolver, 'nonexistent', mockProcessedResources);
392
+
393
+ expect(result).toSucceedAndSatisfy((resolution: ResolutionResult) => {
394
+ expect(resolution.success).toBe(false);
395
+ expect(resolution.resourceId).toBe('nonexistent');
396
+ expect(resolution.error).toBe('Failed to get resource: Resource not found');
397
+ });
398
+ });
399
+
400
+ test('handles missing compiled resource', () => {
401
+ const resourcesWithoutCompiledResource = {
402
+ ...mockProcessedResources,
403
+ compiledCollection: {
404
+ ...mockProcessedResources.compiledCollection,
405
+ resources: [] // Empty resources array
406
+ }
407
+ };
408
+
409
+ const result = resolveResourceDetailed(mockResolver, 'resource1', resourcesWithoutCompiledResource);
410
+
411
+ expect(result).toSucceedAndSatisfy((resolution: ResolutionResult) => {
412
+ expect(resolution.success).toBe(false);
413
+ expect(resolution.resourceId).toBe('resource1');
414
+ expect(resolution.error).toBe('Failed to find compiled resource');
415
+ });
416
+ });
417
+
418
+ test('handles decision resolution failure', () => {
419
+ mockResolver.resolveDecision = jest.fn().mockReturnValue(fail('Decision failed'));
420
+
421
+ const result = resolveResourceDetailed(mockResolver, 'resource1', mockProcessedResources);
422
+
423
+ expect(result).toSucceedAndSatisfy((resolution: ResolutionResult) => {
424
+ expect(resolution.success).toBe(false);
425
+ expect(resolution.resourceId).toBe('resource1');
426
+ expect(resolution.error).toBe('Failed to resolve decision: Decision failed');
427
+ });
428
+ });
429
+
430
+ test('handles resolution options with debug logging', () => {
431
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
432
+
433
+ const result = resolveResourceDetailed(mockResolver, 'resource1', mockProcessedResources, {
434
+ enableDebugLogging: true
435
+ });
436
+
437
+ expect(result).toSucceed();
438
+ expect(consoleSpy).toHaveBeenCalledWith('=== RESOLVING RESOURCE ===');
439
+ expect(consoleSpy).toHaveBeenCalledWith('Resource ID:', 'resource1');
440
+
441
+ consoleSpy.mockRestore();
442
+ });
443
+
444
+ test('builds candidate details with match information', () => {
445
+ const result = resolveResourceDetailed(mockResolver, 'resource1', mockProcessedResources);
446
+
447
+ expect(result).toSucceedAndSatisfy((resolution: ResolutionResult) => {
448
+ expect(resolution.candidateDetails).toBeDefined();
449
+ if (resolution.candidateDetails) {
450
+ expect(resolution.candidateDetails).toHaveLength(2);
451
+
452
+ // First candidate should be matched
453
+ const matchedCandidate = resolution.candidateDetails.find((c) => c.matched);
454
+ expect(matchedCandidate).toBeDefined();
455
+ expect(matchedCandidate!.matchType).toBe('match');
456
+ expect(matchedCandidate!.isDefaultMatch).toBe(false);
457
+
458
+ // Second candidate should be non-matched
459
+ const nonMatchedCandidate = resolution.candidateDetails.find((c) => !c.matched);
460
+ expect(nonMatchedCandidate).toBeDefined();
461
+ expect(nonMatchedCandidate!.matchType).toBe('noMatch');
462
+ expect(nonMatchedCandidate!.isDefaultMatch).toBe(false);
463
+ }
464
+ });
465
+ });
466
+
467
+ test('handles resolver failures gracefully', () => {
468
+ mockResolver.resolveResource = jest.fn().mockReturnValue(fail('Resolution failed'));
469
+
470
+ const result = resolveResourceDetailed(mockResolver, 'resource1', mockProcessedResources);
471
+
472
+ expect(result).toSucceedAndSatisfy((resolution: ResolutionResult) => {
473
+ expect(resolution.success).toBe(true); // Still succeeds but with error info
474
+ expect(resolution.error).toBe('Resolution failed');
475
+ expect(resolution.bestCandidate).toBeUndefined();
476
+ });
477
+ });
478
+ });
479
+
480
+ describe('createResolverWithContext', () => {
481
+ test('filters undefined context values', () => {
482
+ const contextValues = {
483
+ language: 'en',
484
+ territory: undefined,
485
+ platform: 'web'
486
+ };
487
+
488
+ // We can't easily test the internal filtering without complex mocking,
489
+ // so we test that the function doesn't throw with undefined values
490
+ expect(() => createResolverWithContext(mockProcessedResources, contextValues)).not.toThrow();
491
+ });
492
+
493
+ test('handles empty context', () => {
494
+ const result = createResolverWithContext(mockProcessedResources, {});
495
+
496
+ // Should attempt to create resolver even with empty context
497
+ expect(result).toBeDefined();
498
+ });
499
+
500
+ test('applies debug logging option', () => {
501
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
502
+
503
+ createResolverWithContext(mockProcessedResources, { language: 'en' }, { enableDebugLogging: true });
504
+
505
+ expect(consoleSpy).toHaveBeenCalledWith('=== CREATING RESOLVER WITH CONTEXT ===');
506
+
507
+ consoleSpy.mockRestore();
508
+ });
509
+
510
+ test('applies caching option', () => {
511
+ // Test that caching option is processed without errors
512
+ const result = createResolverWithContext(
513
+ mockProcessedResources,
514
+ { language: 'en' },
515
+ { enableCaching: true }
516
+ );
517
+
518
+ expect(result).toBeDefined();
519
+ });
520
+ });
521
+ });