@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,574 @@
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
+ hasFilterValues,
27
+ getFilterSummary,
28
+ createFilteredResourceManagerSimple,
29
+ analyzeFilteredResources,
30
+ type FilterOptions,
31
+ type FilteredResource
32
+ } from '../../../utils/filterResources';
33
+ import { ProcessedResources } from '../../../types';
34
+
35
+ // Mock the tsResIntegration module
36
+ jest.mock('../../../utils/tsResIntegration', () => ({
37
+ createCompiledResourceCollectionManager: jest.fn().mockReturnValue(succeed({}))
38
+ }));
39
+
40
+ describe('filterResources utilities', () => {
41
+ describe('hasFilterValues', () => {
42
+ test('returns false for empty object', () => {
43
+ expect(hasFilterValues({})).toBe(false);
44
+ });
45
+
46
+ test('returns false for object with all undefined values', () => {
47
+ expect(
48
+ hasFilterValues({
49
+ key1: undefined,
50
+ key2: undefined
51
+ })
52
+ ).toBe(false);
53
+ });
54
+
55
+ test('returns false for object with all empty string values', () => {
56
+ expect(
57
+ hasFilterValues({
58
+ key1: '',
59
+ key2: ''
60
+ })
61
+ ).toBe(false);
62
+ });
63
+
64
+ test('returns false for object with mixed undefined and empty string values', () => {
65
+ expect(
66
+ hasFilterValues({
67
+ key1: undefined,
68
+ key2: '',
69
+ key3: undefined
70
+ })
71
+ ).toBe(false);
72
+ });
73
+
74
+ test('returns true for object with at least one meaningful value', () => {
75
+ expect(
76
+ hasFilterValues({
77
+ key1: undefined,
78
+ key2: '',
79
+ key3: 'value'
80
+ })
81
+ ).toBe(true);
82
+ });
83
+
84
+ test('returns true for object with multiple meaningful values', () => {
85
+ expect(
86
+ hasFilterValues({
87
+ key1: 'value1',
88
+ key2: 'value2'
89
+ })
90
+ ).toBe(true);
91
+ });
92
+
93
+ test('returns true for object with meaningful value containing spaces', () => {
94
+ expect(
95
+ hasFilterValues({
96
+ key1: ' value with spaces '
97
+ })
98
+ ).toBe(true);
99
+ });
100
+
101
+ test('returns true for object with zero as string value', () => {
102
+ expect(
103
+ hasFilterValues({
104
+ key1: '0'
105
+ })
106
+ ).toBe(true);
107
+ });
108
+
109
+ test('returns true for object with boolean string values', () => {
110
+ expect(
111
+ hasFilterValues({
112
+ key1: 'false'
113
+ })
114
+ ).toBe(true);
115
+ });
116
+ });
117
+
118
+ describe('getFilterSummary', () => {
119
+ test('returns "No filters" for empty object', () => {
120
+ expect(getFilterSummary({})).toBe('No filters');
121
+ });
122
+
123
+ test('returns "No filters" for object with all undefined values', () => {
124
+ expect(
125
+ getFilterSummary({
126
+ key1: undefined,
127
+ key2: undefined
128
+ })
129
+ ).toBe('No filters');
130
+ });
131
+
132
+ test('returns "No filters" for object with all empty string values', () => {
133
+ expect(
134
+ getFilterSummary({
135
+ key1: '',
136
+ key2: ''
137
+ })
138
+ ).toBe('No filters');
139
+ });
140
+
141
+ test('returns summary for single active filter', () => {
142
+ expect(
143
+ getFilterSummary({
144
+ language: 'en',
145
+ territory: undefined
146
+ })
147
+ ).toBe('language=en');
148
+ });
149
+
150
+ test('returns summary for multiple active filters', () => {
151
+ const result = getFilterSummary({
152
+ language: 'en',
153
+ territory: 'US',
154
+ platform: undefined
155
+ });
156
+
157
+ // Check that it contains both filters (order may vary)
158
+ expect(result).toMatch(/language=en/);
159
+ expect(result).toMatch(/territory=US/);
160
+ expect(result).toMatch(/,/); // Should contain comma separator
161
+ });
162
+
163
+ test('returns summary ignoring empty and undefined values', () => {
164
+ expect(
165
+ getFilterSummary({
166
+ empty: '',
167
+ undefined: undefined,
168
+ language: 'fr',
169
+ nullish: undefined,
170
+ territory: 'CA'
171
+ })
172
+ ).toMatch(/language=fr.*territory=CA|territory=CA.*language=fr/);
173
+ });
174
+
175
+ test('handles special characters in values', () => {
176
+ expect(
177
+ getFilterSummary({
178
+ language: 'en-US',
179
+ territory: 'US/CA'
180
+ })
181
+ ).toMatch(/language=en-US.*territory=US\/CA|territory=US\/CA.*language=en-US/);
182
+ });
183
+
184
+ test('handles numeric string values', () => {
185
+ expect(
186
+ getFilterSummary({
187
+ version: '1.0',
188
+ level: '0'
189
+ })
190
+ ).toMatch(/version=1\.0.*level=0|level=0.*version=1\.0/);
191
+ });
192
+ });
193
+
194
+ describe('createFilteredResourceManagerSimple', () => {
195
+ let mockResourceManager: any;
196
+ let mockSystem: any;
197
+
198
+ beforeEach(() => {
199
+ mockResourceManager = {
200
+ getCompiledResourceCollection: jest.fn(),
201
+ validateContext: jest.fn(),
202
+ clone: jest.fn(),
203
+ resources: new Map([
204
+ ['resource1', {}],
205
+ ['resource2', {}]
206
+ ])
207
+ };
208
+
209
+ mockSystem = {
210
+ resourceManager: mockResourceManager,
211
+ qualifiers: {},
212
+ qualifierTypes: {
213
+ values: jest.fn().mockReturnValue([])
214
+ },
215
+ resourceTypes: {
216
+ values: jest.fn().mockReturnValue([])
217
+ },
218
+ importManager: {},
219
+ contextQualifierProvider: {
220
+ create: jest.fn()
221
+ }
222
+ };
223
+ });
224
+
225
+ test('returns error when original system is undefined', async () => {
226
+ const result = await createFilteredResourceManagerSimple(
227
+ undefined as any,
228
+ {},
229
+ { partialContextMatch: true }
230
+ );
231
+
232
+ expect(result).toFail();
233
+ expect(result.message).toBe('Original system or resourceManager is undefined');
234
+ });
235
+
236
+ test('returns error when resourceManager is undefined', async () => {
237
+ const result = await createFilteredResourceManagerSimple(
238
+ { ...mockSystem, resourceManager: undefined },
239
+ {},
240
+ { partialContextMatch: true }
241
+ );
242
+
243
+ expect(result).toFail();
244
+ expect(result.message).toBe('Original system or resourceManager is undefined');
245
+ });
246
+
247
+ test('skips bundle resources and tries filtering', async () => {
248
+ // Make getCompiledResourceCollection fail to force the filtering path
249
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle available'));
250
+
251
+ // Mock the validation and clone steps to fail gracefully
252
+ mockResourceManager.validateContext.mockReturnValue(fail('Context validation failed'));
253
+
254
+ const result = await createFilteredResourceManagerSimple(
255
+ mockSystem,
256
+ { language: 'en' },
257
+ { partialContextMatch: true }
258
+ );
259
+
260
+ expect(result).toFail();
261
+ expect(result.message).toContain('Failed to validate context or clone');
262
+ });
263
+
264
+ test('filters context values before processing', async () => {
265
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle'));
266
+ mockResourceManager.validateContext.mockReturnValue(succeed({}));
267
+ mockResourceManager.clone.mockReturnValue(fail('Clone failed'));
268
+
269
+ const partialContext = {
270
+ language: 'en',
271
+ territory: undefined,
272
+ platform: 'web'
273
+ };
274
+
275
+ await createFilteredResourceManagerSimple(mockSystem, partialContext);
276
+
277
+ expect(mockResourceManager.validateContext).toHaveBeenCalledWith({
278
+ language: 'en',
279
+ platform: 'web'
280
+ });
281
+ });
282
+
283
+ test('handles validation context error', async () => {
284
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle'));
285
+ mockResourceManager.validateContext.mockReturnValue(fail('Invalid context'));
286
+
287
+ const result = await createFilteredResourceManagerSimple(mockSystem, { language: 'invalid' });
288
+
289
+ expect(result).toFail();
290
+ expect(result.message).toContain('Failed to validate context or clone');
291
+ });
292
+
293
+ test('handles clone error', async () => {
294
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle'));
295
+ mockResourceManager.validateContext.mockReturnValue(succeed({}));
296
+ mockResourceManager.clone.mockReturnValue(fail('Clone failed'));
297
+
298
+ const result = await createFilteredResourceManagerSimple(mockSystem, { language: 'en' });
299
+
300
+ expect(result).toFail();
301
+ expect(result.message).toContain('Failed to validate context or clone');
302
+ });
303
+
304
+ test('passes options to clone correctly', async () => {
305
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle'));
306
+ mockResourceManager.validateContext.mockReturnValue(succeed({ language: 'en' }));
307
+ mockResourceManager.clone.mockReturnValue(fail('Clone failed for test'));
308
+
309
+ const options: FilterOptions = {
310
+ partialContextMatch: false,
311
+ enableDebugLogging: true,
312
+ reduceQualifiers: true
313
+ };
314
+
315
+ await createFilteredResourceManagerSimple(mockSystem, { language: 'en' }, options);
316
+
317
+ expect(mockResourceManager.clone).toHaveBeenCalledWith({
318
+ filterForContext: { language: 'en' },
319
+ reduceQualifiers: true
320
+ });
321
+ });
322
+
323
+ test('handles debug logging option', async () => {
324
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
325
+
326
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle'));
327
+ mockResourceManager.validateContext.mockReturnValue(succeed({}));
328
+ mockResourceManager.clone.mockReturnValue(fail('Clone failed'));
329
+
330
+ await createFilteredResourceManagerSimple(mockSystem, { language: 'en' }, { enableDebugLogging: true });
331
+
332
+ expect(consoleSpy).toHaveBeenCalledWith('=== SIMPLE FILTER CREATION ===');
333
+ expect(consoleSpy).toHaveBeenCalledWith('Original system:', mockSystem);
334
+
335
+ consoleSpy.mockRestore();
336
+ });
337
+
338
+ test('does not log when debug logging is disabled', async () => {
339
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
340
+
341
+ mockResourceManager.getCompiledResourceCollection.mockReturnValue(fail('No bundle'));
342
+ mockResourceManager.validateContext.mockReturnValue(succeed({}));
343
+ mockResourceManager.clone.mockReturnValue(fail('Clone failed'));
344
+
345
+ await createFilteredResourceManagerSimple(
346
+ mockSystem,
347
+ { language: 'en' },
348
+ { enableDebugLogging: false }
349
+ );
350
+
351
+ expect(consoleSpy).not.toHaveBeenCalled();
352
+
353
+ consoleSpy.mockRestore();
354
+ });
355
+ });
356
+
357
+ describe('analyzeFilteredResources', () => {
358
+ let mockOriginalProcessedResources: ProcessedResources;
359
+ let mockFilteredProcessedResources: ProcessedResources;
360
+
361
+ beforeEach(() => {
362
+ mockOriginalProcessedResources = {
363
+ system: {
364
+ resourceManager: {
365
+ getBuiltResource: jest.fn()
366
+ } as any,
367
+ qualifiers: {} as any, // TODO: Fix mock types after refactoring
368
+ qualifierTypes: {} as any,
369
+ resourceTypes: {} as any,
370
+ importManager: {} as any,
371
+ contextQualifierProvider: {} as any
372
+ },
373
+ compiledCollection: {} as any,
374
+ resolver: {} as any,
375
+ resourceCount: 2,
376
+ summary: {
377
+ totalResources: 2,
378
+ resourceIds: [],
379
+ errorCount: 0,
380
+ warnings: []
381
+ }
382
+ };
383
+
384
+ mockFilteredProcessedResources = {
385
+ system: {
386
+ resourceManager: {
387
+ getBuiltResource: jest.fn()
388
+ } as any,
389
+ qualifiers: {} as any, // TODO: Fix mock types after refactoring
390
+ qualifierTypes: {} as any,
391
+ resourceTypes: {} as any,
392
+ importManager: {} as any,
393
+ contextQualifierProvider: {} as any
394
+ },
395
+ compiledCollection: {} as any,
396
+ resolver: {} as any,
397
+ resourceCount: 1,
398
+ summary: {
399
+ totalResources: 1,
400
+ resourceIds: [],
401
+ errorCount: 0,
402
+ warnings: []
403
+ }
404
+ };
405
+ });
406
+
407
+ test('analyzes resources with successful filtering', () => {
408
+ const originalGetBuiltResource = mockOriginalProcessedResources.system.resourceManager
409
+ .getBuiltResource as jest.Mock;
410
+ const filteredGetBuiltResource = mockFilteredProcessedResources.system.resourceManager
411
+ .getBuiltResource as jest.Mock;
412
+
413
+ originalGetBuiltResource
414
+ .mockReturnValueOnce(succeed({ candidates: ['c1', 'c2'] }))
415
+ .mockReturnValueOnce(succeed({ candidates: ['c3'] }));
416
+
417
+ filteredGetBuiltResource
418
+ .mockReturnValueOnce(succeed({ candidates: ['c1'] }))
419
+ .mockReturnValueOnce(succeed({ candidates: ['c3'] }));
420
+
421
+ const result = analyzeFilteredResources(
422
+ ['resource1', 'resource2'],
423
+ mockFilteredProcessedResources,
424
+ mockOriginalProcessedResources
425
+ );
426
+
427
+ expect(result.success).toBe(true);
428
+ expect(result.filteredResources).toHaveLength(2);
429
+
430
+ const resource1 = result.filteredResources[0];
431
+ expect(resource1.id).toBe('resource1');
432
+ expect(resource1.originalCandidateCount).toBe(2);
433
+ expect(resource1.filteredCandidateCount).toBe(1);
434
+ expect(resource1.hasWarning).toBe(false);
435
+
436
+ const resource2 = result.filteredResources[1];
437
+ expect(resource2.id).toBe('resource2');
438
+ expect(resource2.originalCandidateCount).toBe(1);
439
+ expect(resource2.filteredCandidateCount).toBe(1);
440
+ expect(resource2.hasWarning).toBe(false);
441
+
442
+ expect(result.warnings).toEqual([]);
443
+ expect(result.processedResources).toBe(mockFilteredProcessedResources);
444
+ });
445
+
446
+ test('detects resources filtered out completely', () => {
447
+ const originalGetBuiltResource = mockOriginalProcessedResources.system.resourceManager
448
+ .getBuiltResource as jest.Mock;
449
+ const filteredGetBuiltResource = mockFilteredProcessedResources.system.resourceManager
450
+ .getBuiltResource as jest.Mock;
451
+
452
+ originalGetBuiltResource.mockReturnValueOnce(succeed({ candidates: ['c1', 'c2'] }));
453
+
454
+ filteredGetBuiltResource.mockReturnValueOnce(succeed({ candidates: [] }));
455
+
456
+ const result = analyzeFilteredResources(
457
+ ['resource1'],
458
+ mockFilteredProcessedResources,
459
+ mockOriginalProcessedResources
460
+ );
461
+
462
+ expect(result.success).toBe(true);
463
+ expect(result.filteredResources).toHaveLength(1);
464
+
465
+ const resource1 = result.filteredResources[0];
466
+ expect(resource1.id).toBe('resource1');
467
+ expect(resource1.originalCandidateCount).toBe(2);
468
+ expect(resource1.filteredCandidateCount).toBe(0);
469
+ expect(resource1.hasWarning).toBe(true);
470
+
471
+ expect(result.warnings).toEqual(['Resource resource1 has no matching candidates after filtering']);
472
+ });
473
+
474
+ test('handles resources that failed to load originally', () => {
475
+ const originalGetBuiltResource = mockOriginalProcessedResources.system.resourceManager
476
+ .getBuiltResource as jest.Mock;
477
+ const filteredGetBuiltResource = mockFilteredProcessedResources.system.resourceManager
478
+ .getBuiltResource as jest.Mock;
479
+
480
+ originalGetBuiltResource.mockReturnValueOnce(fail('Resource not found'));
481
+
482
+ filteredGetBuiltResource.mockReturnValueOnce(fail('Resource not found'));
483
+
484
+ const result = analyzeFilteredResources(
485
+ ['resource1'],
486
+ mockFilteredProcessedResources,
487
+ mockOriginalProcessedResources
488
+ );
489
+
490
+ expect(result.success).toBe(true);
491
+ expect(result.filteredResources).toHaveLength(1);
492
+
493
+ const resource1 = result.filteredResources[0];
494
+ expect(resource1.id).toBe('resource1');
495
+ expect(resource1.originalCandidateCount).toBe(0);
496
+ expect(resource1.filteredCandidateCount).toBe(0);
497
+ expect(resource1.hasWarning).toBe(false);
498
+
499
+ expect(result.warnings).toEqual([]);
500
+ });
501
+
502
+ test('handles resources that failed to load after filtering', () => {
503
+ const originalGetBuiltResource = mockOriginalProcessedResources.system.resourceManager
504
+ .getBuiltResource as jest.Mock;
505
+ const filteredGetBuiltResource = mockFilteredProcessedResources.system.resourceManager
506
+ .getBuiltResource as jest.Mock;
507
+
508
+ originalGetBuiltResource.mockReturnValueOnce(succeed({ candidates: ['c1'] }));
509
+
510
+ filteredGetBuiltResource.mockReturnValueOnce(fail('Resource filtered out'));
511
+
512
+ const result = analyzeFilteredResources(
513
+ ['resource1'],
514
+ mockFilteredProcessedResources,
515
+ mockOriginalProcessedResources
516
+ );
517
+
518
+ expect(result.success).toBe(true);
519
+ expect(result.filteredResources).toHaveLength(1);
520
+
521
+ const resource1 = result.filteredResources[0];
522
+ expect(resource1.id).toBe('resource1');
523
+ expect(resource1.originalCandidateCount).toBe(1);
524
+ expect(resource1.filteredCandidateCount).toBe(0);
525
+ expect(resource1.hasWarning).toBe(true);
526
+
527
+ expect(result.warnings).toContain('Resource resource1 has no matching candidates after filtering');
528
+ });
529
+
530
+ test('handles empty resource list', () => {
531
+ const result = analyzeFilteredResources(
532
+ [],
533
+ mockFilteredProcessedResources,
534
+ mockOriginalProcessedResources
535
+ );
536
+
537
+ expect(result.success).toBe(true);
538
+ expect(result.filteredResources).toEqual([]);
539
+ expect(result.warnings).toEqual([]);
540
+ });
541
+
542
+ test('handles mixed success and failure scenarios', () => {
543
+ const originalGetBuiltResource = mockOriginalProcessedResources.system.resourceManager
544
+ .getBuiltResource as jest.Mock;
545
+ const filteredGetBuiltResource = mockFilteredProcessedResources.system.resourceManager
546
+ .getBuiltResource as jest.Mock;
547
+
548
+ originalGetBuiltResource
549
+ .mockReturnValueOnce(succeed({ candidates: ['c1', 'c2'] }))
550
+ .mockReturnValueOnce(fail('Original failed'))
551
+ .mockReturnValueOnce(succeed({ candidates: ['c3'] }));
552
+
553
+ filteredGetBuiltResource
554
+ .mockReturnValueOnce(succeed({ candidates: [] }))
555
+ .mockReturnValueOnce(succeed({ candidates: ['c2'] }))
556
+ .mockReturnValueOnce(succeed({ candidates: ['c3'] }));
557
+
558
+ const result = analyzeFilteredResources(
559
+ ['resource1', 'resource2', 'resource3'],
560
+ mockFilteredProcessedResources,
561
+ mockOriginalProcessedResources
562
+ );
563
+
564
+ expect(result.success).toBe(true);
565
+ expect(result.filteredResources).toHaveLength(3);
566
+
567
+ expect(result.filteredResources[0].hasWarning).toBe(true); // Had candidates, now none
568
+ expect(result.filteredResources[1].hasWarning).toBe(false); // Originally failed, now has candidates
569
+ expect(result.filteredResources[2].hasWarning).toBe(false); // Same count
570
+
571
+ expect(result.warnings).toEqual(['Resource resource1 has no matching candidates after filtering']);
572
+ });
573
+ });
574
+ });