@tldraw/utils 4.1.0-next.b6dfe9bccde9 → 4.1.0-next.b73a0d46b63f

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 (160) hide show
  1. package/dist-cjs/index.d.ts +1350 -80
  2. package/dist-cjs/index.js +5 -5
  3. package/dist-cjs/lib/ExecutionQueue.js +79 -0
  4. package/dist-cjs/lib/ExecutionQueue.js.map +2 -2
  5. package/dist-cjs/lib/PerformanceTracker.js +43 -0
  6. package/dist-cjs/lib/PerformanceTracker.js.map +2 -2
  7. package/dist-cjs/lib/array.js +3 -1
  8. package/dist-cjs/lib/array.js.map +2 -2
  9. package/dist-cjs/lib/bind.js.map +2 -2
  10. package/dist-cjs/lib/cache.js +27 -5
  11. package/dist-cjs/lib/cache.js.map +2 -2
  12. package/dist-cjs/lib/control.js +12 -0
  13. package/dist-cjs/lib/control.js.map +2 -2
  14. package/dist-cjs/lib/debounce.js.map +2 -2
  15. package/dist-cjs/lib/error.js.map +2 -2
  16. package/dist-cjs/lib/file.js +76 -11
  17. package/dist-cjs/lib/file.js.map +2 -2
  18. package/dist-cjs/lib/function.js.map +2 -2
  19. package/dist-cjs/lib/hash.js.map +2 -2
  20. package/dist-cjs/lib/id.js.map +2 -2
  21. package/dist-cjs/lib/iterable.js.map +2 -2
  22. package/dist-cjs/lib/json-value.js.map +1 -1
  23. package/dist-cjs/lib/media/apng.js.map +2 -2
  24. package/dist-cjs/lib/media/avif.js.map +2 -2
  25. package/dist-cjs/lib/media/gif.js.map +2 -2
  26. package/dist-cjs/lib/media/media.js +130 -4
  27. package/dist-cjs/lib/media/media.js.map +2 -2
  28. package/dist-cjs/lib/media/png.js +141 -0
  29. package/dist-cjs/lib/media/png.js.map +2 -2
  30. package/dist-cjs/lib/media/webp.js +1 -0
  31. package/dist-cjs/lib/media/webp.js.map +2 -2
  32. package/dist-cjs/lib/network.js.map +2 -2
  33. package/dist-cjs/lib/number.js.map +2 -2
  34. package/dist-cjs/lib/object.js +1 -1
  35. package/dist-cjs/lib/object.js.map +2 -2
  36. package/dist-cjs/lib/perf.js.map +2 -2
  37. package/dist-cjs/lib/reordering.js.map +2 -2
  38. package/dist-cjs/lib/retry.js.map +2 -2
  39. package/dist-cjs/lib/sort.js.map +2 -2
  40. package/dist-cjs/lib/storage.js.map +2 -2
  41. package/dist-cjs/lib/stringEnum.js.map +2 -2
  42. package/dist-cjs/lib/throttle.js.map +2 -2
  43. package/dist-cjs/lib/timers.js +103 -4
  44. package/dist-cjs/lib/timers.js.map +2 -2
  45. package/dist-cjs/lib/types.js.map +1 -1
  46. package/dist-cjs/lib/url.js.map +2 -2
  47. package/dist-cjs/lib/value.js.map +2 -2
  48. package/dist-cjs/lib/version.js.map +2 -2
  49. package/dist-cjs/lib/warn.js.map +2 -2
  50. package/dist-esm/index.d.mts +1350 -80
  51. package/dist-esm/index.mjs +1 -1
  52. package/dist-esm/lib/ExecutionQueue.mjs +79 -0
  53. package/dist-esm/lib/ExecutionQueue.mjs.map +2 -2
  54. package/dist-esm/lib/PerformanceTracker.mjs +43 -0
  55. package/dist-esm/lib/PerformanceTracker.mjs.map +2 -2
  56. package/dist-esm/lib/array.mjs +3 -1
  57. package/dist-esm/lib/array.mjs.map +2 -2
  58. package/dist-esm/lib/bind.mjs.map +2 -2
  59. package/dist-esm/lib/cache.mjs +27 -5
  60. package/dist-esm/lib/cache.mjs.map +2 -2
  61. package/dist-esm/lib/control.mjs +12 -0
  62. package/dist-esm/lib/control.mjs.map +2 -2
  63. package/dist-esm/lib/debounce.mjs.map +2 -2
  64. package/dist-esm/lib/error.mjs.map +2 -2
  65. package/dist-esm/lib/file.mjs +76 -11
  66. package/dist-esm/lib/file.mjs.map +2 -2
  67. package/dist-esm/lib/function.mjs.map +2 -2
  68. package/dist-esm/lib/hash.mjs.map +2 -2
  69. package/dist-esm/lib/id.mjs.map +2 -2
  70. package/dist-esm/lib/iterable.mjs.map +2 -2
  71. package/dist-esm/lib/media/apng.mjs.map +2 -2
  72. package/dist-esm/lib/media/avif.mjs.map +2 -2
  73. package/dist-esm/lib/media/gif.mjs.map +2 -2
  74. package/dist-esm/lib/media/media.mjs +130 -4
  75. package/dist-esm/lib/media/media.mjs.map +2 -2
  76. package/dist-esm/lib/media/png.mjs +141 -0
  77. package/dist-esm/lib/media/png.mjs.map +2 -2
  78. package/dist-esm/lib/media/webp.mjs +1 -0
  79. package/dist-esm/lib/media/webp.mjs.map +2 -2
  80. package/dist-esm/lib/network.mjs.map +2 -2
  81. package/dist-esm/lib/number.mjs.map +2 -2
  82. package/dist-esm/lib/object.mjs.map +2 -2
  83. package/dist-esm/lib/perf.mjs.map +2 -2
  84. package/dist-esm/lib/reordering.mjs.map +2 -2
  85. package/dist-esm/lib/retry.mjs.map +2 -2
  86. package/dist-esm/lib/sort.mjs.map +2 -2
  87. package/dist-esm/lib/storage.mjs.map +2 -2
  88. package/dist-esm/lib/stringEnum.mjs.map +2 -2
  89. package/dist-esm/lib/throttle.mjs.map +2 -2
  90. package/dist-esm/lib/timers.mjs +103 -4
  91. package/dist-esm/lib/timers.mjs.map +2 -2
  92. package/dist-esm/lib/url.mjs.map +2 -2
  93. package/dist-esm/lib/value.mjs.map +2 -2
  94. package/dist-esm/lib/version.mjs.map +2 -2
  95. package/dist-esm/lib/warn.mjs.map +2 -2
  96. package/package.json +1 -1
  97. package/src/lib/ExecutionQueue.test.ts +162 -20
  98. package/src/lib/ExecutionQueue.ts +110 -1
  99. package/src/lib/PerformanceTracker.test.ts +124 -0
  100. package/src/lib/PerformanceTracker.ts +63 -1
  101. package/src/lib/array.test.ts +263 -1
  102. package/src/lib/array.ts +183 -14
  103. package/src/lib/bind.test.ts +47 -0
  104. package/src/lib/bind.ts +69 -4
  105. package/src/lib/cache.test.ts +73 -0
  106. package/src/lib/cache.ts +47 -6
  107. package/src/lib/control.test.ts +50 -0
  108. package/src/lib/control.ts +198 -9
  109. package/src/lib/debounce.ts +28 -3
  110. package/src/lib/error.test.ts +60 -0
  111. package/src/lib/error.ts +27 -1
  112. package/src/lib/file.test.ts +49 -0
  113. package/src/lib/file.ts +117 -12
  114. package/src/lib/function.ts +11 -0
  115. package/src/lib/hash.test.ts +99 -0
  116. package/src/lib/hash.ts +69 -2
  117. package/src/lib/id.test.ts +32 -0
  118. package/src/lib/id.ts +53 -5
  119. package/src/lib/iterable.test.ts +25 -0
  120. package/src/lib/iterable.ts +4 -5
  121. package/src/lib/json-value.ts +71 -4
  122. package/src/lib/media/apng.test.ts +67 -0
  123. package/src/lib/media/apng.ts +38 -21
  124. package/src/lib/media/avif.test.ts +26 -0
  125. package/src/lib/media/avif.ts +34 -0
  126. package/src/lib/media/gif.test.ts +52 -0
  127. package/src/lib/media/gif.ts +25 -2
  128. package/src/lib/media/media.test.ts +58 -0
  129. package/src/lib/media/media.ts +220 -11
  130. package/src/lib/media/png.ts +162 -1
  131. package/src/lib/media/webp.test.ts +81 -0
  132. package/src/lib/media/webp.ts +33 -1
  133. package/src/lib/network.test.ts +38 -0
  134. package/src/lib/network.ts +6 -0
  135. package/src/lib/number.test.ts +74 -0
  136. package/src/lib/number.ts +29 -5
  137. package/src/lib/object.test.ts +236 -0
  138. package/src/lib/object.ts +194 -14
  139. package/src/lib/perf.ts +75 -3
  140. package/src/lib/reordering.test.ts +168 -0
  141. package/src/lib/reordering.ts +62 -4
  142. package/src/lib/retry.test.ts +77 -0
  143. package/src/lib/retry.ts +47 -1
  144. package/src/lib/sort.test.ts +36 -0
  145. package/src/lib/sort.ts +22 -1
  146. package/src/lib/storage.test.ts +130 -0
  147. package/src/lib/storage.tsx +54 -8
  148. package/src/lib/stringEnum.ts +20 -1
  149. package/src/lib/throttle.ts +46 -8
  150. package/src/lib/timers.test.ts +75 -0
  151. package/src/lib/timers.ts +124 -5
  152. package/src/lib/types.ts +126 -4
  153. package/src/lib/url.test.ts +44 -0
  154. package/src/lib/url.ts +40 -1
  155. package/src/lib/value.test.ts +102 -0
  156. package/src/lib/value.ts +67 -3
  157. package/src/lib/version.test.ts +494 -56
  158. package/src/lib/version.ts +36 -1
  159. package/src/lib/warn.test.ts +64 -0
  160. package/src/lib/warn.ts +43 -2
