@gogocat/data-bind 1.11.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 (274) 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 +2772 -2519
  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/form.html +20 -4
  147. package/examples/globalConfig.html +131 -0
  148. package/examples/js/afterRenderDemo.js +190 -0
  149. package/examples/js/appTodo.js +46 -46
  150. package/examples/js/attrBindingDemo.js +2 -2
  151. package/examples/js/dbMonApp.js +24 -26
  152. package/examples/js/dbMonAppReact.jsx +79 -0
  153. package/examples/js/dbMonAppReactive.js +28 -0
  154. package/examples/js/fiberDemo.js +4 -4
  155. package/examples/js/filtersDemo.js +8 -8
  156. package/examples/js/forOfDemo.js +7 -9
  157. package/examples/js/forOfDemoComplex.js +44 -17
  158. package/examples/js/form.js +44 -12
  159. package/examples/js/globalConfig.js +117 -0
  160. package/examples/js/ifBindingDemo.js +16 -16
  161. package/examples/js/reactiveDemo.js +119 -0
  162. package/examples/js/switchBindingDemo.js +8 -8
  163. package/examples/react-dbmonster/dist/bundle.js +43 -0
  164. package/examples/react-dbmonster/package-lock.json +537 -0
  165. package/examples/react-dbmonster/package.json +16 -0
  166. package/examples/react-dbmonster/src/index.jsx +80 -0
  167. package/examples/reactiveDemo.html +127 -0
  168. package/examples/refreshRateTest.html +75 -75
  169. package/index.html +841 -0
  170. package/package.json +31 -34
  171. package/rollup.config.js +79 -36
  172. package/src/{_escape.js → _escape.ts} +19 -17
  173. package/src/applyBinding.ts +179 -0
  174. package/src/{attrBinding.js → attrBinding.ts} +14 -13
  175. package/src/binder.ts +289 -0
  176. package/src/changeBinding.ts +93 -0
  177. package/src/{commentWrapper.js → commentWrapper.ts} +33 -30
  178. package/src/config.ts +107 -0
  179. package/src/createBindingOption.ts +91 -0
  180. package/src/createEventBinding.ts +88 -0
  181. package/src/{cssBinding.js → cssBinding.ts} +13 -11
  182. package/src/{domWalker.js → domWalker.ts} +44 -30
  183. package/src/{forOfBinding.js → forOfBinding.ts} +4 -3
  184. package/src/hoverBinding.ts +84 -0
  185. package/src/{ifBinding.js → ifBinding.ts} +14 -12
  186. package/src/index.ts +53 -0
  187. package/src/{modelBinding.js → modelBinding.ts} +11 -9
  188. package/src/postProcess.ts +22 -0
  189. package/src/{pubSub.js → pubSub.ts} +24 -15
  190. package/src/reactiveProxy.ts +285 -0
  191. package/src/{renderForOfBinding.js → renderForOfBinding.ts} +55 -33
  192. package/src/{renderIfBinding.js → renderIfBinding.ts} +45 -20
  193. package/src/renderIteration.ts +53 -0
  194. package/src/renderTemplate.ts +165 -0
  195. package/src/renderTemplatesBinding.ts +73 -0
  196. package/src/{showBinding.js → showBinding.ts} +4 -3
  197. package/src/{switchBinding.js → switchBinding.ts} +18 -15
  198. package/src/{textBinding.js → textBinding.ts} +5 -4
  199. package/src/types.ts +124 -0
  200. package/src/util.ts +810 -0
  201. package/test/css/reporter.css +9 -9
  202. package/test/fixtures/dataBindBootstrap.html +2 -2
  203. package/test/fixtures/formBindings.html +9 -1
  204. package/test/globals.d.ts +19 -0
  205. package/test/helpers/testHelper.js +46 -11
  206. package/test/mocks/featureAdsResult.json +65 -65
  207. package/test/mocks/searchResult.json +57 -57
  208. package/test/specs/{attrBinding.spec.js → attrBinding.spec.ts} +103 -106
  209. package/test/specs/{binder.spec.js → binder.spec.ts} +29 -27
  210. package/test/specs/blurBinding.spec.ts +60 -0
  211. package/test/specs/chainableUse.spec.ts +125 -0
  212. package/test/specs/clickBinding.spec.ts +194 -0
  213. package/test/specs/{cssBinding.spec.js → cssBinding.spec.ts} +72 -79
  214. package/test/specs/{dataBindBootstrap.spec.js → dataBindBootstrap.spec.ts} +332 -313
  215. package/test/specs/{filter.spec.js → filter.spec.ts} +75 -76
  216. package/test/specs/{forOfBinding.spec.js → forOfBinding.spec.ts} +208 -219
  217. package/test/specs/formBinding.spec.ts +272 -0
  218. package/test/specs/ifBinding.spec.ts +165 -0
  219. package/test/specs/{nestedComponent.spec.js → nestedComponent.spec.ts} +88 -88
  220. package/test/specs/reactiveProxy.spec.ts +465 -0
  221. package/test/specs/{showBinding.spec.js → showBinding.spec.ts} +148 -149
  222. package/test/specs/{switchBinding.spec.js → switchBinding.spec.ts} +172 -173
  223. package/test/specs/templateBinding.spec.ts +273 -0
  224. package/test/specs/{textBinding.spec.js → textBinding.spec.ts} +47 -48
  225. package/test/tsconfig.json +31 -0
  226. package/test-output.txt +200 -0
  227. package/test-reactive.html +224 -0
  228. package/tsconfig.json +28 -0
  229. package/vendors/lodash.custom.js +4577 -4577
  230. package/vendors/lodash.custom.min.js +45 -45
  231. package/vitest.config.js +27 -0
  232. package/.eslintrc.js +0 -1
  233. package/.grunt/grunt-contrib-jasmine/boot.js +0 -161
  234. package/.grunt/grunt-contrib-jasmine/dist/js/dataBind.js +0 -9
  235. package/.grunt/grunt-contrib-jasmine/grunt-template-jasmine-istanbul/reporter.js +0 -23
  236. package/.grunt/grunt-contrib-jasmine/jasmine-html.js +0 -853
  237. package/.grunt/grunt-contrib-jasmine/jasmine.css +0 -271
  238. package/.grunt/grunt-contrib-jasmine/jasmine.js +0 -9761
  239. package/.grunt/grunt-contrib-jasmine/jasmine_favicon.png +0 -0
  240. package/.grunt/grunt-contrib-jasmine/json2.js +0 -489
  241. package/.grunt/grunt-contrib-jasmine/reporter.js +0 -107
  242. package/coverage/coverage.json +0 -1
  243. package/coverage/lcov/lcov-report/base.css +0 -213
  244. package/coverage/lcov/lcov-report/index.html +0 -93
  245. package/coverage/lcov/lcov-report/js/dataBind.js.html +0 -6596
  246. package/coverage/lcov/lcov-report/js/index.html +0 -93
  247. package/coverage/lcov/lcov-report/prettify.css +0 -1
  248. package/coverage/lcov/lcov-report/prettify.js +0 -1
  249. package/coverage/lcov/lcov-report/sort-arrow-sprite.png +0 -0
  250. package/coverage/lcov/lcov-report/sorter.js +0 -158
  251. package/coverage/lcov/lcov.info +0 -1991
  252. package/eslintrc.json +0 -40
  253. package/examples/bootstrap/js/bootstrap.min.js +0 -6
  254. package/examples/bootstrap/js/popper.min.js +0 -5
  255. package/examples/bootstrap/js/searchSuggestion.js +0 -58
  256. package/examples/bootstrap/js/typeahead.jquery.js +0 -1538
  257. package/gruntfile.js +0 -92
  258. package/gulpfile.js +0 -32
  259. package/src/binder.js +0 -422
  260. package/src/changeBinding.js +0 -57
  261. package/src/config.js +0 -65
  262. package/src/createBindingOption.js +0 -66
  263. package/src/createEventBinding.js +0 -46
  264. package/src/eventSystem.js +0 -46
  265. package/src/hoverBinding.js +0 -57
  266. package/src/index.js +0 -26
  267. package/src/renderTemplate.js +0 -128
  268. package/src/util.js +0 -648
  269. package/test/specs/blurBinding.spec.js +0 -57
  270. package/test/specs/formBinding.spec.js +0 -292
  271. package/test/specs/ifBinding.spec.js +0 -169
  272. package/test/specs/templateBinding.spec.js +0 -117
  273. package/vendors/jasmine-jquery.js +0 -841
  274. 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
+ });