@nyaruka/temba-components 0.122.0 → 0.124.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 (262) hide show
  1. package/.github/copilot-instructions.md +181 -0
  2. package/.github/workflows/build.yml +3 -3
  3. package/.github/workflows/cla.yml +6 -6
  4. package/.github/workflows/copilot-setup-steps.yml +86 -0
  5. package/CHANGELOG.md +44 -0
  6. package/demo/drag-drop-demo.html +141 -0
  7. package/demo/index.html +57 -0
  8. package/demo/test-drag-drop.html +94 -0
  9. package/dist/locales/es.js +1 -0
  10. package/dist/locales/es.js.map +1 -1
  11. package/dist/locales/fr.js +1 -0
  12. package/dist/locales/fr.js.map +1 -1
  13. package/dist/locales/pt.js +1 -0
  14. package/dist/locales/pt.js.map +1 -1
  15. package/dist/temba-components.js +366 -247
  16. package/dist/temba-components.js.map +1 -1
  17. package/out-tsc/src/chart/TembaChart.js +81 -14
  18. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  19. package/out-tsc/src/fields/FieldManager.js +27 -34
  20. package/out-tsc/src/fields/FieldManager.js.map +1 -1
  21. package/out-tsc/src/list/RunList.js +13 -8
  22. package/out-tsc/src/list/RunList.js.map +1 -1
  23. package/out-tsc/src/list/SortableList.js +257 -60
  24. package/out-tsc/src/list/SortableList.js.map +1 -1
  25. package/out-tsc/src/locales/es.js +1 -0
  26. package/out-tsc/src/locales/es.js.map +1 -1
  27. package/out-tsc/src/locales/fr.js +1 -0
  28. package/out-tsc/src/locales/fr.js.map +1 -1
  29. package/out-tsc/src/locales/pt.js +1 -0
  30. package/out-tsc/src/locales/pt.js.map +1 -1
  31. package/out-tsc/src/omnibox/Omnibox.js +1 -1
  32. package/out-tsc/src/omnibox/Omnibox.js.map +1 -1
  33. package/out-tsc/src/options/Options.js +36 -13
  34. package/out-tsc/src/options/Options.js.map +1 -1
  35. package/out-tsc/src/select/Select.js +226 -43
  36. package/out-tsc/src/select/Select.js.map +1 -1
  37. package/out-tsc/src/store/AppState.js +3 -3
  38. package/out-tsc/src/store/AppState.js.map +1 -1
  39. package/out-tsc/src/utils/index.js +6 -1
  40. package/out-tsc/src/utils/index.js.map +1 -1
  41. package/out-tsc/src/vectoricon/VectorIcon.js +2 -1
  42. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  43. package/out-tsc/src/webchat/WebChat.js +5 -2
  44. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  45. package/out-tsc/temba-modules.js +0 -2
  46. package/out-tsc/temba-modules.js.map +1 -1
  47. package/out-tsc/test/temba-appstate-language.test.js +176 -0
  48. package/out-tsc/test/temba-appstate-language.test.js.map +1 -0
  49. package/out-tsc/test/temba-chart.test.js +125 -0
  50. package/out-tsc/test/temba-chart.test.js.map +1 -1
  51. package/out-tsc/test/temba-dropdown.test.js +317 -0
  52. package/out-tsc/test/temba-dropdown.test.js.map +1 -0
  53. package/out-tsc/test/temba-flow-editor-node.test.js +273 -0
  54. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -0
  55. package/out-tsc/test/temba-flow-editor.test.js +244 -0
  56. package/out-tsc/test/temba-flow-editor.test.js.map +1 -0
  57. package/out-tsc/test/temba-flow-plumber.test.js +145 -0
  58. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -0
  59. package/out-tsc/test/temba-flow-render.test.js +171 -0
  60. package/out-tsc/test/temba-flow-render.test.js.map +1 -0
  61. package/out-tsc/test/temba-omnibox.test.js +2 -3
  62. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  63. package/out-tsc/test/temba-run-list.test.js +588 -0
  64. package/out-tsc/test/temba-run-list.test.js.map +1 -0
  65. package/out-tsc/test/temba-select.test.js +149 -52
  66. package/out-tsc/test/temba-select.test.js.map +1 -1
  67. package/out-tsc/test/temba-sortable-list.test.js +91 -15
  68. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  69. package/out-tsc/test/temba-toast.test.js +299 -0
  70. package/out-tsc/test/temba-toast.test.js.map +1 -0
  71. package/out-tsc/test/temba-utils-index.test.js +1178 -0
  72. package/out-tsc/test/temba-utils-index.test.js.map +1 -0
  73. package/out-tsc/test/temba-webchat-lightbox-fix.test.js +42 -0
  74. package/out-tsc/test/temba-webchat-lightbox-fix.test.js.map +1 -0
  75. package/out-tsc/test/temba-webchat.test.js +816 -0
  76. package/out-tsc/test/temba-webchat.test.js.map +1 -0
  77. package/out-tsc/test/utils.test.js +33 -1
  78. package/out-tsc/test/utils.test.js.map +1 -1
  79. package/package.json +6 -8
  80. package/screenshots/truth/alert/error.png +0 -0
  81. package/screenshots/truth/alert/info.png +0 -0
  82. package/screenshots/truth/alert/warning.png +0 -0
  83. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  84. package/screenshots/truth/checkbox/checked.png +0 -0
  85. package/screenshots/truth/checkbox/default.png +0 -0
  86. package/screenshots/truth/colorpicker/default.png +0 -0
  87. package/screenshots/truth/colorpicker/focused.png +0 -0
  88. package/screenshots/truth/colorpicker/initialized.png +0 -0
  89. package/screenshots/truth/colorpicker/selected.png +0 -0
  90. package/screenshots/truth/compose/attachments-tab.png +0 -0
  91. package/screenshots/truth/compose/attachments-with-files-focused.png +0 -0
  92. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  93. package/screenshots/truth/compose/intial-text.png +0 -0
  94. package/screenshots/truth/compose/no-counter.png +0 -0
  95. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  96. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  97. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  98. package/screenshots/truth/contacts/badges.png +0 -0
  99. package/screenshots/truth/contacts/chat-failure.png +0 -0
  100. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  101. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  102. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  103. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  104. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  105. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  106. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  107. package/screenshots/truth/content-menu/button-no-items.png +0 -0
  108. package/screenshots/truth/content-menu/items-and-buttons.png +0 -0
  109. package/screenshots/truth/counter/summary.png +0 -0
  110. package/screenshots/truth/counter/text.png +0 -0
  111. package/screenshots/truth/counter/unicode-variables.png +0 -0
  112. package/screenshots/truth/counter/unicode.png +0 -0
  113. package/screenshots/truth/counter/variable.png +0 -0
  114. package/screenshots/truth/date/date-inline.png +0 -0
  115. package/screenshots/truth/date/date.png +0 -0
  116. package/screenshots/truth/date/datetime.png +0 -0
  117. package/screenshots/truth/date/duration.png +0 -0
  118. package/screenshots/truth/date/timedate.png +0 -0
  119. package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
  120. package/screenshots/truth/datepicker/date.png +0 -0
  121. package/screenshots/truth/datepicker/initial-timezone.png +0 -0
  122. package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
  123. package/screenshots/truth/dialog/focused.png +0 -0
  124. package/screenshots/truth/dropdown/after-blur.png +0 -0
  125. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  126. package/screenshots/truth/dropdown/custom-arrow-size.png +0 -0
  127. package/screenshots/truth/dropdown/default.png +0 -0
  128. package/screenshots/truth/dropdown/narrow-toggle.png +0 -0
  129. package/screenshots/truth/dropdown/no-mask.png +0 -0
  130. package/screenshots/truth/dropdown/opened.png +0 -0
  131. package/screenshots/truth/dropdown/positioned.png +0 -0
  132. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  133. package/screenshots/truth/dropdown/with-mask.png +0 -0
  134. package/screenshots/truth/flow/editor-basic.png +0 -0
  135. package/screenshots/truth/label/custom.png +0 -0
  136. package/screenshots/truth/label/danger.png +0 -0
  137. package/screenshots/truth/label/dark.png +0 -0
  138. package/screenshots/truth/label/default-icon.png +0 -0
  139. package/screenshots/truth/label/no-icon.png +0 -0
  140. package/screenshots/truth/label/primary.png +0 -0
  141. package/screenshots/truth/label/secondary.png +0 -0
  142. package/screenshots/truth/label/shadow.png +0 -0
  143. package/screenshots/truth/label/tertiary.png +0 -0
  144. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  145. package/screenshots/truth/list/fields-dragging.png +0 -0
  146. package/screenshots/truth/list/fields-filtered.png +0 -0
  147. package/screenshots/truth/list/fields-hovered.png +0 -0
  148. package/screenshots/truth/list/fields.png +0 -0
  149. package/screenshots/truth/list/items-selected.png +0 -0
  150. package/screenshots/truth/list/items-updated.png +0 -0
  151. package/screenshots/truth/list/items.png +0 -0
  152. package/screenshots/truth/list/sortable-dragging.png +0 -0
  153. package/screenshots/truth/list/sortable-dropped.png +0 -0
  154. package/screenshots/truth/list/sortable.png +0 -0
  155. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  156. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  157. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  158. package/screenshots/truth/menu/menu-root.png +0 -0
  159. package/screenshots/truth/menu/menu-submenu.png +0 -0
  160. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  161. package/screenshots/truth/menu/menu-tasks.png +0 -0
  162. package/screenshots/truth/modax/form.png +0 -0
  163. package/screenshots/truth/modax/simple.png +0 -0
  164. package/screenshots/truth/omnibox/selected.png +0 -0
  165. package/screenshots/truth/options/block.png +0 -0
  166. package/screenshots/truth/run-list/basic.png +0 -0
  167. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  168. package/screenshots/truth/select/disabled-selection.png +0 -0
  169. package/screenshots/truth/select/disabled.png +0 -0
  170. package/screenshots/truth/select/embedded.png +0 -0
  171. package/screenshots/truth/select/empty-options.png +0 -0
  172. package/screenshots/truth/select/expression-selected.png +0 -0
  173. package/screenshots/truth/select/expressions.png +0 -0
  174. package/screenshots/truth/select/functions.png +0 -0
  175. package/screenshots/truth/select/local-options.png +0 -0
  176. package/screenshots/truth/select/multi-reorder-final.png +0 -0
  177. package/screenshots/truth/select/multi-reorder-initial.png +0 -0
  178. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  179. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  180. package/screenshots/truth/select/remote-options.png +0 -0
  181. package/screenshots/truth/select/search-enabled.png +0 -0
  182. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  183. package/screenshots/truth/select/search-selected-focus.png +0 -0
  184. package/screenshots/truth/select/search-selected.png +0 -0
  185. package/screenshots/truth/select/search-with-selected.png +0 -0
  186. package/screenshots/truth/select/searching.png +0 -0
  187. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  188. package/screenshots/truth/select/selected-multi.png +0 -0
  189. package/screenshots/truth/select/selected-single.png +0 -0
  190. package/screenshots/truth/select/selection-clearable.png +0 -0
  191. package/screenshots/truth/select/static-initial-value.png +0 -0
  192. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  193. package/screenshots/truth/select/truncated-selection.png +0 -0
  194. package/screenshots/truth/select/with-placeholder.png +0 -0
  195. package/screenshots/truth/select/without-placeholder.png +0 -0
  196. package/screenshots/truth/slider/custom-min-custom-max-valid-value.png +0 -0
  197. package/screenshots/truth/slider/custom-min-default-max-no-value.png +0 -0
  198. package/screenshots/truth/slider/default-min-custom-max-no-value.png +0 -0
  199. package/screenshots/truth/slider/default-min-default-max-invalid-value.png +0 -0
  200. package/screenshots/truth/slider/default-min-default-max-valid-value.png +0 -0
  201. package/screenshots/truth/slider/update-slider-on-value-change.png +0 -0
  202. package/screenshots/truth/templates/default.png +0 -0
  203. package/screenshots/truth/templates/unapproved.png +0 -0
  204. package/screenshots/truth/textinput/input-disabled.png +0 -0
  205. package/screenshots/truth/textinput/input-focused.png +0 -0
  206. package/screenshots/truth/textinput/input-form.png +0 -0
  207. package/screenshots/truth/textinput/input-inserted.png +0 -0
  208. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  209. package/screenshots/truth/textinput/input-updated.png +0 -0
  210. package/screenshots/truth/textinput/input.png +0 -0
  211. package/screenshots/truth/textinput/textarea-focused.png +0 -0
  212. package/screenshots/truth/textinput/textarea.png +0 -0
  213. package/screenshots/truth/tip/bottom.png +0 -0
  214. package/screenshots/truth/tip/left.png +0 -0
  215. package/screenshots/truth/tip/right.png +0 -0
  216. package/screenshots/truth/tip/top.png +0 -0
  217. package/screenshots/truth/webchat/closed-widget.png +0 -0
  218. package/screenshots/truth/webchat/connected-state.png +0 -0
  219. package/screenshots/truth/webchat/connecting-state.png +0 -0
  220. package/screenshots/truth/webchat/disconnected-state.png +0 -0
  221. package/screenshots/truth/webchat/opened-widget.png +0 -0
  222. package/src/chart/TembaChart.ts +86 -15
  223. package/src/fields/FieldManager.ts +30 -38
  224. package/src/list/RunList.ts +11 -8
  225. package/src/list/SortableList.ts +291 -67
  226. package/src/locales/es.ts +1 -0
  227. package/src/locales/fr.ts +1 -0
  228. package/src/locales/pt.ts +1 -0
  229. package/src/omnibox/Omnibox.ts +1 -1
  230. package/src/options/Options.ts +38 -13
  231. package/src/select/Select.ts +245 -47
  232. package/src/store/AppState.ts +3 -3
  233. package/src/utils/index.ts +17 -5
  234. package/src/vectoricon/VectorIcon.ts +2 -1
  235. package/src/webchat/WebChat.ts +5 -2
  236. package/temba-modules.ts +0 -2
  237. package/test/temba-appstate-language.test.ts +218 -0
  238. package/test/temba-chart.test.ts +161 -1
  239. package/test/temba-dropdown.test.ts +444 -0
  240. package/test/temba-flow-editor-node.test.ts +344 -0
  241. package/test/temba-flow-editor.test.ts +301 -0
  242. package/test/temba-flow-plumber.test.ts +189 -0
  243. package/test/temba-flow-render.test.ts +220 -0
  244. package/test/temba-omnibox.test.ts +2 -3
  245. package/test/temba-run-list.test.ts +774 -0
  246. package/test/temba-select.test.ts +206 -78
  247. package/test/temba-sortable-list.test.ts +108 -15
  248. package/test/temba-toast.test.ts +386 -0
  249. package/test/temba-utils-index.test.ts +1547 -0
  250. package/test/temba-webchat-lightbox-fix.test.ts +57 -0
  251. package/test/temba-webchat.test.ts +1095 -0
  252. package/test/utils.test.ts +56 -2
  253. package/test-assets/list/flow-results.json +17 -0
  254. package/test-assets/list/runs.json +126 -0
  255. package/test-assets/style.css +23 -0
  256. package/web-test-runner.config.mjs +33 -7
  257. package/xliff/es.xlf +3 -0
  258. package/xliff/fr.xlf +3 -0
  259. package/xliff/pt.xlf +3 -0
  260. package/out-tsc/src/outboxmonitor/OutboxMonitor.js +0 -136
  261. package/out-tsc/src/outboxmonitor/OutboxMonitor.js.map +0 -1
  262. package/src/outboxmonitor/OutboxMonitor.ts +0 -148
