@fluidframework/react 0.41.4 → 2.63.0-358419

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 (342) hide show
  1. package/.eslintrc.cjs +11 -0
  2. package/.mocharc.cjs +15 -0
  3. package/CHANGELOG.md +282 -0
  4. package/README.md +122 -273
  5. package/api-extractor/api-extractor-lint-alpha.cjs.json +5 -0
  6. package/api-extractor/api-extractor-lint-alpha.esm.json +5 -0
  7. package/api-extractor/api-extractor-lint-beta.cjs.json +5 -0
  8. package/api-extractor/api-extractor-lint-beta.esm.json +5 -0
  9. package/api-extractor/api-extractor-lint-bundle.json +5 -0
  10. package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
  11. package/api-extractor/api-extractor-lint-public.esm.json +5 -0
  12. package/api-extractor-lint.json +4 -0
  13. package/api-extractor.json +2 -2
  14. package/api-report/react.alpha.api.md +114 -0
  15. package/api-report/react.beta.api.md +7 -0
  16. package/api-report/react.public.api.md +7 -0
  17. package/biome.jsonc +4 -0
  18. package/dist/alpha.d.ts +45 -0
  19. package/dist/beta.d.ts +15 -0
  20. package/dist/index.d.ts +11 -7
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +19 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/package.json +4 -0
  25. package/dist/propNode.d.ts +114 -0
  26. package/dist/propNode.d.ts.map +1 -0
  27. package/dist/propNode.js +43 -0
  28. package/dist/propNode.js.map +1 -0
  29. package/dist/public.d.ts +15 -0
  30. package/dist/reactSharedTreeView.d.ts +119 -0
  31. package/dist/reactSharedTreeView.d.ts.map +1 -0
  32. package/dist/reactSharedTreeView.js +206 -0
  33. package/dist/reactSharedTreeView.js.map +1 -0
  34. package/dist/simpleIdentifier.d.ts +19 -0
  35. package/dist/simpleIdentifier.d.ts.map +1 -0
  36. package/dist/simpleIdentifier.js +33 -0
  37. package/dist/simpleIdentifier.js.map +1 -0
  38. package/dist/useObservation.d.ts +83 -0
  39. package/dist/useObservation.d.ts.map +1 -0
  40. package/dist/useObservation.js +295 -0
  41. package/dist/useObservation.js.map +1 -0
  42. package/dist/useTree.d.ts +80 -0
  43. package/dist/useTree.d.ts.map +1 -0
  44. package/dist/useTree.js +137 -0
  45. package/dist/useTree.js.map +1 -0
  46. package/lib/alpha.d.ts +45 -0
  47. package/lib/beta.d.ts +15 -0
  48. package/lib/index.d.ts +16 -0
  49. package/lib/index.d.ts.map +1 -0
  50. package/lib/index.js +4 -7
  51. package/lib/index.js.map +1 -1
  52. package/lib/package.json +4 -0
  53. package/lib/propNode.d.ts +114 -0
  54. package/lib/propNode.d.ts.map +1 -0
  55. package/lib/propNode.js +36 -0
  56. package/lib/propNode.js.map +1 -0
  57. package/lib/public.d.ts +15 -0
  58. package/lib/reactSharedTreeView.d.ts +119 -0
  59. package/lib/reactSharedTreeView.d.ts.map +1 -0
  60. package/lib/reactSharedTreeView.js +176 -0
  61. package/lib/reactSharedTreeView.js.map +1 -0
  62. package/lib/simpleIdentifier.d.ts +19 -0
  63. package/lib/simpleIdentifier.d.ts.map +1 -0
  64. package/lib/simpleIdentifier.js +29 -0
  65. package/lib/simpleIdentifier.js.map +1 -0
  66. package/lib/test/propNode.spec.js +120 -0
  67. package/lib/test/propNode.spec.js.map +1 -0
  68. package/lib/test/reactSharedTreeView.spec.js +71 -0
  69. package/lib/test/reactSharedTreeView.spec.js.map +1 -0
  70. package/lib/test/simpleIdentifier.spec.js +18 -0
  71. package/lib/test/simpleIdentifier.spec.js.map +1 -0
  72. package/lib/test/useObservation.spec.js +162 -0
  73. package/lib/test/useObservation.spec.js.map +1 -0
  74. package/lib/test/useTree.spec.js +165 -0
  75. package/lib/test/useTree.spec.js.map +1 -0
  76. package/lib/tsdoc-metadata.json +11 -0
  77. package/lib/useObservation.d.ts +83 -0
  78. package/lib/useObservation.d.ts.map +1 -0
  79. package/lib/useObservation.js +266 -0
  80. package/lib/useObservation.js.map +1 -0
  81. package/lib/useTree.d.ts +80 -0
  82. package/lib/useTree.d.ts.map +1 -0
  83. package/lib/useTree.js +105 -0
  84. package/lib/useTree.js.map +1 -0
  85. package/package.json +150 -51
  86. package/react.test-files.tar +0 -0
  87. package/src/index.ts +42 -7
  88. package/src/propNode.ts +164 -0
  89. package/src/reactSharedTreeView.tsx +327 -0
  90. package/src/simpleIdentifier.ts +31 -0
  91. package/src/useObservation.ts +376 -0
  92. package/src/useTree.ts +147 -0
  93. package/tsconfig.cjs.json +7 -0
  94. package/tsconfig.json +12 -15
  95. package/tsdoc.json +4 -0
  96. package/.eslintrc.js +0 -11
  97. package/dist/createContextFluid.d.ts +0 -7
  98. package/dist/createContextFluid.d.ts.map +0 -1
  99. package/dist/createContextFluid.js +0 -46
  100. package/dist/createContextFluid.js.map +0 -1
  101. package/dist/helpers/generateFluidObjectSchema.d.ts +0 -16
  102. package/dist/helpers/generateFluidObjectSchema.d.ts.map +0 -1
  103. package/dist/helpers/generateFluidObjectSchema.js +0 -75
  104. package/dist/helpers/generateFluidObjectSchema.js.map +0 -1
  105. package/dist/helpers/getFluidFromView.d.ts +0 -15
  106. package/dist/helpers/getFluidFromView.d.ts.map +0 -1
  107. package/dist/helpers/getFluidFromView.js +0 -30
  108. package/dist/helpers/getFluidFromView.js.map +0 -1
  109. package/dist/helpers/getFluidState.d.ts +0 -15
  110. package/dist/helpers/getFluidState.d.ts.map +0 -1
  111. package/dist/helpers/getFluidState.js +0 -47
  112. package/dist/helpers/getFluidState.js.map +0 -1
  113. package/dist/helpers/getSchema.d.ts +0 -13
  114. package/dist/helpers/getSchema.d.ts.map +0 -1
  115. package/dist/helpers/getSchema.js +0 -15
  116. package/dist/helpers/getSchema.js.map +0 -1
  117. package/dist/helpers/getViewFromFluid.d.ts +0 -18
  118. package/dist/helpers/getViewFromFluid.d.ts.map +0 -1
  119. package/dist/helpers/getViewFromFluid.js +0 -47
  120. package/dist/helpers/getViewFromFluid.js.map +0 -1
  121. package/dist/helpers/index.d.ts +0 -16
  122. package/dist/helpers/index.d.ts.map +0 -1
  123. package/dist/helpers/index.js +0 -28
  124. package/dist/helpers/index.js.map +0 -1
  125. package/dist/helpers/initializeState.d.ts +0 -22
  126. package/dist/helpers/initializeState.d.ts.map +0 -1
  127. package/dist/helpers/initializeState.js +0 -77
  128. package/dist/helpers/initializeState.js.map +0 -1
  129. package/dist/helpers/internalInterface.d.ts +0 -15
  130. package/dist/helpers/internalInterface.d.ts.map +0 -1
  131. package/dist/helpers/internalInterface.js +0 -7
  132. package/dist/helpers/internalInterface.js.map +0 -1
  133. package/dist/helpers/rootCallbackListener.d.ts +0 -24
  134. package/dist/helpers/rootCallbackListener.d.ts.map +0 -1
  135. package/dist/helpers/rootCallbackListener.js +0 -55
  136. package/dist/helpers/rootCallbackListener.js.map +0 -1
  137. package/dist/helpers/setComponentSchema.d.ts +0 -14
  138. package/dist/helpers/setComponentSchema.d.ts.map +0 -1
  139. package/dist/helpers/setComponentSchema.js +0 -18
  140. package/dist/helpers/setComponentSchema.js.map +0 -1
  141. package/dist/helpers/setFluidState.d.ts +0 -20
  142. package/dist/helpers/setFluidState.d.ts.map +0 -1
  143. package/dist/helpers/setFluidState.js +0 -94
  144. package/dist/helpers/setFluidState.js.map +0 -1
  145. package/dist/helpers/syncState.d.ts +0 -25
  146. package/dist/helpers/syncState.d.ts.map +0 -1
  147. package/dist/helpers/syncState.js +0 -98
  148. package/dist/helpers/syncState.js.map +0 -1
  149. package/dist/helpers/updateStateAndFluidObjectMap.d.ts +0 -29
  150. package/dist/helpers/updateStateAndFluidObjectMap.d.ts.map +0 -1
  151. package/dist/helpers/updateStateAndFluidObjectMap.js +0 -30
  152. package/dist/helpers/updateStateAndFluidObjectMap.js.map +0 -1
  153. package/dist/helpers/utils.d.ts +0 -12
  154. package/dist/helpers/utils.d.ts.map +0 -1
  155. package/dist/helpers/utils.js +0 -74
  156. package/dist/helpers/utils.js.map +0 -1
  157. package/dist/interface.d.ts +0 -444
  158. package/dist/interface.d.ts.map +0 -1
  159. package/dist/interface.js +0 -18
  160. package/dist/interface.js.map +0 -1
  161. package/dist/reactView.d.ts +0 -32
  162. package/dist/reactView.d.ts.map +0 -1
  163. package/dist/reactView.js +0 -79
  164. package/dist/reactView.js.map +0 -1
  165. package/dist/syncedDataObject.d.ts +0 -80
  166. package/dist/syncedDataObject.d.ts.map +0 -1
  167. package/dist/syncedDataObject.js +0 -249
  168. package/dist/syncedDataObject.js.map +0 -1
  169. package/dist/syncedObjects/array/fluidSyncedArray.d.ts +0 -11
  170. package/dist/syncedObjects/array/fluidSyncedArray.d.ts.map +0 -1
  171. package/dist/syncedObjects/array/fluidSyncedArray.js +0 -78
  172. package/dist/syncedObjects/array/fluidSyncedArray.js.map +0 -1
  173. package/dist/syncedObjects/array/index.d.ts +0 -7
  174. package/dist/syncedObjects/array/index.d.ts.map +0 -1
  175. package/dist/syncedObjects/array/index.js +0 -19
  176. package/dist/syncedObjects/array/index.js.map +0 -1
  177. package/dist/syncedObjects/array/interface.d.ts +0 -33
  178. package/dist/syncedObjects/array/interface.d.ts.map +0 -1
  179. package/dist/syncedObjects/array/interface.js +0 -3
  180. package/dist/syncedObjects/array/interface.js.map +0 -1
  181. package/dist/syncedObjects/array/syncedArray.d.ts +0 -30
  182. package/dist/syncedObjects/array/syncedArray.d.ts.map +0 -1
  183. package/dist/syncedObjects/array/syncedArray.js +0 -42
  184. package/dist/syncedObjects/array/syncedArray.js.map +0 -1
  185. package/dist/syncedObjects/counter/fluidSyncedCounter.d.ts +0 -11
  186. package/dist/syncedObjects/counter/fluidSyncedCounter.d.ts.map +0 -1
  187. package/dist/syncedObjects/counter/fluidSyncedCounter.js +0 -79
  188. package/dist/syncedObjects/counter/fluidSyncedCounter.js.map +0 -1
  189. package/dist/syncedObjects/counter/index.d.ts +0 -7
  190. package/dist/syncedObjects/counter/index.d.ts.map +0 -1
  191. package/dist/syncedObjects/counter/index.js +0 -19
  192. package/dist/syncedObjects/counter/index.js.map +0 -1
  193. package/dist/syncedObjects/counter/interface.d.ts +0 -32
  194. package/dist/syncedObjects/counter/interface.d.ts.map +0 -1
  195. package/dist/syncedObjects/counter/interface.js +0 -3
  196. package/dist/syncedObjects/counter/interface.js.map +0 -1
  197. package/dist/syncedObjects/counter/syncedCounter.d.ts +0 -29
  198. package/dist/syncedObjects/counter/syncedCounter.d.ts.map +0 -1
  199. package/dist/syncedObjects/counter/syncedCounter.js +0 -36
  200. package/dist/syncedObjects/counter/syncedCounter.js.map +0 -1
  201. package/dist/syncedObjects/index.d.ts +0 -9
  202. package/dist/syncedObjects/index.d.ts.map +0 -1
  203. package/dist/syncedObjects/index.js +0 -21
  204. package/dist/syncedObjects/index.js.map +0 -1
  205. package/dist/syncedObjects/object/index.d.ts +0 -7
  206. package/dist/syncedObjects/object/index.d.ts.map +0 -1
  207. package/dist/syncedObjects/object/index.js +0 -19
  208. package/dist/syncedObjects/object/index.js.map +0 -1
  209. package/dist/syncedObjects/object/interface.d.ts +0 -13
  210. package/dist/syncedObjects/object/interface.d.ts.map +0 -1
  211. package/dist/syncedObjects/object/interface.js +0 -7
  212. package/dist/syncedObjects/object/interface.js.map +0 -1
  213. package/dist/syncedObjects/object/syncedObject.d.ts +0 -22
  214. package/dist/syncedObjects/object/syncedObject.d.ts.map +0 -1
  215. package/dist/syncedObjects/object/syncedObject.js +0 -46
  216. package/dist/syncedObjects/object/syncedObject.js.map +0 -1
  217. package/dist/syncedObjects/string/index.d.ts +0 -7
  218. package/dist/syncedObjects/string/index.d.ts.map +0 -1
  219. package/dist/syncedObjects/string/index.js +0 -19
  220. package/dist/syncedObjects/string/index.js.map +0 -1
  221. package/dist/syncedObjects/string/interface.d.ts +0 -16
  222. package/dist/syncedObjects/string/interface.d.ts.map +0 -1
  223. package/dist/syncedObjects/string/interface.js +0 -7
  224. package/dist/syncedObjects/string/interface.js.map +0 -1
  225. package/dist/syncedObjects/string/syncedString.d.ts +0 -25
  226. package/dist/syncedObjects/string/syncedString.d.ts.map +0 -1
  227. package/dist/syncedObjects/string/syncedString.js +0 -53
  228. package/dist/syncedObjects/string/syncedString.js.map +0 -1
  229. package/dist/useReducerFluid.d.ts +0 -7
  230. package/dist/useReducerFluid.d.ts.map +0 -1
  231. package/dist/useReducerFluid.js +0 -219
  232. package/dist/useReducerFluid.js.map +0 -1
  233. package/dist/useStateFluid.d.ts +0 -10
  234. package/dist/useStateFluid.d.ts.map +0 -1
  235. package/dist/useStateFluid.js +0 -67
  236. package/dist/useStateFluid.js.map +0 -1
  237. package/lib/createContextFluid.js +0 -23
  238. package/lib/createContextFluid.js.map +0 -1
  239. package/lib/helpers/generateFluidObjectSchema.js +0 -71
  240. package/lib/helpers/generateFluidObjectSchema.js.map +0 -1
  241. package/lib/helpers/getFluidFromView.js +0 -26
  242. package/lib/helpers/getFluidFromView.js.map +0 -1
  243. package/lib/helpers/getFluidState.js +0 -43
  244. package/lib/helpers/getFluidState.js.map +0 -1
  245. package/lib/helpers/getSchema.js +0 -11
  246. package/lib/helpers/getSchema.js.map +0 -1
  247. package/lib/helpers/getViewFromFluid.js +0 -43
  248. package/lib/helpers/getViewFromFluid.js.map +0 -1
  249. package/lib/helpers/index.js +0 -16
  250. package/lib/helpers/index.js.map +0 -1
  251. package/lib/helpers/initializeState.js +0 -73
  252. package/lib/helpers/initializeState.js.map +0 -1
  253. package/lib/helpers/internalInterface.js +0 -6
  254. package/lib/helpers/internalInterface.js.map +0 -1
  255. package/lib/helpers/rootCallbackListener.js +0 -51
  256. package/lib/helpers/rootCallbackListener.js.map +0 -1
  257. package/lib/helpers/setComponentSchema.js +0 -14
  258. package/lib/helpers/setComponentSchema.js.map +0 -1
  259. package/lib/helpers/setFluidState.js +0 -90
  260. package/lib/helpers/setFluidState.js.map +0 -1
  261. package/lib/helpers/syncState.js +0 -94
  262. package/lib/helpers/syncState.js.map +0 -1
  263. package/lib/helpers/updateStateAndFluidObjectMap.js +0 -26
  264. package/lib/helpers/updateStateAndFluidObjectMap.js.map +0 -1
  265. package/lib/helpers/utils.js +0 -67
  266. package/lib/helpers/utils.js.map +0 -1
  267. package/lib/interface.js +0 -8
  268. package/lib/interface.js.map +0 -1
  269. package/lib/reactView.js +0 -56
  270. package/lib/reactView.js.map +0 -1
  271. package/lib/syncedDataObject.js +0 -245
  272. package/lib/syncedDataObject.js.map +0 -1
  273. package/lib/syncedObjects/array/fluidSyncedArray.js +0 -72
  274. package/lib/syncedObjects/array/fluidSyncedArray.js.map +0 -1
  275. package/lib/syncedObjects/array/index.js +0 -7
  276. package/lib/syncedObjects/array/index.js.map +0 -1
  277. package/lib/syncedObjects/array/interface.js +0 -2
  278. package/lib/syncedObjects/array/interface.js.map +0 -1
  279. package/lib/syncedObjects/array/syncedArray.js +0 -37
  280. package/lib/syncedObjects/array/syncedArray.js.map +0 -1
  281. package/lib/syncedObjects/counter/fluidSyncedCounter.js +0 -73
  282. package/lib/syncedObjects/counter/fluidSyncedCounter.js.map +0 -1
  283. package/lib/syncedObjects/counter/index.js +0 -7
  284. package/lib/syncedObjects/counter/index.js.map +0 -1
  285. package/lib/syncedObjects/counter/interface.js +0 -2
  286. package/lib/syncedObjects/counter/interface.js.map +0 -1
  287. package/lib/syncedObjects/counter/syncedCounter.js +0 -31
  288. package/lib/syncedObjects/counter/syncedCounter.js.map +0 -1
  289. package/lib/syncedObjects/index.js +0 -9
  290. package/lib/syncedObjects/index.js.map +0 -1
  291. package/lib/syncedObjects/object/index.js +0 -7
  292. package/lib/syncedObjects/object/index.js.map +0 -1
  293. package/lib/syncedObjects/object/interface.js +0 -6
  294. package/lib/syncedObjects/object/interface.js.map +0 -1
  295. package/lib/syncedObjects/object/syncedObject.js +0 -41
  296. package/lib/syncedObjects/object/syncedObject.js.map +0 -1
  297. package/lib/syncedObjects/string/index.js +0 -7
  298. package/lib/syncedObjects/string/index.js.map +0 -1
  299. package/lib/syncedObjects/string/interface.js +0 -6
  300. package/lib/syncedObjects/string/interface.js.map +0 -1
  301. package/lib/syncedObjects/string/syncedString.js +0 -48
  302. package/lib/syncedObjects/string/syncedString.js.map +0 -1
  303. package/lib/useReducerFluid.js +0 -196
  304. package/lib/useReducerFluid.js.map +0 -1
  305. package/lib/useStateFluid.js +0 -44
  306. package/lib/useStateFluid.js.map +0 -1
  307. package/src/createContextFluid.tsx +0 -33
  308. package/src/helpers/generateFluidObjectSchema.ts +0 -95
  309. package/src/helpers/getFluidFromView.ts +0 -38
  310. package/src/helpers/getFluidState.ts +0 -67
  311. package/src/helpers/getSchema.ts +0 -18
  312. package/src/helpers/getViewFromFluid.ts +0 -68
  313. package/src/helpers/index.tsx +0 -16
  314. package/src/helpers/initializeState.ts +0 -162
  315. package/src/helpers/internalInterface.ts +0 -16
  316. package/src/helpers/rootCallbackListener.ts +0 -104
  317. package/src/helpers/setComponentSchema.ts +0 -21
  318. package/src/helpers/setFluidState.ts +0 -116
  319. package/src/helpers/syncState.ts +0 -159
  320. package/src/helpers/updateStateAndFluidObjectMap.ts +0 -85
  321. package/src/helpers/utils.tsx +0 -109
  322. package/src/interface.ts +0 -617
  323. package/src/reactView.tsx +0 -108
  324. package/src/syncedDataObject.ts +0 -337
  325. package/src/syncedObjects/array/fluidSyncedArray.ts +0 -126
  326. package/src/syncedObjects/array/index.ts +0 -7
  327. package/src/syncedObjects/array/interface.ts +0 -45
  328. package/src/syncedObjects/array/syncedArray.ts +0 -65
  329. package/src/syncedObjects/counter/fluidSyncedCounter.ts +0 -122
  330. package/src/syncedObjects/counter/index.ts +0 -7
  331. package/src/syncedObjects/counter/interface.ts +0 -44
  332. package/src/syncedObjects/counter/syncedCounter.ts +0 -64
  333. package/src/syncedObjects/index.ts +0 -9
  334. package/src/syncedObjects/object/index.ts +0 -7
  335. package/src/syncedObjects/object/interface.ts +0 -14
  336. package/src/syncedObjects/object/syncedObject.ts +0 -55
  337. package/src/syncedObjects/string/index.ts +0 -7
  338. package/src/syncedObjects/string/interface.ts +0 -17
  339. package/src/syncedObjects/string/syncedString.ts +0 -61
  340. package/src/useReducerFluid.tsx +0 -436
  341. package/src/useStateFluid.tsx +0 -84
  342. package/tsconfig.esnext.json +0 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simpleIdentifier.js","sourceRoot":"","sources":["../src/simpleIdentifier.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB,MAAM,KAAK,GAAG,IAAI,OAAO,EAAkB,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC5C,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO,EAAE,CAAC;IACV,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AAChB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nlet counter = 0;\n\nconst idMap = new WeakMap<object, number>();\n\n/**\n * Associates a unique number with an object.\n * @remarks\n * The ID number is tied to the object identity, not the object's contents; modifying the object will not cause it to get a different ID.\n *\n * This can be handy for generating {@link https://react.dev/learn/rendering-lists#where-to-get-your-key | keys for React lists} from TreeNodes.\n *\n * Most cases which could use this function should just use the objects themselves instead of getting IDs from them, since the objects will have the same equality as the IDs.\n * For example, if storing data associated with the objects in a map, using the object as the key is more efficient than getting an ID from it and using that.\n * This functions exists to deal with the edge case where you would like to use object identity, but you can't.\n * React keys are an examples of such a case, since React does not allow objects as keys.\n * @alpha\n */\nexport function objectIdNumber(object: object): number {\n\tconst id = idMap.get(object);\n\tif (id !== undefined) {\n\t\treturn id;\n\t}\n\tcounter++;\n\tidMap.set(object, counter);\n\treturn counter;\n}\n"]}
