@kirbydesign/designsystem 0.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 (552) hide show
  1. package/README.md +7 -0
  2. package/karma.conf.js +34 -0
  3. package/ng-package.json +32 -0
  4. package/ngcc.config.js +3 -0
  5. package/package.json +27 -0
  6. package/src/index.ts +1 -0
  7. package/src/lib/animation/kirby-animation.ts +12 -0
  8. package/src/lib/components/accordion/accordion-item.component.html +25 -0
  9. package/src/lib/components/accordion/accordion-item.component.scss +64 -0
  10. package/src/lib/components/accordion/accordion-item.component.spec.ts +52 -0
  11. package/src/lib/components/accordion/accordion-item.component.ts +27 -0
  12. package/src/lib/components/accordion/accordion.directive.ts +7 -0
  13. package/src/lib/components/accordion/index.ts +2 -0
  14. package/src/lib/components/angular-component-lib/utils.ts +43 -0
  15. package/src/lib/components/app/app.component.html +3 -0
  16. package/src/lib/components/app/app.component.scss +10 -0
  17. package/src/lib/components/app/app.component.spec.ts +52 -0
  18. package/src/lib/components/app/app.component.ts +50 -0
  19. package/src/lib/components/app/app.module.ts +12 -0
  20. package/src/lib/components/app/index.ts +2 -0
  21. package/src/lib/components/avatar/avatar.component.html +6 -0
  22. package/src/lib/components/avatar/avatar.component.scss +153 -0
  23. package/src/lib/components/avatar/avatar.component.spec.ts +207 -0
  24. package/src/lib/components/avatar/avatar.component.ts +32 -0
  25. package/src/lib/components/badge/badge.component.spec.ts +78 -0
  26. package/src/lib/components/button/button.component.html +1 -0
  27. package/src/lib/components/button/button.component.integration.spec.ts +576 -0
  28. package/src/lib/components/button/button.component.scss +286 -0
  29. package/src/lib/components/button/button.component.spec.ts +404 -0
  30. package/src/lib/components/button/button.component.ts +96 -0
  31. package/src/lib/components/calendar/calendar.component.html +49 -0
  32. package/src/lib/components/calendar/calendar.component.initialization.spec.ts +93 -0
  33. package/src/lib/components/calendar/calendar.component.scss +132 -0
  34. package/src/lib/components/calendar/calendar.component.spec.ts +470 -0
  35. package/src/lib/components/calendar/calendar.component.ts +513 -0
  36. package/src/lib/components/calendar/helpers/calendar-cell.model.ts +7 -0
  37. package/src/lib/components/calendar/helpers/calendar-options.model.ts +10 -0
  38. package/src/lib/components/calendar/helpers/calendar.helper.ts +108 -0
  39. package/src/lib/components/calendar/index.ts +2 -0
  40. package/src/lib/components/calendar/options/calendar-year-navigator-config.ts +4 -0
  41. package/src/lib/components/card/card-footer/card-footer.component.html +3 -0
  42. package/src/lib/components/card/card-footer/card-footer.component.scss +19 -0
  43. package/src/lib/components/card/card-footer/card-footer.component.ts +13 -0
  44. package/src/lib/components/card/card-header/card-header.component.html +5 -0
  45. package/src/lib/components/card/card-header/card-header.component.scss +63 -0
  46. package/src/lib/components/card/card-header/card-header.component.ts +20 -0
  47. package/src/lib/components/card/card.component.html +5 -0
  48. package/src/lib/components/card/card.component.scss +56 -0
  49. package/src/lib/components/card/card.component.spec.ts +81 -0
  50. package/src/lib/components/card/card.component.ts +95 -0
  51. package/src/lib/components/card/index.ts +3 -0
  52. package/src/lib/components/chart/chart-js/chart-js.service.integration.spec.ts +320 -0
  53. package/src/lib/components/chart/chart-js/chart-js.service.spec.ts +1004 -0
  54. package/src/lib/components/chart/chart-js/chart-js.service.ts +445 -0
  55. package/src/lib/components/chart/chart-js/chartjs-plugin-marker/chartjs-plugin-marker.ts +254 -0
  56. package/src/lib/components/chart/chart-js/configured-chart-js.ts +39 -0
  57. package/src/lib/components/chart/chart-js/test-utils.ts +171 -0
  58. package/src/lib/components/chart/chart.component.html +7 -0
  59. package/src/lib/components/chart/chart.component.scss +6 -0
  60. package/src/lib/components/chart/chart.component.spec.ts +188 -0
  61. package/src/lib/components/chart/chart.component.ts +148 -0
  62. package/src/lib/components/chart/chart.module.ts +11 -0
  63. package/src/lib/components/chart/chart.types.ts +34 -0
  64. package/src/lib/components/chart/configs/annotations.config.ts +36 -0
  65. package/src/lib/components/chart/configs/chart-config.service.spec.ts +21 -0
  66. package/src/lib/components/chart/configs/chart-config.service.ts +34 -0
  67. package/src/lib/components/chart/configs/global-defaults.config.ts +50 -0
  68. package/src/lib/components/chart/configs/interaction-functions-extensions.config.ts +19 -0
  69. package/src/lib/components/chart/configs/shared.utils.ts +28 -0
  70. package/src/lib/components/chart/configs/type.config.ts +214 -0
  71. package/src/lib/components/chart/index.ts +5 -0
  72. package/src/lib/components/chart-deprecated/chart-deprecated-helper.ts +25 -0
  73. package/src/lib/components/chart-deprecated/chart-deprecated-type.ts +10 -0
  74. package/src/lib/components/chart-deprecated/chart-deprecated.component.scss +3 -0
  75. package/src/lib/components/chart-deprecated/chart-deprecated.component.spec.ts +271 -0
  76. package/src/lib/components/chart-deprecated/chart-deprecated.component.ts +233 -0
  77. package/src/lib/components/chart-deprecated/index.ts +2 -0
  78. package/src/lib/components/chart-deprecated/options/activitygauge.ts +87 -0
  79. package/src/lib/components/chart-deprecated/options/areaspline.ts +95 -0
  80. package/src/lib/components/chart-deprecated/options/bar.ts +93 -0
  81. package/src/lib/components/chart-deprecated/options/column.ts +101 -0
  82. package/src/lib/components/chart-deprecated/options/donut.ts +118 -0
  83. package/src/lib/components/chart-deprecated/options/timeseries.ts +96 -0
  84. package/src/lib/components/checkbox/checkbox.component.html +10 -0
  85. package/src/lib/components/checkbox/checkbox.component.integration.spec.ts +59 -0
  86. package/src/lib/components/checkbox/checkbox.component.scss +130 -0
  87. package/src/lib/components/checkbox/checkbox.component.spec.ts +205 -0
  88. package/src/lib/components/checkbox/checkbox.component.ts +56 -0
  89. package/src/lib/components/chip/chip.component.html +1 -0
  90. package/src/lib/components/chip/chip.component.integration.spec.ts +111 -0
  91. package/src/lib/components/chip/chip.component.scss +49 -0
  92. package/src/lib/components/chip/chip.component.spec.ts +84 -0
  93. package/src/lib/components/chip/chip.component.ts +16 -0
  94. package/src/lib/components/divider/divider.component.html +1 -0
  95. package/src/lib/components/divider/divider.component.scss +22 -0
  96. package/src/lib/components/divider/divider.component.spec.ts +24 -0
  97. package/src/lib/components/divider/divider.component.ts +12 -0
  98. package/src/lib/components/dropdown/dropdown-popover.component.integration.spec.ts +177 -0
  99. package/src/lib/components/dropdown/dropdown-popover.component.spec.ts +1154 -0
  100. package/src/lib/components/dropdown/dropdown.component.html +42 -0
  101. package/src/lib/components/dropdown/dropdown.component.scss +142 -0
  102. package/src/lib/components/dropdown/dropdown.component.spec.ts +1215 -0
  103. package/src/lib/components/dropdown/dropdown.component.ts +538 -0
  104. package/src/lib/components/dropdown/dropdown.types.ts +10 -0
  105. package/src/lib/components/dropdown/keyboard-handler.service.ts +38 -0
  106. package/src/lib/components/empty-state/empty-state.component.html +10 -0
  107. package/src/lib/components/empty-state/empty-state.component.integration.spec.ts +110 -0
  108. package/src/lib/components/empty-state/empty-state.component.scss +50 -0
  109. package/src/lib/components/empty-state/empty-state.component.spec.ts +40 -0
  110. package/src/lib/components/empty-state/empty-state.component.ts +50 -0
  111. package/src/lib/components/fab-sheet/fab-sheet.component.html +13 -0
  112. package/src/lib/components/fab-sheet/fab-sheet.component.scss +67 -0
  113. package/src/lib/components/fab-sheet/fab-sheet.component.spec.ts +29 -0
  114. package/src/lib/components/fab-sheet/fab-sheet.component.ts +100 -0
  115. package/src/lib/components/flag/flag.component.scss +40 -0
  116. package/src/lib/components/flag/flag.component.spec.ts +152 -0
  117. package/src/lib/components/flag/flag.component.ts +18 -0
  118. package/src/lib/components/form-field/_form-field-inputs.shared.scss +62 -0
  119. package/src/lib/components/form-field/directives/date/date-input.directive.spec.ts +127 -0
  120. package/src/lib/components/form-field/directives/date/date-input.directive.ts +94 -0
  121. package/src/lib/components/form-field/directives/decimal-mask/decimal-mask.directive.spec.ts +231 -0
  122. package/src/lib/components/form-field/directives/decimal-mask/decimal-mask.directive.ts +113 -0
  123. package/src/lib/components/form-field/form-field-message/form-field-message.component.html +1 -0
  124. package/src/lib/components/form-field/form-field-message/form-field-message.component.scss +11 -0
  125. package/src/lib/components/form-field/form-field-message/form-field-message.component.ts +12 -0
  126. package/src/lib/components/form-field/form-field.component.html +39 -0
  127. package/src/lib/components/form-field/form-field.component.scss +43 -0
  128. package/src/lib/components/form-field/form-field.component.spec.ts +521 -0
  129. package/src/lib/components/form-field/form-field.component.ts +141 -0
  130. package/src/lib/components/form-field/index.ts +7 -0
  131. package/src/lib/components/form-field/input/input.component.integration.spec.ts +83 -0
  132. package/src/lib/components/form-field/input/input.component.scss +54 -0
  133. package/src/lib/components/form-field/input/input.component.spec.ts +159 -0
  134. package/src/lib/components/form-field/input/input.component.ts +91 -0
  135. package/src/lib/components/form-field/input-counter/input-counter.component.html +1 -0
  136. package/src/lib/components/form-field/input-counter/input-counter.component.spec.ts +184 -0
  137. package/src/lib/components/form-field/input-counter/input-counter.component.ts +41 -0
  138. package/src/lib/components/form-field/textarea/textarea.component.html +2 -0
  139. package/src/lib/components/form-field/textarea/textarea.component.scss +11 -0
  140. package/src/lib/components/form-field/textarea/textarea.component.spec.ts +100 -0
  141. package/src/lib/components/form-field/textarea/textarea.component.ts +64 -0
  142. package/src/lib/components/grid/breakpoint-helper.service.ts +27 -0
  143. package/src/lib/components/grid/grid-card-configuration.ts +7 -0
  144. package/src/lib/components/grid/grid.component.html +6 -0
  145. package/src/lib/components/grid/grid.component.scss +26 -0
  146. package/src/lib/components/grid/grid.component.ts +106 -0
  147. package/src/lib/components/icon/icon-registry.service.spec.ts +107 -0
  148. package/src/lib/components/icon/icon-registry.service.ts +39 -0
  149. package/src/lib/components/icon/icon-settings.ts +8 -0
  150. package/src/lib/components/icon/icon.component.html +1 -0
  151. package/src/lib/components/icon/icon.component.scss +47 -0
  152. package/src/lib/components/icon/icon.component.spec.ts +254 -0
  153. package/src/lib/components/icon/icon.component.ts +89 -0
  154. package/src/lib/components/icon/icon.module.ts +11 -0
  155. package/src/lib/components/icon/index.ts +5 -0
  156. package/src/lib/components/icon/kirby-icon-settings.ts +65 -0
  157. package/src/lib/components/icon/readme.md +16 -0
  158. package/src/lib/components/icon/selection.json +1776 -0
  159. package/src/lib/components/index.ts +67 -0
  160. package/src/lib/components/item/_item.utils.scss +36 -0
  161. package/src/lib/components/item/index.ts +3 -0
  162. package/src/lib/components/item/item.component.html +16 -0
  163. package/src/lib/components/item/item.component.integration.spec.ts +64 -0
  164. package/src/lib/components/item/item.component.scss +156 -0
  165. package/src/lib/components/item/item.component.spec.ts +85 -0
  166. package/src/lib/components/item/item.component.ts +37 -0
  167. package/src/lib/components/item/item.module.ts +14 -0
  168. package/src/lib/components/item/label/label.component.html +3 -0
  169. package/src/lib/components/item/label/label.component.scss +36 -0
  170. package/src/lib/components/item/label/label.component.spec.ts +23 -0
  171. package/src/lib/components/item/label/label.component.ts +16 -0
  172. package/src/lib/components/item-group/item-group.component.html +1 -0
  173. package/src/lib/components/item-group/item-group.component.scss +3 -0
  174. package/src/lib/components/item-group/item-group.component.spec.ts +42 -0
  175. package/src/lib/components/item-group/item-group.component.ts +10 -0
  176. package/src/lib/components/item-sliding/index.ts +2 -0
  177. package/src/lib/components/item-sliding/item-sliding.component.html +20 -0
  178. package/src/lib/components/item-sliding/item-sliding.component.scss +9 -0
  179. package/src/lib/components/item-sliding/item-sliding.component.spec.ts +174 -0
  180. package/src/lib/components/item-sliding/item-sliding.component.ts +23 -0
  181. package/src/lib/components/item-sliding/item-sliding.shared.scss +18 -0
  182. package/src/lib/components/item-sliding/item-sliding.types.ts +13 -0
  183. package/src/lib/components/list/directives/infinite-scroll.directive.spec.ts +131 -0
  184. package/src/lib/components/list/directives/infinite-scroll.directive.ts +137 -0
  185. package/src/lib/components/list/directives/list-item-color.directive.ts +27 -0
  186. package/src/lib/components/list/directives/scroll.model.ts +5 -0
  187. package/src/lib/components/list/helpers/list-helper.spec.ts +109 -0
  188. package/src/lib/components/list/helpers/list-helper.ts +19 -0
  189. package/src/lib/components/list/index.ts +21 -0
  190. package/src/lib/components/list/list-experimental/list-experimental.component.html +4 -0
  191. package/src/lib/components/list/list-experimental/list-experimental.component.scss +6 -0
  192. package/src/lib/components/list/list-experimental/list-experimental.component.spec.ts +112 -0
  193. package/src/lib/components/list/list-experimental/list-experimental.component.ts +21 -0
  194. package/src/lib/components/list/list-header/list-header.component.html +1 -0
  195. package/src/lib/components/list/list-header/list-header.component.scss +10 -0
  196. package/src/lib/components/list/list-header/list-header.component.spec.ts +24 -0
  197. package/src/lib/components/list/list-header/list-header.component.ts +11 -0
  198. package/src/lib/components/list/list-item/list-item.component.html +39 -0
  199. package/src/lib/components/list/list-item/list-item.component.scss +13 -0
  200. package/src/lib/components/list/list-item/list-item.component.ts +144 -0
  201. package/src/lib/components/list/list-section-header/list-section-header.component.html +3 -0
  202. package/src/lib/components/list/list-section-header/list-section-header.component.spec.ts +24 -0
  203. package/src/lib/components/list/list-section-header/list-section-header.component.ts +13 -0
  204. package/src/lib/components/list/list-swipe-action.ts +2 -0
  205. package/src/lib/components/list/list-swipe-action.type.ts +23 -0
  206. package/src/lib/components/list/list.component.html +65 -0
  207. package/src/lib/components/list/list.component.integration.spec.ts +177 -0
  208. package/src/lib/components/list/list.component.scss +243 -0
  209. package/src/lib/components/list/list.component.spec.ts +219 -0
  210. package/src/lib/components/list/list.component.ts +213 -0
  211. package/src/lib/components/list/list.directive.ts +21 -0
  212. package/src/lib/components/list/list.event.ts +8 -0
  213. package/src/lib/components/list/list.module.ts +47 -0
  214. package/src/lib/components/list/pipes/group-by.pipe.spec.ts +71 -0
  215. package/src/lib/components/list/pipes/group-by.pipe.ts +34 -0
  216. package/src/lib/components/loading-overlay/index.ts +2 -0
  217. package/src/lib/components/loading-overlay/loading-overlay.component.html +8 -0
  218. package/src/lib/components/loading-overlay/loading-overlay.component.scss +37 -0
  219. package/src/lib/components/loading-overlay/loading-overlay.component.ts +12 -0
  220. package/src/lib/components/loading-overlay/loading-overlay.service.ts +44 -0
  221. package/src/lib/components/modal/action-sheet/action-sheet.component.html +24 -0
  222. package/src/lib/components/modal/action-sheet/action-sheet.component.scss +47 -0
  223. package/src/lib/components/modal/action-sheet/action-sheet.component.spec.ts +153 -0
  224. package/src/lib/components/modal/action-sheet/action-sheet.component.ts +27 -0
  225. package/src/lib/components/modal/action-sheet/config/action-sheet-config.ts +8 -0
  226. package/src/lib/components/modal/action-sheet/config/action-sheet-item.ts +4 -0
  227. package/src/lib/components/modal/alert/alert.component.html +29 -0
  228. package/src/lib/components/modal/alert/alert.component.scss +21 -0
  229. package/src/lib/components/modal/alert/alert.component.spec.ts +104 -0
  230. package/src/lib/components/modal/alert/alert.component.ts +67 -0
  231. package/src/lib/components/modal/alert/config/alert-config.ts +19 -0
  232. package/src/lib/components/modal/footer/modal-footer.component.html +3 -0
  233. package/src/lib/components/modal/footer/modal-footer.component.scss +57 -0
  234. package/src/lib/components/modal/footer/modal-footer.component.spec.ts +209 -0
  235. package/src/lib/components/modal/footer/modal-footer.component.ts +17 -0
  236. package/src/lib/components/modal/index.ts +9 -0
  237. package/src/lib/components/modal/modal-wrapper/compact/modal-compact-wrapper.component.html +3 -0
  238. package/src/lib/components/modal/modal-wrapper/compact/modal-compact-wrapper.component.scss +8 -0
  239. package/src/lib/components/modal/modal-wrapper/compact/modal-compact-wrapper.component.ts +79 -0
  240. package/src/lib/components/modal/modal-wrapper/config/drawer-supplementary-action.ts +4 -0
  241. package/src/lib/components/modal/modal-wrapper/config/modal-config.helper.ts +3 -0
  242. package/src/lib/components/modal/modal-wrapper/config/modal-config.ts +22 -0
  243. package/src/lib/components/modal/modal-wrapper/modal-wrapper.component.html +43 -0
  244. package/src/lib/components/modal/modal-wrapper/modal-wrapper.component.scss +159 -0
  245. package/src/lib/components/modal/modal-wrapper/modal-wrapper.component.spec.ts +942 -0
  246. package/src/lib/components/modal/modal-wrapper/modal-wrapper.component.ts +587 -0
  247. package/src/lib/components/modal/modal-wrapper/modal-wrapper.testbuilder.ts +155 -0
  248. package/src/lib/components/modal/services/action-sheet.helper.spec.ts +86 -0
  249. package/src/lib/components/modal/services/action-sheet.helper.ts +47 -0
  250. package/src/lib/components/modal/services/alert.helper.spec.ts +89 -0
  251. package/src/lib/components/modal/services/alert.helper.ts +57 -0
  252. package/src/lib/components/modal/services/modal-animation-builder.service.ts +276 -0
  253. package/src/lib/components/modal/services/modal-navigation.service.spec.ts +543 -0
  254. package/src/lib/components/modal/services/modal-navigation.service.ts +331 -0
  255. package/src/lib/components/modal/services/modal.controller.spec.ts +212 -0
  256. package/src/lib/components/modal/services/modal.controller.ts +194 -0
  257. package/src/lib/components/modal/services/modal.helper.spec.ts +828 -0
  258. package/src/lib/components/modal/services/modal.helper.ts +128 -0
  259. package/src/lib/components/modal/services/modal.interfaces.ts +28 -0
  260. package/src/lib/components/page/index.ts +13 -0
  261. package/src/lib/components/page/page-footer/page-footer.component.html +3 -0
  262. package/src/lib/components/page/page-footer/page-footer.component.scss +23 -0
  263. package/src/lib/components/page/page-footer/page-footer.component.spec.ts +52 -0
  264. package/src/lib/components/page/page-footer/page-footer.component.ts +46 -0
  265. package/src/lib/components/page/page.component.html +101 -0
  266. package/src/lib/components/page/page.component.scss +141 -0
  267. package/src/lib/components/page/page.component.spec.ts +224 -0
  268. package/src/lib/components/page/page.component.ts +415 -0
  269. package/src/lib/components/page/page.module.ts +52 -0
  270. package/src/lib/components/popover/popover.component.scss +26 -0
  271. package/src/lib/components/popover/popover.component.spec.ts +0 -0
  272. package/src/lib/components/popover/popover.component.ts +221 -0
  273. package/src/lib/components/progress-circle/progress-circle-ring.component.scss +22 -0
  274. package/src/lib/components/progress-circle/progress-circle-ring.component.spec.ts +143 -0
  275. package/src/lib/components/progress-circle/progress-circle-ring.component.svg +23 -0
  276. package/src/lib/components/progress-circle/progress-circle-ring.component.ts +42 -0
  277. package/src/lib/components/progress-circle/progress-circle.component.html +11 -0
  278. package/src/lib/components/progress-circle/progress-circle.component.scss +28 -0
  279. package/src/lib/components/progress-circle/progress-circle.component.spec.ts +339 -0
  280. package/src/lib/components/progress-circle/progress-circle.component.ts +96 -0
  281. package/src/lib/components/radio/index.ts +2 -0
  282. package/src/lib/components/radio/radio-group/radio-group.component.html +24 -0
  283. package/src/lib/components/radio/radio-group/radio-group.component.spec.ts +1328 -0
  284. package/src/lib/components/radio/radio-group/radio-group.component.ts +272 -0
  285. package/src/lib/components/radio/radio.component.html +9 -0
  286. package/src/lib/components/radio/radio.component.integration.spec.ts +93 -0
  287. package/src/lib/components/radio/radio.component.scss +133 -0
  288. package/src/lib/components/radio/radio.component.spec.ts +244 -0
  289. package/src/lib/components/radio/radio.component.ts +50 -0
  290. package/src/lib/components/range/range.component.html +16 -0
  291. package/src/lib/components/range/range.component.scss +81 -0
  292. package/src/lib/components/range/range.component.ts +105 -0
  293. package/src/lib/components/reorder-list/index.ts +3 -0
  294. package/src/lib/components/reorder-list/reorder-event.ts +17 -0
  295. package/src/lib/components/reorder-list/reorder-list.component.html +30 -0
  296. package/src/lib/components/reorder-list/reorder-list.component.scss +90 -0
  297. package/src/lib/components/reorder-list/reorder-list.component.spec.ts +123 -0
  298. package/src/lib/components/reorder-list/reorder-list.component.ts +86 -0
  299. package/src/lib/components/router-outlet/index.ts +5 -0
  300. package/src/lib/components/router-outlet/router-outlet.component.html +6 -0
  301. package/src/lib/components/router-outlet/router-outlet.component.scss +13 -0
  302. package/src/lib/components/router-outlet/router-outlet.component.spec.ts +28 -0
  303. package/src/lib/components/router-outlet/router-outlet.component.ts +11 -0
  304. package/src/lib/components/router-outlet/router-outlet.module.ts +12 -0
  305. package/src/lib/components/section-header/_section-header.utils.scss +34 -0
  306. package/src/lib/components/section-header/section-header.component.html +3 -0
  307. package/src/lib/components/section-header/section-header.component.scss +15 -0
  308. package/src/lib/components/section-header/section-header.component.spec.ts +59 -0
  309. package/src/lib/components/section-header/section-header.component.ts +9 -0
  310. package/src/lib/components/section-header/section-header.integration.spec.ts +67 -0
  311. package/src/lib/components/segmented-control/segment-item.ts +18 -0
  312. package/src/lib/components/segmented-control/segmented-control.component.html +39 -0
  313. package/src/lib/components/segmented-control/segmented-control.component.scss +84 -0
  314. package/src/lib/components/segmented-control/segmented-control.component.spec.ts +247 -0
  315. package/src/lib/components/segmented-control/segmented-control.component.ts +96 -0
  316. package/src/lib/components/shared/component-configuration.ts +6 -0
  317. package/src/lib/components/shared/component-loader.directive.ts +42 -0
  318. package/src/lib/components/shared/dynamic-component.ts +3 -0
  319. package/src/lib/components/shared/index.ts +9 -0
  320. package/src/lib/components/shared/resize-observer/resize-observer.factory.ts +20 -0
  321. package/src/lib/components/shared/resize-observer/resize-observer.service.ts +65 -0
  322. package/src/lib/components/shared/resize-observer/types/resize-observer-callback.ts +7 -0
  323. package/src/lib/components/shared/resize-observer/types/resize-observer-entry.ts +14 -0
  324. package/src/lib/components/shared/resize-observer/types/resize-observer.ts +17 -0
  325. package/src/lib/components/slide-button/slide-button.component.html +14 -0
  326. package/src/lib/components/slide-button/slide-button.component.scss +81 -0
  327. package/src/lib/components/slide-button/slide-button.component.shared.scss +12 -0
  328. package/src/lib/components/slide-button/slide-button.component.spec.ts +75 -0
  329. package/src/lib/components/slide-button/slide-button.component.ts +81 -0
  330. package/src/lib/components/slides/slides.component.spec.ts +76 -0
  331. package/src/lib/components/slides/slides.component.ts +57 -0
  332. package/src/lib/components/spinner/index.ts +2 -0
  333. package/src/lib/components/spinner/spinner.component.html +4 -0
  334. package/src/lib/components/spinner/spinner.component.scss +37 -0
  335. package/src/lib/components/spinner/spinner.component.ts +13 -0
  336. package/src/lib/components/spinner/spinner.module.ts +12 -0
  337. package/src/lib/components/stock-chart-deprecated/index.ts +2 -0
  338. package/src/lib/components/stock-chart-deprecated/options/stock-chart-deprecated-options.ts +220 -0
  339. package/src/lib/components/stock-chart-deprecated/stock-chart-deprecated.component.scss +3 -0
  340. package/src/lib/components/stock-chart-deprecated/stock-chart-deprecated.component.spec.ts +32 -0
  341. package/src/lib/components/stock-chart-deprecated/stock-chart-deprecated.component.ts +92 -0
  342. package/src/lib/components/tabs/index.ts +5 -0
  343. package/src/lib/components/tabs/tab-button/tab-button.component.html +13 -0
  344. package/src/lib/components/tabs/tab-button/tab-button.component.scss +48 -0
  345. package/src/lib/components/tabs/tab-button/tab-button.component.spec.ts +24 -0
  346. package/src/lib/components/tabs/tab-button/tab-button.component.ts +47 -0
  347. package/src/lib/components/tabs/tab-button/tab-button.events.ts +1 -0
  348. package/src/lib/components/tabs/tabs.component.html +5 -0
  349. package/src/lib/components/tabs/tabs.component.scss +52 -0
  350. package/src/lib/components/tabs/tabs.component.spec.ts +125 -0
  351. package/src/lib/components/tabs/tabs.component.ts +21 -0
  352. package/src/lib/components/tabs/tabs.module.ts +16 -0
  353. package/src/lib/components/tabs/tabs.service.ts +17 -0
  354. package/src/lib/components/toast/config/toast-config.ts +8 -0
  355. package/src/lib/components/toast/index.ts +2 -0
  356. package/src/lib/components/toast/services/toast.controller.ts +18 -0
  357. package/src/lib/components/toast/services/toast.helper.spec.ts +89 -0
  358. package/src/lib/components/toast/services/toast.helper.ts +39 -0
  359. package/src/lib/components/toggle/toggle.component.html +5 -0
  360. package/src/lib/components/toggle/toggle.component.scss +5 -0
  361. package/src/lib/components/toggle/toggle.component.spec.ts +62 -0
  362. package/src/lib/components/toggle/toggle.component.ts +17 -0
  363. package/src/lib/components/toggle-button/index.ts +2 -0
  364. package/src/lib/components/toggle-button/toggle-button.component.html +6 -0
  365. package/src/lib/components/toggle-button/toggle-button.component.spec.ts +23 -0
  366. package/src/lib/components/toggle-button/toggle-button.component.ts +24 -0
  367. package/src/lib/components/toggle-button/toggle-button.module.ts +13 -0
  368. package/src/lib/components/web-component-proxies.component.ts +25 -0
  369. package/src/lib/custom-elements-initializer.ts +19 -0
  370. package/src/lib/directives/element-as-button/element-as-button.directive.spec.ts +31 -0
  371. package/src/lib/directives/element-as-button/element-as-button.directive.ts +26 -0
  372. package/src/lib/directives/fit-heading/fit-heading.directive.ts +131 -0
  373. package/src/lib/directives/fit-heading/fit-heading.module.ts +9 -0
  374. package/src/lib/directives/index.ts +9 -0
  375. package/src/lib/directives/key-handler/key-handler.directive.ts +20 -0
  376. package/src/lib/directives/modal-router-link/modal-router-link.directive.ts +24 -0
  377. package/src/lib/directives/theme-color/theme-color.directive.ts +117 -0
  378. package/src/lib/helpers/color-helper.styles.ts +193 -0
  379. package/src/lib/helpers/color-helper.ts +1 -0
  380. package/src/lib/helpers/deep-copy.ts +13 -0
  381. package/src/lib/helpers/design-token-helper.styles.ts +78 -0
  382. package/src/lib/helpers/design-token-helper.ts +6 -0
  383. package/src/lib/helpers/element-has-ancestor.ts +28 -0
  384. package/src/lib/helpers/index.ts +8 -0
  385. package/src/lib/helpers/line-clamp-helper.ts +28 -0
  386. package/src/lib/helpers/merge-deep.spec.ts +11 -0
  387. package/src/lib/helpers/merge-deep.ts +41 -0
  388. package/src/lib/helpers/platform.service.ts +22 -0
  389. package/src/lib/helpers/string-helper.ts +6 -0
  390. package/src/lib/helpers/theme-color.type.ts +1 -0
  391. package/src/lib/helpers/unique-id-generator.helper.spec.ts +58 -0
  392. package/src/lib/helpers/unique-id-generator.helper.ts +19 -0
  393. package/src/lib/icons/svg/QR.svg +7 -0
  394. package/src/lib/icons/svg/accounts-outline.svg +9 -0
  395. package/src/lib/icons/svg/accounts.svg +9 -0
  396. package/src/lib/icons/svg/add.svg +7 -0
  397. package/src/lib/icons/svg/arrow-back.svg +7 -0
  398. package/src/lib/icons/svg/arrow-down.svg +7 -0
  399. package/src/lib/icons/svg/arrow-more.svg +7 -0
  400. package/src/lib/icons/svg/arrow-up.svg +7 -0
  401. package/src/lib/icons/svg/attach.svg +7 -0
  402. package/src/lib/icons/svg/backspace.svg +9 -0
  403. package/src/lib/icons/svg/calendar.svg +7 -0
  404. package/src/lib/icons/svg/camera.svg +9 -0
  405. package/src/lib/icons/svg/checkbox-outline.svg +7 -0
  406. package/src/lib/icons/svg/checkbox.svg +7 -0
  407. package/src/lib/icons/svg/checkmark-selected.svg +3 -0
  408. package/src/lib/icons/svg/clock.svg +7 -0
  409. package/src/lib/icons/svg/close.svg +7 -0
  410. package/src/lib/icons/svg/cog.svg +7 -0
  411. package/src/lib/icons/svg/copy.svg +9 -0
  412. package/src/lib/icons/svg/edit.svg +7 -0
  413. package/src/lib/icons/svg/filter.svg +7 -0
  414. package/src/lib/icons/svg/flag.svg +9 -0
  415. package/src/lib/icons/svg/flash-off.svg +7 -0
  416. package/src/lib/icons/svg/flash.svg +7 -0
  417. package/src/lib/icons/svg/foreign-payment.svg +7 -0
  418. package/src/lib/icons/svg/help.svg +7 -0
  419. package/src/lib/icons/svg/home.svg +5 -0
  420. package/src/lib/icons/svg/inbox-outline.svg +9 -0
  421. package/src/lib/icons/svg/inbox.svg +9 -0
  422. package/src/lib/icons/svg/information.svg +9 -0
  423. package/src/lib/icons/svg/investment.svg +5 -0
  424. package/src/lib/icons/svg/kirby.svg +3 -0
  425. package/src/lib/icons/svg/link.svg +7 -0
  426. package/src/lib/icons/svg/log-out.svg +7 -0
  427. package/src/lib/icons/svg/menu-outline.svg +9 -0
  428. package/src/lib/icons/svg/menu.svg +9 -0
  429. package/src/lib/icons/svg/misc.svg +5 -0
  430. package/src/lib/icons/svg/moneybag.svg +7 -0
  431. package/src/lib/icons/svg/more.svg +5 -0
  432. package/src/lib/icons/svg/move.svg +7 -0
  433. package/src/lib/icons/svg/payment-card.svg +6 -0
  434. package/src/lib/icons/svg/pension.svg +7 -0
  435. package/src/lib/icons/svg/person-outline.svg +9 -0
  436. package/src/lib/icons/svg/person.svg +9 -0
  437. package/src/lib/icons/svg/remove.svg +6 -0
  438. package/src/lib/icons/svg/reorder.svg +9 -0
  439. package/src/lib/icons/svg/search.svg +7 -0
  440. package/src/lib/icons/svg/share.svg +7 -0
  441. package/src/lib/icons/svg/sort.svg +7 -0
  442. package/src/lib/icons/svg/support.svg +9 -0
  443. package/src/lib/icons/svg/swap.svg +7 -0
  444. package/src/lib/icons/svg/trash.svg +7 -0
  445. package/src/lib/icons/svg/unsubscribe.svg +7 -0
  446. package/src/lib/icons/svg/verify.svg +7 -0
  447. package/src/lib/icons/svg/warning.svg +7 -0
  448. package/src/lib/icons/svg/write-message.svg +7 -0
  449. package/src/lib/index.ts +8 -0
  450. package/src/lib/kirby.module.spec.ts +20 -0
  451. package/src/lib/kirby.module.ts +219 -0
  452. package/src/lib/polyfills/intersection-observer-polyfill-loader.js +14 -0
  453. package/src/lib/polyfills/intersection-observer-polyfill-loader.min.js +1 -0
  454. package/src/lib/polyfills/intersection-observer-polyfill.js +2 -0
  455. package/src/lib/polyfills/intersection-observer-polyfill.min.js +1 -0
  456. package/src/lib/polyfills/resize-observer-polyfill-loader.js +14 -0
  457. package/src/lib/polyfills/resize-observer-polyfill-loader.min.js +1 -0
  458. package/src/lib/polyfills/resize-observer-polyfill.js +2 -0
  459. package/src/lib/polyfills/resize-observer-polyfill.min.js +1 -0
  460. package/src/lib/scss/link.spec.ts +47 -0
  461. package/src/lib/scss/scss-helper.ts +8 -0
  462. package/src/lib/scss/typography.spec.ts +241 -0
  463. package/src/lib/testing/element-css-custom-matchers.d.ts +7 -0
  464. package/src/lib/testing/element-css-custom-matchers.ts +139 -0
  465. package/src/lib/testing/styles.scss +13 -0
  466. package/src/lib/testing/test-helper.ts +158 -0
  467. package/src/lib/types/index.ts +1 -0
  468. package/src/lib/types/window-ref.ts +10 -0
  469. package/src/test.ts +34 -0
  470. package/src/typings.test.d.ts +1 -0
  471. package/testing-base/ng-package.json +3 -0
  472. package/testing-base/src/lib/components/index.ts +66 -0
  473. package/testing-base/src/lib/components/mock.accordion-item.component.ts +21 -0
  474. package/testing-base/src/lib/components/mock.action-sheet.component.ts +27 -0
  475. package/testing-base/src/lib/components/mock.app.component.ts +18 -0
  476. package/testing-base/src/lib/components/mock.avatar.component.ts +38 -0
  477. package/testing-base/src/lib/components/mock.badge.component.ts +20 -0
  478. package/testing-base/src/lib/components/mock.button.component.ts +26 -0
  479. package/testing-base/src/lib/components/mock.calendar.component.ts +36 -0
  480. package/testing-base/src/lib/components/mock.card-footer.component.ts +18 -0
  481. package/testing-base/src/lib/components/mock.card-header.component.ts +23 -0
  482. package/testing-base/src/lib/components/mock.card.component.ts +25 -0
  483. package/testing-base/src/lib/components/mock.chart-deprecated.component.ts +28 -0
  484. package/testing-base/src/lib/components/mock.chart.component.ts +36 -0
  485. package/testing-base/src/lib/components/mock.checkbox.component.ts +26 -0
  486. package/testing-base/src/lib/components/mock.chip.component.ts +21 -0
  487. package/testing-base/src/lib/components/mock.divider.component.ts +20 -0
  488. package/testing-base/src/lib/components/mock.dropdown.component.ts +32 -0
  489. package/testing-base/src/lib/components/mock.empty-state.component.ts +23 -0
  490. package/testing-base/src/lib/components/mock.fab-sheet.component.ts +21 -0
  491. package/testing-base/src/lib/components/mock.flag.component.ts +21 -0
  492. package/testing-base/src/lib/components/mock.form-field-message.component.ts +21 -0
  493. package/testing-base/src/lib/components/mock.form-field.component.ts +23 -0
  494. package/testing-base/src/lib/components/mock.grid.component.ts +21 -0
  495. package/testing-base/src/lib/components/mock.icon.component.ts +22 -0
  496. package/testing-base/src/lib/components/mock.input-counter.component.ts +24 -0
  497. package/testing-base/src/lib/components/mock.input.component.ts +29 -0
  498. package/testing-base/src/lib/components/mock.item-group.component.ts +18 -0
  499. package/testing-base/src/lib/components/mock.item-sliding.component.ts +21 -0
  500. package/testing-base/src/lib/components/mock.item.component.ts +24 -0
  501. package/testing-base/src/lib/components/mock.label.component.ts +20 -0
  502. package/testing-base/src/lib/components/mock.list-experimental.component.ts +18 -0
  503. package/testing-base/src/lib/components/mock.list-header.component.ts +18 -0
  504. package/testing-base/src/lib/components/mock.list-item.component.ts +33 -0
  505. package/testing-base/src/lib/components/mock.list-section-header.component.ts +20 -0
  506. package/testing-base/src/lib/components/mock.list.component.ts +39 -0
  507. package/testing-base/src/lib/components/mock.loading-overlay.component.ts +21 -0
  508. package/testing-base/src/lib/components/mock.modal-footer.component.ts +21 -0
  509. package/testing-base/src/lib/components/mock.page-footer.component.ts +20 -0
  510. package/testing-base/src/lib/components/mock.page.component.ts +152 -0
  511. package/testing-base/src/lib/components/mock.popover.component.ts +22 -0
  512. package/testing-base/src/lib/components/mock.progress-circle.component.ts +22 -0
  513. package/testing-base/src/lib/components/mock.radio-group.component.ts +27 -0
  514. package/testing-base/src/lib/components/mock.radio.component.ts +23 -0
  515. package/testing-base/src/lib/components/mock.range.component.ts +35 -0
  516. package/testing-base/src/lib/components/mock.reorder-list.component.ts +25 -0
  517. package/testing-base/src/lib/components/mock.router-outlet.component.ts +20 -0
  518. package/testing-base/src/lib/components/mock.section-header.component.ts +18 -0
  519. package/testing-base/src/lib/components/mock.segmented-control.component.ts +29 -0
  520. package/testing-base/src/lib/components/mock.slide-button.component.ts +23 -0
  521. package/testing-base/src/lib/components/mock.slides.component.ts +29 -0
  522. package/testing-base/src/lib/components/mock.spinner.component.ts +18 -0
  523. package/testing-base/src/lib/components/mock.tab-button.component.ts +21 -0
  524. package/testing-base/src/lib/components/mock.tabs.component.ts +18 -0
  525. package/testing-base/src/lib/components/mock.textarea.component.ts +26 -0
  526. package/testing-base/src/lib/components/mock.toggle-button.component.ts +21 -0
  527. package/testing-base/src/lib/components/mock.toggle.component.ts +22 -0
  528. package/testing-base/src/lib/components/mock.web-component-proxies.component.ts +32 -0
  529. package/testing-base/src/lib/directives/index.ts +3 -0
  530. package/testing-base/src/lib/directives/mock.accordion.directive.ts +15 -0
  531. package/testing-base/src/lib/directives/mock.fit-heading.directive.ts +19 -0
  532. package/testing-base/src/lib/directives/mock.theme-color.directive.ts +19 -0
  533. package/testing-base/src/lib/index.ts +4 -0
  534. package/testing-base/src/lib/kirby-testing-base.module.ts +10 -0
  535. package/testing-base/src/lib/mock-components.ts +133 -0
  536. package/testing-base/src/lib/mock-directives.ts +9 -0
  537. package/testing-base/src/public_api.ts +1 -0
  538. package/testing-base/src/tsconfig.json +9 -0
  539. package/testing-jasmine/ng-package.json +3 -0
  540. package/testing-jasmine/src/lib/kirby-testing.module.ts +14 -0
  541. package/testing-jasmine/src/lib/mock-providers.ts +68 -0
  542. package/testing-jasmine/src/public_api.ts +1 -0
  543. package/testing-jasmine/src/tsconfig.json +10 -0
  544. package/testing-jest/ng-package.json +3 -0
  545. package/testing-jest/src/lib/kirby-testing.module.ts +14 -0
  546. package/testing-jest/src/lib/mock-providers.ts +76 -0
  547. package/testing-jest/src/public_api.ts +1 -0
  548. package/testing-jest/src/tsconfig.json +10 -0
  549. package/tsconfig.json +17 -0
  550. package/tsconfig.lib.json +23 -0
  551. package/tsconfig.spec.json +9 -0
  552. package/tslint.json +10 -0
