ckeditor5-symfony 1.0.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 (174) hide show
  1. package/dist/ckeditor5-symfony-error.d.ts +7 -0
  2. package/dist/ckeditor5-symfony-error.d.ts.map +1 -0
  3. package/dist/elements/context/context.d.ts +18 -0
  4. package/dist/elements/context/context.d.ts.map +1 -0
  5. package/dist/elements/context/contexts-registry.d.ts +9 -0
  6. package/dist/elements/context/contexts-registry.d.ts.map +1 -0
  7. package/dist/elements/context/index.d.ts +4 -0
  8. package/dist/elements/context/index.d.ts.map +1 -0
  9. package/dist/elements/context/typings.d.ts +34 -0
  10. package/dist/elements/context/typings.d.ts.map +1 -0
  11. package/dist/elements/editable.d.ts +18 -0
  12. package/dist/elements/editable.d.ts.map +1 -0
  13. package/dist/elements/editor/custom-editor-plugins.d.ts +54 -0
  14. package/dist/elements/editor/custom-editor-plugins.d.ts.map +1 -0
  15. package/dist/elements/editor/editor.d.ts +23 -0
  16. package/dist/elements/editor/editor.d.ts.map +1 -0
  17. package/dist/elements/editor/editors-registry.d.ts +9 -0
  18. package/dist/elements/editor/editors-registry.d.ts.map +1 -0
  19. package/dist/elements/editor/index.d.ts +3 -0
  20. package/dist/elements/editor/index.d.ts.map +1 -0
  21. package/dist/elements/editor/plugins/index.d.ts +2 -0
  22. package/dist/elements/editor/plugins/index.d.ts.map +1 -0
  23. package/dist/elements/editor/plugins/sync-editor-with-input.d.ts +6 -0
  24. package/dist/elements/editor/plugins/sync-editor-with-input.d.ts.map +1 -0
  25. package/dist/elements/editor/typings.d.ts +99 -0
  26. package/dist/elements/editor/typings.d.ts.map +1 -0
  27. package/dist/elements/editor/utils/create-editor-in-context.d.ts +44 -0
  28. package/dist/elements/editor/utils/create-editor-in-context.d.ts.map +1 -0
  29. package/dist/elements/editor/utils/index.d.ts +12 -0
  30. package/dist/elements/editor/utils/index.d.ts.map +1 -0
  31. package/dist/elements/editor/utils/is-single-root-editor.d.ts +9 -0
  32. package/dist/elements/editor/utils/is-single-root-editor.d.ts.map +1 -0
  33. package/dist/elements/editor/utils/load-editor-constructor.d.ts +9 -0
  34. package/dist/elements/editor/utils/load-editor-constructor.d.ts.map +1 -0
  35. package/dist/elements/editor/utils/load-editor-plugins.d.ts +20 -0
  36. package/dist/elements/editor/utils/load-editor-plugins.d.ts.map +1 -0
  37. package/dist/elements/editor/utils/load-editor-translations.d.ts +14 -0
  38. package/dist/elements/editor/utils/load-editor-translations.d.ts.map +1 -0
  39. package/dist/elements/editor/utils/normalize-custom-translations.d.ts +11 -0
  40. package/dist/elements/editor/utils/normalize-custom-translations.d.ts.map +1 -0
  41. package/dist/elements/editor/utils/query-all-editor-ids.d.ts +5 -0
  42. package/dist/elements/editor/utils/query-all-editor-ids.d.ts.map +1 -0
  43. package/dist/elements/editor/utils/query-editor-editables.d.ts +25 -0
  44. package/dist/elements/editor/utils/query-editor-editables.d.ts.map +1 -0
  45. package/dist/elements/editor/utils/resolve-editor-config-elements-references.d.ts +9 -0
  46. package/dist/elements/editor/utils/resolve-editor-config-elements-references.d.ts.map +1 -0
  47. package/dist/elements/editor/utils/set-editor-editable-height.d.ts +9 -0
  48. package/dist/elements/editor/utils/set-editor-editable-height.d.ts.map +1 -0
  49. package/dist/elements/editor/utils/wrap-with-watchdog.d.ts +24 -0
  50. package/dist/elements/editor/utils/wrap-with-watchdog.d.ts.map +1 -0
  51. package/dist/elements/index.d.ts +6 -0
  52. package/dist/elements/index.d.ts.map +1 -0
  53. package/dist/elements/register-custom-elements.d.ts +5 -0
  54. package/dist/elements/register-custom-elements.d.ts.map +1 -0
  55. package/dist/elements/ui-part.d.ts +18 -0
  56. package/dist/elements/ui-part.d.ts.map +1 -0
  57. package/dist/index.cjs +5 -0
  58. package/dist/index.cjs.map +1 -0
  59. package/dist/index.d.ts +3 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.mjs +1089 -0
  62. package/dist/index.mjs.map +1 -0
  63. package/dist/shared/async-registry.d.ts +136 -0
  64. package/dist/shared/async-registry.d.ts.map +1 -0
  65. package/dist/shared/camel-case.d.ts +8 -0
  66. package/dist/shared/camel-case.d.ts.map +1 -0
  67. package/dist/shared/debounce.d.ts +2 -0
  68. package/dist/shared/debounce.d.ts.map +1 -0
  69. package/dist/shared/deep-camel-case-keys.d.ts +8 -0
  70. package/dist/shared/deep-camel-case-keys.d.ts.map +1 -0
  71. package/dist/shared/filter-object-values.d.ts +9 -0
  72. package/dist/shared/filter-object-values.d.ts.map +1 -0
  73. package/dist/shared/index.d.ts +15 -0
  74. package/dist/shared/index.d.ts.map +1 -0
  75. package/dist/shared/is-empty-object.d.ts +2 -0
  76. package/dist/shared/is-empty-object.d.ts.map +1 -0
  77. package/dist/shared/is-plain-object.d.ts +8 -0
  78. package/dist/shared/is-plain-object.d.ts.map +1 -0
  79. package/dist/shared/map-object-values.d.ts +11 -0
  80. package/dist/shared/map-object-values.d.ts.map +1 -0
  81. package/dist/shared/once.d.ts +2 -0
  82. package/dist/shared/once.d.ts.map +1 -0
  83. package/dist/shared/shallow-equal.d.ts +9 -0
  84. package/dist/shared/shallow-equal.d.ts.map +1 -0
  85. package/dist/shared/timeout.d.ts +8 -0
  86. package/dist/shared/timeout.d.ts.map +1 -0
  87. package/dist/shared/uid.d.ts +7 -0
  88. package/dist/shared/uid.d.ts.map +1 -0
  89. package/dist/shared/wait-for-dom-ready.d.ts +5 -0
  90. package/dist/shared/wait-for-dom-ready.d.ts.map +1 -0
  91. package/dist/shared/wait-for.d.ts +20 -0
  92. package/dist/shared/wait-for.d.ts.map +1 -0
  93. package/dist/types/can-be-promise.type.d.ts +2 -0
  94. package/dist/types/can-be-promise.type.d.ts.map +1 -0
  95. package/dist/types/index.d.ts +3 -0
  96. package/dist/types/index.d.ts.map +1 -0
  97. package/dist/types/required-by.type.d.ts +2 -0
  98. package/dist/types/required-by.type.d.ts.map +1 -0
  99. package/package.json +40 -0
  100. package/src/ckeditor5-symfony-error.ts +9 -0
  101. package/src/elements/context/context.test.ts +291 -0
  102. package/src/elements/context/context.ts +99 -0
  103. package/src/elements/context/contexts-registry.test.ts +10 -0
  104. package/src/elements/context/contexts-registry.ts +10 -0
  105. package/src/elements/context/index.ts +3 -0
  106. package/src/elements/context/typings.ts +39 -0
  107. package/src/elements/editable.test.ts +334 -0
  108. package/src/elements/editable.ts +114 -0
  109. package/src/elements/editor/custom-editor-plugins.test.ts +103 -0
  110. package/src/elements/editor/custom-editor-plugins.ts +86 -0
  111. package/src/elements/editor/editor.test.ts +438 -0
  112. package/src/elements/editor/editor.ts +279 -0
  113. package/src/elements/editor/editors-registry.test.ts +10 -0
  114. package/src/elements/editor/editors-registry.ts +10 -0
  115. package/src/elements/editor/index.ts +2 -0
  116. package/src/elements/editor/plugins/index.ts +1 -0
  117. package/src/elements/editor/plugins/sync-editor-with-input.ts +78 -0
  118. package/src/elements/editor/typings.ts +114 -0
  119. package/src/elements/editor/utils/create-editor-in-context.ts +90 -0
  120. package/src/elements/editor/utils/index.ts +11 -0
  121. package/src/elements/editor/utils/is-single-root-editor.test.ts +40 -0
  122. package/src/elements/editor/utils/is-single-root-editor.ts +11 -0
  123. package/src/elements/editor/utils/load-editor-constructor.test.ts +62 -0
  124. package/src/elements/editor/utils/load-editor-constructor.ts +29 -0
  125. package/src/elements/editor/utils/load-editor-plugins.test.ts +100 -0
  126. package/src/elements/editor/utils/load-editor-plugins.ts +73 -0
  127. package/src/elements/editor/utils/load-editor-translations.ts +233 -0
  128. package/src/elements/editor/utils/normalize-custom-translations.test.ts +152 -0
  129. package/src/elements/editor/utils/normalize-custom-translations.ts +18 -0
  130. package/src/elements/editor/utils/query-all-editor-ids.ts +9 -0
  131. package/src/elements/editor/utils/query-editor-editables.ts +101 -0
  132. package/src/elements/editor/utils/resolve-editor-config-elements-references.test.ts +93 -0
  133. package/src/elements/editor/utils/resolve-editor-config-elements-references.ts +36 -0
  134. package/src/elements/editor/utils/set-editor-editable-height.test.ts +131 -0
  135. package/src/elements/editor/utils/set-editor-editable-height.ts +15 -0
  136. package/src/elements/editor/utils/wrap-with-watchdog.test.ts +45 -0
  137. package/src/elements/editor/utils/wrap-with-watchdog.ts +51 -0
  138. package/src/elements/index.ts +14 -0
  139. package/src/elements/register-custom-elements.ts +24 -0
  140. package/src/elements/ui-part.test.ts +142 -0
  141. package/src/elements/ui-part.ts +80 -0
  142. package/src/index.ts +6 -0
  143. package/src/shared/async-registry.test.ts +737 -0
  144. package/src/shared/async-registry.ts +353 -0
  145. package/src/shared/camel-case.test.ts +35 -0
  146. package/src/shared/camel-case.ts +11 -0
  147. package/src/shared/debounce.test.ts +72 -0
  148. package/src/shared/debounce.ts +16 -0
  149. package/src/shared/deep-camel-case-keys.test.ts +34 -0
  150. package/src/shared/deep-camel-case-keys.ts +26 -0
  151. package/src/shared/filter-object-values.test.ts +25 -0
  152. package/src/shared/filter-object-values.ts +17 -0
  153. package/src/shared/index.ts +14 -0
  154. package/src/shared/is-empty-object.test.ts +78 -0
  155. package/src/shared/is-empty-object.ts +3 -0
  156. package/src/shared/is-plain-object.test.ts +38 -0
  157. package/src/shared/is-plain-object.ts +15 -0
  158. package/src/shared/map-object-values.test.ts +29 -0
  159. package/src/shared/map-object-values.ts +19 -0
  160. package/src/shared/once.test.ts +116 -0
  161. package/src/shared/once.ts +12 -0
  162. package/src/shared/shallow-equal.test.ts +51 -0
  163. package/src/shared/shallow-equal.ts +30 -0
  164. package/src/shared/timeout.test.ts +65 -0
  165. package/src/shared/timeout.ts +13 -0
  166. package/src/shared/uid.test.ts +25 -0
  167. package/src/shared/uid.ts +8 -0
  168. package/src/shared/wait-for-dom-ready.test.ts +87 -0
  169. package/src/shared/wait-for-dom-ready.ts +21 -0
  170. package/src/shared/wait-for.test.ts +24 -0
  171. package/src/shared/wait-for.ts +56 -0
  172. package/src/types/can-be-promise.type.ts +1 -0
  173. package/src/types/index.ts +2 -0
  174. package/src/types/required-by.type.ts +1 -0