@@ -0,0 +1,218 @@
1
+ import { assert } from '@open-wc/testing';
2
+ import { zustand } from '../src/store/AppState';
3
+
4
+ describe('AppState Language Reset', () => {
5
+ beforeEach(() => {
6
+ // Reset the store state before each test
7
+ const state = zustand.getState();
8
+ zustand.setState({
9
+ ...state,
10
+ languageCode: '',
11
+ isTranslating: false,
12
+ flowDefinition: null,
13
+ flowInfo: null
14
+ });
15
+ });
16
+
17
+ it('should reset language when loading a new flow', () => {
18
+ const state = zustand.getState();
19
+
20
+ // First, load an initial flow to establish state
21
+ const initialFlowContents = {
22
+ definition: {
23
+ language: 'en',
24
+ localization: {},
25
+ name: 'Initial Flow',
26
+ nodes: [],
27
+ uuid: 'initial-uuid',
28
+ type: 'messaging' as const,
29
+ revision: 1,
30
+ spec_version: '14.3',
31
+ _ui: {
32
+ nodes: {},
33
+ languages: []
34
+ }
35
+ },
36
+ info: {
37
+ results: [],
38
+ dependencies: [],
39
+ counts: { nodes: 0, languages: 1 },
40
+ locals: []
41
+ }
42
+ };
43
+
44
+ state.setFlowContents(initialFlowContents);
45
+
46
+ // Simulate having a previous flow with localization
47
+ state.setLanguageCode('es'); // User selected Spanish for localization
48
+ assert.equal(zustand.getState().languageCode, 'es');
49
+ assert.equal(zustand.getState().isTranslating, true); // Now translating from English to Spanish
50
+
51
+ // Simulate loading a new flow with English as default
52
+ const mockFlowContents = {
53
+ definition: {
54
+ language: 'en',
55
+ localization: {},
56
+ name: 'Test Flow',
57
+ nodes: [],
58
+ uuid: 'test-uuid',
59
+ type: 'messaging' as const,
60
+ revision: 1,
61
+ spec_version: '14.3',
62
+ _ui: {
63
+ nodes: {},
64
+ languages: []
65
+ }
66
+ },
67
+ info: {
68
+ results: [],
69
+ dependencies: [],
70
+ counts: { nodes: 0, languages: 1 },
71
+ locals: []
72
+ }
73
+ };
74
+
75
+ state.setFlowContents(mockFlowContents);
76
+
77
+ // The language should reset to the flow's default language
78
+ assert.equal(
79
+ zustand.getState().languageCode,
80
+ 'en',
81
+ 'Language should reset to flow default'
82
+ );
83
+ assert.equal(
84
+ zustand.getState().isTranslating,
85
+ false,
86
+ 'Should not be in translation mode'
87
+ );
88
+ });
89
+
90
+ it('should set isTranslating correctly when languages differ', () => {
91
+ const state = zustand.getState();
92
+
93
+ // Load a flow with Spanish as default
94
+ const mockFlowContents = {
95
+ definition: {
96
+ language: 'es',
97
+ localization: {},
98
+ name: 'Test Flow',
99
+ nodes: [],
100
+ uuid: 'test-uuid',
101
+ type: 'messaging' as const,
102
+ revision: 1,
103
+ spec_version: '14.3',
104
+ _ui: {
105
+ nodes: {},
106
+ languages: []
107
+ }
108
+ },
109
+ info: {
110
+ results: [],
111
+ dependencies: [],
112
+ counts: { nodes: 0, languages: 1 },
113
+ locals: []
114
+ }
115
+ };
116
+
117
+ state.setFlowContents(mockFlowContents);
118
+
119
+ // Should be in Spanish and not translating
120
+ assert.equal(zustand.getState().languageCode, 'es');
121
+ assert.equal(zustand.getState().isTranslating, false);
122
+
123
+ // Now switch to English for localization
124
+ state.setLanguageCode('en');
125
+ assert.equal(zustand.getState().languageCode, 'en');
126
+ assert.equal(
127
+ zustand.getState().isTranslating,
128
+ true,
129
+ 'Should be translating when language differs from flow default'
130
+ );
131
+ });
132
+
133
+ it('should preserve flow default language when no previous language is set', () => {
134
+ const state = zustand.getState();
135
+
136
+ // Load a flow with French as default, no previous language set
137
+ const mockFlowContents = {
138
+ definition: {
139
+ language: 'fr',
140
+ localization: {},
141
+ name: 'Test Flow',
142
+ nodes: [],
143
+ uuid: 'test-uuid',
144
+ type: 'messaging' as const,
145
+ revision: 1,
146
+ spec_version: '14.3',
147
+ _ui: {
148
+ nodes: {},
149
+ languages: []
150
+ }
151
+ },
152
+ info: {
153
+ results: [],
154
+ dependencies: [],
155
+ counts: { nodes: 0, languages: 1 },
156
+ locals: []
157
+ }
158
+ };
159
+
160
+ state.setFlowContents(mockFlowContents);
161
+
162
+ // Should use the flow's default language
163
+ assert.equal(
164
+ zustand.getState().languageCode,
165
+ 'fr',
166
+ 'Should use flow default language'
167
+ );
168
+ assert.equal(
169
+ zustand.getState().isTranslating,
170
+ false,
171
+ 'Should not be translating'
172
+ );
173
+ });
174
+
175
+ it('should handle language switching after flow is loaded', () => {
176
+ const state = zustand.getState();
177
+
178
+ // Load a flow with English as default
179
+ const mockFlowContents = {
180
+ definition: {
181
+ language: 'en',
182
+ localization: {},
183
+ name: 'Test Flow',
184
+ nodes: [],
185
+ uuid: 'test-uuid',
186
+ type: 'messaging' as const,
187
+ revision: 1,
188
+ spec_version: '14.3',
189
+ _ui: {
190
+ nodes: {},
191
+ languages: []
192
+ }
193
+ },
194
+ info: {
195
+ results: [],
196
+ dependencies: [],
197
+ counts: { nodes: 0, languages: 1 },
198
+ locals: []
199
+ }
200
+ };
201
+
202
+ state.setFlowContents(mockFlowContents);
203
+
204
+ // Should start in English, not translating
205
+ assert.equal(zustand.getState().languageCode, 'en');
206
+ assert.equal(zustand.getState().isTranslating, false);
207
+
208
+ // Switch to Spanish for localization
209
+ state.setLanguageCode('es');
210
+ assert.equal(zustand.getState().languageCode, 'es');
211
+ assert.equal(zustand.getState().isTranslating, true);
212
+
213
+ // Switch back to English
214
+ state.setLanguageCode('en');
215
+ assert.equal(zustand.getState().languageCode, 'en');
216
+ assert.equal(zustand.getState().isTranslating, false);
217
+ });
218
+ });
@@ -1,5 +1,9 @@
1
1
  import { expect } from '@open-wc/testing';