@@ -0,0 +1,1328 @@
1
+ import {
2
+ FormControl,
3
+ FormGroup,
4
+ FormsModule,
5
+ ReactiveFormsModule,
6
+ Validators,
7
+ } from '@angular/forms';
8
+ import { IonRadioGroup } from '@ionic/angular';
9
+ import { createHostFactory, SpectatorHost } from '@ngneat/spectator';
10
+ import { Observable, of } from 'rxjs';
11
+
12
+ import { DesignTokenHelper } from '@kirbydesign/core';
13
+
14
+ import { TestHelper } from '../../../testing/test-helper';
15
+ import { ListItemTemplateDirective } from '../../list/list.directive';
16
+ import { RadioComponent } from '../radio.component';
17
+
18
+ import { RadioGroupComponent } from './radio-group.component';
19
+
20
+ const { getColor } = DesignTokenHelper;
21
+
22
+ describe('RadioGroupComponent', () => {
23
+ const createHost = createHostFactory({
24
+ component: RadioGroupComponent,
25
+ declarations: [RadioComponent, ListItemTemplateDirective],
26
+ imports: [TestHelper.ionicModuleForTest, FormsModule, ReactiveFormsModule],
27
+ });
28
+
29
+ describe('with plain binding', () => {
30
+ type defaultDataType = { text: string; value: number; disabled?: boolean };
31
+ type booleanDataType = { text: string; value: boolean; disabled?: boolean };
32
+
33
+ let ionRadioGroup: IonRadioGroup;
34
+ let ionRadioElements: HTMLIonRadioElement[];
35
+ let radios: RadioComponent[];
36
+
37
+ function radioChecked(index: number): boolean {
38
+ return ionRadioElements[index].getAttribute('aria-checked') === 'true';
39
+ }
40
+
41
+ const defaultSelectedIndex = 1;
42
+
43
+ const textItems: string[] = ['Larry', 'Curly', 'Moe'];
44
+
45
+ const dataItems: defaultDataType[] = [
46
+ { text: 'Larry', value: 1 },
47
+ { text: 'Curly', value: 2 },
48
+ { text: 'Moe', value: 3 },
49
+ ];
50
+
51
+ const enum DataScenarioTypes {
52
+ TEXT = 'plain text',
53
+ DATA = 'data items with default property names',
54
+ BOOLEAN_DATA = 'boolean data items with default property names',
55
+ ASYNC_DATA = 'async data items with default property names',
56
+ }
57
+
58
+ const enum TemplateScenarioTypes {
59
+ DEFAULT = 'default item template',
60
+ CUSTOM = 'custom item template',
61
+ SLOTTED = 'slotted radios',
62
+ }
63
+
64
+ const booleanDataItems: booleanDataType[] = [
65
+ { text: 'Larry', value: true },
66
+ { text: 'Curly', value: false },
67
+ ];
68
+
69
+ const dataScenarios = [
70
+ {
71
+ type: DataScenarioTypes.TEXT,
72
+ items: textItems,
73
+ selected: textItems[defaultSelectedIndex],
74
+ },
75
+ {
76
+ type: DataScenarioTypes.DATA,
77
+ items: dataItems,
78
+ selected: dataItems[defaultSelectedIndex],
79
+ },
80
+ {
81
+ type: DataScenarioTypes.ASYNC_DATA,
82
+ items: dataItems,
83
+ selected: dataItems[defaultSelectedIndex],
84
+ },
85
+ {
86
+ type: DataScenarioTypes.BOOLEAN_DATA,
87
+ items: booleanDataItems,
88
+ selected: booleanDataItems[defaultSelectedIndex],
89
+ },
90
+ ];
91
+
92
+ dataScenarios.forEach((dataScenario) => {
93
+ describe(`when bound to ${dataScenario.type}`, () => {
94
+ const itemsTemplateVar =
95
+ dataScenario.type === DataScenarioTypes.ASYNC_DATA ? 'items$ | async' : 'items';
96
+ const templateScenarios = [
97
+ {
98
+ type: TemplateScenarioTypes.DEFAULT,
99
+ template: `<kirby-radio-group [(value)]="selected" [items]="${itemsTemplateVar}"></kirby-radio-group>`,
100
+ },
101
+ {
102
+ type: TemplateScenarioTypes.SLOTTED,
103
+ template: `<kirby-radio-group [(value)]="selected">
104
+ <kirby-radio *ngFor="let item of ${itemsTemplateVar}" [value]="item" [text]="item.text || item"></kirby-radio>
105
+ </kirby-radio-group>`,
106
+ },
107
+ {
108
+ type: TemplateScenarioTypes.CUSTOM,
109
+ template: `<kirby-radio-group [(value)]="selected" [items]="${itemsTemplateVar}">
110
+ <div style="display: flex; flex-direction: row;"
111
+ *kirbyListItemTemplate="let item; let selected = selected; let index = index"
112
+ [attr.is-selected]="selected"
113
+ [attr.index]="index"
114
+ class="item-template">
115
+ <kirby-radio
116
+ [value]="item"
117
+ [text]="item.text"
118
+ [disabled]="item.disabled">
119
+ </kirby-radio>
120
+ <p class="selected">{{selected}}</p>
121
+ <p class="index">{{index}}</p>
122
+ </div>
123
+ </kirby-radio-group>`,
124
+ },
125
+ ];
126
+ templateScenarios.forEach((templateScenario) => {
127
+ describe(`with ${templateScenario.type}`, () => {
128
+ let spectator: SpectatorHost<
129
+ RadioGroupComponent,
130
+ {
131
+ items: string[] | booleanDataType[] | defaultDataType[];
132
+ items$: Observable<string[] | booleanDataType[] | defaultDataType[]>;
133
+ selected: string | booleanDataType | defaultDataType;
134
+ }
135
+ >;
136
+
137
+ describe('and no pre-selected item', () => {
138
+ beforeEach(async () => {
139
+ spectator = createHost(templateScenario.template, {
140
+ hostProps: {
141
+ items: dataScenario.items,
142
+ items$: of(null),
143
+ selected: null,
144
+ },
145
+ });
146
+
147
+ // Set items$ observable after creation, to ensure async rendering:
148
+ spectator.setHostInput('items$', of(dataScenario.items));
149
+
150
+ ionRadioGroup = spectator.query(IonRadioGroup);
151
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
152
+ await TestHelper.whenReady(ionRadioGroupElement);
153
+
154
+ radios = spectator.queryAll(RadioComponent);
155
+ ionRadioElements = spectator.queryAll('ion-radio');
156
+ expect(radios).toHaveLength(dataScenario.items.length);
157
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
158
+ await TestHelper.whenReady(ionRadioElements);
159
+ });
160
+
161
+ describe('selection', () => {
162
+ beforeEach(async () => {
163
+ // Assert initial state:
164
+ expect(ionRadioGroup.value).toBeNull();
165
+ // Assert initial state of radios:
166
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
167
+ ionRadioElements.forEach((_, index) => {
168
+ expect(radioChecked(index)).toBeFalse();
169
+ });
170
+ });
171
+
172
+ it('should not set the value of ion-radio-group', () => {
173
+ expect(ionRadioGroup.value).toBeNull();
174
+ });
175
+
176
+ it('should not have any selected radio', () => {
177
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
178
+ ionRadioElements.forEach((_, index) => {
179
+ expect(radioChecked(index)).toBeFalse();
180
+ });
181
+ });
182
+
183
+ it('should not have selected index', () => {
184
+ expect(spectator.component.selectedIndex).toBe(-1);
185
+ });
186
+
187
+ it('should set the value to the corresponding data item when clicking a radio item', () => {
188
+ spectator.click(ionRadioElements[0]);
189
+ expect(spectator.component.value).toEqual(dataScenario.items[0]);
190
+ });
191
+
192
+ it('should update the bound field when clicking a radio item', () => {
193
+ spectator.click(ionRadioElements[0]);
194
+ expect(spectator.hostComponent.selected).toEqual(dataScenario.items[0]);
195
+ });
196
+
197
+ it('should emit change event when clicking a radio item', () => {
198
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
199
+ spectator.click(ionRadioElements[0]);
200
+ expect(onChangeSpy).toHaveBeenCalledTimes(1);
201
+ expect(onChangeSpy).toHaveBeenCalledWith(dataScenario.items[0]);
202
+ });
203
+
204
+ it('should update the value of ion-radio-group when the bound field is updated', () => {
205
+ spectator.setHostInput('selected', dataScenario.items[2]);
206
+ expect(ionRadioGroup.value).toEqual(dataScenario.items[2]);
207
+ });
208
+
209
+ it('should update the selected radio when the bound field is updated', async () => {
210
+ spectator.setHostInput('selected', dataScenario.items[0]);
211
+ // Wait for radio checked attribute to be updated;
212
+ await TestHelper.whenTrue(() => radioChecked(0));
213
+
214
+ expect(dataScenario.items).not.toHaveLength(0);
215
+ ionRadioElements.forEach((_, index) => {
216
+ if (index === 0) {
217
+ expect(radioChecked(index)).toBeTrue();
218
+ } else {
219
+ expect(radioChecked(index)).toBeFalse();
220
+ }
221
+ });
222
+ });
223
+
224
+ it('should not emit change event when the bound field is updated', () => {
225
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
226
+ spectator.setHostInput('selected', dataScenario.items[2]);
227
+ expect(onChangeSpy).not.toHaveBeenCalled();
228
+ });
229
+ });
230
+
231
+ describe('focus', () => {
232
+ it('should focus the first radio when none is selected', async () => {
233
+ const firstRadio = ionRadioElements[0];
234
+ // Wait for tabindex to be rendered:
235
+ await TestHelper.whenTrue(() => firstRadio.tabIndex === 0);
236
+
237
+ spectator.component.focus();
238
+
239
+ expect(document.activeElement).toEqual(firstRadio);
240
+ });
241
+ });
242
+ });
243
+
244
+ describe('and pre-selected item', () => {
245
+ beforeEach(async () => {
246
+ spectator = createHost(templateScenario.template, {
247
+ hostProps: {
248
+ items: dataScenario.items,
249
+ items$: of(null),
250
+ selected: dataScenario.selected,
251
+ },
252
+ });
253
+
254
+ // Set items$ observable after creation, to ensure async rendering:
255
+ spectator.setHostInput('items$', of(dataScenario.items));
256
+
257
+ ionRadioGroup = spectator.query(IonRadioGroup);
258
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
259
+ await TestHelper.whenReady(ionRadioGroupElement);
260
+
261
+ radios = spectator.queryAll(RadioComponent);
262
+ ionRadioElements = spectator.queryAll('ion-radio');
263
+ expect(radios).toHaveLength(dataScenario.items.length);
264
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
265
+ await TestHelper.whenReady(ionRadioElements);
266
+ });
267
+
268
+ it('should render all items', () => {
269
+ expect(radios).toHaveLength(dataScenario.items.length);
270
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
271
+ });
272
+
273
+ if (templateScenario.type === TemplateScenarioTypes.DEFAULT) {
274
+ it('should set the text of each radio to the corresponding text item / item´s `text` property', () => {
275
+ const expectedTexts = ['Larry', 'Curly', 'Moe'];
276
+ expect(radios.length).toEqual(dataScenario.items.length);
277
+ radios.forEach((radio, index) => {
278
+ expect(radio.text).toEqual(expectedTexts[index]);
279
+ });
280
+ });
281
+
282
+ it('should set the value of each radio to the corresponding data item', () => {
283
+ expect(radios.length).toEqual(dataScenario.items.length);
284
+ radios.forEach((radio, index) => {
285
+ expect(radio.value).toEqual(dataScenario.items[index]);
286
+ });
287
+ });
288
+ }
289
+
290
+ if (templateScenario.type === TemplateScenarioTypes.CUSTOM) {
291
+ it('should set template variable `selected` for each item', () => {
292
+ const templateWrappers = spectator.queryAll('div.item-template');
293
+ const expectedIsSelectedValues = ['false', 'true', 'false'];
294
+ expect(templateWrappers.length).toEqual(dataScenario.items.length);
295
+ templateWrappers.forEach((templateWrapper, index) => {
296
+ expect(templateWrapper).toHaveAttribute(
297
+ 'is-selected',
298
+ expectedIsSelectedValues[index]
299
+ );
300
+ });
301
+ });
302
+
303
+ it('should set template variable `index` for each item', () => {
304
+ const templateWrappers = spectator.queryAll('div.item-template');
305
+ expect(templateWrappers).toHaveLength(dataScenario.items.length);
306
+ templateWrappers.forEach((templateWrapper, index) => {
307
+ expect(templateWrapper).toHaveAttribute('index', String(index));
308
+ });
309
+ });
310
+ }
311
+
312
+ describe('selection', () => {
313
+ beforeEach(async () => {
314
+ // Assert initial state:
315
+ expect(ionRadioGroup.value).toBe(dataScenario.selected);
316
+ // Assert initial state of radios:
317
+ ionRadioElements.forEach((_, index) => {
318
+ if (index === defaultSelectedIndex) {
319
+ expect(radioChecked(index)).toBeTrue();
320
+ } else {
321
+ expect(radioChecked(index)).toBeFalse();
322
+ }
323
+ });
324
+ });
325
+
326
+ it('should set the value of ion-radio-group to the corresponding selected data item', () => {
327
+ expect(ionRadioGroup.value).toBe(dataScenario.selected);
328
+ });
329
+
330
+ it('should have selected radio corresponding to the selected data item', () => {
331
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
332
+ ionRadioElements.forEach((_, index) => {
333
+ if (index === defaultSelectedIndex) {
334
+ expect(radioChecked(index)).toBeTrue();
335
+ } else {
336
+ expect(radioChecked(index)).toBeFalse();
337
+ }
338
+ });
339
+ });
340
+
341
+ it('should have selected index corresponding to the selected data item', () => {
342
+ expect(spectator.component.selectedIndex).toBe(defaultSelectedIndex);
343
+ });
344
+
345
+ it('should set the value to the corresponding data item when clicking a radio item', () => {
346
+ spectator.click(ionRadioElements[0]);
347
+ expect(spectator.component.value).toEqual(dataScenario.items[0]);
348
+ });
349
+
350
+ it('should update the bound field when clicking a radio item', () => {
351
+ spectator.click(ionRadioElements[0]);
352
+ expect(spectator.hostComponent.selected).toEqual(dataScenario.items[0]);
353
+ });
354
+
355
+ it('should emit change event when clicking a radio item', () => {
356
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
357
+ spectator.click(ionRadioElements[0]);
358
+ expect(onChangeSpy).toHaveBeenCalledTimes(1);
359
+ expect(onChangeSpy).toHaveBeenCalledWith(dataScenario.items[0]);
360
+ });
361
+
362
+ it('should update the value of ion-radio-group when the bound field is updated', () => {
363
+ spectator.setHostInput('selected', dataScenario.items[2]);
364
+ expect(ionRadioGroup.value).toEqual(dataScenario.items[2]);
365
+ });
366
+
367
+ it('should update the selected radio when the bound field is updated', async () => {
368
+ const newSelectedItemIndex = 0;
369
+ spectator.setHostInput('selected', dataScenario.items[newSelectedItemIndex]);
370
+ // Wait for radio checked attribute to be updated;
371
+ await TestHelper.whenTrue(() => radioChecked(newSelectedItemIndex));
372
+
373
+ expect(ionRadioElements.length).not.toBe(0);
374
+ ionRadioElements.forEach((_, index) => {
375
+ if (index === newSelectedItemIndex) {
376
+ expect(radioChecked(index)).toBeTrue();
377
+ } else {
378
+ expect(radioChecked(index)).toBeFalse();
379
+ }
380
+ });
381
+ });
382
+
383
+ it('should not emit change event when the bound field is updated', () => {
384
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
385
+ spectator.setHostInput('selected', dataScenario.items[2]);
386
+ expect(onChangeSpy).not.toHaveBeenCalled();
387
+ });
388
+ });
389
+
390
+ describe('enablement', () => {
391
+ it('should not disable the radio items by default', () => {
392
+ radios.forEach((each) => expect(each.disabled).toBeUndefined());
393
+ });
394
+
395
+ it('should disable the radio items when the kirby-radio-group is disabled', () => {
396
+ spectator.setInput('disabled', true);
397
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
398
+ });
399
+
400
+ it('should re-enable the radio items when the kirby-radio-group is enabled', () => {
401
+ spectator.setInput('disabled', true);
402
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
403
+
404
+ spectator.setInput('disabled', false);
405
+ radios.forEach((each) => expect(each.disabled).toBeUndefined());
406
+ });
407
+
408
+ it('should disable the radio items if items are set after the kirby-radio-group is disabled', async () => {
409
+ spectator.setHostInput('items', null);
410
+ spectator.setInput('disabled', true);
411
+ await TestHelper.waitForTimeout(); // Wait a tick
412
+
413
+ spectator.setHostInput('items', dataScenario.items);
414
+ await TestHelper.waitForTimeout(); // Wait a tick
415
+
416
+ radios = spectator.queryAll(RadioComponent);
417
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
418
+ });
419
+
420
+ if (
421
+ dataScenario.type !== DataScenarioTypes.TEXT &&
422
+ templateScenario.type !== TemplateScenarioTypes.SLOTTED
423
+ ) {
424
+ describe('when data items has disabled property', () => {
425
+ beforeEach(() => {
426
+ const itemsWithDisabledProperty = dataItems.map((item) => ({ ...item }));
427
+ itemsWithDisabledProperty[1].disabled = false;
428
+ itemsWithDisabledProperty[2].disabled = true;
429
+
430
+ spectator.setHostInput('items', itemsWithDisabledProperty);
431
+ spectator.setHostInput('items$', of(itemsWithDisabledProperty));
432
+ radios = spectator.queryAll(RadioComponent);
433
+ });
434
+
435
+ it('should disable radio when the corresponding data item´s `disabled` property is true', () => {
436
+ expect(radios[0].disabled).toBeUndefined();
437
+ expect(radios[1].disabled).toBeFalse();
438
+ expect(radios[2].disabled).toBeTrue();
439
+ });
440
+
441
+ it('should disable the radio items when the kirby-radio-group is disabled', () => {
442
+ spectator.setInput('disabled', true);
443
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
444
+ });
445
+
446
+ it('should only re-enable the radio items if the corresponding data item is not disabled when the kirby-radio-group is enabled', () => {
447
+ spectator.setInput('disabled', true);
448
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
449
+
450
+ spectator.setInput('disabled', false);
451
+ expect(radios[0].disabled).toBeUndefined();
452
+ expect(radios[1].disabled).toBeFalse();
453
+ expect(radios[2].disabled).toBeTrue();
454
+ });
455
+ });
456
+ }
457
+ });
458
+
459
+ describe('focus', () => {
460
+ it('should focus the selected radio', async () => {
461
+ const selectedRadio = ionRadioElements[defaultSelectedIndex];
462
+ expect(selectedRadio.getAttribute('aria-checked')).toEqual('true');
463
+ // Wait for tabindex to be rendered:
464
+ await TestHelper.whenTrue(() => selectedRadio.tabIndex === 0);
465
+
466
+ spectator.component.focus();
467
+
468
+ expect(document.activeElement).toEqual(selectedRadio);
469
+ });
470
+ });
471
+
472
+ describe('hasError', () => {
473
+ it('should not have error state by default', () => {
474
+ expect(spectator.component.hasError).toBeFalse;
475
+ expect(spectator.element.classList).not.toContain('error');
476
+ });
477
+
478
+ it('should apply class `error` when hasError=true', () => {
479
+ spectator.setInput('hasError', true);
480
+ spectator.detectChanges();
481
+
482
+ expect(spectator.element.classList).toContain('error');
483
+ });
484
+ });
485
+
486
+ describe('when updating items', () => {
487
+ describe('by shifting items down', () => {
488
+ it('should have selected index corresponding to the selected data item', () => {
489
+ const newItems =
490
+ dataScenario.type === DataScenarioTypes.TEXT
491
+ ? ['New Guy'].concat(textItems)
492
+ : [{ text: 'New Guy', value: 10 }].concat(
493
+ dataScenario.items as defaultDataType[]
494
+ ); // Convert to allow mixing defaultDataType with booleanDataType
495
+
496
+ spectator.setHostInput('items', newItems);
497
+ spectator.setHostInput('items$', of(newItems));
498
+
499
+ expect(spectator.component.selectedIndex).toBe(defaultSelectedIndex + 1);
500
+ });
501
+ });
502
+
503
+ describe('by shifting items up', () => {
504
+ it('should have selected index corresponding to the selected data item', () => {
505
+ const newItems = dataScenario.items.slice(1);
506
+ spectator.setHostInput('items', newItems);
507
+ spectator.setHostInput('items$', of(newItems));
508
+
509
+ expect(spectator.component.selectedIndex).toBe(defaultSelectedIndex - 1);
510
+ });
511
+ });
512
+
513
+ describe('by removing items', () => {
514
+ it('should reset value and selected index', () => {
515
+ spectator.setHostInput('items', null);
516
+ spectator.setHostInput('items$', of(null));
517
+
518
+ expect(spectator.component.value).toBe(null);
519
+ expect(spectator.component.selectedIndex).toBe(-1);
520
+ });
521
+ });
522
+
523
+ if (dataScenario.type !== DataScenarioTypes.TEXT) {
524
+ describe('by replacing items with new instances', () => {
525
+ it('should reset value and selected index', () => {
526
+ let newItems: booleanDataType[] | defaultDataType[];
527
+
528
+ if (DataScenarioTypes.BOOLEAN_DATA) {
529
+ newItems = booleanDataItems.map((item) => ({
530
+ ...item,
531
+ value: !item.value,
532
+ }));
533
+ } else {
534
+ newItems = dataItems.map((item) => ({ ...item, value: item.value + 10 }));
535
+ }
536
+
537
+ spectator.setHostInput('items', newItems);
538
+ spectator.setHostInput('items$', of(newItems));
539
+
540
+ expect(spectator.component.value).toBe(null);
541
+ expect(spectator.component.selectedIndex).toBe(-1);
542
+ });
543
+ });
544
+ }
545
+ });
546
+ });
547
+ });
548
+ });
549
+
550
+ if (dataScenario.type !== DataScenarioTypes.ASYNC_DATA) {
551
+ describe('and configured with selected index', () => {
552
+ const templateScenarios = [
553
+ TemplateScenarioTypes.DEFAULT,
554
+ TemplateScenarioTypes.SLOTTED,
555
+ ];
556
+
557
+ templateScenarios.forEach((templateScenario) => {
558
+ describe(`with ${templateScenario}`, () => {
559
+ let spectator: SpectatorHost<
560
+ RadioGroupComponent,
561
+ { items: string[] | defaultDataType[] | booleanDataType[]; selectedIndex: number }
562
+ >;
563
+
564
+ describe('through template one-time string initialization', () => {
565
+ it('should set the value to the corresponding data item', () => {
566
+ const template =
567
+ templateScenario === TemplateScenarioTypes.DEFAULT
568
+ ? `<kirby-radio-group
569
+ [items]="items"
570
+ selectedIndex="${defaultSelectedIndex}">
571
+ </kirby-radio-group>`
572
+ : `<kirby-radio-group selectedIndex="${defaultSelectedIndex}">
573
+ <kirby-radio *ngFor="let item of items" [value]="item" [text]="item.text || item"></kirby-radio>
574
+ </kirby-radio-group>`;
575
+ spectator = createHost(template, {
576
+ hostProps: {
577
+ items: dataScenario.items,
578
+ selectedIndex: null,
579
+ },
580
+ });
581
+
582
+ expect(spectator.component.value).toEqual(
583
+ dataScenario.items[defaultSelectedIndex]
584
+ );
585
+ });
586
+ });
587
+
588
+ describe('through template property binding', () => {
589
+ const template =
590
+ templateScenario === TemplateScenarioTypes.DEFAULT
591
+ ? `<kirby-radio-group
592
+ [items]="items"
593
+ [selectedIndex]="selectedIndex">
594
+ </kirby-radio-group>`
595
+ : `<kirby-radio-group [selectedIndex]="selectedIndex">
596
+ <kirby-radio *ngFor="let item of items" [value]="item" [text]="item.text || item"></kirby-radio>
597
+ </kirby-radio-group>`;
598
+ const createHostFromScenario = (
599
+ items = dataScenario.items,
600
+ selectedIndex = defaultSelectedIndex
601
+ ) =>
602
+ createHost(template, {
603
+ hostProps: {
604
+ items,
605
+ selectedIndex,
606
+ },
607
+ });
608
+
609
+ it('should set the value to the corresponding data item', () => {
610
+ spectator = createHostFromScenario();
611
+
612
+ expect(spectator.component.value).toEqual(
613
+ dataScenario.items[defaultSelectedIndex]
614
+ );
615
+ });
616
+
617
+ it('set the value to the corresponding data item when setting items after selected index', () => {
618
+ spectator = createHostFromScenario(null, defaultSelectedIndex);
619
+ expect(spectator.component.value).toBeNull();
620
+
621
+ spectator.setHostInput('items', dataScenario.items);
622
+
623
+ expect(spectator.component.value).toEqual(
624
+ dataScenario.items[defaultSelectedIndex]
625
+ );
626
+ });
627
+
628
+ it('set the value to the corresponding data item when setting selected index after items', () => {
629
+ spectator = createHostFromScenario(dataScenario.items, null);
630
+ expect(spectator.component.value).toBeNull();
631
+
632
+ spectator.setHostInput('selectedIndex', defaultSelectedIndex);
633
+
634
+ expect(spectator.component.value).toEqual(
635
+ dataScenario.items[defaultSelectedIndex]
636
+ );
637
+ });
638
+
639
+ describe('when changing selected index', () => {
640
+ beforeEach(async () => {
641
+ spectator = createHostFromScenario();
642
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
643
+ await TestHelper.whenReady(ionRadioGroupElement);
644
+ radios = spectator.queryAll(RadioComponent);
645
+ ionRadioElements = spectator.queryAll('ion-radio');
646
+ });
647
+
648
+ it('should have correct new selected item', async () => {
649
+ const newSelectedIndex = 0;
650
+ spectator.setInput('selectedIndex', newSelectedIndex);
651
+ // Wait for radio checked attribute to be updated;
652
+ await TestHelper.whenTrue(() => radioChecked(newSelectedIndex));
653
+
654
+ expect(spectator.component.value).toEqual(
655
+ dataScenario.items[newSelectedIndex]
656
+ );
657
+ expect(ionRadioElements).toHaveLength(dataScenario.items.length);
658
+ ionRadioElements.forEach((_, index) => {
659
+ if (index === newSelectedIndex) {
660
+ expect(radioChecked(index)).toBeTrue();
661
+ } else {
662
+ expect(radioChecked(index)).toBeFalse();
663
+ }
664
+ });
665
+ });
666
+
667
+ it('should not emit change event', () => {
668
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
669
+
670
+ spectator.setInput('selectedIndex', 0);
671
+
672
+ expect(onChangeSpy).not.toHaveBeenCalled();
673
+ });
674
+ });
675
+ });
676
+
677
+ if (templateScenario === TemplateScenarioTypes.DEFAULT) {
678
+ describe('through input properties', () => {
679
+ it('should set the value to the corresponding data item', () => {
680
+ spectator = createHost('<kirby-radio-group></kirby-radio-group>', {
681
+ props: { selectedIndex: defaultSelectedIndex, items: dataScenario.items },
682
+ });
683
+
684
+ expect(spectator.component.value).toEqual(
685
+ dataScenario.items[defaultSelectedIndex]
686
+ );
687
+ });
688
+
689
+ it('set the value to the corresponding data item when setting items after selected index', () => {
690
+ spectator = createHost('<kirby-radio-group></kirby-radio-group>');
691
+ spectator.setInput('selectedIndex', defaultSelectedIndex);
692
+ spectator.setInput('items', dataScenario.items);
693
+
694
+ expect(spectator.component.value).toEqual(
695
+ dataScenario.items[defaultSelectedIndex]
696
+ );
697
+ });
698
+
699
+ it('set the value to the corresponding data item when setting selected index after items', () => {
700
+ spectator = createHost('<kirby-radio-group></kirby-radio-group>');
701
+ spectator.setInput('items', dataScenario.items);
702
+ spectator.setInput('selectedIndex', defaultSelectedIndex);
703
+
704
+ expect(spectator.component.value).toEqual(
705
+ dataScenario.items[defaultSelectedIndex]
706
+ );
707
+ });
708
+ });
709
+ }
710
+ });
711
+ });
712
+ });
713
+ }
714
+ });
715
+ });
716
+
717
+ describe('when bound to data items with custom property names', () => {
718
+ type customDataType = { title: string; value: number; isNotSelectable?: boolean };
719
+
720
+ let spectator: SpectatorHost<RadioGroupComponent, { items: customDataType[] }>;
721
+
722
+ const customDataItems = [
723
+ { title: 'Larry', value: 1 },
724
+ { title: 'Curly', value: 2, isNotSelectable: false },
725
+ { title: 'Moe', value: 3, isNotSelectable: true },
726
+ ];
727
+
728
+ beforeEach(async () => {
729
+ spectator = createHost(
730
+ `<kirby-radio-group
731
+ [items]="items"
732
+ itemTextProperty="title"
733
+ itemDisabledProperty="isNotSelectable">
734
+ </kirby-radio-group>`,
735
+ {
736
+ hostProps: {
737
+ items: customDataItems,
738
+ },
739
+ }
740
+ );
741
+
742
+ ionRadioGroup = spectator.query(IonRadioGroup);
743
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
744
+ await TestHelper.whenReady(ionRadioGroupElement);
745
+ radios = spectator.queryAll(RadioComponent);
746
+ });
747
+
748
+ it('should set the text of each radio to the corresponding data item´s `title` property', () => {
749
+ expect(radios[0].text).toEqual('Larry');
750
+ expect(radios[1].text).toEqual('Curly');
751
+ expect(radios[2].text).toEqual('Moe');
752
+ });
753
+
754
+ it('should set the value of each radio to the corresponding data item', () => {
755
+ expect(radios[0].value).toEqual(customDataItems[0]);
756
+ expect(radios[1].value).toEqual(customDataItems[1]);
757
+ expect(radios[2].value).toEqual(customDataItems[2]);
758
+ });
759
+
760
+ it('should disable radio when the corresponding data item´s `isNotSelectable` property is true', () => {
761
+ expect(radios[0].disabled).toBeUndefined();
762
+ expect(radios[1].disabled).toBeFalse();
763
+ expect(radios[2].disabled).toBeTrue();
764
+ });
765
+ });
766
+ });
767
+
768
+ describe('implementing ControlValueAccessor interface', () => {
769
+ const items = ['Bacon', 'Sausage', 'Onion'];
770
+ const defaultSelectedIndex = 1;
771
+ let ionRadioElements: HTMLIonRadioElement[];
772
+ let spectator: SpectatorHost<
773
+ RadioGroupComponent,
774
+ {
775
+ items: string[];
776
+ }
777
+ >;
778
+
779
+ beforeEach(async () => {
780
+ spectator = createHost(
781
+ `<kirby-radio-group [items]="items">
782
+ </kirby-radio-group>`,
783
+ {
784
+ hostProps: {
785
+ items: items,
786
+ },
787
+ }
788
+ );
789
+
790
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
791
+ await TestHelper.whenReady(ionRadioGroupElement);
792
+
793
+ ionRadioElements = spectator.queryAll('ion-radio');
794
+ await TestHelper.whenReady(ionRadioElements);
795
+ });
796
+
797
+ describe('when writeValue() function is invoked', () => {
798
+ it('should select the radio', () => {
799
+ const expectedItem = items[defaultSelectedIndex];
800
+ spectator.component.writeValue(expectedItem);
801
+ expect(spectator.component.value).toEqual(expectedItem);
802
+ });
803
+
804
+ it('should not emit change event', () => {
805
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
806
+ spectator.component.writeValue(items[defaultSelectedIndex]);
807
+ expect(onChangeSpy).not.toHaveBeenCalled();
808
+ });
809
+ });
810
+
811
+ it('should invoke callback from registerOnChange() function on change', () => {
812
+ const expectedItem = items[defaultSelectedIndex];
813
+ const onChangeSpy = jasmine.createSpy('_onChangeCallback');
814
+ spectator.component.registerOnChange(onChangeSpy);
815
+ spectator.component._onChange(expectedItem);
816
+ expect(onChangeSpy).toHaveBeenCalledWith(expectedItem);
817
+ });
818
+
819
+ it('should invoke callback from registerOnTouched() function on blur', () => {
820
+ const onTouchedSpy = jasmine.createSpy('_onTouched');
821
+ spectator.component.registerOnTouched(onTouchedSpy);
822
+ ionRadioElements[0].focus();
823
+ ionRadioElements[0].blur();
824
+ expect(onTouchedSpy).toHaveBeenCalled();
825
+ });
826
+
827
+ describe('when setDisabledState() function is invoked', () => {
828
+ it('should set disabled = false when invoked with false', () => {
829
+ spectator.component.disabled = true;
830
+ spectator.component.setDisabledState(false);
831
+ expect(spectator.component.disabled).toBeFalsy();
832
+ });
833
+
834
+ it('should set disabled = true when invoked with true', () => {
835
+ spectator.component.disabled = false;
836
+ spectator.component.setDisabledState(true);
837
+ expect(spectator.component.disabled).toBeTruthy();
838
+ });
839
+ });
840
+ });
841
+
842
+ describe('when used in a form', () => {
843
+ const radioBorderDefault = {
844
+ 'border-width': '1px',
845
+ 'border-color': getColor('semi-dark'),
846
+ };
847
+ const radioBorderErrorState = {
848
+ 'border-width': '1px',
849
+ 'border-color': getColor('danger'),
850
+ };
851
+
852
+ let ionRadioGroup: IonRadioGroup;
853
+ let ionRadioElements: HTMLIonRadioElement[];
854
+ let radios: RadioComponent[];
855
+
856
+ function radioChecked(index: number): boolean {
857
+ return ionRadioElements[index].getAttribute('aria-checked') === 'true';
858
+ }
859
+
860
+ const items = ['Bacon', 'Sausage', 'Onion'];
861
+ const defaultSelectedIndex = 1;
862
+
863
+ describe('with a template-driven form', () => {
864
+ let spectator: SpectatorHost<
865
+ RadioGroupComponent,
866
+ {
867
+ items: string[];
868
+ selected: string;
869
+ }
870
+ >;
871
+
872
+ describe('and no pre-selected item', () => {
873
+ beforeEach(async () => {
874
+ spectator = createHost(
875
+ `<kirby-radio-group [items]="items" [(ngModel)]="selected">
876
+ </kirby-radio-group>`,
877
+ {
878
+ hostProps: {
879
+ items: items,
880
+ selected: null,
881
+ },
882
+ }
883
+ );
884
+
885
+ ionRadioGroup = spectator.query(IonRadioGroup);
886
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
887
+ await TestHelper.whenReady(ionRadioGroupElement);
888
+
889
+ radios = spectator.queryAll(RadioComponent);
890
+ ionRadioElements = spectator.queryAll('ion-radio');
891
+ await TestHelper.whenReady(ionRadioElements);
892
+ });
893
+
894
+ describe('selection', () => {
895
+ it('should update the bound ngModel when clicking a radio item', () => {
896
+ spectator.click(ionRadioElements[0]);
897
+ expect(spectator.hostComponent.selected).toEqual(items[0]);
898
+ });
899
+
900
+ it('should update the value of ion-radio-group when the bound ngModel is updated', async () => {
901
+ const newSelectedValue = items[0];
902
+ await setSelectedOnHostComponent(newSelectedValue);
903
+ expect(ionRadioGroup.value).toEqual(newSelectedValue);
904
+ });
905
+
906
+ it('should update the selected radio when the bound ngModel is updated', async () => {
907
+ const selectedIndex = 0;
908
+ await setSelectedOnHostComponent(items[selectedIndex]);
909
+ // Wait for radio checked attribute to be updated;
910
+ await TestHelper.whenTrue(() => radioChecked(selectedIndex));
911
+
912
+ expect(radioChecked(selectedIndex)).toBeTrue();
913
+ });
914
+
915
+ it('should not emit change event when the bound ngModel is updated', async () => {
916
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
917
+ await setSelectedOnHostComponent(items[0]);
918
+ expect(onChangeSpy).not.toHaveBeenCalled();
919
+ });
920
+ });
921
+ });
922
+
923
+ describe('and pre-selected item', () => {
924
+ beforeEach(async () => {
925
+ spectator = createHost(
926
+ `<kirby-radio-group [items]="items" [(ngModel)]="selected">
927
+ </kirby-radio-group>`,
928
+ {
929
+ hostProps: {
930
+ items: items,
931
+ selected: items[defaultSelectedIndex],
932
+ },
933
+ }
934
+ );
935
+
936
+ await TestHelper.waitForTimeout();
937
+ spectator.detectChanges();
938
+
939
+ ionRadioGroup = spectator.query(IonRadioGroup);
940
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
941
+ await TestHelper.whenReady(ionRadioGroupElement);
942
+
943
+ radios = spectator.queryAll(RadioComponent);
944
+ ionRadioElements = spectator.queryAll('ion-radio');
945
+ await TestHelper.whenReady(ionRadioElements);
946
+ await TestHelper.whenTrue(() => radioChecked(defaultSelectedIndex));
947
+ });
948
+
949
+ describe('selection', () => {
950
+ let newSelectedIndex: number;
951
+
952
+ it('should update the bound ngModel when clicking a different radio item', () => {
953
+ newSelectedIndex = defaultSelectedIndex + 1;
954
+ spectator.click(ionRadioElements[newSelectedIndex]);
955
+ expect(spectator.hostComponent.selected).toEqual(items[newSelectedIndex]);
956
+ });
957
+
958
+ it('should update the value of ion-radio-group when the bound ngModel is updated', async () => {
959
+ const newSelectedValue = items[defaultSelectedIndex + 1];
960
+ await setSelectedOnHostComponent(items[newSelectedValue]);
961
+ expect(ionRadioGroup.value).toEqual(items[newSelectedValue]);
962
+ });
963
+
964
+ it('should update the selected radio when the bound ngModel is updated', async () => {
965
+ newSelectedIndex = defaultSelectedIndex + 1;
966
+ await setSelectedOnHostComponent(items[newSelectedIndex]);
967
+ // Wait for radio checked attribute to be updated;
968
+ await TestHelper.whenTrue(() => radioChecked(newSelectedIndex));
969
+
970
+ expect(radioChecked(defaultSelectedIndex)).toBeFalse();
971
+ expect(radioChecked(newSelectedIndex)).toBeTrue();
972
+ });
973
+
974
+ it('should not emit change event when the bound ngModel is updated', async () => {
975
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
976
+ newSelectedIndex = defaultSelectedIndex + 1;
977
+ await setSelectedOnHostComponent(items[newSelectedIndex]);
978
+ expect(onChangeSpy).not.toHaveBeenCalled();
979
+ });
980
+ });
981
+ });
982
+
983
+ describe('error state when ngModel is required', () => {
984
+ beforeEach(async () => {
985
+ spectator = createHost(
986
+ `<kirby-radio-group [items]="items" [(ngModel)]="selected" required>
987
+ </kirby-radio-group>`,
988
+ {
989
+ hostProps: {
990
+ items: items,
991
+ selected: null,
992
+ },
993
+ }
994
+ );
995
+
996
+ ionRadioGroup = spectator.query(IonRadioGroup);
997
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
998
+ await TestHelper.whenReady(ionRadioGroupElement);
999
+
1000
+ radios = spectator.queryAll(RadioComponent);
1001
+ ionRadioElements = spectator.queryAll('ion-radio');
1002
+ await TestHelper.whenReady(ionRadioElements);
1003
+ });
1004
+
1005
+ describe('when ngModel is not null', () => {
1006
+ beforeEach(async () => {
1007
+ await setSelectedOnHostComponent(items[defaultSelectedIndex]);
1008
+ });
1009
+
1010
+ describe('and component has been touched', () => {
1011
+ beforeEach(() => {
1012
+ ionRadioElements[0].focus();
1013
+ ionRadioElements[0].blur();
1014
+ spectator.detectChanges();
1015
+ });
1016
+
1017
+ it('should not be in error state', () => {
1018
+ ionRadioElements.forEach((ionRadioElement) => {
1019
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1020
+ expect(radioIcon).toHaveComputedStyle(radioBorderDefault);
1021
+ });
1022
+ });
1023
+ });
1024
+
1025
+ describe('and component has not been touched', () => {
1026
+ it('should not be in error state', () => {
1027
+ ionRadioElements.forEach((ionRadioElement) => {
1028
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1029
+ expect(radioIcon).toHaveComputedStyle(radioBorderDefault);
1030
+ });
1031
+ });
1032
+ });
1033
+ });
1034
+
1035
+ describe('when ngModel is null', () => {
1036
+ describe('and component has been touched', () => {
1037
+ beforeEach(() => {
1038
+ ionRadioElements[0].focus();
1039
+ ionRadioElements[0].blur();
1040
+ spectator.detectChanges();
1041
+ });
1042
+
1043
+ it('should be in error state', () => {
1044
+ ionRadioElements.forEach((ionRadioElement) => {
1045
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1046
+ expect(radioIcon).toHaveComputedStyle(radioBorderErrorState);
1047
+ });
1048
+ });
1049
+ });
1050
+
1051
+ describe('and component has not been touched', () => {
1052
+ it('should not be in error state', () => {
1053
+ ionRadioElements.forEach((ionRadioElement) => {
1054
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1055
+ expect(radioIcon).toHaveComputedStyle(radioBorderDefault);
1056
+ });
1057
+ });
1058
+ });
1059
+ });
1060
+ });
1061
+
1062
+ async function setSelectedOnHostComponent(value: any): Promise<void> {
1063
+ spectator.setHostInput('selected', value);
1064
+ await TestHelper.waitForTimeout();
1065
+ spectator.detectChanges();
1066
+ }
1067
+ });
1068
+
1069
+ describe('with a reactive form', () => {
1070
+ let favoriteFoodControl: FormControl;
1071
+
1072
+ let spectator: SpectatorHost<
1073
+ RadioGroupComponent,
1074
+ {
1075
+ favoriteFoodForm: FormGroup;
1076
+ items: string[];
1077
+ }
1078
+ >;
1079
+
1080
+ describe('and no pre-selected item', () => {
1081
+ beforeEach(async () => {
1082
+ favoriteFoodControl = new FormControl();
1083
+
1084
+ spectator = createHost(
1085
+ `<form [formGroup]="favoriteFoodForm">
1086
+ <kirby-radio-group [items]="items" formControlName="favoriteFood">
1087
+ </kirby-radio-group>
1088
+ </form>`,
1089
+ {
1090
+ hostProps: {
1091
+ favoriteFoodForm: new FormGroup({
1092
+ favoriteFood: favoriteFoodControl,
1093
+ }),
1094
+ items: items,
1095
+ },
1096
+ }
1097
+ );
1098
+
1099
+ ionRadioGroup = spectator.query(IonRadioGroup);
1100
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
1101
+ await TestHelper.whenReady(ionRadioGroupElement);
1102
+
1103
+ radios = spectator.queryAll(RadioComponent);
1104
+ ionRadioElements = spectator.queryAll('ion-radio');
1105
+ await TestHelper.whenReady(ionRadioElements);
1106
+ });
1107
+
1108
+ describe('selection', () => {
1109
+ it('should update the value of ion-radio-group when the bound form control is set to a value', async () => {
1110
+ const newFavoriteFood = items[0];
1111
+ await setFormControlValue(newFavoriteFood);
1112
+
1113
+ expect(ionRadioGroup.value).toEqual(newFavoriteFood);
1114
+ });
1115
+
1116
+ it('should update the selected radio when the bound form control is set to a value', async () => {
1117
+ const selectedIndex = 0;
1118
+ await setFormControlValue(items[selectedIndex]);
1119
+ // Wait for radio checked attribute to be updated;
1120
+ await TestHelper.whenTrue(() => radioChecked(selectedIndex));
1121
+
1122
+ expect(radioChecked(selectedIndex)).toBeTrue();
1123
+ });
1124
+
1125
+ it('should not emit change event when the bound form control is set to a value', async () => {
1126
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
1127
+ await setFormControlValue(items[0]);
1128
+ expect(onChangeSpy).not.toHaveBeenCalled();
1129
+ });
1130
+ });
1131
+ });
1132
+
1133
+ describe('and pre-selected item', () => {
1134
+ beforeEach(async () => {
1135
+ favoriteFoodControl = new FormControl(items[defaultSelectedIndex]);
1136
+
1137
+ spectator = createHost(
1138
+ `<form [formGroup]="favoriteFoodForm">
1139
+ <kirby-radio-group [items]="items" formControlName="favoriteFood">
1140
+ </kirby-radio-group>
1141
+ </form>`,
1142
+ {
1143
+ hostProps: {
1144
+ favoriteFoodForm: new FormGroup({
1145
+ favoriteFood: favoriteFoodControl,
1146
+ }),
1147
+ items: items,
1148
+ },
1149
+ }
1150
+ );
1151
+
1152
+ ionRadioGroup = spectator.query(IonRadioGroup);
1153
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
1154
+ await TestHelper.whenReady(ionRadioGroupElement);
1155
+
1156
+ radios = spectator.queryAll(RadioComponent);
1157
+ ionRadioElements = spectator.queryAll('ion-radio');
1158
+ await TestHelper.whenReady(ionRadioElements);
1159
+ });
1160
+
1161
+ describe('selection', () => {
1162
+ it('should have selected index corresponding to the selected data item', () => {
1163
+ expect(spectator.component.selectedIndex).toBe(defaultSelectedIndex);
1164
+ });
1165
+
1166
+ it('should update the bound form control when clicking a different radio item', () => {
1167
+ spectator.click(ionRadioElements[defaultSelectedIndex]);
1168
+ expect(favoriteFoodControl.value).toEqual(items[defaultSelectedIndex]);
1169
+ });
1170
+
1171
+ it('should update the value of ion-radio-group when the bound form control is updated', async () => {
1172
+ const newControlValue = items[defaultSelectedIndex + 1];
1173
+ await setFormControlValue(newControlValue);
1174
+ expect(ionRadioGroup.value).toEqual(newControlValue);
1175
+ });
1176
+
1177
+ it('should update the selected radio when the bound form control is updated', async () => {
1178
+ const newSelectedIndex = defaultSelectedIndex + 1;
1179
+ await setFormControlValue(items[newSelectedIndex]);
1180
+ // Wait for radio checked attribute to be updated;
1181
+ await TestHelper.whenTrue(() => radioChecked(newSelectedIndex));
1182
+
1183
+ expect(radioChecked(defaultSelectedIndex)).toBeFalse();
1184
+ expect(radioChecked(newSelectedIndex)).toBeTrue();
1185
+ });
1186
+
1187
+ it('should not emit change event when the bound form control is updated', async () => {
1188
+ const onChangeSpy = spyOn(spectator.component.valueChange, 'emit');
1189
+ await setFormControlValue(items[defaultSelectedIndex + 1]);
1190
+ expect(onChangeSpy).not.toHaveBeenCalled();
1191
+ });
1192
+ });
1193
+
1194
+ describe('enablement', () => {
1195
+ it('should not disable the radio items by default', () => {
1196
+ radios.forEach((each) => expect(each.disabled).toBeUndefined());
1197
+ });
1198
+
1199
+ it('should disable the radio items when the bound form control is disabled', () => {
1200
+ favoriteFoodControl.disable();
1201
+ spectator.detectChanges();
1202
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
1203
+ });
1204
+
1205
+ it('should re-enable the radio items when the bound form control is enabled', () => {
1206
+ favoriteFoodControl.disable();
1207
+ spectator.detectChanges();
1208
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
1209
+
1210
+ favoriteFoodControl.enable();
1211
+ spectator.detectChanges();
1212
+ radios.forEach((each) => expect(each.disabled).toBeUndefined());
1213
+ });
1214
+
1215
+ it('should disable the radios if radios are set after the bound form control is disabled', async () => {
1216
+ spectator.setHostInput('items', null);
1217
+ favoriteFoodControl.disable();
1218
+ await TestHelper.waitForTimeout();
1219
+ spectator.detectChanges();
1220
+
1221
+ spectator.setHostInput('items', items);
1222
+ await TestHelper.waitForTimeout();
1223
+ spectator.detectChanges();
1224
+
1225
+ radios = spectator.queryAll(RadioComponent);
1226
+ radios.forEach((each) => expect(each.disabled).toBeTrue());
1227
+ });
1228
+ });
1229
+ });
1230
+
1231
+ describe('error state when the bound form control is required', () => {
1232
+ beforeEach(async () => {
1233
+ favoriteFoodControl = new FormControl(null, Validators.required);
1234
+
1235
+ spectator = createHost(
1236
+ `<form [formGroup]="favoriteFoodForm">
1237
+ <kirby-radio-group [items]="items" formControlName="favoriteFood">
1238
+ </kirby-radio-group>
1239
+ </form>`,
1240
+ {
1241
+ hostProps: {
1242
+ favoriteFoodForm: new FormGroup({
1243
+ favoriteFood: favoriteFoodControl,
1244
+ }),
1245
+ items: items,
1246
+ },
1247
+ }
1248
+ );
1249
+
1250
+ ionRadioGroup = spectator.query(IonRadioGroup);
1251
+ const ionRadioGroupElement = spectator.query('ion-radio-group');
1252
+ await TestHelper.whenReady(ionRadioGroupElement);
1253
+
1254
+ radios = spectator.queryAll(RadioComponent);
1255
+ ionRadioElements = spectator.queryAll('ion-radio');
1256
+ await TestHelper.whenReady(ionRadioElements);
1257
+ });
1258
+
1259
+ describe('when the value of the bound form control is not null', () => {
1260
+ beforeEach(async () => {
1261
+ await setFormControlValue(items[defaultSelectedIndex]);
1262
+ });
1263
+
1264
+ describe('and the component has been touched', () => {
1265
+ beforeEach(() => {
1266
+ ionRadioElements[0].focus();
1267
+ ionRadioElements[0].blur();
1268
+ spectator.detectChanges();
1269
+ });
1270
+
1271
+ it('should not be in error state', () => {
1272
+ ionRadioElements.forEach((ionRadioElement) => {
1273
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1274
+ expect(radioIcon).toHaveComputedStyle(radioBorderDefault);
1275
+ });
1276
+ });
1277
+ });
1278
+
1279
+ describe('and the component has not been touched', () => {
1280
+ it('should not be in error state', () => {
1281
+ ionRadioElements.forEach((ionRadioElement) => {
1282
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1283
+ expect(radioIcon).toHaveComputedStyle(radioBorderDefault);
1284
+ });
1285
+ });
1286
+ });
1287
+ });
1288
+
1289
+ describe('when the value of the bound form control is null', () => {
1290
+ describe('and the component has been touched', () => {
1291
+ beforeEach(() => {
1292
+ ionRadioElements[0].focus();
1293
+ ionRadioElements[0].blur();
1294
+ spectator.detectChanges();
1295
+ });
1296
+
1297
+ it('should be in error state', () => {
1298
+ ionRadioElements.forEach((ionRadioElement) => {
1299
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1300
+ expect(radioIcon).toHaveComputedStyle(radioBorderErrorState);
1301
+ });
1302
+ });
1303
+ });
1304
+
1305
+ describe('and the component has not been touched', () => {
1306
+ beforeEach(async () => {
1307
+ await TestHelper.waitForTimeout();
1308
+ spectator.detectChanges();
1309
+ });
1310
+
1311
+ it('should not be in error state', () => {
1312
+ ionRadioElements.forEach((ionRadioElement) => {
1313
+ const radioIcon = ionRadioElement.shadowRoot.querySelector('[part=container]');
1314
+ expect(radioIcon).toHaveComputedStyle(radioBorderDefault);
1315
+ });
1316
+ });
1317
+ });
1318
+ });
1319
+ });
1320
+
1321
+ async function setFormControlValue(value: any): Promise<void> {
1322
+ favoriteFoodControl.setValue(value);
1323
+ await TestHelper.waitForTimeout();
1324
+ spectator.detectChanges();
1325
+ }
1326
+ });
1327
+ });
1328
+ });