@shopify/flash-list 2.0.0-alpha.3 → 2.0.0-alpha.4

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 (221) hide show
  1. package/dist/AnimatedFlashList.d.ts +0 -1
  2. package/dist/AnimatedFlashList.d.ts.map +1 -1
  3. package/dist/FlashList.js +2 -3
  4. package/dist/FlashList.js.map +1 -1
  5. package/dist/FlashListProps.d.ts +3 -3
  6. package/dist/FlashListProps.d.ts.map +1 -1
  7. package/dist/GridLayoutProviderWithProps.js +1 -2
  8. package/dist/GridLayoutProviderWithProps.js.map +1 -1
  9. package/dist/MasonryFlashList.d.ts +2 -2
  10. package/dist/MasonryFlashList.d.ts.map +1 -1
  11. package/dist/MasonryFlashList.js.map +1 -1
  12. package/dist/PureComponentWrapper.js +1 -1
  13. package/dist/PureComponentWrapper.js.map +1 -1
  14. package/dist/__tests__/AverageWindow.test.js.map +1 -1
  15. package/dist/__tests__/ConsecutiveNumbers.test.d.ts +2 -0
  16. package/dist/__tests__/ConsecutiveNumbers.test.d.ts.map +1 -0
  17. package/dist/__tests__/ConsecutiveNumbers.test.js +224 -0
  18. package/dist/__tests__/ConsecutiveNumbers.test.js.map +1 -0
  19. package/dist/__tests__/FlashList.test.js.map +1 -1
  20. package/dist/__tests__/GridLayoutManager.test.d.ts +2 -0
  21. package/dist/__tests__/GridLayoutManager.test.d.ts.map +1 -0
  22. package/dist/__tests__/GridLayoutManager.test.js +69 -0
  23. package/dist/__tests__/GridLayoutManager.test.js.map +1 -0
  24. package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -1
  25. package/dist/__tests__/LinearLayoutManager.test.d.ts +2 -0
  26. package/dist/__tests__/LinearLayoutManager.test.d.ts.map +1 -0
  27. package/dist/__tests__/LinearLayoutManager.test.js +140 -0
  28. package/dist/__tests__/LinearLayoutManager.test.js.map +1 -0
  29. package/dist/__tests__/MasonryFlashList.test.js.map +1 -1
  30. package/dist/__tests__/MasonryLayoutManager.test.d.ts +2 -0
  31. package/dist/__tests__/MasonryLayoutManager.test.d.ts.map +1 -0
  32. package/dist/__tests__/MasonryLayoutManager.test.js +148 -0
  33. package/dist/__tests__/MasonryLayoutManager.test.js.map +1 -0
  34. package/dist/__tests__/RecycleKeyManager.test.d.ts +2 -0
  35. package/dist/__tests__/RecycleKeyManager.test.d.ts.map +1 -0
  36. package/dist/__tests__/RecycleKeyManager.test.js +210 -0
  37. package/dist/__tests__/RecycleKeyManager.test.js.map +1 -0
  38. package/dist/__tests__/RecyclerView.test.d.ts +2 -0
  39. package/dist/__tests__/RecyclerView.test.d.ts.map +1 -0
  40. package/dist/__tests__/RecyclerView.test.js +59 -0
  41. package/dist/__tests__/RecyclerView.test.js.map +1 -0
  42. package/dist/__tests__/ViewabilityHelper.test.js.map +1 -1
  43. package/dist/__tests__/findVisibleIndex.test.d.ts +2 -0
  44. package/dist/__tests__/findVisibleIndex.test.d.ts.map +1 -0
  45. package/dist/__tests__/findVisibleIndex.test.js +259 -0
  46. package/dist/__tests__/findVisibleIndex.test.js.map +1 -0
  47. package/dist/__tests__/helpers/createLayoutManager.d.ts +34 -0
  48. package/dist/__tests__/helpers/createLayoutManager.d.ts.map +1 -0
  49. package/dist/__tests__/helpers/createLayoutManager.js +111 -0
  50. package/dist/__tests__/helpers/createLayoutManager.js.map +1 -0
  51. package/dist/__tests__/helpers/mountFlashList.d.ts +2 -2
  52. package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -1
  53. package/dist/__tests__/helpers/mountFlashList.js +2 -2
  54. package/dist/__tests__/helpers/mountFlashList.js.map +1 -1
  55. package/dist/__tests__/helpers/mountMasonryFlashList.d.ts +2 -2
  56. package/dist/__tests__/helpers/mountMasonryFlashList.d.ts.map +1 -1
  57. package/dist/__tests__/helpers/mountMasonryFlashList.js +2 -2
  58. package/dist/__tests__/helpers/mountMasonryFlashList.js.map +1 -1
  59. package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -1
  60. package/dist/benchmark/AutoScrollHelper.js +2 -2
  61. package/dist/benchmark/AutoScrollHelper.js.map +1 -1
  62. package/dist/benchmark/JSFPSMonitor.js.map +1 -1
  63. package/dist/benchmark/roundToDecimalPlaces.js +1 -2
  64. package/dist/benchmark/roundToDecimalPlaces.js.map +1 -1
  65. package/dist/benchmark/useBenchmark.js +2 -3
  66. package/dist/benchmark/useBenchmark.js.map +1 -1
  67. package/dist/benchmark/useBlankAreaTracker.js +1 -2
  68. package/dist/benchmark/useBlankAreaTracker.js.map +1 -1
  69. package/dist/benchmark/useDataMultiplier.js +1 -2
  70. package/dist/benchmark/useDataMultiplier.js.map +1 -1
  71. package/dist/benchmark/useFlatListBenchmark.d.ts +0 -1
  72. package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -1
  73. package/dist/benchmark/useFlatListBenchmark.js +1 -2
  74. package/dist/benchmark/useFlatListBenchmark.js.map +1 -1
  75. package/dist/enableNewCore.js +2 -3
  76. package/dist/enableNewCore.js.map +1 -1
  77. package/dist/errors/CustomError.js.map +1 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/native/auto-layout/AutoLayoutView.d.ts +1 -1
  82. package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -1
  83. package/dist/native/auto-layout/AutoLayoutView.js +1 -1
  84. package/dist/native/auto-layout/AutoLayoutView.js.map +1 -1
  85. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -1
  86. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +1 -1
  87. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -1
  88. package/dist/native/config/PlatformHelper.android.d.ts.map +1 -1
  89. package/dist/native/config/PlatformHelper.d.ts.map +1 -1
  90. package/dist/native/config/PlatformHelper.ios.d.ts.map +1 -1
  91. package/dist/native/config/PlatformHelper.web.d.ts.map +1 -1
  92. package/dist/recyclerview/RecycleKeyManager.js.map +1 -1
  93. package/dist/recyclerview/RecyclerView.d.ts +1 -1
  94. package/dist/recyclerview/RecyclerView.d.ts.map +1 -1
  95. package/dist/recyclerview/RecyclerView.js +8 -12
  96. package/dist/recyclerview/RecyclerView.js.map +1 -1
  97. package/dist/recyclerview/RecyclerViewContextProvider.d.ts +0 -1
  98. package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -1
  99. package/dist/recyclerview/RecyclerViewContextProvider.js +2 -2
  100. package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -1
  101. package/dist/recyclerview/RecyclerViewManager.d.ts +2 -0
  102. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -1
  103. package/dist/recyclerview/RecyclerViewManager.js +20 -9
  104. package/dist/recyclerview/RecyclerViewManager.js.map +1 -1
  105. package/dist/recyclerview/RecyclerViewProps.d.ts +1 -1
  106. package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -1
  107. package/dist/recyclerview/ViewHolder.d.ts.map +1 -1
  108. package/dist/recyclerview/ViewHolder.js +3 -3
  109. package/dist/recyclerview/ViewHolder.js.map +1 -1
  110. package/dist/recyclerview/ViewHolderCollection.d.ts +1 -1
  111. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -1
  112. package/dist/recyclerview/ViewHolderCollection.js +0 -1
  113. package/dist/recyclerview/ViewHolderCollection.js.map +1 -1
  114. package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -1
  115. package/dist/recyclerview/components/ScrollAnchor.js +4 -6
  116. package/dist/recyclerview/components/ScrollAnchor.js.map +1 -1
  117. package/dist/recyclerview/components/StickyHeaders.d.ts +1 -1
  118. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -1
  119. package/dist/recyclerview/components/StickyHeaders.js +4 -13
  120. package/dist/recyclerview/components/StickyHeaders.js.map +1 -1
  121. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +1 -1
  122. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -1
  123. package/dist/recyclerview/helpers/ConsecutiveNumbers.js +2 -2
  124. package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -1
  125. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +3 -1
  126. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -1
  127. package/dist/recyclerview/helpers/EngagedIndicesTracker.js +14 -3
  128. package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -1
  129. package/dist/recyclerview/hooks/useBoundDetection.d.ts +0 -1
  130. package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -1
  131. package/dist/recyclerview/hooks/useBoundDetection.js +4 -6
  132. package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -1
  133. package/dist/recyclerview/hooks/useLayoutState.js +1 -2
  134. package/dist/recyclerview/hooks/useLayoutState.js.map +1 -1
  135. package/dist/recyclerview/hooks/useOnLoad.d.ts +2 -2
  136. package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -1
  137. package/dist/recyclerview/hooks/useOnLoad.js +5 -4
  138. package/dist/recyclerview/hooks/useOnLoad.js.map +1 -1
  139. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -1
  140. package/dist/recyclerview/hooks/useRecyclerViewController.js +109 -112
  141. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -1
  142. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -1
  143. package/dist/recyclerview/hooks/useRecyclingState.js +1 -2
  144. package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -1
  145. package/dist/recyclerview/hooks/useSecondaryProps.d.ts +1 -1
  146. package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -1
  147. package/dist/recyclerview/hooks/useSecondaryProps.js +9 -9
  148. package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -1
  149. package/dist/recyclerview/hooks/useUnmountFlag.d.ts +0 -1
  150. package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -1
  151. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -1
  152. package/dist/recyclerview/layout-managers/GridLayoutManager.js +2 -5
  153. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -1
  154. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +1 -0
  155. package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -1
  156. package/dist/recyclerview/layout-managers/LayoutManager.js +5 -1
  157. package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -1
  158. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +1 -2
  159. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -1
  160. package/dist/recyclerview/layout-managers/LinearLayoutManager.js +3 -3
  161. package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -1
  162. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -1
  163. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +3 -5
  164. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -1
  165. package/dist/recyclerview/utils/adjustOffsetForRTL.js +1 -2
  166. package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -1
  167. package/dist/recyclerview/utils/componentUtils.d.ts +1 -1
  168. package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -1
  169. package/dist/recyclerview/utils/componentUtils.js.map +1 -1
  170. package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -1
  171. package/dist/recyclerview/utils/findVisibleIndex.js +3 -5
  172. package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -1
  173. package/dist/recyclerview/utils/measureLayout.d.ts +24 -0
  174. package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -1
  175. package/dist/recyclerview/utils/measureLayout.js +38 -6
  176. package/dist/recyclerview/utils/measureLayout.js.map +1 -1
  177. package/dist/specs/AutoLayoutNativeComponent.d.ts +1 -2
  178. package/dist/specs/AutoLayoutNativeComponent.d.ts.map +1 -1
  179. package/dist/specs/CellContainerNativeComponent.d.ts +0 -1
  180. package/dist/specs/CellContainerNativeComponent.d.ts.map +1 -1
  181. package/dist/tsconfig.tsbuildinfo +1 -1
  182. package/dist/utils/AverageWindow.js.map +1 -1
  183. package/dist/utils/ContentContainerUtils.d.ts.map +1 -1
  184. package/dist/utils/ContentContainerUtils.js.map +1 -1
  185. package/dist/viewability/ViewabilityHelper.js.map +1 -1
  186. package/dist/viewability/ViewabilityManager.d.ts.map +1 -1
  187. package/dist/viewability/ViewabilityManager.js.map +1 -1
  188. package/package.json +3 -3
  189. package/src/FlashList.tsx +1 -1
  190. package/src/FlashListProps.ts +1 -1
  191. package/src/__tests__/ConsecutiveNumbers.test.ts +232 -0
  192. package/src/__tests__/GridLayoutManager.test.ts +113 -0
  193. package/src/__tests__/LinearLayoutManager.test.ts +227 -0
  194. package/src/__tests__/MasonryLayoutManager.test.ts +202 -0
  195. package/src/__tests__/RecycleKeyManager.test.ts +254 -0
  196. package/src/__tests__/RecyclerView.test.tsx +69 -0
  197. package/src/__tests__/findVisibleIndex.test.ts +369 -0
  198. package/src/__tests__/helpers/createLayoutManager.ts +142 -0
  199. package/src/index.ts +2 -1
  200. package/src/recyclerview/RecyclerView.tsx +15 -14
  201. package/src/recyclerview/RecyclerViewContextProvider.ts +1 -0
  202. package/src/recyclerview/RecyclerViewManager.ts +26 -9
  203. package/src/recyclerview/RecyclerViewProps.ts +2 -1
  204. package/src/recyclerview/ViewHolder.tsx +8 -3
  205. package/src/recyclerview/ViewHolderCollection.tsx +3 -2
  206. package/src/recyclerview/components/ScrollAnchor.tsx +3 -3
  207. package/src/recyclerview/components/StickyHeaders.tsx +7 -14
  208. package/src/recyclerview/helpers/ConsecutiveNumbers.ts +2 -2
  209. package/src/recyclerview/helpers/EngagedIndicesTracker.ts +20 -7
  210. package/src/recyclerview/hooks/useBoundDetection.ts +2 -2
  211. package/src/recyclerview/hooks/useOnLoad.ts +7 -4
  212. package/src/recyclerview/hooks/useRecyclerViewController.tsx +103 -109
  213. package/src/recyclerview/hooks/useSecondaryProps.tsx +7 -5
  214. package/src/recyclerview/layout-managers/GridLayoutManager.ts +2 -5
  215. package/src/recyclerview/layout-managers/LayoutManager.ts +7 -2
  216. package/src/recyclerview/layout-managers/LinearLayoutManager.ts +12 -8
  217. package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +3 -4
  218. package/src/recyclerview/utils/componentUtils.ts +1 -1
  219. package/src/recyclerview/utils/findVisibleIndex.ts +1 -2
  220. package/src/recyclerview/utils/measureLayout.ts +39 -0
  221. package/src/viewability/ViewToken.ts +1 -1