@@ -0,0 +1,737 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import type { Destructible } from './async-registry';
4
+
5
+ import { AsyncRegistry } from './async-registry';
6
+
7
+ describe('async registry', () => {
8
+ let registry: AsyncRegistry<Mockitem>;
9
+
10
+ beforeEach(() => {
11
+ registry = new AsyncRegistry<Mockitem>();
12
+ });
13
+
14
+ describe('register', () => {
15
+ it('should register an item with a given ID', () => {
16
+ const item = createMockItem('item1');
17
+
18
+ registry.register('item1', item);
19
+
20
+ expect(registry.getItems()).toContain(item);
21
+ });
22
+
23
+ it('should register the first item as the default (null ID) item', async () => {
24
+ const item1 = createMockItem('item1');
25
+
26
+ registry.register('item1', item1);
27
+
28
+ const promise = registry.execute(null, item => item);
29
+
30
+ await expect(promise).resolves.toBe(item1);
31
+ });
32
+
33
+ it('should not override the default item if one is already set', async () => {
34
+ const item1 = createMockItem('item1');
35
+ const item2 = createMockItem('item2');
36
+
37
+ registry.register('item1', item1);
38
+ registry.register('item2', item2);
39
+
40
+ const defaultitem = await registry.execute(null, item => item);
41
+
42
+ expect(defaultitem).toBe(item1);
43
+ expect(defaultitem).not.toBe(item2);
44
+ });
45
+
46
+ it('should throw an error if an item with the same ID is already registered', () => {
47
+ const item = createMockItem('item1');
48
+
49
+ registry.register('item1', item);
50
+ expect(() => registry.register('item1', item)).toThrow(
51
+ 'Item with ID "item1" is already registered.',
52
+ );
53
+ });
54
+
55
+ it('should reset errors when registering an item', () => {
56
+ const error = new Error('Initialization failed');
57
+ registry.error('item1', error);
58
+
59
+ const watcher = vi.fn();
60
+ registry.watch(watcher);
61
+
62
+ let errorsMap = getErrorsMap(watcher);
63
+ expect(errorsMap.get('item1')).toBe(error);
64
+
65
+ watcher.mockClear();
66
+
67
+ const item = createMockItem('item1');
68
+ registry.register('item1', item);
69
+
70
+ registry.watch(watcher);
71
+ errorsMap = getErrorsMap(watcher);
72
+ expect(errorsMap.has('item1')).toBe(false);
73
+ });
74
+
75
+ it('should execute pending callbacks when item is registered', async () => {
76
+ const callback = vi.fn((item: Mockitem) => `executed on ${item.name}`);
77
+
78
+ const promise = registry.execute('item1', callback);
79
+
80
+ expect(callback).not.toHaveBeenCalled();
81
+
82
+ const item = createMockItem('item1');
83
+ registry.register('item1', item);
84
+
85
+ await promise;
86
+
87
+ expect(callback).toHaveBeenCalledWith(item);
88
+ });
89
+ });
90
+
91
+ describe('unregister', () => {
92
+ it('should unregister an item', () => {
93
+ const item = createMockItem('item1');
94
+
95
+ registry.register('item1', item);
96
+ registry.unregister('item1');
97
+ expect(registry.getItems()).not.toContain(item);
98
+ });
99
+
100
+ it('should throw an error if trying to unregister an item that is not registered', () => {
101
+ expect(() => registry.unregister('nonexistent')).toThrow(
102
+ 'Item with ID "nonexistent" is not registered.',
103
+ );
104
+ });
105
+
106
+ it('should also unregister the default item if the unregistered item was the default one', async () => {
107
+ const item1 = createMockItem('item1');
108
+
109
+ registry.register('item1', item1); // This also registers it as default
110
+
111
+ // Check it is the default
112
+ const promise = registry.execute(null, item => item);
113
+
114
+ await expect(promise).resolves.toBe(item1);
115
+
116
+ registry.unregister('item1');
117
+
118
+ // Now check that the default is also gone
119
+ expect(() => registry.unregister(null)).toThrow(
120
+ 'Item with ID "null" is not registered.',
121
+ );
122
+ });
123
+ });
124
+
125
+ describe('execute', () => {
126
+ it('should execute a function on a registered item', async () => {
127
+ const item = createMockItem('item1');
128
+
129
+ registry.register('item1', item);
130
+ const result = await registry.execute(
131
+ 'item1',
132
+ (e: Mockitem) => `executed on ${e.name}`,
133
+ );
134
+
135
+ expect(result).toBe('executed on item1');
136
+ });
137
+
138
+ it('should queue a function to be executed when the item is registered', async () => {
139
+ const promise = registry.execute(
140
+ 'item1',
141
+ (e: Mockitem) => `executed on ${e.name}`,
142
+ );
143
+
144
+ // It shouldn't resolve yet
145
+ const onResolve = vi.fn();
146
+
147
+ void promise.then(onResolve);
148
+ await new Promise(resolve => setTimeout(resolve, 0)); // wait for promise to potentially resolve
149
+
150
+ expect(onResolve).not.toHaveBeenCalled();
151
+
152
+ const item = createMockItem('item1');
153
+
154
+ registry.register('item1', item);
155
+
156
+ const result = await promise;
157
+
158
+ expect(result).toBe('executed on item1');
159
+ expect(onResolve).toHaveBeenCalledWith('executed on item1');
160
+ });
161
+
162
+ it('should execute multiple queued functions for the same item', async () => {
163
+ const promise1 = registry.execute(
164
+ 'item1',
165
+ (e: Mockitem) => `executed 1 on ${e.name}`,
166
+ );
167
+ const promise2 = registry.execute(
168
+ 'item1',
169
+ (e: Mockitem) => `executed 2 on ${e.name}`,
170
+ );
171
+ const item = createMockItem('item1');
172
+
173
+ registry.register('item1', item);
174
+
175
+ const [result1, result2] = await Promise.all([promise1, promise2]);
176
+
177
+ expect(result1).toBe('executed 1 on item1');
178
+ expect(result2).toBe('executed 2 on item1');
179
+ });
180
+
181
+ it('should work with the default item', async () => {
182
+ const item1 = createMockItem('item1');
183
+
184
+ registry.register('item1', item1); // Registers as default
185
+
186
+ const result = await registry.execute(
187
+ null,
188
+ (e: Mockitem) => `executed on default ${e.name}`,
189
+ );
190
+
191
+ expect(result).toBe('executed on default item1');
192
+ });
193
+
194
+ it('should call onError callback if error exists for the item', async () => {
195
+ const error = new Error('Initialization failed');
196
+ const onError = vi.fn();
197
+
198
+ registry.error('item1', error);
199
+
200
+ const promise = registry.execute(
201
+ 'item1',
202
+ (e: Mockitem) => `executed on ${e.name}`,
203
+ onError,
204
+ );
205
+
206
+ await expect(promise).rejects.toThrow('Initialization failed');
207
+ expect(onError).toHaveBeenCalledWith(error);
208
+ });
209
+
210
+ it('should reject promise if error exists and no onError callback provided', async () => {
211
+ const error = new Error('Initialization failed');
212
+
213
+ registry.error('item1', error);
214
+
215
+ const promise = registry.execute(
216
+ 'item1',
217
+ (e: Mockitem) => `executed on ${e.name}`,
218
+ );
219
+
220
+ await expect(promise).rejects.toThrow('Initialization failed');
221
+ });
222
+
223
+ it('should reject promise when error is registered after execute without onError callback', async () => {
224
+ const error = new Error('Initialization failed');
225
+
226
+ const promise = registry.execute(
227
+ 'item1',
228
+ (e: Mockitem) => `executed on ${e.name}`,
229
+ );
230
+
231
+ registry.error('item1', error);
232
+
233
+ await expect(promise).rejects.toThrow('Initialization failed');
234
+ });
235
+ });
236
+
237
+ describe('getitems', () => {
238
+ it('should return all registered items', () => {
239
+ const item1 = createMockItem('item1');
240
+ const item2 = createMockItem('item2');
241
+
242
+ registry.register('item1', item1);
243
+ registry.register('item2', item2);
244
+
245
+ const items = registry.getItems();
246
+
247
+ expect(items).toHaveLength(3); // item1, item2, and default (which is item1)
248
+ expect(items).toContain(item1);
249
+ expect(items).toContain(item2);
250
+ });
251
+
252
+ it('should return unique items if some point to the same instance', () => {
253
+ const item1 = createMockItem('item1');
254
+
255
+ registry.register('item1', item1); // This also registers it as default
256
+
257
+ const items = registry.getItems();
258
+
259
+ expect(items).toHaveLength(2); // item1 and default (which is item1)
260
+ expect(items.filter(e => e === item1)).toHaveLength(2);
261
+ });
262
+ });
263
+
264
+ describe('hasitem', () => {
265
+ it('should return true if an item with the given ID is registered', () => {
266
+ const item = createMockItem('item1');
267
+
268
+ registry.register('item1', item);
269
+
270
+ expect(registry.hasItem('item1')).toBe(true);
271
+ });
272
+
273
+ it('should return false if an item with the given ID is not registered', () => {
274
+ expect(registry.hasItem('nonexistent')).toBe(false);
275
+ });
276
+ });
277
+
278
+ describe('error', () => {
279
+ it('should register an error for an item', () => {
280
+ const error = new Error('Initialization failed');
281
+
282
+ registry.error('item1', error);
283
+
284
+ const watcher = vi.fn();
285
+ registry.watch(watcher);
286
+
287
+ const errorsMap = getErrorsMap(watcher);
288
+ expect(errorsMap.get('item1')).toBe(error);
289
+ });
290
+
291
+ it('should set as default error if this is the first error and no items exist', () => {
292
+ const error = new Error('Initialization failed');
293
+
294
+ registry.error('item1', error);
295
+
296
+ const watcher = vi.fn();
297
+ registry.watch(watcher);
298
+
299
+ const errorsMap = getErrorsMap(watcher);
300
+ expect(errorsMap.get('item1')).toBe(error);
301
+ expect(errorsMap.get(null)).toBe(error);
302
+ });
303
+
304
+ it('should not set as default error if items already exist', () => {
305
+ const item = createMockItem('item1');
306
+ registry.register('item1', item);
307
+
308
+ const error = new Error('Initialization failed');
309
+ registry.error('item2', error);
310
+
311
+ const watcher = vi.fn();
312
+ registry.watch(watcher);
313
+
314
+ const errorsMap = getErrorsMap(watcher);
315
+ expect(errorsMap.get('item2')).toBe(error);
316
+ expect(errorsMap.get(null)).toBeUndefined();
317
+ });
318
+
319
+ it('should remove item if it was registered before error', () => {
320
+ const item = createMockItem('item1');
321
+ registry.register('item1', item);
322
+
323
+ expect(registry.hasItem('item1')).toBe(true);
324
+
325
+ const error = new Error('Initialization failed');
326
+ registry.error('item1', error);
327
+
328
+ expect(registry.hasItem('item1')).toBe(false);
329
+ });
330
+
331
+ it('should notify watchers when error is registered', () => {
332
+ const watcher = vi.fn();
333
+ registry.watch(watcher);
334
+
335
+ watcher.mockClear();
336
+
337
+ const error = new Error('Initialization failed');
338
+ registry.error('item1', error);
339
+
340
+ expect(watcher).toHaveBeenCalled();
341
+ });
342
+ });
343
+
344
+ describe('resetErrors', () => {
345
+ it('should reset errors for an item', () => {
346
+ const error = new Error('Initialization failed');
347
+ registry.error('item1', error);
348
+
349
+ const watcher = vi.fn();
350
+ registry.watch(watcher);
351
+
352
+ let errorsMap = getErrorsMap(watcher);
353
+ expect(errorsMap.get('item1')).toBe(error);
354
+
355
+ watcher.mockClear();
356
+ registry.resetErrors('item1');
357
+
358
+ registry.watch(watcher);
359
+ errorsMap = getErrorsMap(watcher);
360
+ expect(errorsMap.has('item1')).toBe(false);
361
+ });
362
+
363
+ it('should clear default error if it is the same as the specific error', () => {
364
+ const error = new Error('Initialization failed');
365
+ registry.error('item1', error);
366
+
367
+ const watcher = vi.fn();
368
+ registry.watch(watcher);
369
+
370
+ let errorsMap = getErrorsMap(watcher);
371
+ expect(errorsMap.get('item1')).toBe(error);
372
+ expect(errorsMap.get(null)).toBe(error);
373
+
374
+ watcher.mockClear();
375
+ registry.resetErrors('item1');
376
+
377
+ registry.watch(watcher);
378
+ errorsMap = getErrorsMap(watcher);
379
+
380
+ expect(errorsMap.has('item1')).toBe(false);
381
+ expect(errorsMap.has(null)).toBe(false);
382
+ });
383
+
384
+ it('should be called when registering an item after error', () => {
385
+ const error = new Error('Initialization failed');
386
+ registry.error('item1', error);
387
+
388
+ const watcher = vi.fn();
389
+ registry.watch(watcher);
390
+
391
+ let errorsMap = getErrorsMap(watcher);
392
+ expect(errorsMap.get('item1')).toBe(error);
393
+
394
+ watcher.mockClear();
395
+
396
+ const item = createMockItem('item1');
397
+ registry.register('item1', item);
398
+
399
+ registry.watch(watcher);
400
+ errorsMap = getErrorsMap(watcher);
401
+ expect(errorsMap.has('item1')).toBe(false);
402
+ });
403
+ });
404
+
405
+ describe('waitFor', () => {
406
+ it('should return a promise that resolves with the item instance', async () => {
407
+ const item1 = createMockItem('item1');
408
+ registry.register('item1', item1);
409
+
410
+ const result = await registry.waitFor('item1');
411
+
412
+ expect(result).toBe(item1);
413
+ });
414
+
415
+ it('should wait for the item to be registered before resolving', async () => {
416
+ const promise = registry.waitFor('item1');
417
+ const item1 = createMockItem('item1');
418
+
419
+ registry.register('item1', item1);
420
+
421
+ expect(await promise).toBe(item1);
422
+ });
423
+
424
+ it('should reject if error is registered after waitFor call', async () => {
425
+ const promise = registry.waitFor('item1');
426
+
427
+ registry.error('item1', 'Failed to initialize');
428
+
429
+ await expect(promise).rejects.toThrow('Failed to initialize');
430
+ });
431
+
432
+ it('should reject with timeout error if item is not registered within timeout', async () => {
433
+ vi.useFakeTimers();
434
+ const promise = registry.waitFor('item1', 100);
435
+
436
+ vi.advanceTimersByTime(100);
437
+
438
+ await expect(promise).rejects.toThrow('Timeout waiting for item with ID "item1" to be registered.');
439
+ vi.useRealTimers();
440
+ });
441
+
442
+ it('should cleanup timer when item is registered before timeout', async () => {
443
+ vi.useFakeTimers();
444
+ const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
445
+ const promise = registry.waitFor('item1', 100);
446
+
447
+ const item = createMockItem('item1');
448
+ registry.register('item1', item);
449
+
450
+ await promise;
451
+
452
+ expect(clearTimeoutSpy).toHaveBeenCalled();
453
+ vi.useRealTimers();
454
+ });
455
+
456
+ it('should cleanup timer when error occurs before timeout', async () => {
457
+ vi.useFakeTimers();
458
+ const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
459
+ const promise = registry.waitFor('item1', 100);
460
+
461
+ registry.error('item1', new Error('fail'));
462
+
463
+ await expect(promise).rejects.toThrow('fail');
464
+
465
+ expect(clearTimeoutSpy).toHaveBeenCalled();
466
+ vi.useRealTimers();
467
+ });
468
+
469
+ it('should not try to clear timer if timeout exceeded before item registration', async () => {
470
+ vi.useFakeTimers();
471
+ const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
472
+ const promise = registry.waitFor('item1', 100);
473
+
474
+ // Trigger timeout
475
+ vi.advanceTimersByTime(100);
476
+
477
+ await expect(promise).rejects.toThrow('Timeout');
478
+
479
+ // Clear spy to reset calls made by internal mechanisms
480
+ clearTimeoutSpy.mockClear();
481
+
482
+ // Now register item - verify logic in success callback
483
+ const item = createMockItem('item1');
484
+ registry.register('item1', item);
485
+
486
+ expect(clearTimeoutSpy).not.toHaveBeenCalled();
487
+
488
+ vi.useRealTimers();
489
+ });
490
+
491
+ it('should not try to clear timer if timeout exceeded before error registration', async () => {
492
+ vi.useFakeTimers();
493
+ const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
494
+ const promise = registry.waitFor('item1', 100);
495
+
496
+ // Trigger timeout
497
+ vi.advanceTimersByTime(100);
498
+
499
+ await expect(promise).rejects.toThrow('Timeout');
500
+
501
+ clearTimeoutSpy.mockClear();
502
+
503
+ // Now register error - verify logic in error callback
504
+ registry.error('item1', new Error('late fail'));
505
+
506
+ expect(clearTimeoutSpy).not.toHaveBeenCalled();
507
+
508
+ vi.useRealTimers();
509
+ });
510
+ });
511
+
512
+ describe('destroyAll', () => {
513
+ it('should destroy all registered items', async () => {
514
+ const item1 = createMockItem('item1');
515
+ const item2 = createMockItem('item2');
516
+
517
+ registry.register('item1', item1);
518
+ registry.register('item2', item2);
519
+
520
+ await registry.destroyAll();
521
+
522
+ expect(registry.getItems()).toHaveLength(0);
523
+ });
524
+
525
+ it('should clear the registry after destroying all items', async () => {
526
+ const item1 = createMockItem('item1');
527
+
528
+ registry.register('item1', item1);
529
+
530
+ await registry.destroyAll();
531
+
532
+ expect(registry.getItems()).toHaveLength(0);
533
+ });
534
+
535
+ it('should call destroy method on each unique item', async () => {
536
+ const destroyMock1 = vi.fn().mockResolvedValue(undefined);
537
+ const destroyMock2 = vi.fn().mockResolvedValue(undefined);
538
+
539
+ const item1 = {
540
+ name: 'item1',
541
+ destroy: destroyMock1,
542
+ } as unknown as Mockitem;
543
+
544
+ const item2 = {
545
+ name: 'item2',
546
+ destroy: destroyMock2,
547
+ } as unknown as Mockitem;
548
+
549
+ registry.register('item1', item1);
550
+ registry.register('item2', item2);
551
+
552
+ await registry.destroyAll();
553
+
554
+ expect(destroyMock1).toHaveBeenCalledOnce();
555
+ expect(destroyMock2).toHaveBeenCalledOnce();
556
+ });
557
+
558
+ it('should call destroy only once for items registered under multiple IDs', async () => {
559
+ const destroyMock = vi.fn().mockResolvedValue(undefined);
560
+ const item1 = {
561
+ name: 'item1',
562
+ destroy: destroyMock,
563
+ } as unknown as Mockitem;
564
+
565
+ registry.register('item1', item1);
566
+ await registry.destroyAll();
567
+
568
+ expect(destroyMock).toHaveBeenCalledOnce();
569
+ });
570
+ });
571
+
572
+ describe('watch/unwatch', () => {
573
+ it('should call watcher immediately with current items state', () => {
574
+ const item1 = createMockItem('item1');
575
+ registry.register('item1', item1);
576
+
577
+ const watcher = vi.fn();
578
+ registry.watch(watcher);
579
+
580
+ expect(watcher).toHaveBeenCalledOnce();
581
+ expect(watcher).toHaveBeenCalledWith(
582
+ expect.any(Map),
583
+ expect.any(Map),
584
+ );
585
+
586
+ const itemsMap = getItemsMap(watcher);
587
+ expect(itemsMap.get('item1')).toBe(item1);
588
+ expect(itemsMap.get(null)).toBe(item1);
589
+ });
590
+
591
+ it('should call watcher when item is registered', () => {
592
+ const watcher = vi.fn();
593
+ registry.watch(watcher);
594
+
595
+ expect(watcher).toHaveBeenCalledTimes(1);
596
+
597
+ const item1 = createMockItem('item1');
598
+ registry.register('item1', item1);
599
+
600
+ expect(watcher).toHaveBeenCalledTimes(3);
601
+ });
602
+
603
+ it('should call watcher when item is unregistered', () => {
604
+ const item1 = createMockItem('item1');
605
+ registry.register('item1', item1);
606
+
607
+ const watcher = vi.fn();
608
+ registry.watch(watcher);
609
+
610
+ watcher.mockClear();
611
+ registry.unregister('item1');
612
+
613
+ expect(watcher).toHaveBeenCalledTimes(2); // Unregister + default unregister
614
+ });
615
+
616
+ it('should call watcher when all items are destroyed', async () => {
617
+ const item1 = createMockItem('item1');
618
+ const item2 = createMockItem('item2');
619
+ registry.register('item1', item1);
620
+ registry.register('item2', item2);
621
+
622
+ const watcher = vi.fn();
623
+ registry.watch(watcher);
624
+
625
+ watcher.mockClear();
626
+ await registry.destroyAll();
627
+
628
+ expect(watcher).toHaveBeenCalledOnce();
629
+ expect(getItemsMap(watcher).size).toBe(0);
630
+ });
631
+
632
+ it('should allow multiple watchers', () => {
633
+ const watcher1 = vi.fn();
634
+ const watcher2 = vi.fn();
635
+
636
+ registry.watch(watcher1);
637
+ registry.watch(watcher2);
638
+
639
+ const item1 = createMockItem('item1');
640
+ registry.register('item1', item1);
641
+
642
+ expect(watcher1).toHaveBeenCalled();
643
+ expect(watcher2).toHaveBeenCalled();
644
+ });
645
+
646
+ it('should provide a copy of items map to watchers', () => {
647
+ const item1 = createMockItem('item1');
648
+ registry.register('item1', item1);
649
+
650
+ const watcher = vi.fn();
651
+ registry.watch(watcher);
652
+
653
+ getItemsMap(watcher).clear();
654
+ expect(registry.getItems()).toHaveLength(2); // Still has item1 and default
655
+ });
656
+
657
+ it('should stop calling watcher after unwatch', () => {
658
+ const watcher = vi.fn();
659
+ registry.watch(watcher);
660
+
661
+ registry.unwatch(watcher);
662
+ watcher.mockClear();
663
+
664
+ const item1 = createMockItem('item1');
665
+ registry.register('item1', item1);
666
+
667
+ expect(watcher).not.toHaveBeenCalled();
668
+ });
669
+
670
+ it('should return unwatch function from watch', () => {
671
+ const watcher = vi.fn();
672
+ const unwatch = registry.watch(watcher);
673
+
674
+ unwatch();
675
+ watcher.mockClear();
676
+
677
+ const item1 = createMockItem('item1');
678
+ registry.register('item1', item1);
679
+
680
+ expect(watcher).not.toHaveBeenCalled();
681
+ });
682
+
683
+ it('should handle unwatching non-existent watcher gracefully', () => {
684
+ const watcher = vi.fn();
685
+
686
+ expect(() => registry.unwatch(watcher)).not.toThrow();
687
+ });
688
+
689
+ it('should call watcher with errors map when error is registered', () => {
690
+ const watcher = vi.fn();
691
+ registry.watch(watcher);
692
+
693
+ watcher.mockClear();
694
+
695
+ const error = new Error('Initialization failed');
696
+ registry.error('item1', error);
697
+
698
+ expect(watcher).toHaveBeenCalled();
699
+ expect(getErrorsMap(watcher).get('item1')).toBe(error);
700
+ });
701
+
702
+ it('should provide a copy of errors map to watchers', () => {
703
+ const error = new Error('Test error');
704
+ registry.error('item1', error);
705
+
706
+ const watcher = vi.fn();
707
+ registry.watch(watcher);
708
+
709
+ getErrorsMap(watcher).clear();
710
+
711
+ watcher.mockClear();
712
+ registry.error('item2', new Error('Another error'));
713
+
714
+ expect(watcher).toHaveBeenCalled();
715
+ expect(getErrorsMap(watcher).has('item1')).toBe(true);
716
+ });
717
+ });
718
+ });
719
+
720
+ type Mockitem = Destructible & {
721
+ name: string;
722
+ };
723
+
724
+ function createMockItem(name: string): Mockitem {
725
+ return {
726
+ name,
727
+ destroy: () => {},
728
+ } as unknown as Mockitem;
729
+ }
730
+
731
+ function getItemsMap(watcher: ReturnType<typeof vi.fn>): Map<string | null, any> {
732
+ return watcher.mock.calls[0]![0];
733
+ }
734
+
735
+ function getErrorsMap(watcher: ReturnType<typeof vi.fn>): Map<string | null, Error> {
736
+ return watcher.mock.calls[0]![1];
737
+ }