@gogocat/data-bind 1.12.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/.editorconfig +14 -14
  2. package/.vscode/launch.json +12 -12
  3. package/CONFIGURATION.md +294 -0
  4. package/REACTIVE_MODE.md +553 -0
  5. package/README.md +266 -829
  6. package/babel.config.json +30 -0
  7. package/dist/js/_escape.d.ts +14 -0
  8. package/dist/js/_escape.d.ts.map +1 -0
  9. package/dist/js/applyBinding.d.ts +11 -0
  10. package/dist/js/applyBinding.d.ts.map +1 -0
  11. package/dist/js/attrBinding.d.ts +12 -0
  12. package/dist/js/attrBinding.d.ts.map +1 -0
  13. package/dist/js/binder.d.ts +67 -0
  14. package/dist/js/binder.d.ts.map +1 -0
  15. package/dist/js/changeBinding.d.ts +19 -0
  16. package/dist/js/changeBinding.d.ts.map +1 -0
  17. package/dist/js/commentWrapper.d.ts +39 -0
  18. package/dist/js/commentWrapper.d.ts.map +1 -0
  19. package/dist/js/config.d.ts +55 -0
  20. package/dist/js/config.d.ts.map +1 -0
  21. package/dist/js/createBindingOption.d.ts +32 -0
  22. package/dist/js/createBindingOption.d.ts.map +1 -0
  23. package/dist/js/createEventBinding.d.ts +10 -0
  24. package/dist/js/createEventBinding.d.ts.map +1 -0
  25. package/dist/js/cssBinding.d.ts +15 -0
  26. package/dist/js/cssBinding.d.ts.map +1 -0
  27. package/dist/js/dataBind.js +2756 -2530
  28. package/dist/js/dataBind.min.js +8 -1
  29. package/dist/js/dataBind.min.js.map +1 -1
  30. package/dist/js/domWalker.d.ts +9 -0
  31. package/dist/js/domWalker.d.ts.map +1 -0
  32. package/dist/js/forOfBinding.d.ts +12 -0
  33. package/dist/js/forOfBinding.d.ts.map +1 -0
  34. package/dist/js/hoverBinding.d.ts +13 -0
  35. package/dist/js/hoverBinding.d.ts.map +1 -0
  36. package/dist/js/ifBinding.d.ts +12 -0
  37. package/dist/js/ifBinding.d.ts.map +1 -0
  38. package/dist/js/index.d.ts +10 -0
  39. package/dist/js/index.d.ts.map +1 -0
  40. package/dist/js/modelBinding.d.ts +12 -0
  41. package/dist/js/modelBinding.d.ts.map +1 -0
  42. package/dist/js/postProcess.d.ts +3 -0
  43. package/dist/js/postProcess.d.ts.map +1 -0
  44. package/dist/js/pubSub.d.ts +11 -0
  45. package/dist/js/pubSub.d.ts.map +1 -0
  46. package/dist/js/reactiveProxy.d.ts +28 -0
  47. package/dist/js/reactiveProxy.d.ts.map +1 -0
  48. package/dist/js/renderForOfBinding.d.ts +8 -0
  49. package/dist/js/renderForOfBinding.d.ts.map +1 -0
  50. package/dist/js/renderIfBinding.d.ts +22 -0
  51. package/dist/js/renderIfBinding.d.ts.map +1 -0
  52. package/dist/js/renderIteration.d.ts +16 -0
  53. package/dist/js/renderIteration.d.ts.map +1 -0
  54. package/dist/js/renderTemplate.d.ts +14 -0
  55. package/dist/js/renderTemplate.d.ts.map +1 -0
  56. package/dist/js/renderTemplatesBinding.d.ts +19 -0
  57. package/dist/js/renderTemplatesBinding.d.ts.map +1 -0
  58. package/dist/js/showBinding.d.ts +13 -0
  59. package/dist/js/showBinding.d.ts.map +1 -0
  60. package/dist/js/switchBinding.d.ts +13 -0
  61. package/dist/js/switchBinding.d.ts.map +1 -0
  62. package/dist/js/textBinding.d.ts +13 -0
  63. package/dist/js/textBinding.d.ts.map +1 -0
  64. package/dist/js/types/_escape.d.ts +14 -0
  65. package/dist/js/types/_escape.d.ts.map +1 -0
  66. package/dist/js/types/applyBinding.d.ts +11 -0
  67. package/dist/js/types/applyBinding.d.ts.map +1 -0
  68. package/dist/js/types/attrBinding.d.ts +12 -0
  69. package/dist/js/types/attrBinding.d.ts.map +1 -0
  70. package/dist/js/types/binder.d.ts +67 -0
  71. package/dist/js/types/binder.d.ts.map +1 -0
  72. package/dist/js/types/changeBinding.d.ts +19 -0
  73. package/dist/js/types/changeBinding.d.ts.map +1 -0
  74. package/dist/js/types/commentWrapper.d.ts +39 -0
  75. package/dist/js/types/commentWrapper.d.ts.map +1 -0
  76. package/dist/js/types/config.d.ts +55 -0
  77. package/dist/js/types/config.d.ts.map +1 -0
  78. package/dist/js/types/createBindingOption.d.ts +32 -0
  79. package/dist/js/types/createBindingOption.d.ts.map +1 -0
  80. package/dist/js/types/createEventBinding.d.ts +10 -0
  81. package/dist/js/types/createEventBinding.d.ts.map +1 -0
  82. package/dist/js/types/cssBinding.d.ts +15 -0
  83. package/dist/js/types/cssBinding.d.ts.map +1 -0
  84. package/dist/js/types/domWalker.d.ts +9 -0
  85. package/dist/js/types/domWalker.d.ts.map +1 -0
  86. package/dist/js/types/forOfBinding.d.ts +12 -0
  87. package/dist/js/types/forOfBinding.d.ts.map +1 -0
  88. package/dist/js/types/hoverBinding.d.ts +13 -0
  89. package/dist/js/types/hoverBinding.d.ts.map +1 -0
  90. package/dist/js/types/ifBinding.d.ts +12 -0
  91. package/dist/js/types/ifBinding.d.ts.map +1 -0
  92. package/dist/js/types/index.d.ts +10 -0
  93. package/dist/js/types/index.d.ts.map +1 -0
  94. package/dist/js/types/modelBinding.d.ts +12 -0
  95. package/dist/js/types/modelBinding.d.ts.map +1 -0
  96. package/dist/js/types/postProcess.d.ts +3 -0
  97. package/dist/js/types/postProcess.d.ts.map +1 -0
  98. package/dist/js/types/pubSub.d.ts +11 -0
  99. package/dist/js/types/pubSub.d.ts.map +1 -0
  100. package/dist/js/types/reactiveProxy.d.ts +28 -0
  101. package/dist/js/types/reactiveProxy.d.ts.map +1 -0
  102. package/dist/js/types/renderForOfBinding.d.ts +8 -0
  103. package/dist/js/types/renderForOfBinding.d.ts.map +1 -0
  104. package/dist/js/types/renderIfBinding.d.ts +22 -0
  105. package/dist/js/types/renderIfBinding.d.ts.map +1 -0
  106. package/dist/js/types/renderIteration.d.ts +16 -0
  107. package/dist/js/types/renderIteration.d.ts.map +1 -0
  108. package/dist/js/types/renderTemplate.d.ts +14 -0
  109. package/dist/js/types/renderTemplate.d.ts.map +1 -0
  110. package/dist/js/types/renderTemplatesBinding.d.ts +19 -0
  111. package/dist/js/types/renderTemplatesBinding.d.ts.map +1 -0
  112. package/dist/js/types/showBinding.d.ts +13 -0
  113. package/dist/js/types/showBinding.d.ts.map +1 -0
  114. package/dist/js/types/switchBinding.d.ts +13 -0
  115. package/dist/js/types/switchBinding.d.ts.map +1 -0
  116. package/dist/js/types/textBinding.d.ts +13 -0
  117. package/dist/js/types/textBinding.d.ts.map +1 -0
  118. package/dist/js/types/types.d.ts +111 -0
  119. package/dist/js/types/types.d.ts.map +1 -0
  120. package/dist/js/types/util.d.ts +119 -0
  121. package/dist/js/types/util.d.ts.map +1 -0
  122. package/dist/js/types.d.ts +111 -0
  123. package/dist/js/types.d.ts.map +1 -0
  124. package/dist/js/util.d.ts +119 -0
  125. package/dist/js/util.d.ts.map +1 -0
  126. package/eslint.config.js +124 -0
  127. package/examples/DBMONSTER_COMPARISON.md +123 -0
  128. package/examples/afterRenderDemo.html +119 -0
  129. package/examples/bootstrap/css/animate.css +1579 -1579
  130. package/examples/bootstrap/css/bootstrap.min.css +6 -6
  131. package/examples/bootstrap/css/homeservices.css +378 -390
  132. package/examples/bootstrap/css/open-iconic.css +511 -511
  133. package/examples/bootstrap/fonts/open-iconic.svg +543 -543
  134. package/examples/bootstrap/js/compMessageDialog.js +20 -19
  135. package/examples/bootstrap/js/compSearchBar.js +12 -19
  136. package/examples/bootstrap/js/compSearchResults.js +50 -46
  137. package/examples/bootstrap/js/featureAdsResult.json +65 -65
  138. package/examples/bootstrap/js/searchResult.json +57 -57
  139. package/examples/bootstrap.html +343 -332
  140. package/examples/css/baseTodo.css +141 -141
  141. package/examples/css/dbMonsterStyles.css +27 -27
  142. package/examples/css/indexTodo.css +374 -374
  143. package/examples/dbmonsterForOfReactive.html +40 -0
  144. package/examples/dbmonsterReact.html +19 -0
  145. package/examples/forOfBindingSimpleDebug.html +45 -0
  146. package/examples/globalConfig.html +131 -0
  147. package/examples/js/afterRenderDemo.js +190 -0
  148. package/examples/js/appTodo.js +46 -46
  149. package/examples/js/attrBindingDemo.js +2 -2
  150. package/examples/js/dbMonApp.js +24 -26
  151. package/examples/js/dbMonAppReact.jsx +79 -0
  152. package/examples/js/dbMonAppReactive.js +28 -0
  153. package/examples/js/fiberDemo.js +4 -4
  154. package/examples/js/filtersDemo.js +8 -8
  155. package/examples/js/forOfDemo.js +7 -9
  156. package/examples/js/forOfDemoComplex.js +44 -17
  157. package/examples/js/form.js +14 -14
  158. package/examples/js/globalConfig.js +117 -0
  159. package/examples/js/ifBindingDemo.js +16 -16
  160. package/examples/js/reactiveDemo.js +119 -0
  161. package/examples/js/switchBindingDemo.js +8 -8
  162. package/examples/react-dbmonster/dist/bundle.js +43 -0
  163. package/examples/react-dbmonster/package-lock.json +537 -0
  164. package/examples/react-dbmonster/package.json +16 -0
  165. package/examples/react-dbmonster/src/index.jsx +80 -0
  166. package/examples/reactiveDemo.html +127 -0
  167. package/examples/refreshRateTest.html +75 -75
  168. package/index.html +841 -0
  169. package/package.json +31 -34
  170. package/rollup.config.js +79 -36
  171. package/src/{_escape.js → _escape.ts} +19 -17
  172. package/src/{applyBinding.js → applyBinding.ts} +27 -18
  173. package/src/{attrBinding.js → attrBinding.ts} +14 -13
  174. package/src/{binder.js → binder.ts} +289 -181
  175. package/src/changeBinding.ts +93 -0
  176. package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
  177. package/src/config.ts +107 -0
  178. package/src/{createBindingOption.js → createBindingOption.ts} +39 -15
  179. package/src/createEventBinding.ts +88 -0
  180. package/src/{cssBinding.js → cssBinding.ts} +13 -11
  181. package/src/{domWalker.js → domWalker.ts} +44 -30
  182. package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
  183. package/src/hoverBinding.ts +84 -0
  184. package/src/{ifBinding.js → ifBinding.ts} +14 -12
  185. package/src/index.ts +53 -0
  186. package/src/{modelBinding.js → modelBinding.ts} +11 -9
  187. package/src/{postProcess.js → postProcess.ts} +6 -4
  188. package/src/{pubSub.js → pubSub.ts} +24 -21
  189. package/src/reactiveProxy.ts +285 -0
  190. package/src/{renderForOfBinding.js → renderForOfBinding.ts} +54 -32
  191. package/src/{renderIfBinding.js → renderIfBinding.ts} +41 -19
  192. package/src/{renderIteration.js → renderIteration.ts} +24 -8
  193. package/src/renderTemplate.ts +165 -0
  194. package/src/renderTemplatesBinding.ts +73 -0
  195. package/src/{showBinding.js → showBinding.ts} +4 -3
  196. package/src/{switchBinding.js → switchBinding.ts} +18 -15
  197. package/src/{textBinding.js → textBinding.ts} +5 -4
  198. package/src/types.ts +124 -0
  199. package/src/util.ts +810 -0
  200. package/test/css/reporter.css +9 -9
  201. package/test/globals.d.ts +19 -0
  202. package/test/helpers/testHelper.js +46 -11
  203. package/test/mocks/featureAdsResult.json +65 -65
  204. package/test/mocks/searchResult.json +57 -57
  205. package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
  206. package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
  207. package/test/specs/blurBinding.spec.ts +60 -0
  208. package/test/specs/chainableUse.spec.ts +125 -0
  209. package/test/specs/clickBinding.spec.ts +194 -0
  210. package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
  211. package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
  212. package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
  213. package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
  214. package/test/specs/formBinding.spec.ts +272 -0
  215. package/test/specs/ifBinding.spec.ts +165 -0
  216. package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
  217. package/test/specs/reactiveProxy.spec.ts +465 -0
  218. package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
  219. package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
  220. package/test/specs/templateBinding.spec.ts +273 -0
  221. package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
  222. package/test/tsconfig.json +31 -0
  223. package/test-output.txt +200 -0
  224. package/test-reactive.html +224 -0
  225. package/tsconfig.json +28 -0
  226. package/vendors/lodash.custom.js +4577 -4577
  227. package/vendors/lodash.custom.min.js +45 -45
  228. package/vitest.config.js +27 -0
  229. package/.eslintrc.js +0 -1
  230. package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
  231. package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
  232. package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
  233. package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
  234. package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
  235. package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
  236. package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
  237. package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
  238. package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
  239. package/coverage/coverage.json +0 -1
  240. package/coverage/lcov/lcov-report/base.css +0 -213
  241. package/coverage/lcov/lcov-report/index.html +0 -93
  242. package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
  243. package/coverage/lcov/lcov-report/js/index.html +0 -93
  244. package/coverage/lcov/lcov-report/prettify.css +0 -1
  245. package/coverage/lcov/lcov-report/prettify.js +0 -1
  246. package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
  247. package/coverage/lcov/lcov-report/sorter.js +0 -158
  248. package/coverage/lcov/lcov.info +0 -1991
  249. package/eslintrc.json +0 -40
  250. package/examples/bootstrap/js/bootstrap.min.js +0 -6
  251. package/examples/bootstrap/js/popper.min.js +0 -5
  252. package/examples/bootstrap/js/searchSuggestion.js +0 -58
  253. package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
  254. package/gruntfile.js +0 -92
  255. package/gulpfile.js +0 -32
  256. package/src/applyBindingExport.js +0 -5
  257. package/src/changeBinding.js +0 -63
  258. package/src/config.js +0 -66
  259. package/src/createEventBinding.js +0 -46
  260. package/src/eventSystem.js +0 -46
  261. package/src/hoverBinding.js +0 -57
  262. package/src/index.js +0 -26
  263. package/src/renderTemplate.js +0 -128
  264. package/src/renderTemplatesBinding.js +0 -44
  265. package/src/util.js +0 -648
  266. package/test/specs/blurBinding.spec.js +0 -57
  267. package/test/specs/formBinding.spec.js +0 -316
  268. package/test/specs/ifBinding.spec.js +0 -169
  269. package/test/specs/templateBinding.spec.js +0 -117
  270. package/vendors/jasmine-jquery.js +0 -841
  271. package/vendors/jquery-3.2.1.min.js +0 -4