@@ -0,0 +1,202 @@
1
+ import { RVLayoutManager } from "../recyclerview/layout-managers/LayoutManager";
2
+ import { RVMasonryLayoutManagerImpl } from "../recyclerview/layout-managers/MasonryLayoutManager";
3
+
4
+ import {
5
+ getAllLayouts,
6
+ LayoutManagerType,
7
+ createLayoutParams,
8
+ createLayoutManager,
9
+ createMockLayoutInfo,
10
+ } from "./helpers/createLayoutManager";
11
+
12
+ describe("MasonryLayoutManager", () => {
13
+ const windowSize = { width: 400, height: 900 };
14
+ const defaultParams = {
15
+ windowSize,
16
+ maxColumns: 2,
17
+ optimizeItemArrangement: true,
18
+ };
19
+
20
+ // Helper to get column heights
21
+ const getColumnHeights = (manager: RVLayoutManager): number[] => {
22
+ return (manager as RVMasonryLayoutManagerImpl)["columnHeights"];
23
+ };
24
+
25
+ describe("Vertical Masonry Layout", () => {
26
+ it("should distribute items into columns based on height", () => {
27
+ const manager = createLayoutManager(
28
+ LayoutManagerType.MASONRY,
29
+ defaultParams
30
+ );
31
+ const layoutInfos = [
32
+ createMockLayoutInfo(0, 200, 100), // Col 0
33
+ createMockLayoutInfo(1, 200, 150), // Col 1
34
+ createMockLayoutInfo(2, 200, 120), // Col 0 (shorter)
35
+ createMockLayoutInfo(3, 200, 80), // Col 1 (shorter)
36
+ createMockLayoutInfo(4, 200, 200), // Col 0 (shorter)
37
+ ];
38
+ manager.modifyLayout(layoutInfos, 5);
39
+ const layouts = getAllLayouts(manager);
40
+
41
+ expect(layouts[0].x).toBe(0);
42
+ expect(layouts[0].y).toBe(0);
43
+
44
+ expect(layouts[1].x).toBe(200); // Second column
45
+ expect(layouts[1].y).toBe(0);
46
+
47
+ expect(layouts[2].x).toBe(0); // Back to first column
48
+ expect(layouts[2].y).toBe(100); // Below item 0
49
+
50
+ expect(layouts[3].x).toBe(200); // Still first column
51
+ expect(layouts[3].y).toBe(150); // Below item 2 (100 + 120)
52
+
53
+ expect(layouts[4].x).toBe(0); // Second column
54
+ expect(layouts[4].y).toBe(220); // Below item 1
55
+ });
56
+
57
+ it("should respect maxColumns configuration", () => {
58
+ const manager = createLayoutManager(LayoutManagerType.MASONRY, {
59
+ ...defaultParams,
60
+ maxColumns: 3,
61
+ });
62
+ const layoutInfos = [
63
+ createMockLayoutInfo(0, 133, 100), // Col 0
64
+ createMockLayoutInfo(1, 133, 150), // Col 1
65
+ createMockLayoutInfo(2, 133, 120), // Col 2
66
+ createMockLayoutInfo(3, 133, 80), // Col 0
67
+ ];
68
+ manager.modifyLayout(layoutInfos, 4);
69
+ const layouts = getAllLayouts(manager);
70
+ const colWidth = windowSize.width / 3;
71
+
72
+ expect(layouts[0].x).toBeCloseTo(0);
73
+ expect(layouts[1].x).toBeCloseTo(colWidth);
74
+ expect(layouts[2].x).toBeCloseTo(colWidth * 2);
75
+ expect(layouts[3].x).toBeCloseTo(0); // Placed in the shortest column (Col 0)
76
+ expect(layouts[3].y).toBeCloseTo(100); // Below item 0
77
+ });
78
+
79
+ it("should calculate total layout size correctly", () => {
80
+ const manager = createLayoutManager(
81
+ LayoutManagerType.MASONRY,
82
+ defaultParams
83
+ );
84
+ const layoutInfos = [
85
+ createMockLayoutInfo(0, 200, 100), // Col 0
86
+ createMockLayoutInfo(1, 200, 150), // Col 1
87
+ createMockLayoutInfo(2, 200, 120), // Col 0
88
+ ];
89
+ manager.modifyLayout(layoutInfos, 3);
90
+ const layoutSize = manager.getLayoutSize();
91
+
92
+ expect(layoutSize.width).toBe(400);
93
+ // Height is the tallest column height
94
+ const heights = getColumnHeights(manager);
95
+ expect(layoutSize.height).toBeCloseTo(Math.max(...heights)); // Max of [220, 150]
96
+ expect(layoutSize.height).toBeCloseTo(220);
97
+ });
98
+ });
99
+
100
+ describe("Layout Modifications", () => {
101
+ it("should update layout when items are added", () => {
102
+ const manager = createLayoutManager(
103
+ LayoutManagerType.MASONRY,
104
+ defaultParams
105
+ );
106
+ const initialInfos = [
107
+ createMockLayoutInfo(0, 200, 100), // Col 0 H=100
108
+ createMockLayoutInfo(1, 200, 150), // Col 1 H=150
109
+ ];
110
+ manager.modifyLayout(initialInfos, 2);
111
+ expect(getAllLayouts(manager).length).toBe(2);
112
+ expect(getColumnHeights(manager)).toEqual([100, 150]);
113
+
114
+ // Add item, should go to Col 0
115
+ const newLayoutInfos = [createMockLayoutInfo(2, 200, 120)];
116
+ manager.modifyLayout(newLayoutInfos, 3);
117
+
118
+ const layouts = getAllLayouts(manager);
119
+ expect(layouts.length).toBe(3);
120
+ expect(layouts[2].x).toBe(0); // Col 0
121
+ expect(layouts[2].y).toBe(100); // Below item 0
122
+ expect(getColumnHeights(manager)).toEqual([220, 150]); // 100+120, 150
123
+ });
124
+
125
+ it("should handle removing items (requires full recalculation)", () => {
126
+ const manager = createLayoutManager(
127
+ LayoutManagerType.MASONRY,
128
+ defaultParams
129
+ );
130
+ const initialInfos = [
131
+ createMockLayoutInfo(0, 200, 100), // Col 0 H=100
132
+ createMockLayoutInfo(1, 200, 150), // Col 1 H=150
133
+ createMockLayoutInfo(2, 200, 120), // Col 0 H=220
134
+ ];
135
+ manager.modifyLayout(initialInfos, 3);
136
+ expect(getColumnHeights(manager)).toEqual([220, 150]);
137
+
138
+ // Remove item 2 (from Col 0) - Masonry usually recalculates fully
139
+ // We simulate this by passing the remaining items
140
+ const remainingInfos = [
141
+ createMockLayoutInfo(0, 200, 100),
142
+ createMockLayoutInfo(1, 200, 150),
143
+ ];
144
+ manager.modifyLayout(remainingInfos, 2);
145
+
146
+ const layouts = getAllLayouts(manager);
147
+ expect(layouts.length).toBe(2);
148
+ expect(getColumnHeights(manager)).toEqual([100, 150]); // Back to original state
149
+ });
150
+
151
+ it("should recalculate layout when window size changes", () => {
152
+ const manager = createLayoutManager(
153
+ LayoutManagerType.MASONRY,
154
+ defaultParams
155
+ );
156
+ const initialInfos = [
157
+ createMockLayoutInfo(0, 200, 100), // Col 0
158
+ createMockLayoutInfo(1, 200, 150), // Col 1
159
+ createMockLayoutInfo(2, 200, 120), // Col 0
160
+ ];
161
+ manager.modifyLayout(initialInfos, 3);
162
+ const initialLayouts = getAllLayouts(manager);
163
+ expect(initialLayouts[1].x).toBe(200);
164
+
165
+ // Change window size and columns
166
+ manager.updateLayoutParams(
167
+ createLayoutParams({
168
+ ...defaultParams,
169
+ maxColumns: 3,
170
+ windowSize: { width: 600, height: 900 },
171
+ })
172
+ );
173
+ // modifyLayout needs to be called again as dimensions depend on width
174
+ const updatedInfos = [
175
+ createMockLayoutInfo(0, 200, 100), // New width = 600/3 = 200
176
+ createMockLayoutInfo(1, 200, 150),
177
+ createMockLayoutInfo(2, 200, 120),
178
+ ];
179
+ manager.modifyLayout(updatedInfos, 3);
180
+
181
+ const updatedLayouts = getAllLayouts(manager);
182
+ expect(updatedLayouts[0].width).toBe(200);
183
+ expect(updatedLayouts[1].x).toBe(200); // Col 1 starts at 200
184
+ expect(updatedLayouts[2].x).toBe(400); // Col 2 starts at 400
185
+ expect(getColumnHeights(manager)).toEqual([100, 150, 120]);
186
+ });
187
+ });
188
+
189
+ describe("Empty Layout", () => {
190
+ it("should return zero size for empty layout", () => {
191
+ const manager = createLayoutManager(
192
+ LayoutManagerType.MASONRY,
193
+ defaultParams
194
+ );
195
+ manager.modifyLayout([], 0);
196
+ const layoutSize = manager.getLayoutSize();
197
+ expect(layoutSize.width).toBe(0);
198
+ expect(layoutSize.height).toBe(0);
199
+ expect(getAllLayouts(manager).length).toBe(0);
200
+ });
201
+ });
202
+ });
@@ -0,0 +1,254 @@
1
+ import { RecycleKeyManagerImpl } from "../recyclerview/RecycleKeyManager";
2
+
3
+ describe("RecycleKeyManagerImpl", () => {
4
+ let keyManager: RecycleKeyManagerImpl;
5
+
6
+ beforeEach(() => {
7
+ // Initialize a new manager before each test
8
+ keyManager = new RecycleKeyManagerImpl();
9
+ });
10
+
11
+ describe("constructor", () => {
12
+ it("should initialize with default maxItems", () => {
13
+ // Test default maxItems value (implicitly tested via other methods)
14
+ // We can potentially expose maxItems for testing or test its effect
15
+ expect(keyManager).toBeDefined(); // Basic check
16
+ });
17
+
18
+ it("should initialize with specified maxItems", () => {
19
+ const specificMaxItems = 10;
20
+ keyManager = new RecycleKeyManagerImpl(specificMaxItems);
21
+ // Test the effect of maxItems limit later in getKey tests
22
+ expect(keyManager).toBeDefined(); // Basic check
23
+ });
24
+ });
25
+
26
+ describe("getKey", () => {
27
+ it("should generate a new key for a new item type without stableId", () => {
28
+ const key = keyManager.getKey("typeA", "item1");
29
+ expect(key).toBeDefined();
30
+ expect(typeof key).toBe("string");
31
+ expect(keyManager.hasKeyInPool(key)).toBe(false);
32
+ });
33
+
34
+ it("should generate a different key for a different item type", () => {
35
+ const keyA = keyManager.getKey("typeA", "item1");
36
+ const keyB = keyManager.getKey("typeB", "item2");
37
+ expect(keyA).not.toEqual(keyB);
38
+ expect(keyManager.hasKeyInPool(keyA)).toBe(false);
39
+ expect(keyManager.hasKeyInPool(keyB)).toBe(false);
40
+ });
41
+
42
+ it("should generate sequential keys when pool is empty", () => {
43
+ const key1 = keyManager.getKey("typeA", "item1");
44
+ const key2 = keyManager.getKey("typeA", "item2");
45
+ expect(parseInt(key2, 10)).toBe(parseInt(key1, 10) + 1);
46
+ });
47
+
48
+ it("should return the existing key for a known stableId", () => {
49
+ const stableId = "stable1";
50
+ const key1 = keyManager.getKey("typeA", stableId);
51
+ const key2 = keyManager.getKey("typeA", stableId);
52
+ const key3 = keyManager.getKey("typeB", stableId); // Different type, same stableId
53
+ expect(key2).toEqual(key1);
54
+ expect(key3).toEqual(key1); // Should still return the key associated with the stableId
55
+ expect(keyManager.hasKeyInPool(key1)).toBe(false);
56
+ });
57
+
58
+ it("should reuse a key from the pool for the same item type", () => {
59
+ const key1 = keyManager.getKey("typeA", "item1");
60
+ keyManager.recycleKey(key1);
61
+ const key2 = keyManager.getKey("typeA", "item2");
62
+ expect(key2).toEqual(key1); // Should reuse the recycled key
63
+ expect(keyManager.hasKeyInPool(key1)).toBe(false);
64
+ });
65
+
66
+ it("should reuse the specified currentKey if it exists in the pool", () => {
67
+ const key1 = keyManager.getKey("typeA", "item1");
68
+ const key2 = keyManager.getKey("typeA", "item2");
69
+ keyManager.recycleKey(key1);
70
+ keyManager.recycleKey(key2);
71
+
72
+ const reusedKey = keyManager.getKey("typeA", "item3", key1);
73
+ expect(reusedKey).toEqual(key1);
74
+ expect(keyManager.hasKeyInPool(key1)).toBe(false);
75
+ // key2 should still be in the pool
76
+ expect(keyManager.hasKeyInPool(key2)).toBe(true);
77
+ });
78
+
79
+ it("should reuse any key from the pool if currentKey does not exist", () => {
80
+ const key1 = keyManager.getKey("typeA", "item1");
81
+ const key2 = keyManager.getKey("typeA", "item2");
82
+ keyManager.recycleKey(key1);
83
+ keyManager.recycleKey(key2);
84
+
85
+ const nonExistentKey = "nonExistentKey";
86
+ const reusedKey = keyManager.getKey("typeA", "item3", nonExistentKey);
87
+
88
+ // It should have reused either key1 or key2
89
+ expect([key1, key2]).toContain(reusedKey);
90
+ expect(keyManager.hasKeyInPool(reusedKey)).toBe(false);
91
+ });
92
+
93
+ it("should prioritize stableId over pool reuse", () => {
94
+ const stableId = "stable1";
95
+ const key1 = keyManager.getKey("typeA", stableId); // Assign key1 to stable1
96
+ const key2 = keyManager.getKey("typeA", "item2");
97
+ keyManager.recycleKey(key2); // Put key2 in the pool
98
+
99
+ // Request key for stable1 again
100
+ const key3 = keyManager.getKey("typeA", stableId);
101
+ expect(key3).toEqual(key1); // Should return key1 associated with stableId
102
+ expect(keyManager.hasKeyInPool(key1)).toBe(false);
103
+ // key2 should still be in the pool
104
+ expect(keyManager.hasKeyInPool(key2)).toBe(true);
105
+ });
106
+
107
+ it("should assign stableId to a reused key", () => {
108
+ const key1 = keyManager.getKey("typeA", "item1");
109
+ keyManager.recycleKey(key1);
110
+
111
+ const stableId = "stableReuse";
112
+ const reusedKey = keyManager.getKey("typeA", stableId); // Reuse key1 and assign stableId
113
+ expect(reusedKey).toEqual(key1);
114
+
115
+ // Verify stableId mapping
116
+ const keyForStableId = keyManager.getKey("typeA", stableId);
117
+ expect(keyForStableId).toEqual(key1);
118
+ });
119
+
120
+ it("should handle ensurePoolSize when maxItems is exceeded", () => {
121
+ const maxItems = 2;
122
+ keyManager = new RecycleKeyManagerImpl(maxItems);
123
+
124
+ const key1 = keyManager.getKey("typeA", "item1"); // Active: {key1}
125
+ const key2 = keyManager.getKey("typeA", "item2"); // Active: {key1, key2}
126
+ expect(keyManager.hasKeyInPool(key1)).toBe(false);
127
+ expect(keyManager.hasKeyInPool(key2)).toBe(false);
128
+
129
+ // This should trigger ensurePoolSize and recycle key1 (oldest)
130
+ const key3 = keyManager.getKey("typeA", "item3"); // Active: {key2, key3}
131
+
132
+ expect(keyManager.hasKeyInPool(key1)).toBe(true); // key1 should be recycled
133
+ expect(keyManager.hasKeyInPool(key2)).toBe(false);
134
+ expect(keyManager.hasKeyInPool(key3)).toBe(false);
135
+
136
+ // This should trigger ensurePoolSize and recycle key2
137
+ const key4 = keyManager.getKey("typeA", "item4"); // Active: {key3, key4}
138
+ expect(keyManager.hasKeyInPool(key2)).toBe(true); // key2 should be recycled
139
+ expect(keyManager.hasKeyInPool(key3)).toBe(false);
140
+ expect(keyManager.hasKeyInPool(key4)).toBe(false);
141
+ });
142
+ });
143
+
144
+ describe("recycleKey", () => {
145
+ it("should add the key back to the correct pool", () => {
146
+ const keyA = keyManager.getKey("typeA", "item1");
147
+ const keyB = keyManager.getKey("typeB", "item2");
148
+
149
+ expect(keyManager.hasKeyInPool(keyA)).toBe(false);
150
+ expect(keyManager.hasKeyInPool(keyB)).toBe(false);
151
+
152
+ keyManager.recycleKey(keyA);
153
+ expect(keyManager.hasKeyInPool(keyA)).toBe(true);
154
+ expect(keyManager.hasKeyInPool(keyB)).toBe(false); // keyB should remain active
155
+
156
+ // Verify reuse from correct pool
157
+ const reusedKeyA = keyManager.getKey("typeA", "item3");
158
+ expect(reusedKeyA).toEqual(keyA);
159
+ });
160
+
161
+ it("should do nothing if the key does not exist or is already recycled", () => {
162
+ const key = keyManager.getKey("typeA", "item1");
163
+ keyManager.recycleKey(key); // Recycle it once
164
+
165
+ // Get current state (how many keys active, how many in pool)
166
+ // We need internal access or specific methods to check pool size directly.
167
+ // Let's assume internal state is correct after first recycle.
168
+
169
+ keyManager.recycleKey(key); // Try recycling again
170
+ keyManager.recycleKey("nonExistentKey"); // Try recycling non-existent key
171
+
172
+ // Verify state hasn't changed unexpectedly
173
+ expect(keyManager.hasKeyInPool(key)).toBe(true); // Still in pool
174
+ const newKey = keyManager.getKey("typeA", "item2");
175
+ expect(newKey).toEqual(key); // Should still reuse the original key
176
+ });
177
+ });
178
+
179
+ describe("hasKeyInPool", () => {
180
+ it("should return false for an active key", () => {
181
+ const key = keyManager.getKey("typeA", "item1");
182
+ expect(keyManager.hasKeyInPool(key)).toBe(false);
183
+ });
184
+
185
+ it("should return true for a recycled key", () => {
186
+ const key = keyManager.getKey("typeA", "item1");
187
+ keyManager.recycleKey(key);
188
+ expect(keyManager.hasKeyInPool(key)).toBe(true);
189
+ });
190
+
191
+ it("should return true for a key that was never generated", () => {
192
+ expect(keyManager.hasKeyInPool("nonExistentKey")).toBe(true);
193
+ });
194
+ });
195
+
196
+ describe("clearPool", () => {
197
+ it("should clear all keys from all pools", () => {
198
+ const keyA1 = keyManager.getKey("typeA", "item1");
199
+ const keyA2 = keyManager.getKey("typeA", "item2");
200
+ const keyB1 = keyManager.getKey("typeB", "item3");
201
+
202
+ keyManager.recycleKey(keyA1);
203
+ keyManager.recycleKey(keyB1);
204
+
205
+ expect(keyManager.hasKeyInPool(keyA1)).toBe(true);
206
+ expect(keyManager.hasKeyInPool(keyA2)).toBe(false); // Still active
207
+ expect(keyManager.hasKeyInPool(keyB1)).toBe(true);
208
+
209
+ keyManager.clearPool();
210
+
211
+ expect(keyManager.hasKeyInPool(keyA1)).toBe(true); // Still not active
212
+ expect(keyManager.hasKeyInPool(keyB1)).toBe(true); // Still not active
213
+ expect(keyManager.hasKeyInPool(keyA2)).toBe(false); // Active keys remain
214
+
215
+ // Getting new keys should generate new ones, not reuse cleared ones
216
+ const newKeyA = keyManager.getKey("typeA", "item4");
217
+ const newKeyB = keyManager.getKey("typeB", "item5");
218
+
219
+ expect(newKeyA).not.toEqual(keyA1);
220
+ expect(newKeyB).not.toEqual(keyB1);
221
+ // Depending on implementation, newKeyA might be keyA1 if keyCounter wasn't reset
222
+ // Check if key reuse happens after clearPool -> it shouldn't reuse from the *pool*
223
+ const keyA3 = keyManager.getKey("typeA", "item5"); // Generate another A key
224
+ keyManager.recycleKey(keyA3); // Recycle it
225
+ keyManager.clearPool(); // Clear pools again
226
+ keyManager.getKey("typeA", "item6"); // Get a new key
227
+ expect(keyManager.hasKeyInPool(keyA3)).toBe(true); // keyA3 is not active
228
+ // keyA4 should be newly generated, not reused from the cleared pool
229
+ // Note: Due to sequential key generation, it *might* match a previous key numerically
230
+ // but the crucial part is it wasn't reused *from the pool*.
231
+ });
232
+
233
+ it("should not affect active keys or stableId mappings", () => {
234
+ const stableId = "stableClear";
235
+ const keyA = keyManager.getKey("typeA", stableId);
236
+ const keyB = keyManager.getKey("typeB", "item2");
237
+ keyManager.recycleKey(keyB); // Put keyB in pool
238
+
239
+ expect(keyManager.hasKeyInPool(keyA)).toBe(false);
240
+ expect(keyManager.hasKeyInPool(keyB)).toBe(true);
241
+ expect(keyManager.getKey("typeA", stableId)).toEqual(keyA); // Check stableId mapping
242
+
243
+ keyManager.clearPool();
244
+
245
+ expect(keyManager.hasKeyInPool(keyA)).toBe(false); // keyA still active
246
+ expect(keyManager.hasKeyInPool(keyB)).toBe(true); // keyB still considered "in pool" (i.e., not active)
247
+ expect(keyManager.getKey("typeA", stableId)).toEqual(keyA); // StableId mapping persists
248
+
249
+ // Trying to get a key of type B should generate a new one
250
+ const newKeyB = keyManager.getKey("typeB", "item3");
251
+ expect(newKeyB).not.toEqual(keyB); // Because pool was cleared
252
+ });
253
+ });
254
+ });
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import "@quilted/react-testing/matchers";
4
+ import { render } from "@quilted/react-testing";
5
+
6
+ import { RecyclerView } from "../recyclerview/RecyclerView";
7
+
8
+ // Mock measureLayout to return fixed dimensions
9
+ jest.mock("../recyclerview/utils/measureLayout", () => {
10
+ const originalModule = jest.requireActual(
11
+ "../recyclerview/utils/measureLayout"
12
+ );
13
+ return {
14
+ ...originalModule,
15
+ measureParentSize: jest.fn().mockImplementation(() => ({
16
+ x: 0,
17
+ y: 0,
18
+ width: 400,
19
+ height: 900,
20
+ })),
21
+ measureChildContainerLayout: jest.fn().mockImplementation(() => ({
22
+ x: 0,
23
+ y: 0,
24
+ width: 400,
25
+ height: 900,
26
+ })),
27
+ measureItemLayout: jest.fn().mockImplementation(() => ({
28
+ x: 0,
29
+ y: 0,
30
+ width: 100,
31
+ height: 100,
32
+ })),
33
+ };
34
+ });
35
+
36
+ describe("RecyclerView", () => {
37
+ beforeEach(() => {
38
+ jest.clearAllMocks();
39
+ jest.useFakeTimers();
40
+ });
41
+
42
+ it("renders items ", () => {
43
+ const result = render(
44
+ <RecyclerView
45
+ data={[
46
+ "One",
47
+ "Two",
48
+ "Three",
49
+ "Four",
50
+ "Five",
51
+ "Six",
52
+ "Seven",
53
+ "Eight",
54
+ "Nine",
55
+ "Ten",
56
+ "Eleven",
57
+ "Twelve",
58
+ "Thirteen",
59
+ "Fourteen",
60
+ "Fifteen",
61
+ ]}
62
+ renderItem={({ item }) => <Text>{item}</Text>}
63
+ />
64
+ );
65
+
66
+ expect(result).toContainReactComponent(Text, { children: "One" });
67
+ expect(result).not.toContainReactComponent(Text, { children: "Eleven" });
68
+ });
69
+ });