@@ -0,0 +1,120 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { SchemaFactory } from "@fluidframework/tree";
7
+ import { allowUnused } from "@fluidframework/tree/internal";
8
+ import { toPropTreeNode, toPropTreeRecord, unwrapPropTreeNode, } from "../propNode.js";
9
+ describe("propNode", () => {
10
+ it("PropTreeNode", () => {
11
+ const builder = new SchemaFactory("tree-react-api");
12
+ class Inventory extends builder.object("Inventory", {
13
+ nuts: builder.number,
14
+ }) {
15
+ }
16
+ const node = new Inventory({ nuts: 5 });
17
+ const prop = toPropTreeNode(node);
18
+ // @ts-expect-error Read access should be removed
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
20
+ const nuts = prop.nuts;
21
+ const node2 = unwrapPropTreeNode(prop);
22
+ assert.equal(node2, node);
23
+ });
24
+ it("PropTreeNode value", () => {
25
+ const value = 5;
26
+ // toPropTreeNode leaves leaves alone
27
+ const prop = toPropTreeNode(value);
28
+ const node2 = unwrapPropTreeNode(prop);
29
+ assert.equal(node2, value);
30
+ });
31
+ it("NodeRecord", () => {
32
+ const builder = new SchemaFactory("tree-react-api");
33
+ class Inventory extends builder.object("Inventory", { nuts: builder.number }) {
34
+ }
35
+ const record = toPropTreeRecord({ num: 5, node: new Inventory({ nuts: 5 }) });
36
+ const prop = record.node;
37
+ // @ts-expect-error Read access should be removed
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39
+ const nuts = prop.nuts;
40
+ const node = unwrapPropTreeNode(prop);
41
+ assert.equal(node, prop);
42
+ });
43
+ it("WrapPropTreeNodeRecord", () => {
44
+ const builder = new SchemaFactory("tree-react-api");
45
+ class Node extends builder.object("Node", {}) {
46
+ }
47
+ allowUnused();
48
+ });
49
+ it("IsMappableObjectType", () => {
50
+ allowUnused();
51
+ allowUnused();
52
+ allowUnused();
53
+ allowUnused();
54
+ // Interestingly, maps are not nominally typed:
55
+ allowUnused();
56
+ // Constructors are not mappable:
57
+ allowUnused();
58
+ // Nor are functions
59
+ allowUnused();
60
+ class Nominal {
61
+ constructor(x) {
62
+ this._nominal = undefined;
63
+ }
64
+ }
65
+ // Instances of classes with private or protected fields are also not mappable:
66
+ allowUnused();
67
+ // TreeNode is nominally type so not mappable:
68
+ allowUnused();
69
+ // Primitives are mappable since mapping over them leaves them unchanged in a generic context:
70
+ allowUnused();
71
+ allowUnused();
72
+ allowUnused();
73
+ // @ts-expect-error MappedNumberDirect is not a number:
74
+ allowUnused();
75
+ allowUnused();
76
+ allowUnused();
77
+ allowUnused();
78
+ // @ts-expect-error Union is lossy when mapped due to having lossy members:
79
+ allowUnused();
80
+ allowUnused();
81
+ allowUnused();
82
+ });
83
+ it("WrapNodes", () => {
84
+ const builder = new SchemaFactory("tree-react-api");
85
+ class Node extends builder.object("Node", {}) {
86
+ }
87
+ class Nominal {
88
+ constructor(x) {
89
+ this._nominal = undefined;
90
+ }
91
+ }
92
+ allowUnused();
93
+ allowUnused();
94
+ allowUnused();
95
+ allowUnused();
96
+ // Must not break existing PropTreeNode types:
97
+ allowUnused();
98
+ allowUnused();
99
+ // Does not break maps
100
+ allowUnused();
101
+ // Avoids breaking nominal types while recursing into objects:
102
+ allowUnused();
103
+ class Nominal2 {
104
+ constructor() {
105
+ this._nominal = undefined;
106
+ this.child = new Node({});
107
+ }
108
+ }
109
+ allowUnused();
110
+ // Does not break unions
111
+ allowUnused();
112
+ // DeepWrappedMixed removed the nominal typing! Thats bad!
113
+ allowUnused();
114
+ // Child transformed correctly:
115
+ allowUnused();
116
+ // But lost the nominal typing:
117
+ allowUnused();
118
+ });
119
+ });
120
+ //# sourceMappingURL=propNode.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propNode.spec.js","sourceRoot":"","sources":["../../src/test/propNode.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { SchemaFactory, type TreeNode } from \"@fluidframework/tree\";\nimport { allowUnused } from \"@fluidframework/tree/internal\";\nimport type { requireAssignableTo } from \"@fluidframework/tree/internal\";\n\nimport {\n\ttoPropTreeNode,\n\ttoPropTreeRecord,\n\tunwrapPropTreeNode,\n\ttype IsMappableObjectType,\n\ttype PropTreeNode,\n\ttype WrapNodes,\n\ttype WrapPropTreeNodeRecord,\n} from \"../propNode.js\";\n\ndescribe(\"propNode\", () => {\n\tit(\"PropTreeNode\", () => {\n\t\tconst builder = new SchemaFactory(\"tree-react-api\");\n\n\t\tclass Inventory extends builder.object(\"Inventory\", {\n\t\t\tnuts: builder.number,\n\t\t}) {}\n\n\t\tconst node = new Inventory({ nuts: 5 });\n\n\t\tconst prop = toPropTreeNode(node);\n\n\t\t// @ts-expect-error Read access should be removed\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\tconst nuts = prop.nuts;\n\n\t\tconst node2 = unwrapPropTreeNode(prop);\n\n\t\tassert.equal(node2, node);\n\t});\n\n\tit(\"PropTreeNode value\", () => {\n\t\tconst value = 5;\n\n\t\t// toPropTreeNode leaves leaves alone\n\t\tconst prop: 5 = toPropTreeNode(value);\n\n\t\tconst node2: 5 = unwrapPropTreeNode(prop);\n\n\t\tassert.equal(node2, value);\n\t});\n\n\tit(\"NodeRecord\", () => {\n\t\tconst builder = new SchemaFactory(\"tree-react-api\");\n\n\t\tclass Inventory extends builder.object(\"Inventory\", { nuts: builder.number }) {}\n\n\t\tconst record = toPropTreeRecord({ num: 5, node: new Inventory({ nuts: 5 }) });\n\t\tconst prop = record.node;\n\n\t\t// @ts-expect-error Read access should be removed\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\tconst nuts = prop.nuts;\n\n\t\tconst node = unwrapPropTreeNode(prop);\n\n\t\tassert.equal(node, prop);\n\t});\n\n\tit(\"WrapPropTreeNodeRecord\", () => {\n\t\tconst builder = new SchemaFactory(\"tree-react-api\");\n\t\tclass Node extends builder.object(\"Node\", {}) {}\n\n\t\ttype Wrapped = WrapPropTreeNodeRecord<{ a: 1; b: Node }>;\n\t\tallowUnused<requireAssignableTo<Wrapped, { a: 1; b: PropTreeNode<Node> }>>();\n\t});\n\n\tit(\"IsMappableObjectType\", () => {\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<{ x: number }>, true>>();\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<{ readonly x: number }>, true>>();\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<Record<string, number>>, true>>();\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<number[]>, true>>();\n\n\t\t// Interestingly, maps are not nominally typed:\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<Map<number, number>>, true>>();\n\t\t// Constructors are not mappable:\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<typeof Map>, false>>();\n\t\t// Nor are functions\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<() => number>, false>>();\n\n\t\tclass Nominal {\n\t\t\tpublic constructor(x: number) {}\n\t\t\tprotected readonly _nominal = undefined;\n\t\t}\n\n\t\t// Instances of classes with private or protected fields are also not mappable:\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<Nominal>, false>>();\n\t\t// TreeNode is nominally type so not mappable:\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<TreeNode>, false>>();\n\n\t\t// Primitives are mappable since mapping over them leaves them unchanged in a generic context:\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<number>, true>>();\n\n\t\ttype Mapped<T> = {\n\t\t\t[P in keyof T]: T[P];\n\t\t};\n\n\t\ttype MappedNumberGeneric = Mapped<number>;\n\n\t\tallowUnused<requireAssignableTo<MappedNumberGeneric, number>>();\n\t\tallowUnused<requireAssignableTo<number, MappedNumberGeneric>>();\n\n\t\t// Oddly, mapping over a primitive type does change its type if done non-generically\n\n\t\ttype MappedNumberDirect = {\n\t\t\t[P in keyof number]: number[P];\n\t\t};\n\n\t\t// @ts-expect-error MappedNumberDirect is not a number:\n\t\tallowUnused<requireAssignableTo<MappedNumberDirect, number>>();\n\n\t\t// Unions:\n\t\t// True Case: All members are mappable\n\t\ttype SimpleUnion = { a: 1 } | { b: 2 };\n\t\ttype MappedSimpleUnion = Mapped<SimpleUnion>;\n\t\tallowUnused<requireAssignableTo<MappedSimpleUnion, SimpleUnion>>();\n\t\tallowUnused<requireAssignableTo<SimpleUnion, MappedSimpleUnion>>();\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<SimpleUnion>, true>>();\n\n\t\t// False Case: Not all members are mappable\n\t\ttype Union = { a: 1 } | { b: 2 } | number | Nominal;\n\t\ttype MappedUnion = Mapped<Union>;\n\n\t\t// @ts-expect-error Union is lossy when mapped due to having lossy members:\n\t\tallowUnused<requireAssignableTo<MappedUnion, Union>>();\n\t\tallowUnused<requireAssignableTo<Union, MappedUnion>>();\n\t\tallowUnused<requireAssignableTo<IsMappableObjectType<Union>, false>>();\n\t});\n\n\tit(\"WrapNodes\", () => {\n\t\tconst builder = new SchemaFactory(\"tree-react-api\");\n\n\t\tclass Node extends builder.object(\"Node\", {}) {}\n\n\t\tclass Nominal {\n\t\t\tpublic constructor(x: number) {}\n\t\t\tprotected readonly _nominal = undefined;\n\t\t}\n\n\t\tallowUnused<requireAssignableTo<WrapNodes<Set<number>>, Set<number>>>();\n\t\tallowUnused<requireAssignableTo<WrapNodes<Nominal>, Nominal>>();\n\t\tallowUnused<requireAssignableTo<WrapNodes<Node>, PropTreeNode<Node>>>();\n\t\tallowUnused<\n\t\t\trequireAssignableTo<WrapNodes<{ a: 1; b: Node }>, { a: 1; b: PropTreeNode<Node> }>\n\t\t>();\n\t\t// Must not break existing PropTreeNode types:\n\t\tallowUnused<requireAssignableTo<WrapNodes<PropTreeNode<Node>>, PropTreeNode<Node>>>();\n\n\t\t// Does not break maps, though also doesn't wrap the values:\n\t\t// Note: WrappedMap intentionally preserves the named type here: intellisense for this should be `type WrappedMap = Map<number, Node>`.\n\t\t// We have no known way to test that is preserved, since a flattened mapped type would assignable in both directions.\n\t\ttype WrappedMap = WrapNodes<Map<number, Node>>;\n\t\tallowUnused<requireAssignableTo<WrappedMap, Map<number, Node>>>();\n\n\t\t// Does not break maps\n\t\tallowUnused<requireAssignableTo<WrapNodes<Map<number, number>>, Map<number, number>>>();\n\n\t\t// Avoids breaking nominal types while recursing into objects:\n\t\tallowUnused<\n\t\t\trequireAssignableTo<\n\t\t\t\tWrapNodes<{ a: 1; b: Node; c: Nominal; d: typeof Nominal; e: { inner: Node } }>,\n\t\t\t\t{\n\t\t\t\t\ta: 1;\n\t\t\t\t\tb: PropTreeNode<Node>;\n\t\t\t\t\tc: Nominal;\n\t\t\t\t\td: typeof Nominal;\n\t\t\t\t\te: { inner: PropTreeNode<Node> };\n\t\t\t\t}\n\t\t\t>\n\t\t>();\n\n\t\tclass Nominal2 {\n\t\t\tprotected readonly _nominal = undefined;\n\t\t\tpublic readonly child: Node = new Node({});\n\t\t}\n\n\t\ttype WrappedNominal2 = WrapNodes<Nominal2>;\n\t\tallowUnused<requireAssignableTo<WrappedNominal2, Nominal2>>();\n\n\t\t// Does not break unions\n\t\tallowUnused<\n\t\t\trequireAssignableTo<\n\t\t\t\tWrapNodes<{ a: 1 } | { b: Node } | number | Nominal | Node>,\n\t\t\t\t{ a: 1 } | { b: PropTreeNode<Node> } | number | Nominal | PropTreeNode<Node>\n\t\t\t>\n\t\t>();\n\n\t\t/**\n\t\t * Example deep mapped type which wraps TreeNodes in PropTreeNodes:\n\t\t * this causes issues which is why its not used in WrapNodes.\n\t\t *\n\t\t * Note that this doesn't handle all cases as it still fails to handle methods/properties that read from nodes (handling that is impossible),\n\t\t * and methods which return nodes (handling that is possible if they include TreeNode in the return type, but impossible if not).\n\t\t */\n\t\ttype MappedDeep<T> = T extends TreeNode\n\t\t\t? PropTreeIfNode<T>\n\t\t\t: T extends object\n\t\t\t\t? {\n\t\t\t\t\t\treadonly [P in keyof T]: MappedDeep<T[P]>;\n\t\t\t\t\t}\n\t\t\t\t: T;\n\t\ttype PropTreeIfNode<T> = T extends TreeNode ? PropTreeNode<T> : T;\n\n\t\ttype MappedDeepMixed = MappedDeep<{ a: 1; b: Node; c: Nominal; d: typeof Nominal }>;\n\n\t\t// DeepWrappedMixed removed the nominal typing! Thats bad!\n\t\tallowUnused<requireAssignableTo<MappedDeepMixed[\"c\"], { unrelated?: unknown }>>();\n\n\t\t// @ts-expect-error The fact that \"d\" was a constructor was erased. Thats bad!\n\t\ttype Y = ConstructorParameters<MappedDeepMixed[\"d\"]>;\n\n\t\t// This correctly transforms the child field, but destroys the nominal typing of Nominal2\n\t\ttype MappedDeepNominal2 = MappedDeep<Nominal2>;\n\n\t\t// Child transformed correctly:\n\t\tallowUnused<\n\t\t\trequireAssignableTo<MappedDeepNominal2, { readonly child: PropTreeNode<Node> }>\n\t\t>();\n\t\t// But lost the nominal typing:\n\t\tallowUnused<\n\t\t\trequireAssignableTo<{ readonly child: PropTreeNode<Node> }, MappedDeepNominal2>\n\t\t>();\n\t});\n});\n"]}
@@ -0,0 +1,71 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { TinyliciousClient } from "@fluidframework/tinylicious-client";
7
+ import { SchemaFactory, TreeViewConfiguration } from "@fluidframework/tree";
8
+ import { independentView } from "@fluidframework/tree/internal";
9
+ import { render } from "@testing-library/react";
10
+ import globalJsdom from "global-jsdom";
11
+ import * as React from "react";
12
+ import { treeDataObject, TreeViewComponent } from "../reactSharedTreeView.js";
13
+ describe("reactSharedTreeView", () => {
14
+ it("treeDataObject", async () => {
15
+ const builder = new SchemaFactory("tree-react-api");
16
+ class Inventory extends builder.object("Contoso:InventoryItem-1.0.0", {
17
+ nuts: builder.number,
18
+ bolts: builder.number,
19
+ }) {
20
+ }
21
+ const containerSchema = {
22
+ initialObjects: {
23
+ // TODO: it seems odd that DataObjects in container schema need both a key under initialObjects where they are,
24
+ // as well as a key under the root data object, and SharedObjects only need one key.
25
+ // Maybe we can default the shared object's key to be derived from the data objects key by default?
26
+ tree: treeDataObject(new TreeViewConfiguration({ schema: Inventory }), () => new Inventory({ nuts: 5, bolts: 6 })),
27
+ },
28
+ };
29
+ // TODO: Ideally we would use a local-server service-client, but one does not appear to exist.
30
+ const tinyliciousClient = new TinyliciousClient();
31
+ const { container } = await tinyliciousClient.createContainer(containerSchema, "2");
32
+ const dataObject = container.initialObjects.tree;
33
+ assert.equal(dataObject.treeView.root.nuts, 5);
34
+ dataObject.treeView.root.nuts += 1;
35
+ assert.equal(dataObject.treeView.root.bolts, 6);
36
+ });
37
+ describe("dom tests", () => {
38
+ let cleanup;
39
+ before(() => {
40
+ cleanup = globalJsdom();
41
+ });
42
+ after(() => {
43
+ cleanup();
44
+ });
45
+ // Run without strict mode to make sure it works in a normal production setup.
46
+ // Run with strict mode to potentially detect additional issues.
47
+ // Note that React's strict mode is not more strict,
48
+ // but instead drastically changes when the component code and its effects run to exercise more edge cases and detect certain kinds of bugs.
49
+ for (const reactStrictMode of [false, true]) {
50
+ describe(`StrictMode: ${reactStrictMode}`, () => {
51
+ const builder = new SchemaFactory("tree-react-api");
52
+ class Item extends builder.object("Item", {}) {
53
+ }
54
+ const View = ({ root }) => (React.createElement("span", null, "View"));
55
+ it("TreeViewComponent", () => {
56
+ const view = independentView(new TreeViewConfiguration({ schema: Item }), {});
57
+ const content = React.createElement(TreeViewComponent, { viewComponent: View, tree: { treeView: view } });
58
+ const rendered = render(content, { reactStrictMode });
59
+ // Ensure that viewing an incompatible document displays an error.
60
+ assert.match(rendered.baseElement.textContent ?? "", /Document is incompatible/);
61
+ // Ensure that changes in compatibility are detected and invalidate the view,
62
+ // and that compatible documents show the content from `viewComponent`
63
+ view.initialize(new Item({}));
64
+ rendered.rerender(content);
65
+ assert.equal(rendered.baseElement.textContent, "View");
66
+ });
67
+ });
68
+ }
69
+ });
70
+ });
71
+ //# sourceMappingURL=reactSharedTreeView.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactSharedTreeView.spec.js","sourceRoot":"","sources":["../../src/test/reactSharedTreeView.spec.tsx"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport type { ContainerSchema } from \"@fluidframework/fluid-static\";\nimport { TinyliciousClient } from \"@fluidframework/tinylicious-client\";\nimport { SchemaFactory, TreeViewConfiguration } from \"@fluidframework/tree\";\nimport { independentView } from \"@fluidframework/tree/internal\";\nimport { render } from \"@testing-library/react\";\nimport globalJsdom from \"global-jsdom\";\nimport * as React from \"react\";\n\nimport type { PropTreeNode } from \"../propNode.js\";\nimport { treeDataObject, TreeViewComponent } from \"../reactSharedTreeView.js\";\n\ndescribe(\"reactSharedTreeView\", () => {\n\tit(\"treeDataObject\", async () => {\n\t\tconst builder = new SchemaFactory(\"tree-react-api\");\n\n\t\tclass Inventory extends builder.object(\"Contoso:InventoryItem-1.0.0\", {\n\t\t\tnuts: builder.number,\n\t\t\tbolts: builder.number,\n\t\t}) {}\n\n\t\tconst containerSchema = {\n\t\t\tinitialObjects: {\n\t\t\t\t// TODO: it seems odd that DataObjects in container schema need both a key under initialObjects where they are,\n\t\t\t\t// as well as a key under the root data object, and SharedObjects only need one key.\n\t\t\t\t// Maybe we can default the shared object's key to be derived from the data objects key by default?\n\t\t\t\ttree: treeDataObject(\n\t\t\t\t\tnew TreeViewConfiguration({ schema: Inventory }),\n\t\t\t\t\t() => new Inventory({ nuts: 5, bolts: 6 }),\n\t\t\t\t),\n\t\t\t},\n\t\t} satisfies ContainerSchema;\n\n\t\t// TODO: Ideally we would use a local-server service-client, but one does not appear to exist.\n\t\tconst tinyliciousClient = new TinyliciousClient();\n\n\t\tconst { container } = await tinyliciousClient.createContainer(containerSchema, \"2\");\n\t\tconst dataObject = container.initialObjects.tree;\n\t\tassert.equal(dataObject.treeView.root.nuts, 5);\n\t\tdataObject.treeView.root.nuts += 1;\n\t\tassert.equal(dataObject.treeView.root.bolts, 6);\n\t});\n\n\tdescribe(\"dom tests\", () => {\n\t\tlet cleanup: () => void;\n\n\t\tbefore(() => {\n\t\t\tcleanup = globalJsdom();\n\t\t});\n\n\t\tafter(() => {\n\t\t\tcleanup();\n\t\t});\n\n\t\t// Run without strict mode to make sure it works in a normal production setup.\n\t\t// Run with strict mode to potentially detect additional issues.\n\t\t// Note that React's strict mode is not more strict,\n\t\t// but instead drastically changes when the component code and its effects run to exercise more edge cases and detect certain kinds of bugs.\n\t\tfor (const reactStrictMode of [false, true]) {\n\t\t\tdescribe(`StrictMode: ${reactStrictMode}`, () => {\n\t\t\t\tconst builder = new SchemaFactory(\"tree-react-api\");\n\n\t\t\t\tclass Item extends builder.object(\"Item\", {}) {}\n\n\t\t\t\tconst View = ({ root }: { root: PropTreeNode<Item> }): React.JSX.Element => (\n\t\t\t\t\t<span>View</span>\n\t\t\t\t);\n\n\t\t\t\tit(\"TreeViewComponent\", () => {\n\t\t\t\t\tconst view = independentView(new TreeViewConfiguration({ schema: Item }), {});\n\t\t\t\t\tconst content = <TreeViewComponent viewComponent={View} tree={{ treeView: view }} />;\n\t\t\t\t\tconst rendered = render(content, { reactStrictMode });\n\n\t\t\t\t\t// Ensure that viewing an incompatible document displays an error.\n\t\t\t\t\tassert.match(rendered.baseElement.textContent ?? \"\", /Document is incompatible/);\n\t\t\t\t\t// Ensure that changes in compatibility are detected and invalidate the view,\n\t\t\t\t\t// and that compatible documents show the content from `viewComponent`\n\t\t\t\t\tview.initialize(new Item({}));\n\t\t\t\t\trendered.rerender(content);\n\t\t\t\t\tassert.equal(rendered.baseElement.textContent, \"View\");\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t});\n});\n"]}
@@ -0,0 +1,18 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { objectIdNumber } from "../simpleIdentifier.js";
7
+ describe("simpleIdentifier", () => {
8
+ it("objectIdNumber", () => {
9
+ const a = {};
10
+ const b = {};
11
+ const aId = objectIdNumber(a);
12
+ const bId = objectIdNumber(b);
13
+ assert.equal(aId, objectIdNumber(a));
14
+ assert.equal(bId, objectIdNumber(b));
15
+ assert.notEqual(aId, bId);
16
+ });
17
+ });
18
+ //# sourceMappingURL=simpleIdentifier.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simpleIdentifier.spec.js","sourceRoot":"","sources":["../../src/test/simpleIdentifier.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,GAAW,EAAE,CAAC;QACrB,MAAM,CAAC,GAAW,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { objectIdNumber } from \"../simpleIdentifier.js\";\n\ndescribe(\"simpleIdentifier\", () => {\n\tit(\"objectIdNumber\", () => {\n\t\tconst a: object = {};\n\t\tconst b: object = {};\n\t\tconst aId = objectIdNumber(a);\n\t\tconst bId = objectIdNumber(b);\n\t\tassert.equal(aId, objectIdNumber(a));\n\t\tassert.equal(bId, objectIdNumber(b));\n\t\tassert.notEqual(aId, bId);\n\t});\n});\n"]}
@@ -0,0 +1,162 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { render } from "@testing-library/react";
7
+ import globalJsdom from "global-jsdom";
8
+ import * as React from "react";
9
+ import { useObservation, useObservationStrict, useObservationWithEffects, } from "../useObservation.js";
10
+ // There is much more coverage of useObservation via useTree tests.
11
+ describe("useObservation", () => {
12
+ describe("dom tests", () => {
13
+ let cleanup;
14
+ before(() => {
15
+ cleanup = globalJsdom();
16
+ });
17
+ after(() => {
18
+ cleanup();
19
+ });
20
+ const observationHooks = [
21
+ useObservation,
22
+ useObservationStrict,
23
+ useObservationWithEffects,
24
+ ];
25
+ for (const useObservationHook of observationHooks) {
26
+ // eslint-disable-next-line @typescript-eslint/ban-types
27
+ describe(useObservationHook.name, () => {
28
+ for (const reactStrictMode of [false, true]) {
29
+ /**
30
+ * Check then clear the contents of `log`.
31
+ *
32
+ * @remarks When in StrictMode, React may double render, so that case is not checked for an exact match.
33
+ */
34
+ // eslint-disable-next-line no-inner-declarations
35
+ function checkRenderLog(log, expected) {
36
+ if (reactStrictMode) {
37
+ assert.deepEqual(new Set(log), new Set(expected));
38
+ }
39
+ else {
40
+ assert.deepEqual(log, expected);
41
+ }
42
+ log.length = 0;
43
+ }
44
+ describe(`StrictMode: ${reactStrictMode}`, () => {
45
+ it("useObservation", async () => {
46
+ const log = [];
47
+ const unsubscribe = () => {
48
+ log.push("unsubscribe");
49
+ };
50
+ function TestComponent() {
51
+ log.push("render");
52
+ return useObservationHook((invalidate) => {
53
+ log.push(`useObservation`);
54
+ return {
55
+ result: (React.createElement("button", { onClick: () => {
56
+ // In real usage, this would unsubscribe from any events.
57
+ log.push("click");
58
+ invalidate();
59
+ } }, "Invalidate")),
60
+ unsubscribe,
61
+ };
62
+ }, { onInvalidation: () => log.push("invalidated") });
63
+ }
64
+ const content = React.createElement(TestComponent, null);
65
+ const rendered = render(content, { reactStrictMode });
66
+ checkRenderLog(log, ["render", "useObservation"]);
67
+ rendered.rerender(content);
68
+ assertLogEmpty(log);
69
+ const button = rendered.baseElement.querySelector("button") ??
70
+ assert.fail("button not found");
71
+ button.click();
72
+ checkRenderLog(log, ["click", "invalidated"]);
73
+ rendered.rerender(content);
74
+ checkRenderLog(log, [
75
+ "render",
76
+ ...(reactStrictMode && useObservationHook !== useObservationWithEffects
77
+ ? ["unsubscribe"]
78
+ : []),
79
+ "useObservation",
80
+ ]);
81
+ });
82
+ // This requires waiting for finalizers.
83
+ // Forcing two async GCs seems to work robustly, so this is enabled, but if it becomes flakey, it can be tweaked and/or skipped.
84
+ it(`unsubscribe on unmount`, async () => {
85
+ assert(global.gc);
86
+ const log = [];
87
+ const unsubscribe = () => {
88
+ log.push("unsubscribe");
89
+ };
90
+ function TestComponent() {
91
+ return useObservationHook((invalidate) => ({
92
+ result: React.createElement("br", null),
93
+ unsubscribe,
94
+ }));
95
+ }
96
+ const rendered = render(React.createElement(TestComponent, null), { reactStrictMode });
97
+ assertLogEmpty(log);
98
+ rendered.unmount();
99
+ // Unsubscribe on unmount is done via FinalizationRegistry, so force a GC and wait for it.
100
+ // For this to pass on NodeJs experimentally is has been found that this must either do:
101
+ // 1. a sync GC then a wait of 8 seconds (but this sometimes fails after multiple runs unless a debugger takes a heap snapshot, possible due to some JIT optimization that breaks it).
102
+ // 2. two async GCs in a row.
103
+ // Since the second option is both more robust and faster, that is what is used here.
104
+ for (let index = 0; index < 2; index++) {
105
+ await global.gc({ type: "major", execution: "async" });
106
+ if (log.length > 0) {
107
+ break;
108
+ }
109
+ }
110
+ checkRenderLog(log, ["unsubscribe"]);
111
+ });
112
+ it("invalidate after unmount", () => {
113
+ const log = [];
114
+ let logUnsubscribe = true;
115
+ const unsubscribe = () => {
116
+ if (logUnsubscribe)
117
+ log.push("unsubscribe");
118
+ };
119
+ const invalidateCallbacks = [];
120
+ function TestComponent() {
121
+ log.push("render");
122
+ return useObservationHook((invalidate) => {
123
+ invalidateCallbacks.push(invalidate);
124
+ return {
125
+ result: React.createElement("br", null),
126
+ unsubscribe,
127
+ };
128
+ }, { onInvalidation: () => log.push("invalidated") });
129
+ }
130
+ const rendered = render(React.createElement(TestComponent, null), { reactStrictMode });
131
+ checkRenderLog(log, ["render"]);
132
+ // After unmount, unsubscribe could happen at any time due to finalizer,so suppress logging it to prevent the test from possibly becoming flaky.
133
+ logUnsubscribe = false;
134
+ rendered.unmount();
135
+ assert(invalidateCallbacks.length === (reactStrictMode ? 2 : 1));
136
+ // Invalidate after unmount.
137
+ // Since this can happen in real use, due to unsubscribe delay while waiting for finalizer, ensure it does not cause issues.
138
+ // This should be a no-op, but since it does a React SetState after unmount, React could object to it.
139
+ for (const callback of invalidateCallbacks) {
140
+ callback();
141
+ }
142
+ // Confirm the invalidation happened..
143
+ // If we didn't suppress unsubscribe logging, and the finalizer had run, this could fail (which is why we suppress it).
144
+ checkRenderLog(log, ["invalidated"]);
145
+ });
146
+ });
147
+ }
148
+ });
149
+ }
150
+ });
151
+ });
152
+ /**
153
+ * Assert that an array is empty.
154
+ *
155
+ * @remarks
156
+ * Not inlined because doing so causes TypeScript to infer the array type as never[] afterwards and breaks push.
157
+ * Better than asserting length is 0 as this gets a better error message on failure.
158
+ */
159
+ function assertLogEmpty(log) {
160
+ assert.deepEqual(log, []);
161
+ }
162
+ //# sourceMappingURL=useObservation.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useObservation.spec.js","sourceRoot":"","sources":["../../src/test/useObservation.spec.tsx"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { render } from \"@testing-library/react\";\nimport globalJsdom from \"global-jsdom\";\nimport * as React from \"react\";\n\nimport {\n\tuseObservation,\n\tuseObservationStrict,\n\tuseObservationWithEffects,\n} from \"../useObservation.js\";\n\n// There is much more coverage of useObservation via useTree tests.\n\ndescribe(\"useObservation\", () => {\n\tdescribe(\"dom tests\", () => {\n\t\tlet cleanup: () => void;\n\n\t\tbefore(() => {\n\t\t\tcleanup = globalJsdom();\n\t\t});\n\n\t\tafter(() => {\n\t\t\tcleanup();\n\t\t});\n\n\t\tconst observationHooks: (typeof useObservation)[] = [\n\t\t\tuseObservation,\n\t\t\tuseObservationStrict,\n\t\t\tuseObservationWithEffects,\n\t\t] as const;\n\n\t\tfor (const useObservationHook of observationHooks) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/ban-types\n\t\t\tdescribe((useObservationHook as Function).name, () => {\n\t\t\t\tfor (const reactStrictMode of [false, true]) {\n\t\t\t\t\t/**\n\t\t\t\t\t * Check then clear the contents of `log`.\n\t\t\t\t\t *\n\t\t\t\t\t * @remarks When in StrictMode, React may double render, so that case is not checked for an exact match.\n\t\t\t\t\t */\n\t\t\t\t\t// eslint-disable-next-line no-inner-declarations\n\t\t\t\t\tfunction checkRenderLog(log: string[], expected: readonly string[]): void {\n\t\t\t\t\t\tif (reactStrictMode) {\n\t\t\t\t\t\t\tassert.deepEqual(new Set(log), new Set(expected));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tassert.deepEqual(log, expected);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlog.length = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\tdescribe(`StrictMode: ${reactStrictMode}`, () => {\n\t\t\t\t\t\tit(\"useObservation\", async () => {\n\t\t\t\t\t\t\tconst log: string[] = [];\n\n\t\t\t\t\t\t\tconst unsubscribe = (): void => {\n\t\t\t\t\t\t\t\tlog.push(\"unsubscribe\");\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tfunction TestComponent(): JSX.Element {\n\t\t\t\t\t\t\t\tlog.push(\"render\");\n\t\t\t\t\t\t\t\treturn useObservationHook(\n\t\t\t\t\t\t\t\t\t(invalidate) => {\n\t\t\t\t\t\t\t\t\t\tlog.push(`useObservation`);\n\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\tresult: (\n\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// In real usage, this would unsubscribe from any events.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlog.push(\"click\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinvalidate();\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tInvalidate\n\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\tunsubscribe,\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{ onInvalidation: () => log.push(\"invalidated\") },\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst content = <TestComponent />;\n\n\t\t\t\t\t\t\tconst rendered = render(content, { reactStrictMode });\n\t\t\t\t\t\t\tcheckRenderLog(log, [\"render\", \"useObservation\"]);\n\n\t\t\t\t\t\t\trendered.rerender(content);\n\t\t\t\t\t\t\tassertLogEmpty(log);\n\n\t\t\t\t\t\t\tconst button =\n\t\t\t\t\t\t\t\trendered.baseElement.querySelector(\"button\") ??\n\t\t\t\t\t\t\t\tassert.fail(\"button not found\");\n\t\t\t\t\t\t\tbutton.click();\n\n\t\t\t\t\t\t\tcheckRenderLog(log, [\"click\", \"invalidated\"]);\n\n\t\t\t\t\t\t\trendered.rerender(content);\n\n\t\t\t\t\t\t\tcheckRenderLog(log, [\n\t\t\t\t\t\t\t\t\"render\",\n\t\t\t\t\t\t\t\t...(reactStrictMode && useObservationHook !== useObservationWithEffects\n\t\t\t\t\t\t\t\t\t? [\"unsubscribe\"]\n\t\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t\t\t\"useObservation\",\n\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// This requires waiting for finalizers.\n\t\t\t\t\t\t// Forcing two async GCs seems to work robustly, so this is enabled, but if it becomes flakey, it can be tweaked and/or skipped.\n\t\t\t\t\t\tit(`unsubscribe on unmount`, async () => {\n\t\t\t\t\t\t\tassert(global.gc);\n\n\t\t\t\t\t\t\tconst log: string[] = [];\n\n\t\t\t\t\t\t\tconst unsubscribe = (): void => {\n\t\t\t\t\t\t\t\tlog.push(\"unsubscribe\");\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tfunction TestComponent(this: unknown): JSX.Element {\n\t\t\t\t\t\t\t\treturn useObservationHook((invalidate) => ({\n\t\t\t\t\t\t\t\t\tresult: <br />,\n\t\t\t\t\t\t\t\t\tunsubscribe,\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rendered = render(<TestComponent />, { reactStrictMode });\n\n\t\t\t\t\t\t\tassertLogEmpty(log);\n\t\t\t\t\t\t\trendered.unmount();\n\n\t\t\t\t\t\t\t// Unsubscribe on unmount is done via FinalizationRegistry, so force a GC and wait for it.\n\t\t\t\t\t\t\t// For this to pass on NodeJs experimentally is has been found that this must either do:\n\t\t\t\t\t\t\t// 1. a sync GC then a wait of 8 seconds (but this sometimes fails after multiple runs unless a debugger takes a heap snapshot, possible due to some JIT optimization that breaks it).\n\t\t\t\t\t\t\t// 2. two async GCs in a row.\n\t\t\t\t\t\t\t// Since the second option is both more robust and faster, that is what is used here.\n\t\t\t\t\t\t\tfor (let index = 0; index < 2; index++) {\n\t\t\t\t\t\t\t\tawait global.gc({ type: \"major\", execution: \"async\" });\n\t\t\t\t\t\t\t\tif (log.length > 0) {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcheckRenderLog(log, [\"unsubscribe\"]);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tit(\"invalidate after unmount\", () => {\n\t\t\t\t\t\t\tconst log: string[] = [];\n\n\t\t\t\t\t\t\tlet logUnsubscribe = true;\n\n\t\t\t\t\t\t\tconst unsubscribe = (): void => {\n\t\t\t\t\t\t\t\tif (logUnsubscribe) log.push(\"unsubscribe\");\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tconst invalidateCallbacks: (() => void)[] = [];\n\n\t\t\t\t\t\t\tfunction TestComponent(): JSX.Element {\n\t\t\t\t\t\t\t\tlog.push(\"render\");\n\t\t\t\t\t\t\t\treturn useObservationHook(\n\t\t\t\t\t\t\t\t\t(invalidate) => {\n\t\t\t\t\t\t\t\t\t\tinvalidateCallbacks.push(invalidate);\n\t\t\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\t\t\tresult: <br />,\n\t\t\t\t\t\t\t\t\t\t\tunsubscribe,\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{ onInvalidation: () => log.push(\"invalidated\") },\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rendered = render(<TestComponent />, { reactStrictMode });\n\n\t\t\t\t\t\t\tcheckRenderLog(log, [\"render\"]);\n\n\t\t\t\t\t\t\t// After unmount, unsubscribe could happen at any time due to finalizer,so suppress logging it to prevent the test from possibly becoming flaky.\n\t\t\t\t\t\t\tlogUnsubscribe = false;\n\n\t\t\t\t\t\t\trendered.unmount();\n\n\t\t\t\t\t\t\tassert(invalidateCallbacks.length === (reactStrictMode ? 2 : 1));\n\n\t\t\t\t\t\t\t// Invalidate after unmount.\n\t\t\t\t\t\t\t// Since this can happen in real use, due to unsubscribe delay while waiting for finalizer, ensure it does not cause issues.\n\t\t\t\t\t\t\t// This should be a no-op, but since it does a React SetState after unmount, React could object to it.\n\t\t\t\t\t\t\tfor (const callback of invalidateCallbacks) {\n\t\t\t\t\t\t\t\tcallback();\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Confirm the invalidation happened..\n\t\t\t\t\t\t\t// If we didn't suppress unsubscribe logging, and the finalizer had run, this could fail (which is why we suppress it).\n\t\t\t\t\t\t\tcheckRenderLog(log, [\"invalidated\"]);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t});\n});\n\n/**\n * Assert that an array is empty.\n *\n * @remarks\n * Not inlined because doing so causes TypeScript to infer the array type as never[] afterwards and breaks push.\n * Better than asserting length is 0 as this gets a better error message on failure.\n */\nfunction assertLogEmpty(log: string[]): void {\n\tassert.deepEqual(log, []);\n}\n"]}
@@ -0,0 +1,165 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { SchemaFactory } from "@fluidframework/tree";
7
+ import { render } from "@testing-library/react";
8
+ import globalJsdom from "global-jsdom";
9
+ import * as React from "react";
10
+ import { toPropTreeNode } from "../propNode.js";
11
+ import { objectIdNumber } from "../simpleIdentifier.js";
12
+ import { usePropTreeNode, withMemoizedTreeObservations, withTreeObservations, } from "../useTree.js";
13
+ describe("useTree", () => {
14
+ describe("dom tests", () => {
15
+ let cleanup;
16
+ before(() => {
17
+ cleanup = globalJsdom();
18
+ });
19
+ after(() => {
20
+ cleanup();
21
+ });
22
+ it("withTreeObservations example", () => {
23
+ const builder = new SchemaFactory("example");
24
+ class Item extends builder.object("Item", { text: SchemaFactory.string }) {
25
+ }
26
+ const ItemComponentBug = ({ item }) => (React.createElement("span", null, item.text) // Reading `text`, a mutable value from a React prop, causes an invalidation bug.
27
+ );
28
+ const ItemComponent = withTreeObservations(({ item }) => React.createElement("span", null, item.text));
29
+ const ItemParentComponent = ({ item }) => (React.createElement(ItemComponent, { item: item }));
30
+ const InvalidItemParentComponent = ({ item, }) => (
31
+ // @ts-expect-error PropTreeNode turns this invalidation bug into a compile error
32
+ React.createElement("span", null, item.text));
33
+ });
34
+ for (const reactStrictMode of [false, true]) {
35
+ /**
36
+ * Check then clear, the contents of `log`.
37
+ *
38
+ * When in StrictMode, React may double render, so that case is not checked for an exact match.
39
+ */
40
+ // eslint-disable-next-line no-inner-declarations
41
+ function checkRenderLog(log, expected) {
42
+ if (reactStrictMode) {
43
+ assert.deepEqual(new Set(log), new Set(expected));
44
+ }
45
+ else {
46
+ assert.deepEqual(log, expected);
47
+ }
48
+ log.length = 0;
49
+ }
50
+ describe(`StrictMode: ${reactStrictMode}`, () => {
51
+ it("usePropTreeNode", () => {
52
+ const builder = new SchemaFactory("tree-react-api");
53
+ class Point extends builder.object("Point", {
54
+ x: builder.number,
55
+ y: builder.number,
56
+ }) {
57
+ }
58
+ const log = [];
59
+ function PointComponent(props) {
60
+ log.push("render");
61
+ const { x, y } = usePropTreeNode(props.node, (node) => {
62
+ log.push(`usePropTreeNode`);
63
+ return {
64
+ x: node.x,
65
+ y: node.y,
66
+ };
67
+ });
68
+ return React.createElement("span", null, `x: ${x}, y: ${y}`);
69
+ }
70
+ function ParentComponent(props) {
71
+ log.push("parent");
72
+ return React.createElement(PointComponent, { node: props.node });
73
+ }
74
+ const point = new Point({ x: 1, y: 1 });
75
+ const propPoint = toPropTreeNode(point);
76
+ const content = React.createElement(ParentComponent, { node: propPoint });
77
+ const rendered = render(content, { reactStrictMode });
78
+ assert.equal(rendered.baseElement.textContent, "x: 1, y: 1");
79
+ checkRenderLog(log, ["parent", "render", "usePropTreeNode"]);
80
+ rendered.rerender(content);
81
+ assertLogEmpty(log);
82
+ point.x = 2;
83
+ assertLogEmpty(log);
84
+ rendered.rerender(content);
85
+ // Parent which passed node down did not rerender, but PointComponent which read from it did:
86
+ checkRenderLog(log, ["render", "usePropTreeNode"]);
87
+ assert.equal(rendered.baseElement.textContent, "x: 2, y: 1");
88
+ });
89
+ });
90
+ describe("withTreeObservations and array", () => {
91
+ const builder = new SchemaFactory("tree-react-api");
92
+ class Item extends builder.object("Item", {
93
+ x: SchemaFactory.number,
94
+ }) {
95
+ }
96
+ class Collection extends builder.array("Collection", Item) {
97
+ }
98
+ beforeEach(() => {
99
+ // Ensure the log starts empty for each test.
100
+ log.length = 0;
101
+ });
102
+ const log = [];
103
+ const ItemComponent = withMemoizedTreeObservations((props) => {
104
+ log.push(`Item: ${props.item.x}`);
105
+ return React.createElement("span", null, `${props.item.x}`);
106
+ }, { onInvalidation: () => log.push("Item invalidated") });
107
+ const CollectionComponent = withTreeObservations((props) => {
108
+ log.push("Collection");
109
+ const items = props.collection.map((item) => (React.createElement(ItemComponent, { key: objectIdNumber(item), item: item })));
110
+ return React.createElement("div", null, items);
111
+ }, { onInvalidation: () => log.push("Collection invalidated") });
112
+ const ParentComponent = withTreeObservations((props) => {
113
+ log.push("Parent");
114
+ return React.createElement(CollectionComponent, { collection: props.node });
115
+ }, { onInvalidation: () => log.push("Parent invalidated") });
116
+ it("empty", () => {
117
+ const collection = new Collection([]);
118
+ const content = React.createElement(ParentComponent, { node: collection });
119
+ render(content, { reactStrictMode });
120
+ checkRenderLog(log, ["Parent", "Collection"]);
121
+ });
122
+ // This confirms that modifying an array does not needlessly invalid parents and reuses children (if they use memo)
123
+ it("array editing: insertion", () => {
124
+ const collection = new Collection([{ x: 1 }, { x: 2 }, { x: 3 }]);
125
+ const content = React.createElement(ParentComponent, { node: collection });
126
+ const rendered = render(content, { reactStrictMode });
127
+ checkRenderLog(log, ["Parent", "Collection", "Item: 1", "Item: 2", "Item: 3"]);
128
+ collection.insertAtEnd(new Item({ x: 4 }));
129
+ checkRenderLog(log, ["Collection invalidated"]);
130
+ rendered.rerender(content);
131
+ checkRenderLog(log, ["Collection", "Item: 4"]);
132
+ assert.equal(rendered.baseElement.textContent, "1234");
133
+ });
134
+ // This confirms the same as the above, but testes some harder cases.
135
+ // For example this one depends on stable keys to reusing children due to indexes changing.
136
+ it("array editing: general", () => {
137
+ const collection = new Collection([{ x: 1 }, { x: 2 }, { x: 3 }]);
138
+ const content = React.createElement(ParentComponent, { node: collection });
139
+ const rendered = render(content, { reactStrictMode });
140
+ checkRenderLog(log, ["Parent", "Collection", "Item: 1", "Item: 2", "Item: 3"]);
141
+ collection.moveToEnd(0);
142
+ checkRenderLog(log, ["Collection invalidated"]);
143
+ rendered.rerender(content);
144
+ checkRenderLog(log, ["Collection"]);
145
+ collection.removeAt(1);
146
+ collection.insertAtStart(new Item({ x: 4 }));
147
+ checkRenderLog(log, ["Collection invalidated"]);
148
+ rendered.rerender(content);
149
+ checkRenderLog(log, ["Collection", "Item: 4"]);
150
+ assert.equal(rendered.baseElement.textContent, "421");
151
+ });
152
+ });
153
+ }
154
+ });
155
+ });
156
+ /**
157
+ * Assert that an array is empty.
158
+ *
159
+ * Not inlined because doing so causes TypeScript to infer the array type as never[] afterwards and breaks push.
160
+ * Better than asserting length is 0 as this gets a better error message on failure.
161
+ */
162
+ function assertLogEmpty(log) {
163
+ assert.deepEqual(log, []);
164
+ }
165
+ //# sourceMappingURL=useTree.spec.js.map