@tinya-code/gas-admin-ui 0.2.0-alpha.12 → 0.2.0-alpha.15

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 (369) hide show
  1. package/README.md +222 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +3 -15
  4. package/src/lib/actions/action-registry.spec.ts +52 -0
  5. package/src/lib/actions/action-registry.ts +126 -0
  6. package/src/lib/actions/action-service.spec.ts +215 -0
  7. package/src/lib/actions/action-service.ts +87 -0
  8. package/src/lib/actions/descriptors.spec.ts +155 -0
  9. package/src/lib/actions/descriptors.ts +154 -0
  10. package/src/lib/columns/column-cell.ts +33 -0
  11. package/src/lib/columns/column-defaults.spec.ts +99 -0
  12. package/src/lib/columns/column-defaults.ts +84 -0
  13. package/src/lib/columns/column-format.spec.ts +274 -0
  14. package/src/lib/columns/descriptors.ts +113 -0
  15. package/src/lib/columns/format-badge.ts +51 -0
  16. package/src/lib/columns/format-boolean.ts +58 -0
  17. package/src/lib/columns/format-currency.ts +30 -0
  18. package/src/lib/columns/format-date.ts +35 -0
  19. package/src/lib/columns/format-number.ts +32 -0
  20. package/src/lib/columns/format-text.ts +23 -0
  21. package/src/lib/columns/format-thumbnail.ts +63 -0
  22. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  23. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  24. package/src/lib/core/guards/auth.guard.ts +48 -0
  25. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  27. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  28. package/src/lib/core/models/api.model.ts +104 -0
  29. package/src/lib/core/models/schema-descriptions.ts +118 -0
  30. package/src/lib/core/models/schema.model.ts +1097 -0
  31. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  32. package/src/lib/core/registry/composition-validator.ts +270 -0
  33. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  34. package/src/lib/core/registry/field-validators.ts +440 -0
  35. package/src/lib/core/registry/render-host.spec.ts +208 -0
  36. package/src/lib/core/registry/render-host.ts +646 -0
  37. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  38. package/src/lib/core/registry/render-registry.ts +312 -0
  39. package/src/lib/core/registry/schema-catalog.ts +156 -0
  40. package/src/lib/core/services/api.service.spec.ts +1142 -0
  41. package/src/lib/core/services/api.service.ts +371 -0
  42. package/src/lib/core/services/auth.service.spec.ts +653 -0
  43. package/src/lib/core/services/auth.service.ts +503 -0
  44. package/src/lib/core/services/config.service.spec.ts +121 -0
  45. package/src/lib/core/services/config.service.ts +119 -0
  46. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  47. package/src/lib/core/services/data-source.service.ts +142 -0
  48. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  49. package/src/lib/core/services/error-mapper.service.ts +49 -0
  50. package/src/lib/core/services/notification.service.ts +73 -0
  51. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  52. package/src/lib/core/services/theme.service.spec.ts +106 -0
  53. package/src/lib/core/services/theme.service.ts +86 -0
  54. package/src/lib/core/services/transport.spec.ts +198 -0
  55. package/src/lib/core/services/transport.ts +164 -0
  56. package/src/lib/core/services/upload.service.ts +81 -0
  57. package/src/lib/core/testing/categories.schema.ts +125 -0
  58. package/src/lib/core/testing/orders.schema.ts +372 -0
  59. package/src/lib/core/testing/products.schema.ts +258 -0
  60. package/src/lib/core/testing/site.schema.ts +195 -0
  61. package/src/lib/core/testing/test-catalog.ts +21 -0
  62. package/src/lib/core/tokens.ts +112 -0
  63. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  64. package/src/lib/core/utils/clipboard.ts +90 -0
  65. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  66. package/src/lib/core/utils/context-resolver.ts +101 -0
  67. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  68. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  69. package/src/lib/core/utils/source-options.spec.ts +96 -0
  70. package/src/lib/core/utils/source-options.ts +54 -0
  71. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  72. package/src/lib/core/utils/template-interpolator.ts +56 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  74. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  75. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  76. package/src/lib/fields/boolean/boolean.ts +40 -0
  77. package/src/lib/fields/color/color.descriptor.ts +12 -0
  78. package/src/lib/fields/color/color.ts +34 -0
  79. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  80. package/src/lib/fields/currency/currency.ts +67 -0
  81. package/src/lib/fields/date/date.descriptor.ts +12 -0
  82. package/src/lib/fields/date/date.ts +36 -0
  83. package/src/lib/fields/descriptors.ts +122 -0
  84. package/src/lib/fields/display/display-badge.ts +65 -0
  85. package/src/lib/fields/display/display-date.ts +39 -0
  86. package/src/lib/fields/display/display-defaults.ts +84 -0
  87. package/src/lib/fields/display/display-file.ts +41 -0
  88. package/src/lib/fields/display/display-image.ts +54 -0
  89. package/src/lib/fields/display/display-list.ts +107 -0
  90. package/src/lib/fields/display/display-number.ts +41 -0
  91. package/src/lib/fields/display/display-text.ts +42 -0
  92. package/src/lib/fields/display/display.spec.ts +242 -0
  93. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  94. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  95. package/src/lib/fields/email/email.descriptor.ts +12 -0
  96. package/src/lib/fields/email/email.ts +28 -0
  97. package/src/lib/fields/field-aria.ts +60 -0
  98. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  99. package/src/lib/fields/field-host/field-host.ts +47 -0
  100. package/src/lib/fields/field-node.ts +88 -0
  101. package/src/lib/fields/file/file.descriptor.ts +14 -0
  102. package/src/lib/fields/file/file.spec.ts +134 -0
  103. package/src/lib/fields/file/file.ts +281 -0
  104. package/src/lib/fields/group/group.descriptor.ts +21 -0
  105. package/src/lib/fields/group/group.ts +110 -0
  106. package/src/lib/fields/image/image.descriptor.ts +12 -0
  107. package/src/lib/fields/image/image.spec.ts +117 -0
  108. package/src/lib/fields/image/image.ts +281 -0
  109. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  110. package/src/lib/fields/key-value/key-value.ts +283 -0
  111. package/src/lib/fields/list/list.descriptor.ts +21 -0
  112. package/src/lib/fields/list/list.ts +376 -0
  113. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  114. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  115. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  116. package/src/lib/fields/number/number.descriptor.ts +12 -0
  117. package/src/lib/fields/number/number.ts +40 -0
  118. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  119. package/src/lib/fields/phone/phone.ts +46 -0
  120. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  121. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  122. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  123. package/src/lib/fields/relation/relation.spec.ts +193 -0
  124. package/src/lib/fields/relation/relation.ts +224 -0
  125. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  126. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  127. package/src/lib/fields/select/radio-mode.ts +25 -0
  128. package/src/lib/fields/select/select.descriptor.ts +12 -0
  129. package/src/lib/fields/select/select.spec.ts +162 -0
  130. package/src/lib/fields/select/select.ts +210 -0
  131. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  132. package/src/lib/fields/slug/slug.ts +67 -0
  133. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  134. package/src/lib/fields/string-list/string-list.ts +222 -0
  135. package/src/lib/fields/text/text.descriptor.ts +12 -0
  136. package/src/lib/fields/text/text.ts +35 -0
  137. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  138. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  139. package/src/lib/fields/textarea/textarea.ts +126 -0
  140. package/src/lib/fields/time/time.descriptor.ts +12 -0
  141. package/src/lib/fields/time/time.ts +28 -0
  142. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  143. package/src/lib/fields/url/url.descriptor.ts +12 -0
  144. package/src/lib/fields/url/url.ts +53 -0
  145. package/src/lib/gas-admin-ui.spec.ts +21 -0
  146. package/src/lib/gas-admin-ui.ts +9 -0
  147. package/src/lib/layouts/descriptors.ts +9 -0
  148. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  149. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  150. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  151. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  152. package/src/lib/pages/form-view/form-model.ts +88 -0
  153. package/src/lib/pages/form-view/form-schema.ts +255 -0
  154. package/src/lib/pages/form-view/form-view.ts +1622 -0
  155. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  156. package/src/lib/pages/list-view/list-view.ts +882 -0
  157. package/src/lib/pages/login/login.spec.ts +109 -0
  158. package/src/lib/pages/login/login.ts +227 -0
  159. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  160. package/src/lib/shared/components/badge/badge.ts +35 -0
  161. package/src/lib/shared/components/button/button.ts +65 -0
  162. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  163. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  164. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  165. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  166. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  167. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  168. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  169. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  170. package/src/lib/shared/components/modal/modal.ts +145 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  172. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  173. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  174. package/src/lib/shared/components/toast/toast.ts +58 -0
  175. package/src/lib/shared/utils/col-span.ts +25 -0
  176. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  177. package/src/lib/shared/utils/depends-on.ts +48 -0
  178. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  179. package/src/lib/shared/utils/field-path.ts +49 -0
  180. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  181. package/src/lib/shared/utils/seed.spec.ts +64 -0
  182. package/src/lib/shared/utils/seed.ts +149 -0
  183. package/src/lib/shared/utils/slugify.ts +13 -0
  184. package/src/lib/shared/utils/validators.ts +41 -0
  185. package/src/lib/shell/layout/layout.ts +63 -0
  186. package/src/lib/shell/shell.routes.spec.ts +59 -0
  187. package/src/lib/shell/shell.routes.ts +122 -0
  188. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  189. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  190. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  191. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  192. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  193. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  194. package/src/lib/views/composer/composer-host.ts +248 -0
  195. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  196. package/src/lib/views/composer/view-context.spec.ts +117 -0
  197. package/src/lib/views/composer/view-context.ts +109 -0
  198. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  199. package/src/lib/views/composer/view-slot.ts +178 -0
  200. package/src/lib/views/descriptors.ts +46 -0
  201. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  202. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  203. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  206. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  207. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  208. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  209. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  210. package/src/lib/widgets/descriptors.ts +29 -0
  211. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  212. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  213. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  216. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  217. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  218. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  219. package/src/lib/widgets/record-list/record-list.ts +115 -0
  220. package/src/lib/widgets/series.spec.ts +42 -0
  221. package/src/lib/widgets/series.ts +56 -0
  222. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  223. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  224. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  225. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  226. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  227. package/src/public-api.ts +104 -0
  228. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  229. package/tsconfig.lib.json +13 -0
  230. package/tsconfig.lib.prod.json +11 -0
  231. package/tsconfig.spec.json +10 -0
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  233. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  235. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  237. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  239. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  241. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  243. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  245. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  247. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  249. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  251. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  253. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  255. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  257. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  259. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  261. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  263. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  265. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  267. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  269. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  271. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  273. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  275. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  277. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  279. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  281. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  283. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  285. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  287. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  289. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  291. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  293. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  295. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  297. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  299. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  301. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  303. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  305. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  307. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  309. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  311. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  313. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  315. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  317. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  319. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  321. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  323. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  325. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  327. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  329. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  333. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  335. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  337. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  338. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  340. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  342. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  344. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  346. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  348. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  350. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  352. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  354. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  356. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  358. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  360. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  362. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  364. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  366. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  368. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  369. package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
@@ -0,0 +1,557 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { ActivatedRoute } from '@angular/router';
3
+ import { of } from 'rxjs';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { ApiError } from '../../core/models/api.model';
6
+ import type {
7
+ CalendarViewConfig,
8
+ ResourceSchema,
9
+ SelectionBinding,
10
+ } from '../../core/models/schema.model';
11
+ import { DataSourceService, type SourceRow } from '../../core/services/data-source.service';
12
+ import {
13
+ agendaDayLabel,
14
+ daysInRange,
15
+ eventsByDay,
16
+ formatDay,
17
+ mapCalendarRows,
18
+ monthGridDays,
19
+ parseDay,
20
+ rangeLabelFor,
21
+ REASON_END_BEFORE_START,
22
+ REASON_INVALID_END,
23
+ REASON_UNDATED,
24
+ shiftCursor,
25
+ shiftMonths,
26
+ todayCivil,
27
+ CALENDAR_TODAY,
28
+ visibleRangeFor,
29
+ weekDays,
30
+ } from './calendar-model';
31
+ import { CalendarView } from './calendar-view';
32
+
33
+ /**
34
+ * R9.8.3 — generic `CalendarView`.
35
+ *
36
+ * The pure model (civil-day math, row→event mapping) is tested directly;
37
+ * the component is tested against a stubbed `DataSourceService` + a stubbed
38
+ * `ActivatedRoute.data` carrying `uiView` (the same pattern as
39
+ * `resource-page.spec.ts`).
40
+ */
41
+
42
+ // Deterministic fixtures — 2026-10-12 is a MONDAY and 2026-10-01 a
43
+ // Thursday, so the October grid starts Monday 2026-09-28 (42-day window
44
+ // ending 2026-11-08).
45
+ const MONDAY = '2026-10-12';
46
+ const MONTH_GRID_START = '2026-09-28';
47
+ const MONTH_GRID_END = '2026-11-08';
48
+
49
+ const BASE_CONFIG: CalendarViewConfig = {
50
+ startField: 'start',
51
+ endField: 'end',
52
+ titleField: 'name',
53
+ colorField: 'status',
54
+ defaultView: 'month',
55
+ };
56
+
57
+ function calendarSchema(config: CalendarViewConfig | null = BASE_CONFIG): ResourceSchema {
58
+ return {
59
+ id: 'cal_events',
60
+ label: 'Evento',
61
+ labelPlural: 'Eventos',
62
+ kind: 'collection',
63
+ endpoint: { list: '/admin/cal_events' },
64
+ keyField: 'slug',
65
+ titleField: 'name',
66
+ listColumns: [],
67
+ fields: [
68
+ { type: 'text', key: 'slug', label: 'Slug' },
69
+ { type: 'text', key: 'name', label: 'Nombre' },
70
+ { type: 'date', key: 'start', label: 'Inicio' },
71
+ { type: 'date', key: 'end', label: 'Fin' },
72
+ { type: 'time', key: 'end_hour', label: 'Hora de fin' },
73
+ { type: 'select', key: 'status', label: 'Estado', options: [] },
74
+ ],
75
+ uiViews:
76
+ config === null ? { agenda: { type: 'list' } } : { agenda: { type: 'calendar', config } },
77
+ };
78
+ }
79
+
80
+ type RowsBehavior = SourceRow[] | 'pending' | 'reject';
81
+
82
+ interface Harness {
83
+ fixture: ComponentFixture<CalendarView>;
84
+ component: CalendarView;
85
+ rowsMock: ReturnType<typeof vi.fn>;
86
+ el: HTMLElement;
87
+ }
88
+
89
+ async function create(options: {
90
+ rows?: RowsBehavior;
91
+ config?: CalendarViewConfig | null;
92
+ /** Route data override — selection contract travels here too. */
93
+ data?: Record<string, unknown>;
94
+ }): Promise<Harness> {
95
+ const rowsBehavior = options.rows ?? [];
96
+ const rowsMock = vi.fn(() => {
97
+ if (rowsBehavior === 'pending') return new Promise<SourceRow[]>(() => {});
98
+ if (rowsBehavior === 'reject') {
99
+ return Promise.reject(new ApiError(500, 'Fallo del servidor'));
100
+ }
101
+ return Promise.resolve(rowsBehavior);
102
+ });
103
+
104
+ const schema = calendarSchema(options.config === null ? null : (options.config ?? BASE_CONFIG));
105
+
106
+ await TestBed.configureTestingModule({
107
+ imports: [CalendarView],
108
+ providers: [
109
+ {
110
+ // `uiView` names the view within `uiViews`; the schema travels by
111
+ // route data because the shell mounts views with `loadComponent`
112
+ // (no input bindings) and the schema registry is immutable.
113
+ provide: ActivatedRoute,
114
+ useValue: {
115
+ data: of({ uiView: 'agenda', schema, ...(options.data ?? {}) }),
116
+ },
117
+ },
118
+ { provide: DataSourceService, useValue: { rows: rowsMock } },
119
+ { provide: CALENDAR_TODAY, useValue: () => '2026-10-01' },
120
+ ],
121
+ }).compileComponents();
122
+
123
+ const fixture = TestBed.createComponent(CalendarView);
124
+ fixture.detectChanges();
125
+ await fixture.whenStable();
126
+ fixture.detectChanges();
127
+ return { fixture, component: fixture.componentInstance, rowsMock, el: fixture.nativeElement };
128
+ }
129
+
130
+ /** Flush a cursor/mode change plus the load it triggers. */
131
+ async function settle(fixture: ComponentFixture<CalendarView>): Promise<void> {
132
+ fixture.detectChanges();
133
+ await fixture.whenStable();
134
+ fixture.detectChanges();
135
+ }
136
+
137
+ // ─────────────────────────────────────────────────────────────
138
+ // Pure model — civil-day parsing and ranges
139
+ // ─────────────────────────────────────────────────────────────
140
+
141
+ describe('calendar-model — civil days', () => {
142
+ it('parsea YYYY-MM-DD como medianoche UTC (sin shift de zona horaria)', () => {
143
+ expect(parseDay('2026-10-12')).toBe(Date.UTC(2026, 9, 12));
144
+ expect(formatDay(Date.UTC(2026, 9, 12))).toBe('2026-10-12');
145
+ });
146
+
147
+ it('rechaza vacíos, basura y días inexistentes', () => {
148
+ for (const bad of ['', ' ', null, undefined, 42, '2026-13-01', '2026-02-30', '12/10/2026']) {
149
+ expect(parseDay(bad)).toBeNull();
150
+ }
151
+ });
152
+
153
+ it('deriva el rango visible por modo conSemana lunes-primero', () => {
154
+ // month = grilla de 6 semanas (lunes previo al día 1)
155
+ expect(visibleRangeFor('month', '2026-10-15')).toEqual({
156
+ desde: MONTH_GRID_START,
157
+ hasta: MONTH_GRID_END,
158
+ });
159
+ // week: miércoles cae en la semana del lunes 12
160
+ expect(visibleRangeFor('week', '2026-10-14')).toEqual({
161
+ desde: '2026-10-12',
162
+ hasta: '2026-10-18',
163
+ });
164
+ // el propio lunes abre su semana; el domingo la cierra
165
+ expect(visibleRangeFor('week', MONDAY)).toEqual({ desde: '2026-10-12', hasta: '2026-10-18' });
166
+ expect(visibleRangeFor('week', '2026-10-18')).toEqual({
167
+ desde: '2026-10-12',
168
+ hasta: '2026-10-18',
169
+ });
170
+ // day = el cursor; agenda = ventana fija de 30 días
171
+ expect(visibleRangeFor('day', MONDAY)).toEqual({ desde: MONDAY, hasta: MONDAY });
172
+ expect(visibleRangeFor('agenda', MONDAY)).toEqual({ desde: MONDAY, hasta: '2026-11-10' });
173
+ });
174
+
175
+ it('la grilla del mes tiene 42 días lunes-primero y contiene el día 1', () => {
176
+ const grid = monthGridDays('2026-10-15');
177
+ expect(grid).toHaveLength(42);
178
+ expect(grid[0]).toBe(MONTH_GRID_START);
179
+ expect(grid[41]).toBe(MONTH_GRID_END);
180
+ expect(grid).toContain('2026-10-01');
181
+ expect(parseDay(grid[0])! % (7 * 86_400_000)).toBe(Date.UTC(1970, 0, 5)); // lunes
182
+ });
183
+
184
+ it('la semana del cursor son 7 días lunes-primero', () => {
185
+ expect(weekDays(MONDAY)).toHaveLength(7);
186
+ expect(weekDays(MONDAY)[0]).toBe(MONDAY);
187
+ });
188
+
189
+ it('shiftMonths acota el día al largo del mes destino', () => {
190
+ expect(formatDay(shiftMonths(Date.UTC(2026, 0, 31), 1))).toBe('2026-02-28');
191
+ });
192
+
193
+ it('shiftCursor avanza una unidad por modo (agenda: 30 días)', () => {
194
+ expect(shiftCursor('day', MONDAY, 1)).toBe('2026-10-13');
195
+ expect(shiftCursor('week', MONDAY, -1)).toBe('2026-10-05');
196
+ expect(shiftCursor('month', MONDAY, 1)).toBe('2026-11-12');
197
+ expect(shiftCursor('agenda', MONDAY, 1)).toBe('2026-11-11');
198
+ });
199
+
200
+ it('los rótulos se construyen con partes civiles', () => {
201
+ expect(rangeLabelFor('month', '2026-10-15')).toBe('octubre de 2026');
202
+ expect(rangeLabelFor('week', MONDAY)).toBe('12 oct – 18 oct 2026');
203
+ expect(agendaDayLabel(MONDAY)).toContain('12 de octubre de 2026');
204
+ });
205
+ });
206
+
207
+ // ─────────────────────────────────────────────────────────────
208
+ // Pure model — row → event mapping
209
+ // ─────────────────────────────────────────────────────────────
210
+
211
+ describe('calendar-model — mapCalendarRows', () => {
212
+ it('mapea una fila válida a un evento con clave, título, color y span real', () => {
213
+ const row: SourceRow = {
214
+ slug: 'r1',
215
+ name: 'Demo',
216
+ start: '2026-10-10',
217
+ end: '2026-10-12',
218
+ status: 'confirmado',
219
+ };
220
+ const mapping = mapCalendarRows([row], BASE_CONFIG, { keyField: 'slug' });
221
+ expect(mapping.undated).toHaveLength(0);
222
+ expect(mapping.invalid).toHaveLength(0);
223
+ expect(mapping.events).toHaveLength(1);
224
+ expect(mapping.events[0]).toMatchObject({
225
+ key: 'r1',
226
+ title: 'Demo',
227
+ startDay: '2026-10-10',
228
+ endDay: '2026-10-12',
229
+ color: 'confirmado',
230
+ });
231
+ });
232
+
233
+ it('una fila sin inicio válido queda en «undated», nunca oculta', () => {
234
+ const rows: SourceRow[] = [
235
+ { slug: 'r2', name: 'Sin fecha', start: '' },
236
+ { slug: 'r3', name: 'Basura', start: 'no-date' },
237
+ ];
238
+ const mapping = mapCalendarRows(rows, BASE_CONFIG, { keyField: 'slug' });
239
+ expect(mapping.events).toHaveLength(0);
240
+ expect(mapping.undated.map((row) => row['slug'])).toEqual(['r2', 'r3']);
241
+ expect(REASON_UNDATED.length).toBeGreaterThan(0);
242
+ });
243
+
244
+ it('fin < inicio queda como intervalo inválido, no como evento', () => {
245
+ const row: SourceRow = { slug: 'r4', name: 'Mal', start: '2026-10-12', end: '2026-10-10' };
246
+ const mapping = mapCalendarRows([row], BASE_CONFIG, { keyField: 'slug' });
247
+ expect(mapping.events).toHaveLength(0);
248
+ expect(mapping.invalid).toHaveLength(1);
249
+ expect(mapping.invalid[0].reason).toBe(REASON_END_BEFORE_START);
250
+ });
251
+
252
+ it('un endField de tipo time no span días (queda en el día de inicio)', () => {
253
+ const timeConfig: CalendarViewConfig = { ...BASE_CONFIG, endField: 'end_hour' };
254
+ const row: SourceRow = { slug: 'r5', name: 'Turno', start: '2026-10-12', end_hour: '18:30' };
255
+ const mapping = mapCalendarRows([row], timeConfig, {
256
+ keyField: 'slug',
257
+ endFieldType: 'time',
258
+ });
259
+ expect(mapping.events[0].endDay).toBe('2026-10-12');
260
+ });
261
+
262
+ it('un endField date vacío es evento de un solo día', () => {
263
+ const row: SourceRow = { slug: 'r6', name: 'Un día', start: '2026-10-12', end: '' };
264
+ const mapping = mapCalendarRows([row], BASE_CONFIG, { keyField: 'slug' });
265
+ expect(mapping.events[0].endDay).toBe('2026-10-12');
266
+ expect(mapping.invalid).toHaveLength(0);
267
+ });
268
+
269
+ it('un fin ilegible queda como intervalo inválido con motivo visible', () => {
270
+ const row: SourceRow = { slug: 'r9', name: 'Raro', start: '2026-10-12', end: 'fin?' };
271
+ const mapping = mapCalendarRows([row], BASE_CONFIG, { keyField: 'slug' });
272
+ expect(mapping.events).toHaveLength(0);
273
+ expect(mapping.invalid[0].reason).toBe(REASON_INVALID_END);
274
+ });
275
+
276
+ it('pinta un intervalo multi-día en cada día que toca, acotado al rango', () => {
277
+ const row: SourceRow = { slug: 'r7', name: 'Feriado', start: '2026-10-10', end: '2026-10-12' };
278
+ const { events } = mapCalendarRows([row], BASE_CONFIG, { keyField: 'slug' });
279
+ const byDay = eventsByDay(events, { desde: '2026-10-11', hasta: '2026-10-31' });
280
+ // El día 10 queda fuera del rango: se acolcha el pintado, no el span real.
281
+ expect([...byDay.keys()]).toEqual(['2026-10-11', '2026-10-12']);
282
+ expect(events[0].startDay).toBe('2026-10-10');
283
+ expect(daysInRange({ desde: '2026-10-11', hasta: '2026-10-13' })).toEqual([
284
+ '2026-10-11',
285
+ '2026-10-12',
286
+ '2026-10-13',
287
+ ]);
288
+ });
289
+ });
290
+
291
+ // ─────────────────────────────────────────────────────────────
292
+ // Component
293
+ // ─────────────────────────────────────────────────────────────
294
+
295
+ describe('CalendarView', () => {
296
+ it('pinta el evento en el día correcto de la grilla del mes', async () => {
297
+ const { fixture, component, el } = await create({
298
+ rows: [
299
+ { slug: 'e1', name: 'Lanzamiento', start: '2026-10-15' },
300
+ { slug: 'e2', name: 'Charla', start: '2026-10-05', end: '2026-10-07' },
301
+ ],
302
+ });
303
+ component.cursor.set(MONDAY);
304
+ await settle(fixture);
305
+
306
+ const cell = (day: string): string =>
307
+ el.querySelector(`[data-day="${day}"]`)?.textContent ?? '';
308
+ expect(cell('2026-10-15')).toContain('Lanzamiento');
309
+ expect(cell('2026-10-16')).not.toContain('Lanzamiento');
310
+ // intervalo multi-día pintado en cada día que toca
311
+ expect(cell('2026-10-05')).toContain('Charla');
312
+ expect(cell('2026-10-06')).toContain('Charla');
313
+ expect(cell('2026-10-07')).toContain('Charla');
314
+ expect(cell('2026-10-08')).not.toContain('Charla');
315
+ // días fuera del mes deprimidos visualmente
316
+ expect(el.querySelector('[data-day="2026-09-28"]')?.className).toContain('opacity-40');
317
+ });
318
+
319
+ it('muestra el skeleton mientras la fuente no responde', async () => {
320
+ const { el } = await create({ rows: 'pending' });
321
+ expect(el.querySelector('app-skeleton')).not.toBeNull();
322
+ expect(el.querySelector('section')?.getAttribute('aria-busy')).toBe('true');
323
+ });
324
+
325
+ it('pinta el estado de error con el mensaje mapeado', async () => {
326
+ const { el } = await create({ rows: 'reject' });
327
+ expect(el.querySelector('app-empty-state')?.textContent).toContain(
328
+ 'No se pudo cargar el calendario',
329
+ );
330
+ expect(el.textContent).toContain('Fallo del servidor');
331
+ });
332
+
333
+ it('pinta el estado vacío cuando no hay eventos válidos', async () => {
334
+ const { el } = await create({ rows: [] });
335
+ expect(el.textContent).toContain('Sin eventos');
336
+ });
337
+
338
+ it('muestra la sección visible de registros sin fecha válida', async () => {
339
+ const { el } = await create({
340
+ rows: [
341
+ { slug: 'x1', name: 'Sin fecha', start: '' },
342
+ { slug: 'x2', name: 'Mal rango', start: '2026-10-12', end: '2026-10-01' },
343
+ ],
344
+ });
345
+ const section = el.querySelector('[aria-label="Registros sin fecha válida"]');
346
+ expect(section).not.toBeNull();
347
+ expect(el.textContent).toContain('Sin fecha');
348
+ expect(el.textContent).toContain('Mal rango');
349
+ expect(el.textContent).toContain(REASON_UNDATED);
350
+ expect(el.textContent).toContain(REASON_END_BEFORE_START);
351
+ });
352
+
353
+ it('navega el rango visible con previous/next/today', async () => {
354
+ const { fixture, component, el } = await create({ rows: [] });
355
+ const label = (): string => el.querySelector('h2')?.textContent ?? '';
356
+
357
+ component.cursor.set(MONDAY);
358
+ await settle(fixture);
359
+ expect(label()).toContain('octubre de 2026');
360
+
361
+ component.next();
362
+ await settle(fixture);
363
+ expect(label()).toContain('noviembre de 2026');
364
+
365
+ component.previous();
366
+ component.previous();
367
+ await settle(fixture);
368
+ expect(label()).toContain('septiembre de 2026');
369
+
370
+ component.today();
371
+ await settle(fixture);
372
+ expect(label()).toContain(rangeLabelFor('month', '2026-10-01'));
373
+ });
374
+
375
+ it('cambia la vista al elegir otro modo', async () => {
376
+ const { fixture, component, el } = await create({
377
+ rows: [{ slug: 'e1', name: 'Lanzamiento', start: '2026-10-15' }],
378
+ });
379
+ component.cursor.set(MONDAY);
380
+ await settle(fixture);
381
+ const label = (): string => el.querySelector('h2')?.textContent ?? '';
382
+ const dayCells = (): number => el.querySelectorAll('[data-day]').length;
383
+
384
+ expect(dayCells()).toBe(42); // mes: grilla de 6 semanas
385
+
386
+ component.setMode('week');
387
+ await settle(fixture);
388
+ expect(dayCells()).toBe(7);
389
+ expect(label()).toBe('12 oct – 18 oct 2026');
390
+
391
+ // El evento cae el 15: para verlo en modo Día el cursor debe estar ahí.
392
+ component.setMode('day');
393
+ component.cursor.set('2026-10-15');
394
+ await settle(fixture);
395
+ expect(dayCells()).toBe(1);
396
+ expect(el.textContent).toContain('Lanzamiento');
397
+
398
+ component.setMode('agenda');
399
+ await settle(fixture);
400
+ expect(label()).toBe('15 oct – 13 nov 2026');
401
+ // agenda agrupa por día con encabezado; sólo días con eventos
402
+ const headings = [...el.querySelectorAll('ol li h3')].map((h) => h.textContent ?? '');
403
+ expect(headings).toHaveLength(1);
404
+ expect(headings[0]).toContain('15 de octubre de 2026');
405
+ });
406
+
407
+ it('expone los botones accesibles de navegación y modo', async () => {
408
+ const { el } = await create({ rows: [] });
409
+ expect(el.querySelector('[aria-label="Período anterior"]')).not.toBeNull();
410
+ expect(el.querySelector('[aria-label="Período siguiente"]')).not.toBeNull();
411
+ const pressed = [...el.querySelectorAll('[aria-pressed]')].map((b) => ({
412
+ label: b.textContent?.trim(),
413
+ pressed: b.getAttribute('aria-pressed'),
414
+ }));
415
+ expect(pressed).toEqual([
416
+ { label: 'Mes', pressed: 'true' },
417
+ { label: 'Semana', pressed: 'false' },
418
+ { label: 'Día', pressed: 'false' },
419
+ { label: 'Agenda', pressed: 'false' },
420
+ ]);
421
+ });
422
+
423
+ it('pinta la config inválida como estado vacío, sin pedir filas', async () => {
424
+ const { el, rowsMock } = await create({ config: null });
425
+ expect(el.textContent).toContain('Configuración inválida');
426
+ expect(rowsMock).not.toHaveBeenCalled();
427
+ });
428
+
429
+ it('sin ningún schema en route data pinta «Configuración inválida», no el grid', async () => {
430
+ // Regression the old spec missed: the shell mounts views via
431
+ // `loadComponent` (no input bindings), so `input.required` never received
432
+ // a schema in production. With neither `schema` nor `resourceId` in route
433
+ // data there is nothing to resolve, and the only honest render is the
434
+ // invalid-config empty state (no grid, no fetch).
435
+ const rowsMock = vi.fn(() => Promise.resolve<SourceRow[]>([]));
436
+ await TestBed.configureTestingModule({
437
+ imports: [CalendarView],
438
+ providers: [
439
+ { provide: ActivatedRoute, useValue: { data: of({ uiView: 'agenda' }) } },
440
+ { provide: DataSourceService, useValue: { rows: rowsMock } },
441
+ { provide: CALENDAR_TODAY, useValue: () => '2026-10-01' },
442
+ ],
443
+ }).compileComponents();
444
+ const fixture = TestBed.createComponent(CalendarView);
445
+ fixture.detectChanges();
446
+ await fixture.whenStable();
447
+ fixture.detectChanges();
448
+ expect(fixture.nativeElement.textContent).toContain('Configuración inválida');
449
+ expect(rowsMock).not.toHaveBeenCalled();
450
+ });
451
+ });
452
+
453
+ // ─────────────────────────────────────────────────────────────
454
+ // Optimization branch — desde/hasta ctx
455
+ // ─────────────────────────────────────────────────────────────
456
+
457
+ describe('CalendarView — rango backend (desde/hasta)', () => {
458
+ it('pasa desde/hasta como ctx cuando la fuente declara view', async () => {
459
+ const { component, fixture, rowsMock } = await create({
460
+ rows: [],
461
+ config: {
462
+ ...BASE_CONFIG,
463
+ source: {
464
+ resource: 'oth',
465
+ view: 'por_rango',
466
+ params: { desde: '{desde}', hasta: '{hasta}' },
467
+ },
468
+ },
469
+ });
470
+ component.cursor.set(MONDAY);
471
+ await settle(fixture);
472
+ expect(rowsMock).toHaveBeenLastCalledWith(expect.objectContaining({ view: 'por_rango' }), {
473
+ desde: MONTH_GRID_START,
474
+ hasta: MONTH_GRID_END,
475
+ });
476
+ });
477
+
478
+ it('no pasa ctx de rango cuando la fuente no declara view', async () => {
479
+ const { component, fixture, rowsMock } = await create({
480
+ rows: [],
481
+ config: { ...BASE_CONFIG, source: { resource: 'oth' } },
482
+ });
483
+ component.cursor.set(MONDAY);
484
+ await settle(fixture);
485
+ expect(rowsMock).toHaveBeenLastCalledWith(
486
+ expect.objectContaining({ resource: 'oth' }),
487
+ undefined,
488
+ );
489
+ });
490
+
491
+ it('pide el listado del propio recurso cuando no declara source', async () => {
492
+ // With no `source`, the component falls back to the resource's own
493
+ // listing. The schema travels by route data (fixture), so the fetch asks
494
+ // for `{ resource: <schema.id> }` with no range ctx.
495
+ const { rowsMock } = await create({ rows: [] });
496
+ expect(rowsMock).toHaveBeenCalledWith({ resource: 'cal_events' }, undefined);
497
+ });
498
+ });
499
+
500
+ // ─────────────────────────────────────────────────────────────
501
+ // R9.9.4 · decision table — the event is inert until a binding declares it
502
+ // ─────────────────────────────────────────────────────────────
503
+
504
+ describe('CalendarView — contrato de selección declarado (R9.9.4)', () => {
505
+ const ROUTE: SelectionBinding = {
506
+ on: 'open',
507
+ mode: 'route',
508
+ target: { resource: 'orders', view: 'detail', params: ['key'] },
509
+ };
510
+ const ROWS: SourceRow[] = [
511
+ { slug: 'ev-1', name: 'Reunión', start: '2026-10-10', end: '2026-10-10' },
512
+ ];
513
+
514
+ function primerEvento(el: HTMLElement): HTMLElement | null {
515
+ return el.querySelector('[data-day] li[data-event-key]') as HTMLElement | null;
516
+ }
517
+
518
+ it("mode:'route' ⇒ el evento emite con la acción declarada", async () => {
519
+ const emit = vi.fn();
520
+ const h = await create({ rows: ROWS, data: { select: ROUTE, emit } });
521
+
522
+ const li = primerEvento(h.el);
523
+ expect(li, 'el evento debe estar pintado en el grid del mes').not.toBeNull();
524
+ li?.click();
525
+
526
+ expect(emit).toHaveBeenCalledTimes(1);
527
+ expect(emit).toHaveBeenCalledWith({
528
+ resource: 'cal_events',
529
+ key: 'ev-1',
530
+ action: 'open',
531
+ });
532
+ });
533
+
534
+ it("mode:'none' ⇒ el evento permanece inerte", async () => {
535
+ const emit = vi.fn();
536
+ const h = await create({
537
+ rows: ROWS,
538
+ data: { select: { on: 'open', mode: 'none' }, emit },
539
+ });
540
+
541
+ primerEvento(h.el)?.click();
542
+
543
+ expect(emit).not.toHaveBeenCalled();
544
+ });
545
+
546
+ it('sin select declarado ⇒ el evento no emite nada (comportamiento actual)', async () => {
547
+ const emit = vi.fn();
548
+ const h = await create({ rows: ROWS, data: { emit } });
549
+
550
+ primerEvento(h.el)?.click();
551
+
552
+ expect(emit).not.toHaveBeenCalled();
553
+ expect(
554
+ h.el.querySelector('[data-day] li[data-event-key]')?.hasAttribute('data-selectable'),
555
+ ).toBe(false);
556
+ });
557
+ });