@@ -1,83 +1,521 @@
1
- import { vi } from 'vitest'
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
2
  import { clearRegisteredVersionsForTests, registerTldrawLibraryVersion } from './version'
3
3
 
4
- vi.useFakeTimers()
4
+ describe('version utilities', () => {
5
+ let mockConsoleLog: ReturnType<typeof vi.fn>
5
6
 
6
- describe('registerTldrawLibraryVersion', () => {
7
- afterEach(() => {
7
+ beforeEach(() => {
8
+ mockConsoleLog = vi.fn()
9
+ vi.stubGlobal('console', { log: mockConsoleLog })
10
+ vi.useFakeTimers()
8
11
  clearRegisteredVersionsForTests()
12
+ })
13
+
14
+ afterEach(() => {
15
+ vi.unstubAllGlobals()
9
16
  vi.restoreAllMocks()
17
+ vi.useRealTimers()
18
+ clearRegisteredVersionsForTests()
19
+ })
20
+
21
+ describe('clearRegisteredVersionsForTests', () => {
22
+ it('should clear all registered versions', () => {
23
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
24
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
25
+
26
+ clearRegisteredVersionsForTests()
27
+
28
+ // After clearing, registering the same versions should not trigger warnings
29
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
30
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
31
+
32
+ vi.runAllTimers()
33
+
34
+ expect(mockConsoleLog).not.toHaveBeenCalled()
35
+ })
36
+
37
+ it('should reset warning state', () => {
38
+ // Register conflicting versions to trigger warning
39
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
40
+ registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'esm')
41
+
42
+ vi.runAllTimers()
43
+
44
+ expect(mockConsoleLog).toHaveBeenCalled()
45
+ mockConsoleLog.mockClear()
46
+
47
+ clearRegisteredVersionsForTests()
48
+
49
+ // After clearing, same conflicting versions should trigger warning again
50
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
51
+ registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'esm')
52
+
53
+ vi.runAllTimers()
54
+
55
+ expect(mockConsoleLog).toHaveBeenCalled()
56
+ })
57
+
58
+ it('should clear scheduled timeout', () => {
59
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
60
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
61
+
62
+ clearRegisteredVersionsForTests()
63
+
64
+ expect(clearTimeoutSpy).toHaveBeenCalled()
65
+ })
66
+
67
+ it('should handle multiple calls without error', () => {
68
+ clearRegisteredVersionsForTests()
69
+ clearRegisteredVersionsForTests()
70
+ clearRegisteredVersionsForTests()
71
+
72
+ expect(() => clearRegisteredVersionsForTests()).not.toThrow()
73
+ })
74
+
75
+ it('should handle clearing when no timeout is scheduled', () => {
76
+ const _clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
77
+ clearRegisteredVersionsForTests()
78
+
79
+ // Should not throw when clearing empty state
80
+ expect(() => clearRegisteredVersionsForTests()).not.toThrow()
81
+ })
82
+ })
83
+
84
+ describe('registerTldrawLibraryVersion', () => {
85
+ it('should register a library version with valid parameters', () => {
86
+ expect(() => registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')).not.toThrow()
87
+
88
+ // Should schedule a timeout check
89
+ expect(vi.getTimerCount()).toBeGreaterThan(0)
90
+ })
91
+
92
+ it('should handle missing name parameter', () => {
93
+ expect(() => registerTldrawLibraryVersion(undefined, '2.0.0', 'esm')).not.toThrow()
94
+
95
+ // Should not schedule timeout when parameters are missing
96
+ expect(vi.getTimerCount()).toBe(0)
97
+ })
98
+
99
+ it('should handle missing version parameter', () => {
100
+ expect(() => registerTldrawLibraryVersion('@tldraw/editor', undefined, 'esm')).not.toThrow()
101
+
102
+ expect(vi.getTimerCount()).toBe(0)
103
+ })
104
+
105
+ it('should handle missing modules parameter', () => {
106
+ expect(() => registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', undefined)).not.toThrow()
107
+
108
+ expect(vi.getTimerCount()).toBe(0)
109
+ })
110
+
111
+ it('should handle all missing parameters', () => {
112
+ expect(() => registerTldrawLibraryVersion()).not.toThrow()
113
+
114
+ expect(vi.getTimerCount()).toBe(0)
115
+ })
116
+
117
+ it('should throw error in build environment when parameters missing', () => {
118
+ const originalBuildFlag = (globalThis as any).TLDRAW_LIBRARY_IS_BUILD
119
+ ;(globalThis as any).TLDRAW_LIBRARY_IS_BUILD = true
120
+
121
+ expect(() => registerTldrawLibraryVersion(undefined, '2.0.0', 'esm')).toThrow(
122
+ 'Missing name/version/module system in built version of tldraw library'
123
+ )
124
+ ;(globalThis as any).TLDRAW_LIBRARY_IS_BUILD = originalBuildFlag
125
+ })
126
+
127
+ it('should schedule timeout check on first registration', () => {
128
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
129
+
130
+ expect(vi.getTimerCount()).toBe(1)
131
+ })
132
+
133
+ it('should not schedule multiple timeouts for multiple registrations', () => {
134
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
135
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
136
+
137
+ expect(vi.getTimerCount()).toBe(1)
138
+ })
139
+
140
+ it('should handle setTimeout failure gracefully', () => {
141
+ vi.spyOn(global, 'setTimeout').mockImplementation(() => {
142
+ throw new Error('setTimeout not available')
143
+ })
144
+
145
+ // Should call check immediately when setTimeout fails
146
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
147
+ registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'esm')
148
+
149
+ expect(mockConsoleLog).toHaveBeenCalled()
150
+ })
151
+ })
152
+
153
+ describe('version conflict detection', () => {
154
+ it('should detect version conflicts between different versions', () => {
155
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
156
+ registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'esm')
157
+
158
+ vi.runAllTimers()
159
+
160
+ expect(mockConsoleLog).toHaveBeenCalled()
161
+ const logMessage = mockConsoleLog.mock.calls[0][0]
162
+ expect(logMessage).toContain('multiple versions of tldraw libraries')
163
+ })
164
+
165
+ it('should not warn for identical versions', () => {
166
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
167
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
168
+
169
+ vi.runAllTimers()
170
+
171
+ expect(mockConsoleLog).not.toHaveBeenCalled()
172
+ })
173
+
174
+ it('should detect module type conflicts', () => {
175
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
176
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'cjs')
177
+
178
+ vi.runAllTimers()
179
+
180
+ expect(mockConsoleLog).toHaveBeenCalled()
181
+ const logMessage = mockConsoleLog.mock.calls[0][0]
182
+ expect(logMessage).toContain('multiple instances')
183
+ })
184
+
185
+ it('should warn only once per session', () => {
186
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
187
+ registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'esm')
188
+
189
+ vi.runAllTimers()
190
+
191
+ expect(mockConsoleLog).toHaveBeenCalledTimes(1)
192
+
193
+ // Register more conflicting versions
194
+ registerTldrawLibraryVersion('@tldraw/tldraw', '1.8.0', 'esm')
195
+
196
+ // Should not trigger another warning since didWarn is true
197
+ expect(mockConsoleLog).toHaveBeenCalledTimes(1)
198
+ })
199
+
200
+ it('should handle complex version scenarios', () => {
201
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
202
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
203
+ registerTldrawLibraryVersion('@tldraw/store', '1.9.0', 'esm')
204
+ registerTldrawLibraryVersion('@tldraw/validate', '2.0.0', 'esm')
205
+
206
+ vi.runAllTimers()
207
+
208
+ expect(mockConsoleLog).toHaveBeenCalled()
209
+ const logMessage = mockConsoleLog.mock.calls[0][0]
210
+ expect(logMessage).toContain('multiple versions')
211
+ expect(logMessage).toContain('v2.0.0')
212
+ expect(logMessage).toContain('@tldraw/store')
213
+ })
214
+
215
+ it('should handle pre-release versions', () => {
216
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0-alpha.1', 'esm')
217
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0-beta.1', 'esm')
218
+
219
+ vi.runAllTimers()
220
+
221
+ expect(mockConsoleLog).toHaveBeenCalled()
222
+ })
223
+
224
+ it('should sort versions correctly', () => {
225
+ registerTldrawLibraryVersion('@tldraw/editor', '1.0.0', 'esm')
226
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
227
+ registerTldrawLibraryVersion('@tldraw/editor', '1.5.0', 'esm')
228
+
229
+ vi.runAllTimers()
230
+
231
+ expect(mockConsoleLog).toHaveBeenCalled()
232
+ const logMessage = mockConsoleLog.mock.calls[0][0]
233
+ // Latest version should be 2.0.0
234
+ expect(logMessage).toContain('v2.0.0')
235
+ })
236
+
237
+ it('should handle invalid version formats gracefully', () => {
238
+ registerTldrawLibraryVersion('@tldraw/editor', 'invalid-version', 'esm')
239
+ registerTldrawLibraryVersion('@tldraw/editor', 'also-invalid', 'esm')
240
+
241
+ expect(() => vi.runAllTimers()).not.toThrow()
242
+ })
243
+
244
+ it('should handle empty version list', () => {
245
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
246
+
247
+ // Clear versions but let timer run
248
+ clearRegisteredVersionsForTests()
249
+
250
+ expect(() => vi.runAllTimers()).not.toThrow()
251
+ })
252
+
253
+ it('should format console output with colors', () => {
254
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
255
+ registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'esm')
256
+
257
+ vi.runAllTimers()
258
+
259
+ expect(mockConsoleLog).toHaveBeenCalled()
260
+ const logMessage = mockConsoleLog.mock.calls[0][0]
261
+ // Check for ANSI color codes
262
+ expect(logMessage).toContain('\x1B[')
263
+ })
264
+
265
+ it('should show both version mismatches and module duplicates in output', () => {
266
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
267
+ registerTldrawLibraryVersion('@tldraw/tldraw', '1.9.0', 'esm')
268
+
269
+ vi.runAllTimers()
270
+
271
+ expect(mockConsoleLog).toHaveBeenCalled()
272
+ const logMessage = mockConsoleLog.mock.calls[0][0]
273
+ expect(logMessage).toContain('latest version')
274
+ expect(logMessage).toContain('not on the latest version')
275
+ })
10
276
  })
11
277
 
12
- it('doesnt log anything if all versions are the same', () => {
13
- const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
278
+ describe('integration scenarios', () => {
279
+ it('should handle typical multi-package setup', () => {
280
+ const packages = [
281
+ { name: '@tldraw/editor', version: '2.0.0', modules: 'esm' },
282
+ { name: '@tldraw/tldraw', version: '2.0.0', modules: 'esm' },
283
+ { name: '@tldraw/store', version: '2.0.0', modules: 'esm' },
284
+ { name: '@tldraw/validate', version: '2.0.0', modules: 'esm' },
285
+ ]
286
+
287
+ packages.forEach((pkg) => {
288
+ registerTldrawLibraryVersion(pkg.name, pkg.version, pkg.modules)
289
+ })
290
+
291
+ vi.runAllTimers()
292
+
293
+ // All same version - should not warn
294
+ expect(mockConsoleLog).not.toHaveBeenCalled()
295
+ })
296
+
297
+ it('should handle package manager deduplication scenario', () => {
298
+ // The current implementation actually treats multiple registrations of the same
299
+ // package+version+modules as duplicates, so this will trigger a warning
300
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
301
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
302
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
303
+
304
+ vi.runAllTimers()
305
+
306
+ // Multiple registrations of same version are detected as module duplicates
307
+ expect(mockConsoleLog).toHaveBeenCalled()
308
+ const logMessage = mockConsoleLog.mock.calls[0][0]
309
+ expect(logMessage).toContain('multiple instances')
310
+ })
311
+
312
+ it('should handle bundler misconfiguration scenario', () => {
313
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
314
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'cjs')
315
+
316
+ vi.runAllTimers()
317
+
318
+ expect(mockConsoleLog).toHaveBeenCalled()
319
+ const logMessage = mockConsoleLog.mock.calls[0][0]
320
+ expect(logMessage).toContain('bundler is misconfigured')
321
+ expect(logMessage).toContain('ES Modules')
322
+ expect(logMessage).toContain('CommonJS')
323
+ })
324
+
325
+ it('should handle mixed version and module conflicts', () => {
326
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
327
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'cjs')
328
+ registerTldrawLibraryVersion('@tldraw/tldraw', '1.9.0', 'esm')
329
+
330
+ vi.runAllTimers()
331
+
332
+ expect(mockConsoleLog).toHaveBeenCalled()
333
+ const logMessage = mockConsoleLog.mock.calls[0][0]
334
+ // Should show version conflict first (higher priority)
335
+ expect(logMessage).toContain('multiple versions')
336
+ })
14
337
 
15
- registerTldrawLibraryVersion('tldraw', '1.0.0', 'esm')
16
- registerTldrawLibraryVersion('@tldraw/editor', '1.0.0', 'esm')
17
- registerTldrawLibraryVersion('@tldraw/utils', '1.0.0', 'esm')
18
- registerTldrawLibraryVersion('@tldraw/tlschema', '1.0.0', 'esm')
338
+ it('should handle gradual package registration', () => {
339
+ // Register packages over time
340
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
19
341
 
20
- vi.runAllTimers()
342
+ vi.runAllTimers()
21
343
 
22
- expect(consoleLogSpy).toHaveBeenCalledTimes(0)
344
+ expect(mockConsoleLog).not.toHaveBeenCalled()
345
+
346
+ // Clear warning state and add conflicting version
347
+ clearRegisteredVersionsForTests()
348
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
349
+ registerTldrawLibraryVersion('@tldraw/tldraw', '1.9.0', 'esm')
350
+
351
+ vi.runAllTimers()
352
+
353
+ expect(mockConsoleLog).toHaveBeenCalled()
354
+ })
355
+ })
356
+
357
+ describe('edge cases and error handling', () => {
358
+ it('should handle extremely long package names', () => {
359
+ const longName = '@tldraw/' + 'x'.repeat(1000)
360
+
361
+ expect(() => registerTldrawLibraryVersion(longName, '2.0.0', 'esm')).not.toThrow()
362
+ })
363
+
364
+ it('should handle special characters in package names', () => {
365
+ const specialName = '@tldraw/test-特殊字符-🎨'
366
+
367
+ expect(() => registerTldrawLibraryVersion(specialName, '2.0.0', 'esm')).not.toThrow()
368
+ })
369
+
370
+ it('should handle unusual version strings', () => {
371
+ const versions = [
372
+ '2.0.0-alpha.1',
373
+ '2.0.0-beta.1+build.123',
374
+ '2.0.0-rc.1',
375
+ '0.0.0',
376
+ '999.999.999',
377
+ ]
378
+
379
+ versions.forEach((version) => {
380
+ expect(() => registerTldrawLibraryVersion('@tldraw/test', version, 'esm')).not.toThrow()
381
+ })
382
+ })
383
+
384
+ it('should handle unusual module system values', () => {
385
+ const moduleSystems = ['esm', 'cjs', 'umd', 'amd', 'custom']
386
+
387
+ moduleSystems.forEach((modules) => {
388
+ expect(() => registerTldrawLibraryVersion('@tldraw/test', '2.0.0', modules)).not.toThrow()
389
+ })
390
+ })
391
+
392
+ it('should handle very large number of registrations', () => {
393
+ for (let i = 0; i < 100; i++) {
394
+ registerTldrawLibraryVersion(`@tldraw/package-${i}`, '2.0.0', 'esm')
395
+ }
396
+
397
+ expect(() => vi.runAllTimers()).not.toThrow()
398
+ })
399
+
400
+ it('should handle global object pollution', () => {
401
+ // This test checks that the system is robust against different global contexts
402
+ // We can't easily pollute the global object due to property descriptors,
403
+ // but we can test that the system works with different global contexts
404
+ expect(() => registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')).not.toThrow()
405
+
406
+ // Register a different library to verify state tracking works
407
+ expect(() => registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')).not.toThrow()
408
+
409
+ vi.runAllTimers()
410
+
411
+ // Should not warn for same versions
412
+ expect(mockConsoleLog).not.toHaveBeenCalled()
413
+ })
23
414
  })
24
415
 
25
- it('logs if not all versions match', () => {
26
- const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
416
+ describe('timeout behavior', () => {
417
+ it('should use 100ms delay for setTimeout', () => {
418
+ const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
419
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
420
+
421
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 100)
422
+ })
423
+
424
+ it('should clear timeout after execution', () => {
425
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
426
+
427
+ vi.runAllTimers()
428
+
429
+ // The timeout should be nulled after execution
430
+ // This is internal behavior but we can test by checking that scheduling works again
431
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
432
+
433
+ expect(vi.getTimerCount()).toBe(1)
434
+ })
27
435
 
28
- registerTldrawLibraryVersion('tldraw', '1.0.0', 'esm')
29
- registerTldrawLibraryVersion('@tldraw/editor', '1.1.0', 'esm')
30
- registerTldrawLibraryVersion('@tldraw/utils', '1.1.0', 'esm')
31
- registerTldrawLibraryVersion('@tldraw/utils', '1.2.0', 'esm')
32
- registerTldrawLibraryVersion('@tldraw/tlschema', '1.2.0', 'esm')
436
+ it('should not schedule new timeout if one is already pending', () => {
437
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
438
+ registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
439
+ registerTldrawLibraryVersion('@tldraw/store', '2.0.0', 'esm')
33
440
 
34
- vi.runAllTimers()
441
+ expect(vi.getTimerCount()).toBe(1)
442
+ })
35
443
 
36
- expect(consoleLogSpy).toHaveBeenCalledTimes(1)
37
- expect(consoleLogSpy.mock.lastCall).toMatchInlineSnapshot(`
38
- [
39
- "[tldraw] You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.
444
+ it('should handle timeout clearing correctly', () => {
445
+ const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
446
+ registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
40
447
 
41
- The latest version you have installed is v1.2.0. The following libraries are on the latest version:
42
- • ✅ @tldraw/tlschema
448
+ clearRegisteredVersionsForTests()
43
449
 
44
- The following libraries are not on the latest version, or have multiple versions installed:
45
- • ❌ tldraw (v1.0.0)
46
- • ❌ @tldraw/editor (v1.1.0)
47
- • ❌ @tldraw/utils (v1.1.0, v1.2.0)",
48
- ]
49
- `)
450
+ expect(clearTimeoutSpy).toHaveBeenCalled()
451
+ })
50
452
  })
51
453
 
52
- it('logs if multiple versions of te same library are installed', () => {
53
- const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
454
+ describe('original test cases (updated)', () => {
455
+ it('doesnt log anything if all versions are the same', () => {
456
+ registerTldrawLibraryVersion('tldraw', '1.0.0', 'esm')
457
+ registerTldrawLibraryVersion('@tldraw/editor', '1.0.0', 'esm')
458
+ registerTldrawLibraryVersion('@tldraw/utils', '1.0.0', 'esm')
459
+ registerTldrawLibraryVersion('@tldraw/tlschema', '1.0.0', 'esm')
460
+
461
+ vi.runAllTimers()
462
+
463
+ expect(mockConsoleLog).toHaveBeenCalledTimes(0)
464
+ })
465
+
466
+ it('logs if not all versions match', () => {
467
+ registerTldrawLibraryVersion('tldraw', '1.0.0', 'esm')
468
+ registerTldrawLibraryVersion('@tldraw/editor', '1.1.0', 'esm')
469
+ registerTldrawLibraryVersion('@tldraw/utils', '1.1.0', 'esm')
470
+ registerTldrawLibraryVersion('@tldraw/utils', '1.2.0', 'esm')
471
+ registerTldrawLibraryVersion('@tldraw/tlschema', '1.2.0', 'esm')
472
+
473
+ vi.runAllTimers()
474
+
475
+ expect(mockConsoleLog).toHaveBeenCalledTimes(1)
476
+ expect(mockConsoleLog.mock.calls[0]).toMatchInlineSnapshot(`
477
+ [
478
+ "[tldraw] You have multiple versions of tldraw libraries installed. This can lead to bugs and unexpected behavior.
479
+
480
+ The latest version you have installed is v1.2.0. The following libraries are on the latest version:
481
+ • ✅ @tldraw/tlschema
482
+
483
+ The following libraries are not on the latest version, or have multiple versions installed:
484
+ • ❌ tldraw (v1.0.0)
485
+ • ❌ @tldraw/editor (v1.1.0)
486
+ • ❌ @tldraw/utils (v1.1.0, v1.2.0)",
487
+ ]
488
+ `)
489
+ })
54
490
 
55
- registerTldrawLibraryVersion('tldraw', '1.1.0', 'esm')
56
- registerTldrawLibraryVersion('@tldraw/editor', '1.1.0', 'esm')
57
- registerTldrawLibraryVersion('@tldraw/editor', '1.1.0', 'cjs')
58
- registerTldrawLibraryVersion('@tldraw/utils', '1.1.0', 'esm')
59
- registerTldrawLibraryVersion('@tldraw/utils', '1.1.0', 'cjs')
60
- registerTldrawLibraryVersion('@tldraw/tlschema', '1.1.0', 'esm')
491
+ it('logs if multiple versions of the same library are installed', () => {
492
+ registerTldrawLibraryVersion('tldraw', '1.1.0', 'esm')
493
+ registerTldrawLibraryVersion('@tldraw/editor', '1.1.0', 'esm')
494
+ registerTldrawLibraryVersion('@tldraw/editor', '1.1.0', 'cjs')
495
+ registerTldrawLibraryVersion('@tldraw/utils', '1.1.0', 'esm')
496
+ registerTldrawLibraryVersion('@tldraw/utils', '1.1.0', 'cjs')
497
+ registerTldrawLibraryVersion('@tldraw/tlschema', '1.1.0', 'esm')
61
498
 
62
- vi.runAllTimers()
499
+ vi.runAllTimers()
63
500
 
64
- expect(consoleLogSpy).toHaveBeenCalledTimes(1)
65
- expect(consoleLogSpy.mock.lastCall).toMatchInlineSnapshot(`
66
- [
67
- "[tldraw] You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. 
501
+ expect(mockConsoleLog).toHaveBeenCalledTimes(1)
502
+ expect(mockConsoleLog.mock.calls[0]).toMatchInlineSnapshot(`
503
+ [
504
+ "[tldraw] You have multiple instances of some tldraw libraries active. This can lead to bugs and unexpected behavior. 
68
505
 
69
- This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.
506
+ This usually means that your bundler is misconfigured, and is importing the same library multiple times - usually once as an ES Module, and once as a CommonJS module.
70
507
 
71
- The following libraries have been imported multiple times:
72
- • ❌ @tldraw/editor v1.1.0:
73
- 1. ES Modules
74
- 2. CommonJS
75
- • ❌ @tldraw/utils v1.1.0:
76
- 1. ES Modules
77
- 2. CommonJS
508
+ The following libraries have been imported multiple times:
509
+ • ❌ @tldraw/editor v1.1.0:
510
+ 1. ES Modules
511
+ 2. CommonJS
512
+ • ❌ @tldraw/utils v1.1.0:
513
+ 1. ES Modules
514
+ 2. CommonJS
78
515
 
79
- You should configure your bundler to only import one version of each library.",
80
- ]
81
- `)
516
+ You should configure your bundler to only import one version of each library.",
517
+ ]
518
+ `)
519
+ })
82
520
  })
83
521
  })
@@ -38,6 +38,22 @@ function getLibraryVersions(): TldrawLibraryVersionInfo {
38
38
  return info
39
39
  }
40
40
 
41
+ /**
42
+ * Clears all registered library versions and resets warning state.
43
+ * This function is intended for testing purposes only to reset the global version tracking state.
44
+ * @returns void
45
+ * @example
46
+ * ```ts
47
+ * // In a test setup
48
+ * beforeEach(() => {
49
+ * clearRegisteredVersionsForTests()
50
+ * })
51
+ *
52
+ * // Now version tracking starts fresh for each test
53
+ * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
54
+ * ```
55
+ * @internal
56
+ */
41
57
  export function clearRegisteredVersionsForTests() {
42
58
  const info = getLibraryVersions()
43
59
  info.versions = []
@@ -48,7 +64,26 @@ export function clearRegisteredVersionsForTests() {
48
64
  }
49
65
  }
50
66
 
51
- /** @internal */
67
+ /**
68
+ * Registers a tldraw library version for conflict detection.
69
+ * This function tracks different tldraw library versions to warn about potential conflicts
70
+ * when multiple versions are loaded simultaneously.
71
+ * @param name - The name of the tldraw library package (e.g., '\@tldraw/editor').
72
+ * @param version - The semantic version string (e.g., '2.0.0').
73
+ * @param modules - The module system being used ('esm' or 'cjs').
74
+ * @returns void
75
+ * @example
76
+ * ```ts
77
+ * // Register a library version during package initialization
78
+ * registerTldrawLibraryVersion('@tldraw/editor', '2.0.0', 'esm')
79
+ * registerTldrawLibraryVersion('@tldraw/tldraw', '2.0.0', 'esm')
80
+ *
81
+ * // If conflicting versions are detected, warnings will be logged:
82
+ * registerTldrawLibraryVersion('@tldraw/editor', '1.9.0', 'cjs')
83
+ * // Console warning about version mismatch will appear
84
+ * ```
85
+ * @internal
86
+ */
52
87
  export function registerTldrawLibraryVersion(name?: string, version?: string, modules?: string) {
53
88
  if (!name || !version || !modules) {
54
89
  if ((globalThis as any).TLDRAW_LIBRARY_IS_BUILD) {