@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,60 @@
1
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
2
+ import {waitFor} from '@testing-library/dom';
3
+
4
+ describe('Given [data-bind-comp="blur-component"] initised', () => {
5
+ const namespace: any = {};
6
+ const testBlurValue = 'onBlur called';
7
+
8
+ beforeEach(async () => {
9
+ loadFixture('test/fixtures/blurBinding.html');
10
+
11
+ namespace.viewModel = {
12
+ heading: 'blur component test',
13
+ myData: 'blur component',
14
+ onFocusFn(_e: Event, _$element: any) {},
15
+ onBlurFn(_e: Event, _$element: any) {
16
+ this.myData = testBlurValue;
17
+ this.updateView();
18
+ },
19
+ updateView(opt?: any) {
20
+ this.APP.render(opt);
21
+ },
22
+ };
23
+
24
+ namespace.blurComponent = dataBind.init(document.querySelector('[data-bind-comp="blur-component"]'), namespace.viewModel);
25
+ await namespace.blurComponent.render();
26
+ });
27
+
28
+ afterEach(() => {
29
+ // clean up app
30
+ // clean up all app/components
31
+ for (const prop in namespace) {
32
+ if (Object.prototype.hasOwnProperty.call(namespace, prop)) {
33
+ delete namespace[prop];
34
+ }
35
+ }
36
+ });
37
+
38
+ it('should render heading defined in viewModel', async () => {
39
+ await waitFor(() => {
40
+ expect(document.getElementById('heading')!.textContent).toBe(namespace.viewModel.heading);
41
+ expect((document.getElementById('blurInput') as HTMLInputElement).value).toBe(namespace.viewModel.myData);
42
+ }, {timeout: 500});
43
+ });
44
+
45
+ it('should update "blurInput" value after onBlur', async () => {
46
+ await waitFor(() => {
47
+ const $blurInput = document.getElementById('blurInput');
48
+ expect($blurInput).not.toBeNull();
49
+ }, {timeout: 500});
50
+
51
+ const $blurInput = document.getElementById('blurInput')!;
52
+
53
+ // Use the test helper to simulate blur event
54
+ simulateBlur($blurInput);
55
+
56
+ await waitFor(() => {
57
+ expect(($blurInput as HTMLInputElement).value).toBe(testBlurValue);
58
+ }, {timeout: 500});
59
+ });
60
+ });
@@ -0,0 +1,125 @@
1
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
2
+
3
+ describe('Chainable use() API', () => {
4
+ beforeEach(() => {
5
+ document.body.innerHTML = '';
6
+ });
7
+
8
+ afterEach(() => {
9
+ document.body.innerHTML = '';
10
+ });
11
+
12
+ it('should support chaining use() with init()', () => {
13
+ document.body.innerHTML = `
14
+ <div id="app" data-bind-comp="test">
15
+ <span data-bind-text="value"></span>
16
+ </div>
17
+ `;
18
+
19
+ const viewModel = {
20
+ value: 'Hello',
21
+ };
22
+
23
+ // Chain use() with init()
24
+ const app = (window as any).dataBind
25
+ .use({reactive: false})
26
+ .init(document.getElementById('app'), viewModel);
27
+
28
+ expect(app).toBeDefined();
29
+ expect(app.isReactive).toBe(false);
30
+ });
31
+
32
+ it('should support multiple use() calls in chain', () => {
33
+ document.body.innerHTML = `
34
+ <div id="app" data-bind-comp="test">
35
+ <span data-bind-text="value"></span>
36
+ </div>
37
+ `;
38
+
39
+ const viewModel = {
40
+ value: 'Hello',
41
+ };
42
+
43
+ // Multiple chained use() calls (last one wins)
44
+ const app = (window as any).dataBind
45
+ .use({reactive: true})
46
+ .use({trackChanges: true})
47
+ .use({reactive: false})
48
+ .init(document.getElementById('app'), viewModel);
49
+
50
+ expect(app).toBeDefined();
51
+ expect(app.isReactive).toBe(false);
52
+ });
53
+
54
+ it('should work with traditional use() then init() pattern', () => {
55
+ document.body.innerHTML = `
56
+ <div id="app" data-bind-comp="test">
57
+ <span data-bind-text="value"></span>
58
+ </div>
59
+ `;
60
+
61
+ const viewModel = {
62
+ value: 'Hello',
63
+ };
64
+
65
+ // Traditional pattern (still works)
66
+ (window as any).dataBind.use({reactive: false});
67
+ const app = (window as any).dataBind.init(
68
+ document.getElementById('app'),
69
+ viewModel,
70
+ );
71
+
72
+ expect(app).toBeDefined();
73
+ expect(app.isReactive).toBe(false);
74
+ });
75
+
76
+ it('should allow instance options to override chained use() settings', () => {
77
+ document.body.innerHTML = `
78
+ <div id="app" data-bind-comp="test">
79
+ <span data-bind-text="value"></span>
80
+ </div>
81
+ `;
82
+
83
+ const viewModel = {
84
+ value: 'Hello',
85
+ };
86
+
87
+ // use() sets reactive: false, but instance option overrides to true
88
+ const app = (window as any).dataBind
89
+ .use({reactive: false})
90
+ .init(document.getElementById('app'), viewModel, {reactive: true});
91
+
92
+ expect(app).toBeDefined();
93
+ expect(app.isReactive).toBe(true);
94
+ });
95
+
96
+ it('should reset to default after using chained use()', () => {
97
+ document.body.innerHTML = `
98
+ <div id="app1" data-bind-comp="test1">
99
+ <span data-bind-text="value"></span>
100
+ </div>
101
+ <div id="app2" data-bind-comp="test2">
102
+ <span data-bind-text="value"></span>
103
+ </div>
104
+ `;
105
+
106
+ const viewModel = {value: 'Hello'};
107
+
108
+ // First component with chained use
109
+ const app1 = (window as any).dataBind
110
+ .use({reactive: false})
111
+ .init(document.getElementById('app1'), viewModel);
112
+
113
+ // Second component should inherit the global setting from use()
114
+ const app2 = (window as any).dataBind.init(
115
+ document.getElementById('app2'),
116
+ viewModel,
117
+ );
118
+
119
+ expect(app1.isReactive).toBe(false);
120
+ expect(app2.isReactive).toBe(false); // Global setting persists
121
+
122
+ // Reset to default
123
+ (window as any).dataBind.use({reactive: true});
124
+ });
125
+ });
@@ -0,0 +1,194 @@
1
+ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
2
+ import {waitFor} from '@testing-library/dom';
3
+
4
+ describe('Click Binding - Event Handler Deduplication', () => {
5
+ let namespace: any;
6
+
7
+ const loadFixture = (fixturePath: string) => {
8
+ const xhr = new XMLHttpRequest();
9
+ xhr.open('GET', fixturePath, false);
10
+ xhr.send();
11
+ document.body.innerHTML = xhr.responseText;
12
+ };
13
+
14
+ beforeEach(() => {
15
+ namespace = (window as any).namespace || {};
16
+ (window as any).namespace = namespace;
17
+ });
18
+
19
+ afterEach(() => {
20
+ document.body.innerHTML = '';
21
+ delete (window as any).namespace;
22
+ });
23
+
24
+ it('should only call click handler once per click, even after re-render', async () => {
25
+ // Create a simple fixture
26
+ document.body.innerHTML = `
27
+ <div data-bind-comp="test-component">
28
+ <button id="testButton" data-bind-click="handleClick">Click Me</button>
29
+ <span id="counter" data-bind-text="count"></span>
30
+ </div>
31
+ `;
32
+
33
+ const clickSpy = vi.fn();
34
+ const viewModel = {
35
+ count: 0,
36
+ handleClick() {
37
+ clickSpy();
38
+ this.count++;
39
+ },
40
+ };
41
+
42
+ const component = (window as any).dataBind.init(
43
+ document.querySelector('[data-bind-comp="test-component"]'),
44
+ viewModel,
45
+ );
46
+
47
+ await component.render();
48
+
49
+ const button = document.getElementById('testButton') as HTMLButtonElement;
50
+
51
+ // First click
52
+ button.click();
53
+
54
+ await waitFor(() => {
55
+ expect(clickSpy).toHaveBeenCalledTimes(1);
56
+ expect(component.viewModel.count).toBe(1);
57
+ });
58
+
59
+ // Wait for reactive render to complete
60
+ await new Promise(resolve => setTimeout(resolve, 100));
61
+
62
+ // Second click - should still only call once
63
+ clickSpy.mockClear();
64
+ button.click();
65
+
66
+ await waitFor(() => {
67
+ expect(clickSpy).toHaveBeenCalledTimes(1);
68
+ expect(component.viewModel.count).toBe(2);
69
+ });
70
+ });
71
+
72
+ it('should properly remove old handlers when re-rendering with template binding', async () => {
73
+ document.body.innerHTML = `
74
+ <div data-bind-comp="test-component">
75
+ <div data-bind-for="item in items">
76
+ <button class="item-button" data-bind-click="$root.handleItemClick($index)">
77
+ Item <span data-bind-text="$index"></span>
78
+ </button>
79
+ </div>
80
+ </div>
81
+ `;
82
+
83
+ const clickSpy = vi.fn();
84
+ const viewModel = {
85
+ items: [1, 2, 3],
86
+ handleItemClick(e: Event, el: HTMLElement, index: number) {
87
+ clickSpy(index);
88
+ },
89
+ };
90
+
91
+ const component = (window as any).dataBind.init(
92
+ document.querySelector('[data-bind-comp="test-component"]'),
93
+ viewModel,
94
+ );
95
+
96
+ await component.render();
97
+
98
+ const buttons = document.querySelectorAll('.item-button') as NodeListOf<HTMLButtonElement>;
99
+ expect(buttons.length).toBe(3);
100
+
101
+ // Click first button
102
+ buttons[0].click();
103
+
104
+ await waitFor(() => {
105
+ expect(clickSpy).toHaveBeenCalledTimes(1);
106
+ expect(clickSpy).toHaveBeenCalledWith(0);
107
+ });
108
+
109
+ // Wait for any reactive updates
110
+ await new Promise(resolve => setTimeout(resolve, 100));
111
+
112
+ // Update items to trigger re-render
113
+ clickSpy.mockClear();
114
+ component.viewModel.items = [1, 2, 3, 4];
115
+
116
+ await waitFor(() => {
117
+ const newButtons = document.querySelectorAll('.item-button') as NodeListOf<HTMLButtonElement>;
118
+ expect(newButtons.length).toBe(4);
119
+ });
120
+
121
+ // Click first button again - should only fire once
122
+ const newButtons = document.querySelectorAll('.item-button') as NodeListOf<HTMLButtonElement>;
123
+ newButtons[0].click();
124
+
125
+ await waitFor(() => {
126
+ expect(clickSpy).toHaveBeenCalledTimes(1);
127
+ expect(clickSpy).toHaveBeenCalledWith(0);
128
+ });
129
+ });
130
+
131
+ it('should handle multiple event types without conflicts', async () => {
132
+ document.body.innerHTML = `
133
+ <div data-bind-comp="test-component">
134
+ <button
135
+ id="multiEventButton"
136
+ data-bind-click="handleClick"
137
+ data-bind-dblclick="handleDblClick"
138
+ >Multi Event Button</button>
139
+ <span id="clickCount" data-bind-text="clickCount"></span>
140
+ <span id="dblClickCount" data-bind-text="dblClickCount"></span>
141
+ </div>
142
+ `;
143
+
144
+ const viewModel = {
145
+ clickCount: 0,
146
+ dblClickCount: 0,
147
+ handleClick() {
148
+ this.clickCount++;
149
+ },
150
+ handleDblClick() {
151
+ this.dblClickCount++;
152
+ },
153
+ };
154
+
155
+ const component = (window as any).dataBind.init(
156
+ document.querySelector('[data-bind-comp="test-component"]'),
157
+ viewModel,
158
+ );
159
+
160
+ await component.render();
161
+
162
+ const button = document.getElementById('multiEventButton') as HTMLButtonElement;
163
+
164
+ // Single click
165
+ button.click();
166
+
167
+ await waitFor(() => {
168
+ expect(component.viewModel.clickCount).toBe(1);
169
+ expect(component.viewModel.dblClickCount).toBe(0);
170
+ });
171
+
172
+ // Wait for reactive render
173
+ await new Promise(resolve => setTimeout(resolve, 100));
174
+
175
+ // Double click
176
+ button.dispatchEvent(new MouseEvent('dblclick', {bubbles: true}));
177
+
178
+ await waitFor(() => {
179
+ expect(component.viewModel.clickCount).toBe(1);
180
+ expect(component.viewModel.dblClickCount).toBe(1);
181
+ });
182
+
183
+ // Wait for reactive render
184
+ await new Promise(resolve => setTimeout(resolve, 100));
185
+
186
+ // Another click - should still only increment by 1
187
+ button.click();
188
+
189
+ await waitFor(() => {
190
+ expect(component.viewModel.clickCount).toBe(2);
191
+ expect(component.viewModel.dblClickCount).toBe(1);
192
+ });
193
+ });
194
+ });
@@ -1,79 +1,72 @@
1
- describe('Given [data-bind-comp="css-component"] inited', () => {
2
- const namespace = {};
3
-
4
- jasmine.getFixtures().fixturesPath = 'test';
5
-
6
- beforeEach(() => {
7
- loadFixtures('./fixtures/cssBinding.html');
8
-
9
- namespace.viewModel = {
10
- heading: 'myCssComponent',
11
- testOneCss: {
12
- a: true,
13
- b: true,
14
- c: true,
15
- },
16
- getTestTwoCss: function($data, oldValue, el) {
17
- return {
18
- e: true,
19
- f: true,
20
- };
21
- },
22
- updateView: function(opt) {
23
- this.APP.render(opt);
24
- },
25
- };
26
-
27
- namespace.myCssComponent = dataBind.init(document.querySelector('[data-bind-comp="css-component"]'), namespace.viewModel);
28
-
29
- namespace.myCssComponent.render();
30
- });
31
-
32
- afterEach(() => {
33
- // clean up all app/components
34
- for (const prop in namespace) {
35
- if (namespace.hasOwnProperty(prop)) {
36
- delete namespace[prop];
37
- }
38
- }
39
- });
40
-
41
- it('Then [data-bind-comp="myCssComponent"] should have render', (done) => {
42
- setTimeout(() => {
43
- const $heading = document.getElementById('myCssComponentHeading');
44
- expect($heading.textContent).toBe(namespace.viewModel.heading);
45
- done();
46
- }, 200);
47
- });
48
-
49
- it('should apply css bindings', (done) => {
50
- setTimeout(() => {
51
- const $testCssOne = document.getElementById('testCssOne');
52
- const testCssOneClassName = $testCssOne.className;
53
- const $testCssTwo = document.getElementById('testCssTwo');
54
- const testCssTwoClassName = $testCssTwo.className;
55
-
56
- expect(testCssOneClassName).toBe('testCssOne a b c');
57
- expect(testCssTwoClassName).toBe('testCssTwo x y z e f');
58
- done();
59
- }, 200);
60
- });
61
-
62
- it('should remove duplicated css', (done) => {
63
- namespace.viewModel.testOneCss = {
64
- a: true,
65
- b: false,
66
- c: true,
67
- testCssOne: true,
68
- };
69
- namespace.viewModel.updateView();
70
-
71
- setTimeout(() => {
72
- const $testCssOne = document.getElementById('testCssOne');
73
- const testCssOneClassName = $testCssOne.className;
74
-
75
- expect(testCssOneClassName).toBe('testCssOne a c');
76
- done();
77
- }, 200);
78
- });
79
- });
1
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
2
+ import {waitFor} from '@testing-library/dom';
3
+
4
+ describe('Given [data-bind-comp="css-component"] inited', () => {
5
+ const namespace: any = {};
6
+
7
+ beforeEach(() => {
8
+ loadFixture('test/fixtures/cssBinding.html');
9
+
10
+ namespace.viewModel = {
11
+ heading: 'myCssComponent',
12
+ testOneCss: {
13
+ a: true,
14
+ b: true,
15
+ c: true,
16
+ },
17
+ getTestTwoCss(_$data: any, _oldValue: any, _el: any) {
18
+ return {
19
+ e: true,
20
+ f: true,
21
+ };
22
+ },
23
+ updateView(opt?: any) {
24
+ this.APP.render(opt);
25
+ },
26
+ };
27
+
28
+ namespace.myCssComponent = dataBind.init(document.querySelector('[data-bind-comp="css-component"]'), namespace.viewModel);
29
+
30
+ namespace.myCssComponent.render();
31
+ });
32
+
33
+ afterEach(() => {
34
+ // clean up all app/components
35
+ for (const prop in namespace) {
36
+ if (Object.prototype.hasOwnProperty.call(namespace, prop)) {
37
+ delete namespace[prop];
38
+ }
39
+ }
40
+ });
41
+
42
+ it('Then [data-bind-comp="myCssComponent"] should have render', async () => {
43
+ await waitFor(() => {
44
+ const $heading = document.getElementById('myCssComponentHeading')!;
45
+ expect($heading.textContent).toBe(namespace.viewModel.heading);
46
+ }, {timeout: 500});
47
+ });
48
+
49
+ it('should apply css bindings', async () => {
50
+ await waitFor(() => {
51
+ const $testCssOne = document.getElementById('testCssOne')!;
52
+ const testCssOneClassName = $testCssOne.className;
53
+ const $testCssTwo = document.getElementById('testCssTwo')!;
54
+ const testCssTwoClassName = $testCssTwo.className;
55
+
56
+ expect(testCssOneClassName).toBe('testCssOne a b c');
57
+ expect(testCssTwoClassName).toBe('testCssTwo x y z e f');
58
+ }, {timeout: 500});
59
+ });
60
+
61
+ it('should remove duplicated css', async () => {
62
+ namespace.viewModel.testOneCss.b = false;
63
+ namespace.viewModel.updateView();
64
+
65
+ await waitFor(() => {
66
+ const $testCssOne = document.getElementById('testCssOne')!;
67
+ const testCssOneClassName = $testCssOne.className;
68
+
69
+ expect(testCssOneClassName).toBe('testCssOne a c');
70
+ }, {timeout: 500});
71
+ });
72
+ });