2
- import { RapidChartData, TembaChart } from '../src/chart/TembaChart';
2
+ import {
3
+ RapidChartData,
4
+ TembaChart,
5
+ formatDurationFromSeconds
6
+ } from '../src/chart/TembaChart';
3
7
  import { getComponent } from './utils.test';
4
8
 
5
9
  const sampleData: RapidChartData = {
@@ -52,4 +56,160 @@ describe('temba-chart', () => {
52
56
  // now others should be everything but general and support
53
57
  expect(chart.datasets[2].data).to.deep.equal([58, 28, 10, 1, 0, 19, 20]);
54
58
  });
59
+
60
+ it('supports duration formatting', async () => {
61
+ const chart: TembaChart = await getChart();
62
+
63
+ // Test that formatDuration property exists and defaults to false
64
+ expect(chart.formatDuration).to.equal(false);
65
+
66
+ // Test that we can set formatDuration to true
67
+ chart.formatDuration = true;
68
+ expect(chart.formatDuration).to.equal(true);
69
+ });
70
+
71
+ it('formats duration values correctly', async () => {
72
+ const chart: TembaChart = await getChart();
73
+
74
+ // Access the formatDurationFromSeconds function through the chart's module
75
+ // We need to test the duration formatting logic
76
+ const durationData: RapidChartData = {
77
+ labels: ['Day 1', 'Day 2', 'Day 3'],
78
+ datasets: [
79
+ {
80
+ label: 'Process Time',
81
+ data: [68787, 958000, 3661] // seconds that should be formatted as durations
82
+ }
83
+ ]
84
+ };
85
+
86
+ chart.formatDuration = true;
87
+ chart.data = durationData;
88
+ await chart.updateComplete;
89
+
90
+ // Wait for the chart to be created after data is set
91
+ await new Promise((resolve) => setTimeout(resolve, 100));
92
+
93
+ // Test that the chart was created and has the duration formatting enabled
94
+ expect(chart.formatDuration).to.equal(true);
95
+ expect(chart.chart).to.exist;
96
+
97
+ // Test that the chart configuration includes the duration formatting
98
+ const chartConfig = chart.chart.options;
99
+ expect(chartConfig.scales.y.ticks).to.exist;
100
+ expect(chartConfig.scales.y.ticks.callback).to.be.a('function');
101
+
102
+ // Test the tick callback function formatting
103
+ const tickCallback = chartConfig.scales.y.ticks.callback;
104
+ expect(tickCallback.call({}, 68787, 0, [])).to.equal('19h 6m');
105
+ expect(tickCallback.call({}, 958000, 1, [])).to.equal('11d 2h');
106
+ expect(tickCallback.call({}, 3661, 2, [])).to.equal('1h 1m');
107
+ expect(tickCallback.call({}, 120, 3, [])).to.equal('2m');
108
+ expect(tickCallback.call({}, 0, 4, [])).to.equal('0s');
109
+
110
+ // Test tooltip formatting
111
+ expect(chartConfig.plugins.tooltip.callbacks.label).to.be.a('function');
112
+ const tooltipCallback = chartConfig.plugins.tooltip.callbacks.label;
113
+
114
+ const mockContext = {
115
+ dataset: { label: 'Process Time' },
116
+ parsed: { y: 68787 }
117
+ };
118
+ expect(tooltipCallback.call({}, mockContext)).to.equal(
119
+ 'Process Time: 19h 6m'
120
+ );
121
+ });
122
+
123
+ it('formats various duration edge cases correctly', async () => {
124
+ const chart: TembaChart = await getChart();
125
+ chart.formatDuration = true;
126
+ chart.data = sampleData;
127
+ await chart.updateComplete;
128
+
129
+ // Wait for the chart to be created after data is set
130
+ await new Promise((resolve) => setTimeout(resolve, 100));
131
+
132
+ expect(chart.chart).to.exist;
133
+ const tickCallback = chart.chart.options.scales.y.ticks.callback;
134
+
135
+ // Test edge cases for duration formatting
136
+ expect(tickCallback.call({}, 0, 0, [])).to.equal('0s');
137
+ expect(tickCallback.call({}, 1, 1, [])).to.equal('1s');
138
+ expect(tickCallback.call({}, 59, 2, [])).to.equal('59s');
139
+ expect(tickCallback.call({}, 60, 3, [])).to.equal('1m');
140
+ expect(tickCallback.call({}, 61, 4, [])).to.equal('1m 1s');
141
+ expect(tickCallback.call({}, 3600, 5, [])).to.equal('1h');
142
+ expect(tickCallback.call({}, 3661, 6, [])).to.equal('1h 1m');
143
+ expect(tickCallback.call({}, 86400, 7, [])).to.equal('1d');
144
+ expect(tickCallback.call({}, 90061, 8, [])).to.equal('1d 1h'); // 1 day, 1 hour, 1 minute, 1 second - should show only first two units
145
+ expect(tickCallback.call({}, 604800, 9, [])).to.equal('7d'); // 1 week in seconds
146
+ expect(tickCallback.call({}, 1209600, 10, [])).to.equal('14d'); // 2 weeks in seconds
147
+ });
148
+
149
+ it('respects formatDuration property state', async () => {
150
+ const chart: TembaChart = await getChart();
151
+
152
+ // Test default state
153
+ expect(chart.formatDuration).to.equal(false);
154
+
155
+ chart.data = sampleData;
156
+ await chart.updateComplete;
157
+
158
+ // Test that formatDuration property can be toggled
159
+ chart.formatDuration = true;
160
+ expect(chart.formatDuration).to.equal(true);
161
+
162
+ chart.formatDuration = false;
163
+ expect(chart.formatDuration).to.equal(false);
164
+ });
165
+ });
166
+
167
+ describe('formatDurationFromSeconds', () => {
168
+ it('formats zero correctly', () => {
169
+ expect(formatDurationFromSeconds(0)).to.equal('0s');
170
+ });
171
+
172
+ it('formats seconds only', () => {
173
+ expect(formatDurationFromSeconds(1)).to.equal('1s');
174
+ expect(formatDurationFromSeconds(30)).to.equal('30s');
175
+ expect(formatDurationFromSeconds(59)).to.equal('59s');
176
+ });
177
+
178
+ it('formats minutes and seconds', () => {
179
+ expect(formatDurationFromSeconds(60)).to.equal('1m');
180
+ expect(formatDurationFromSeconds(61)).to.equal('1m 1s');
181
+ expect(formatDurationFromSeconds(90)).to.equal('1m 30s');
182
+ expect(formatDurationFromSeconds(120)).to.equal('2m');
183
+ expect(formatDurationFromSeconds(3599)).to.equal('59m 59s');
184
+ });
185
+
186
+ it('formats hours and minutes', () => {
187
+ expect(formatDurationFromSeconds(3600)).to.equal('1h');
188
+ expect(formatDurationFromSeconds(3661)).to.equal('1h 1m');
189
+ expect(formatDurationFromSeconds(7200)).to.equal('2h');
190
+ expect(formatDurationFromSeconds(68787)).to.equal('19h 6m');
191
+ });
192
+
193
+ it('formats days and hours', () => {
194
+ expect(formatDurationFromSeconds(86400)).to.equal('1d');
195
+ expect(formatDurationFromSeconds(90000)).to.equal('1d 1h');
196
+ expect(formatDurationFromSeconds(958000)).to.equal('11d 2h');
197
+ });
198
+
199
+ it('shows only two most significant units', () => {
200
+ // 1 day, 1 hour, 1 minute, 1 second - should show only "1d 1h"
201
+ expect(formatDurationFromSeconds(90061)).to.equal('1d 1h');
202
+
203
+ // 2 hours, 30 minutes, 45 seconds - should show only "2h 30m"
204
+ expect(formatDurationFromSeconds(9045)).to.equal('2h 30m');
205
+
206
+ // 5 minutes, 30 seconds - should show "5m 30s"
207
+ expect(formatDurationFromSeconds(330)).to.equal('5m 30s');
208
+ });
209
+
210
+ it('handles large durations', () => {
211
+ expect(formatDurationFromSeconds(604800)).to.equal('7d'); // 1 week
212
+ expect(formatDurationFromSeconds(1209600)).to.equal('14d'); // 2 weeks
213
+ expect(formatDurationFromSeconds(2678400)).to.equal('31d'); // ~1 month
214
+ });
55
215
  });