@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
@@ -1,27 +1,29 @@
1
- describe('Given dataBind lib loaded', () => {
2
- it('Then dataBind object exists', () => {
3
- expect(typeof dataBind).toBe('object');
4
- });
5
-
6
- it('Then "dataBind.init" should be a functions', () => {
7
- expect(typeof dataBind.init).toBe('function');
8
- });
9
-
10
- it('Then "dataBind.use" should be a functions', () => {
11
- expect(typeof dataBind.use).toBe('function');
12
- });
13
-
14
- it('Should throw error if mounting root element does not exits', () => {
15
- const viewModel = {};
16
-
17
- expect(function() {
18
- dataBind.init(document.getElementById('#xyz'), viewModel);
19
- }).toThrowError();
20
- });
21
-
22
- it('Should throw error if viewModel does not exits', () => {
23
- expect(function() {
24
- testApp = dataBind.init(document.getElementById('#xyz'));
25
- }).toThrowError();
26
- });
27
- });
1
+ import {describe, it, expect} from 'vitest';
2
+
3
+ describe('Given dataBind lib loaded', () => {
4
+ it('Then dataBind object exists', () => {
5
+ expect(typeof dataBind).toBe('object');
6
+ });
7
+
8
+ it('Then "dataBind.init" should be a functions', () => {
9
+ expect(typeof dataBind.init).toBe('function');
10
+ });
11
+
12
+ it('Then "dataBind.use" should be a functions', () => {
13
+ expect(typeof dataBind.use).toBe('function');
14
+ });
15
+
16
+ it('Should throw error if mounting root element does not exits', () => {
17
+ const viewModel = {};
18
+
19
+ expect(() => {
20
+ dataBind.init(document.getElementById('#xyz'), viewModel);
21
+ }).toThrow();
22
+ });
23
+
24
+ it('Should throw error if viewModel does not exits', () => {
25
+ expect(() => {
26
+ dataBind.init(document.getElementById('#xyz'), undefined as any);
27
+ }).toThrow();
28
+ });
29
+ });
@@ -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
+ });