@@ -0,0 +1,465 @@
1
+ import {describe, it, expect, beforeEach, vi} from 'vitest';
2
+ import {createReactiveProxy, createReactiveArray, toRaw, isProxySupported} from '../../src/reactiveProxy';
3
+
4
+ describe('Reactive Proxy', () => {
5
+ describe('isProxySupported', () => {
6
+ it('should return true in modern browsers', () => {
7
+ expect(isProxySupported()).toBe(true);
8
+ });
9
+ });
10
+
11
+ describe('createReactiveProxy', () => {
12
+ it('should trigger onChange when property is set', () => {
13
+ const onChange = vi.fn();
14
+ const target = {name: 'John', age: 30};
15
+ const proxy = createReactiveProxy(target, {onChange});
16
+
17
+ proxy.name = 'Jane';
18
+
19
+ expect(onChange).toHaveBeenCalledTimes(1);
20
+ expect(proxy.name).toBe('Jane');
21
+ });
22
+
23
+ it('should not trigger onChange when property value is unchanged', () => {
24
+ const onChange = vi.fn();
25
+ const target = {name: 'John'};
26
+ const proxy = createReactiveProxy(target, {onChange});
27
+
28
+ proxy.name = 'John'; // Same value
29
+
30
+ expect(onChange).not.toHaveBeenCalled();
31
+ });
32
+
33
+ it('should trigger onChange when nested object property is set', () => {
34
+ const onChange = vi.fn();
35
+ const target = {
36
+ user: {
37
+ profile: {
38
+ name: 'John',
39
+ },
40
+ },
41
+ };
42
+ const proxy = createReactiveProxy(target, {onChange, deep: true});
43
+
44
+ proxy.user.profile.name = 'Jane';
45
+
46
+ expect(onChange).toHaveBeenCalled();
47
+ expect(proxy.user.profile.name).toBe('Jane');
48
+ });
49
+
50
+ it('should trigger onChange when property is deleted', () => {
51
+ const onChange = vi.fn();
52
+ const target = {name: 'John', age: 30};
53
+ const proxy = createReactiveProxy(target, {onChange}) as any;
54
+
55
+ delete proxy.age;
56
+
57
+ expect(onChange).toHaveBeenCalled();
58
+ expect(proxy.age).toBeUndefined();
59
+ });
60
+
61
+ it('should not re-proxy already proxied objects', () => {
62
+ const onChange = vi.fn();
63
+ const target = {name: 'John'};
64
+ const proxy1 = createReactiveProxy(target, {onChange});
65
+ const proxy2 = createReactiveProxy(proxy1, {onChange});
66
+
67
+ expect(proxy1).toBe(proxy2);
68
+ });
69
+
70
+ it('should skip proxying special properties', () => {
71
+ const onChange = vi.fn();
72
+ const target = {
73
+ name: 'John',
74
+ APP: {test: 'data'},
75
+ $root: {test: 'data'},
76
+ };
77
+ const proxy = createReactiveProxy(target, {onChange, deep: true});
78
+
79
+ // These should not trigger onChange
80
+ proxy.APP = {test: 'new'};
81
+ proxy.$root = {test: 'new'};
82
+
83
+ expect(onChange).toHaveBeenCalledTimes(2);
84
+ });
85
+
86
+ it('should work with trackChanges option', () => {
87
+ const onChange = vi.fn();
88
+ const target = {name: 'John', age: 30};
89
+ const proxy = createReactiveProxy(target, {onChange, trackChanges: true});
90
+
91
+ proxy.name = 'Jane';
92
+ proxy.age = 25;
93
+
94
+ expect(onChange).toHaveBeenCalledTimes(2);
95
+ });
96
+
97
+ it('should not proxy non-objects', () => {
98
+ const onChange = vi.fn();
99
+ const target = null as any;
100
+ const proxy = createReactiveProxy(target, {onChange});
101
+
102
+ expect(proxy).toBe(null);
103
+ });
104
+
105
+ it('should cache nested proxies', () => {
106
+ const onChange = vi.fn();
107
+ const target = {
108
+ nested: {value: 1},
109
+ };
110
+ const proxy = createReactiveProxy(target, {onChange, deep: true});
111
+
112
+ const nested1 = proxy.nested;
113
+ const nested2 = proxy.nested;
114
+
115
+ expect(nested1).toBe(nested2); // Same proxy instance
116
+ });
117
+
118
+ it('should clear cached proxy when property changes', () => {
119
+ const onChange = vi.fn();
120
+ const target = {
121
+ nested: {value: 1},
122
+ };
123
+ const proxy = createReactiveProxy(target, {onChange, deep: true});
124
+
125
+ const nested1 = proxy.nested;
126
+ proxy.nested = {value: 2};
127
+ const nested2 = proxy.nested;
128
+
129
+ expect(nested1).not.toBe(nested2); // Different proxy instance
130
+ });
131
+ });
132
+
133
+ describe('createReactiveArray', () => {
134
+ it('should trigger onChange when array element is set', () => {
135
+ const onChange = vi.fn();
136
+ const target = [1, 2, 3];
137
+ const proxy = createReactiveArray(target, onChange, {onChange});
138
+
139
+ proxy[0] = 99;
140
+
141
+ expect(onChange).toHaveBeenCalled();
142
+ expect(proxy[0]).toBe(99);
143
+ });
144
+
145
+ it('should not trigger onChange when element value is unchanged', () => {
146
+ const onChange = vi.fn();
147
+ const target = [1, 2, 3];
148
+ const proxy = createReactiveArray(target, onChange, {onChange});
149
+
150
+ proxy[0] = 1; // Same value
151
+
152
+ expect(onChange).not.toHaveBeenCalled();
153
+ });
154
+
155
+ it('should trigger onChange on push', () => {
156
+ const onChange = vi.fn();
157
+ const target = [1, 2, 3];
158
+ const proxy = createReactiveArray(target, onChange, {onChange});
159
+
160
+ proxy.push(4);
161
+
162
+ expect(onChange).toHaveBeenCalled();
163
+ expect(proxy.length).toBe(4);
164
+ expect(proxy[3]).toBe(4);
165
+ });
166
+
167
+ it('should trigger onChange on pop', () => {
168
+ const onChange = vi.fn();
169
+ const target = [1, 2, 3];
170
+ const proxy = createReactiveArray(target, onChange, {onChange});
171
+
172
+ const popped = proxy.pop();
173
+
174
+ expect(onChange).toHaveBeenCalled();
175
+ expect(popped).toBe(3);
176
+ expect(proxy.length).toBe(2);
177
+ });
178
+
179
+ it('should trigger onChange on shift', () => {
180
+ const onChange = vi.fn();
181
+ const target = [1, 2, 3];
182
+ const proxy = createReactiveArray(target, onChange, {onChange});
183
+
184
+ const shifted = proxy.shift();
185
+
186
+ expect(onChange).toHaveBeenCalled();
187
+ expect(shifted).toBe(1);
188
+ expect(proxy.length).toBe(2);
189
+ });
190
+
191
+ it('should trigger onChange on unshift', () => {
192
+ const onChange = vi.fn();
193
+ const target = [1, 2, 3];
194
+ const proxy = createReactiveArray(target, onChange, {onChange});
195
+
196
+ proxy.unshift(0);
197
+
198
+ expect(onChange).toHaveBeenCalled();
199
+ expect(proxy.length).toBe(4);
200
+ expect(proxy[0]).toBe(0);
201
+ });
202
+
203
+ it('should trigger onChange on splice', () => {
204
+ const onChange = vi.fn();
205
+ const target = [1, 2, 3];
206
+ const proxy = createReactiveArray(target, onChange, {onChange});
207
+
208
+ proxy.splice(1, 1, 99);
209
+
210
+ expect(onChange).toHaveBeenCalled();
211
+ expect(proxy.length).toBe(3);
212
+ expect(proxy[1]).toBe(99);
213
+ });
214
+
215
+ it('should trigger onChange on sort', () => {
216
+ const onChange = vi.fn();
217
+ const target = [3, 1, 2];
218
+ const proxy = createReactiveArray(target, onChange, {onChange});
219
+
220
+ proxy.sort();
221
+
222
+ expect(onChange).toHaveBeenCalled();
223
+ expect(proxy[0]).toBe(1);
224
+ });
225
+
226
+ it('should trigger onChange on reverse', () => {
227
+ const onChange = vi.fn();
228
+ const target = [1, 2, 3];
229
+ const proxy = createReactiveArray(target, onChange, {onChange});
230
+
231
+ proxy.reverse();
232
+
233
+ expect(onChange).toHaveBeenCalled();
234
+ expect(proxy[0]).toBe(3);
235
+ });
236
+
237
+ it('should trigger onChange on fill', () => {
238
+ const onChange = vi.fn();
239
+ const target = [1, 2, 3];
240
+ const proxy = createReactiveArray(target, onChange, {onChange});
241
+
242
+ proxy.fill(0);
243
+
244
+ expect(onChange).toHaveBeenCalled();
245
+ expect(proxy[0]).toBe(0);
246
+ });
247
+
248
+ it('should work with array of objects', () => {
249
+ const onChange = vi.fn();
250
+ const target = [{text: 'Task 1', done: false}];
251
+ const proxy = createReactiveArray(target, onChange, {onChange, deep: true});
252
+
253
+ proxy[0].done = true;
254
+
255
+ expect(onChange).toHaveBeenCalled();
256
+ expect(proxy[0].done).toBe(true);
257
+ });
258
+
259
+ it('should work with nested arrays', () => {
260
+ const onChange = vi.fn();
261
+ const target = [[1, 2], [3, 4]];
262
+ const proxy = createReactiveArray(target, onChange, {onChange, deep: true});
263
+
264
+ (proxy[0] as number[]).push(99);
265
+
266
+ expect(onChange).toHaveBeenCalled();
267
+ expect((proxy[0] as number[]).length).toBe(3);
268
+ });
269
+
270
+ it('should not re-proxy already proxied arrays', () => {
271
+ const onChange = vi.fn();
272
+ const target = [1, 2, 3];
273
+ const proxy1 = createReactiveArray(target, onChange, {onChange});
274
+ const proxy2 = createReactiveArray(proxy1, onChange, {onChange});
275
+
276
+ expect(proxy1).toBe(proxy2);
277
+ });
278
+
279
+ it('should trigger onChange when array element is deleted', () => {
280
+ const onChange = vi.fn();
281
+ const target = [1, 2, 3];
282
+ const proxy = createReactiveArray(target, onChange, {onChange}) as any;
283
+
284
+ delete proxy[1];
285
+
286
+ expect(onChange).toHaveBeenCalled();
287
+ expect(proxy[1]).toBeUndefined();
288
+ });
289
+ });
290
+
291
+ describe('toRaw', () => {
292
+ it('should return original object from proxy', () => {
293
+ const onChange = vi.fn();
294
+ const target = {name: 'John'};
295
+ const proxy = createReactiveProxy(target, {onChange});
296
+
297
+ const raw = toRaw(proxy);
298
+
299
+ expect(raw).toBe(target);
300
+ });
301
+
302
+ it('should return same object if not a proxy', () => {
303
+ const target = {name: 'John'};
304
+ const raw = toRaw(target);
305
+
306
+ expect(raw).toBe(target);
307
+ });
308
+
309
+ it('should return original array from array proxy', () => {
310
+ const onChange = vi.fn();
311
+ const target = [1, 2, 3];
312
+ const proxy = createReactiveArray(target, onChange, {onChange});
313
+
314
+ const raw = toRaw(proxy);
315
+
316
+ expect(raw).toBe(target);
317
+ });
318
+ });
319
+
320
+ describe('Deep reactivity integration', () => {
321
+ it('should handle complex nested structure', () => {
322
+ const onChange = vi.fn();
323
+ const target = {
324
+ user: {
325
+ profile: {
326
+ name: 'John',
327
+ address: {
328
+ city: 'New York',
329
+ },
330
+ },
331
+ todos: [
332
+ {text: 'Task 1', done: false},
333
+ {text: 'Task 2', done: false},
334
+ ],
335
+ },
336
+ };
337
+ const proxy = createReactiveProxy(target, {onChange, deep: true});
338
+
339
+ // Test deep object modification
340
+ proxy.user.profile.address.city = 'London';
341
+ expect(onChange).toHaveBeenCalled();
342
+ expect(proxy.user.profile.address.city).toBe('London');
343
+
344
+ onChange.mockClear();
345
+
346
+ // Test array modification
347
+ proxy.user.todos.push({text: 'Task 3', done: false});
348
+ expect(onChange).toHaveBeenCalled();
349
+ expect(proxy.user.todos.length).toBe(3);
350
+
351
+ onChange.mockClear();
352
+
353
+ // Test array element property modification
354
+ proxy.user.todos[0].done = true;
355
+ expect(onChange).toHaveBeenCalled();
356
+ expect(proxy.user.todos[0].done).toBe(true);
357
+ });
358
+
359
+ it('should work with multiple changes in sequence', () => {
360
+ const onChange = vi.fn();
361
+ const target = {
362
+ name: 'John',
363
+ age: 30,
364
+ items: [1, 2, 3],
365
+ };
366
+ const proxy = createReactiveProxy(target, {onChange, deep: true});
367
+
368
+ // Test each change individually
369
+ onChange.mockClear();
370
+ proxy.name = 'Jane';
371
+ expect(onChange).toHaveBeenCalled();
372
+ expect(proxy.name).toBe('Jane');
373
+
374
+ onChange.mockClear();
375
+ proxy.age = 25;
376
+ expect(onChange).toHaveBeenCalled();
377
+ expect(proxy.age).toBe(25);
378
+
379
+ onChange.mockClear();
380
+ proxy.items.push(4);
381
+ expect(onChange).toHaveBeenCalled();
382
+ expect(proxy.items.length).toBe(4);
383
+
384
+ onChange.mockClear();
385
+ proxy.items[0] = 99;
386
+ expect(onChange).toHaveBeenCalled();
387
+ expect(proxy.items[0]).toBe(99);
388
+ });
389
+
390
+ it('should handle circular references gracefully', () => {
391
+ const onChange = vi.fn();
392
+ const target: any = {
393
+ name: 'John',
394
+ };
395
+ target.self = target; // Circular reference
396
+
397
+ // Should not throw
398
+ expect(() => {
399
+ createReactiveProxy(target, {onChange, deep: true});
400
+ }).not.toThrow();
401
+ });
402
+ });
403
+
404
+ describe('Edge cases', () => {
405
+ it('should handle functions in viewModel', () => {
406
+ const onChange = vi.fn();
407
+ const target = {
408
+ name: 'John',
409
+ greet() {
410
+ return `Hello, ${this.name}`;
411
+ },
412
+ };
413
+ const proxy = createReactiveProxy(target, {onChange});
414
+
415
+ expect(proxy.greet()).toBe('Hello, John');
416
+ expect(onChange).not.toHaveBeenCalled(); // Function access shouldn't trigger onChange
417
+ });
418
+
419
+ it('should handle getters in viewModel', () => {
420
+ const onChange = vi.fn();
421
+ const target = {
422
+ firstName: 'John',
423
+ lastName: 'Doe',
424
+ get fullName() {
425
+ return `${this.firstName} ${this.lastName}`;
426
+ },
427
+ };
428
+ const proxy = createReactiveProxy(target, {onChange});
429
+
430
+ expect(proxy.fullName).toBe('John Doe');
431
+
432
+ proxy.firstName = 'Jane';
433
+ expect(onChange).toHaveBeenCalled();
434
+ expect(proxy.fullName).toBe('Jane Doe');
435
+ });
436
+
437
+ it('should handle symbols', () => {
438
+ const onChange = vi.fn();
439
+ const sym = Symbol('test');
440
+ const target = {
441
+ name: 'John',
442
+ [sym]: 'symbol value',
443
+ };
444
+ const proxy = createReactiveProxy(target, {onChange}) as any;
445
+
446
+ expect(proxy[sym]).toBe('symbol value');
447
+ expect(onChange).not.toHaveBeenCalled();
448
+ });
449
+
450
+ it('should handle null and undefined values', () => {
451
+ const onChange = vi.fn();
452
+ const target = {
453
+ nullValue: null as any,
454
+ undefinedValue: undefined as any,
455
+ };
456
+ const proxy = createReactiveProxy(target, {onChange});
457
+
458
+ proxy.nullValue = null;
459
+ expect(onChange).not.toHaveBeenCalled(); // Same value
460
+
461
+ proxy.undefinedValue = 'value';
462
+ expect(onChange).toHaveBeenCalled();
463
+ });
464
+ });
465
+ });