@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,21 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { GasAdminUi } from './gas-admin-ui';
3
+
4
+ describe('GasAdminUi', () => {
5
+ let component: GasAdminUi;
6
+ let fixture: ComponentFixture<GasAdminUi>;
7
+
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [GasAdminUi],
11
+ }).compileComponents();
12
+
13
+ fixture = TestBed.createComponent(GasAdminUi);
14
+ component = fixture.componentInstance;
15
+ await fixture.whenStable();
16
+ });
17
+
18
+ it('should create', () => {
19
+ expect(component).toBeTruthy();
20
+ });
21
+ });
@@ -0,0 +1,9 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ imports: [],
5
+ selector: 'gas-gas-admin-ui',
6
+ styles: ``,
7
+ template: ` <p>gas-admin-ui works!</p> `,
8
+ })
9
+ export class GasAdminUi {}
@@ -0,0 +1,9 @@
1
+ import type { LayoutDescriptor } from '../core/registry/render-registry';
2
+
3
+ export const layoutDescriptors: readonly LayoutDescriptor[] = [
4
+ { kind: 'layout', type: 'grid', since: '9.2' },
5
+ { kind: 'layout', type: 'tabs', since: '9.2' },
6
+ { kind: 'layout', type: 'sections', since: '9.2' },
7
+ { kind: 'layout', type: 'steps', since: '9.2' },
8
+ { kind: 'layout', type: 'columns', since: '9.2' },
9
+ ];
@@ -0,0 +1,167 @@
1
+ import { Component, computed, inject, input, resource } from '@angular/core';
2
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
3
+ import { catchError, of, switchMap } from 'rxjs';
4
+
5
+ import type { DataSource, ResourceSchema, WidgetSchema } from '../../core/models/schema.model';
6
+ import { ApiService } from '../../core/services/api.service';
7
+ import { DataSourceService, type SourceRow } from '../../core/services/data-source.service';
8
+ import { EmptyState } from '../../shared/components/empty-state/empty-state';
9
+ import { WidgetHost } from '../../widgets/widget-host/widget-host';
10
+
11
+ /**
12
+ * Panel de métricas — tercer `kind` del registry (refactormotor.md F4).
13
+ *
14
+ * Sólo lectura: no hay CRUD y los datos llegan de un endpoint declarativo
15
+ * (Etapa 2, S4). Los widgets se declaran en `widgets` del schema — no en
16
+ * `fields`, porque un KPI no es un campo (ver `ResourceSchema.widgets`).
17
+ *
18
+ * Dos formas de dato, la que declare cada widget (F8 · R6.5):
19
+ *
20
+ * - **payload**: el `key` del widget es su clave en la respuesta del
21
+ * endpoint; `source: { kind: 'payload' }` lee la respuesta entera.
22
+ * - **fuente declarada** (`source: { resource, view?, params? }`): el widget
23
+ * pide sus filas al `DataSourceService` y deja de depender de que un
24
+ * handler haya armado `data[key]`. Acá se piden TODAS juntas, así que un
25
+ * panel con seis fuentes iguales hace una vuelta, no seis (la caché del
26
+ * servicio es compartida con relations y selects).
27
+ */
28
+ @Component({
29
+ selector: 'app-dashboard',
30
+ imports: [EmptyState, WidgetHost],
31
+ template: `
32
+ <header class="mb-6">
33
+ <h1 class="font-display text-2xl font-semibold">{{ schema().labelPlural }}</h1>
34
+ <p class="text-sm text-neutral">Dashboard · {{ widgets().length }} widgets</p>
35
+ </header>
36
+
37
+ @if (falloFuentes()) {
38
+ <!-- Degradación visible (F9): sin este aviso el fallo de una fuente
39
+ se confundiría con «el panel no tiene datos». -->
40
+ <p class="mb-4 text-sm text-danger" role="alert">
41
+ No se pudieron cargar los datos de algunos widgets.
42
+ </p>
43
+ }
44
+
45
+ @if (widgets().length === 0) {
46
+ <app-empty-state
47
+ title="Sin widgets declarados"
48
+ message="Los widgets se declaran en la propiedad widgets del schema del dashboard."
49
+ />
50
+ } @else {
51
+ <div class="grid grid-cols-12 gap-4">
52
+ @for (widget of widgets(); track widget.key) {
53
+ <app-widget-host [widget]="widget" [data]="datum(widget)" [loading]="cargando()" />
54
+ }
55
+ </div>
56
+ }
57
+ `,
58
+ })
59
+ export class Dashboard {
60
+ private readonly api = inject(ApiService, { optional: true });
61
+ private readonly ds = inject(DataSourceService);
62
+
63
+ /** Descriptor del dashboard (lo resuelve `ResourcePage` contra el registry). */
64
+ readonly schema = input.required<ResourceSchema>();
65
+
66
+ /**
67
+ * Respuesta del endpoint de métricas indexada por `key` de widget.
68
+ * Si se pasa explícitamente (como en tests con fixtures), se usa directamente;
69
+ * de lo contrario se obtienen los datos de forma reactiva desde el endpoint `get`.
70
+ */
71
+ readonly data = input<Record<string, unknown> | null>(null);
72
+
73
+ private readonly fetchedData = toSignal(
74
+ toObservable(this.schema).pipe(
75
+ switchMap((schema) => {
76
+ const getEndpoint = schema.endpoint?.get;
77
+ // `of({})` = SIN get contratado: ya hay respuesta (vacía). El
78
+ // `null` inicial del signal en cambio marca «todavía viaja».
79
+ if (!getEndpoint || !this.api) return of<Record<string, unknown>>({});
80
+ return this.api
81
+ .request<Record<string, unknown>>('GET', getEndpoint)
82
+ .pipe(catchError(() => of<Record<string, unknown>>({})));
83
+ }),
84
+ ),
85
+ { initialValue: null },
86
+ );
87
+
88
+ protected readonly effectiveData = computed<Record<string, unknown>>(() => {
89
+ const override = this.data();
90
+ if (override !== null && override !== undefined) return override;
91
+ return this.fetchedData() ?? {};
92
+ });
93
+
94
+ protected readonly widgets = computed(() => this.schema().widgets ?? []);
95
+
96
+ // ── Fuentes declaradas (F8 · R6.5) ──────────────────────────────────────
97
+
98
+ /**
99
+ * Claves de los widgets con fuente declarada, en una cadena: es el
100
+ * `params` del `resource` y sólo cambia cuando cambia el panel. `''`
101
+ * (ninguna fuente) ⇒ `undefined` ⇒ el resource queda idle y no pide nada.
102
+ */
103
+ private readonly clavesFuente = computed(() =>
104
+ this.widgets()
105
+ .map((widget) => (esFuente(widget.source) ? widget.key : ''))
106
+ .filter((key) => key !== '')
107
+ .join('|'),
108
+ );
109
+
110
+ /**
111
+ * Filas de todos los widgets con `source`, pedidas de una sola vez.
112
+ *
113
+ * Cada fila queda bajo `widget.key`, que es el mismo contrato de lectura
114
+ * que tiene el payload: `datum()` devuelve filas cuando hay fuente y la
115
+ * clave de siempre cuando no, y el descriptor del widget traduce la forma
116
+ * que necesita.
117
+ */
118
+ private readonly fuentes = resource({
119
+ params: () => (this.clavesFuente() === '' ? undefined : this.clavesFuente()),
120
+ loader: async () => {
121
+ const filas: Record<string, SourceRow[]> = {};
122
+ for (const widget of this.widgets()) {
123
+ if (!esFuente(widget.source)) continue;
124
+ filas[widget.key] = await this.ds.rows(widget.source);
125
+ }
126
+ return filas;
127
+ },
128
+ });
129
+
130
+ /** ¿Alguna fuente del panel falló? (`status()` no tira; `value()` sí.) */
131
+ protected readonly falloFuentes = computed(() => this.fuentes.status() === 'error');
132
+
133
+ /**
134
+ * Estado de carga del panel — mientras dure, cada tarjeta reserva su
135
+ * espacio con skeleton (`cargar ≠ vacío`, doc/ux-ui-mobile.md §estados).
136
+ * Cubre LOS dos caminos de dato: el payload (`null` inicial = sin
137
+ * respuesta todavía) y las fuentes declaradas (`resource` en loading;
138
+ * con params vacíos queda idle, que NO es carga).
139
+ */
140
+ protected readonly cargando = computed(() => {
141
+ const payloadViaja = this.data() === null && this.fetchedData() === null;
142
+ const fuentesViajan = this.fuentes.status() === 'loading';
143
+ return payloadViaja || fuentesViajan;
144
+ });
145
+
146
+ /** `null` explícito cuando la clave no vino: el widget muestra su vacío. */
147
+ protected datum(widget: WidgetSchema): unknown {
148
+ // Fuente declarada: el dato son SUS filas, no una clave del payload.
149
+ if (esFuente(widget.source)) {
150
+ // `value()` del resource TIRA en estado error (contract de Angular):
151
+ // se mira el estado antes de leerlo. En carga devuelve `null` igual
152
+ // que el camino de payload, que también arranca vacío.
153
+ return this.fuentes.status() === 'error'
154
+ ? null
155
+ : (this.fuentes.value()?.[widget.key] ?? null);
156
+ }
157
+ const allData = this.effectiveData();
158
+ // `source` lo declara el schema: el orquestador no sabe qué tipo es
159
+ // ni qué claves compone cada widget.
160
+ return widget.source?.kind === 'payload' ? allData : (allData[widget.key] ?? null);
161
+ }
162
+ }
163
+
164
+ /** ¿Este widget declara una fuente (`{ resource, … }`) y no el payload? */
165
+ function esFuente(source: WidgetSchema['source']): source is DataSource {
166
+ return source !== undefined && source !== null && 'resource' in source;
167
+ }
@@ -0,0 +1,333 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { provideRouter, Router } from '@angular/router';
3
+ import { NEVER, of, throwError } from 'rxjs';
4
+ import { describe, expect, it, beforeEach, vi } from 'vitest';
5
+
6
+ import type { ResourceSchema } from '../../core/models/schema.model';
7
+ import { ActionService } from '../../actions/action-service';
8
+ import { ApiService } from '../../core/services/api.service';
9
+ import { API_URL } from '../../core/tokens';
10
+ import { DetailView } from './detail-view';
11
+
12
+ describe('DetailView', () => {
13
+ let fixture: ComponentFixture<DetailView>;
14
+
15
+ const mockSchema: ResourceSchema = {
16
+ id: 'orders',
17
+ label: 'Pedido',
18
+ labelPlural: 'Pedidos',
19
+ kind: 'collection',
20
+ endpoint: { list: '/admin/orders', get: '/admin/orders/{key}' },
21
+ keyField: 'ref',
22
+ titleField: 'customer_name',
23
+ listColumns: [],
24
+ fields: [
25
+ { key: 'ref', label: 'Referencia', type: 'text' },
26
+ { key: 'customer_name', label: 'Cliente', type: 'text' },
27
+ { key: 'pickup_date', label: 'Fecha Retiro', type: 'date' },
28
+ {
29
+ key: 'status',
30
+ label: 'Estado',
31
+ type: 'select',
32
+ options: [
33
+ { value: 'PENDIENTE', label: 'Pendiente' },
34
+ { value: 'ENTREGADO', label: 'Entregado' },
35
+ ],
36
+ },
37
+ ],
38
+ detail: {
39
+ enabled: true,
40
+ headerFields: ['ref', 'status'],
41
+ allowEdit: true,
42
+ allowDelete: false,
43
+ },
44
+ };
45
+
46
+ const mockRecord = {
47
+ ref: 'ORD-42',
48
+ customer_name: 'Ana García',
49
+ pickup_date: '2026-10-10',
50
+ status: 'PENDIENTE',
51
+ };
52
+
53
+ const getSpy = vi.fn();
54
+
55
+ beforeEach(async () => {
56
+ getSpy.mockReset();
57
+ getSpy.mockImplementation(() => of(null));
58
+ await TestBed.configureTestingModule({
59
+ imports: [DetailView],
60
+ providers: [
61
+ {
62
+ provide: ApiService,
63
+ useValue: {
64
+ get: getSpy,
65
+ request: () => of(null),
66
+ },
67
+ },
68
+ // `AuthService` pide `API_URL` (convención de specs, R7.5)
69
+ { provide: API_URL, useValue: '/api' },
70
+ // R8.8: `DetailView` inyecta `Router` (trigger → confirmación +
71
+ // ruta) — convención de list-view.quick-edit.spec.
72
+ provideRouter([]),
73
+ ],
74
+ }).compileComponents();
75
+ fixture = TestBed.createComponent(DetailView);
76
+ });
77
+
78
+ /**
79
+ * Monta la vista y espera los display del registro.
80
+ *
81
+ * `fixture.whenStable()` no basta: el `load()` de cada descriptor es un
82
+ * `import()` ajeno al rastreo de Angular. Se espera acotadamente mientras
83
+ * quede algún `<app-skeleton>`; si un componente nunca monta, la espera se
84
+ * agota y falla la aserción del test (acotación, no espera infinita).
85
+ */
86
+ async function montar(fixture: ComponentFixture<DetailView>): Promise<void> {
87
+ fixture.detectChanges();
88
+ await fixture.whenStable();
89
+ for (
90
+ let intento = 0;
91
+ intento < 100 && fixture.nativeElement.querySelector('app-skeleton');
92
+ intento++
93
+ ) {
94
+ fixture.detectChanges();
95
+ await new Promise((resolve) => setTimeout(resolve, 1));
96
+ }
97
+ fixture.detectChanges();
98
+ }
99
+
100
+ it('1. renderiza la cabecera con el título del registro y los badges de headerFields', async () => {
101
+ fixture.componentRef.setInput('schema', mockSchema);
102
+ fixture.componentRef.setInput('record', mockRecord);
103
+ await montar(fixture);
104
+
105
+ const root = fixture.nativeElement as HTMLElement;
106
+ expect(root.querySelector('h1')?.textContent?.trim()).toBe('Ana García');
107
+ // badge de ref y status en la cabecera
108
+ expect(root.textContent).toContain('ORD-42');
109
+ expect(root.textContent).toContain('Pendiente');
110
+ // Los headerFields salen como chips (R4.2): `ref` en neutral y
111
+ // `status` en la clase de estado que ya usaba la ficha.
112
+ const cabeceras = root.querySelectorAll('header app-badge');
113
+ expect(cabeceras.length, 'deben ser 2 los chips de la cabecera').toBe(2);
114
+ expect(cabeceras[0].className).toContain('bg-neutral');
115
+ expect(cabeceras[1].className).toContain('bg-accent/20');
116
+ });
117
+
118
+ it('2. renderiza las secciones de campos con sus valores', async () => {
119
+ fixture.componentRef.setInput('schema', mockSchema);
120
+ fixture.componentRef.setInput('record', mockRecord);
121
+ await montar(fixture);
122
+
123
+ const root = fixture.nativeElement as HTMLElement;
124
+ expect(root.textContent).toContain('Ana García');
125
+ expect(root.textContent).toContain('2026');
126
+ // label del campo
127
+ expect(root.textContent).toContain('Referencia');
128
+ expect(root.textContent).toContain('Cliente');
129
+ });
130
+
131
+ it('3. emite el evento edit al hacer clic en el botón Editar', () => {
132
+ fixture.componentRef.setInput('schema', mockSchema);
133
+ fixture.componentRef.setInput('record', mockRecord);
134
+ fixture.detectChanges();
135
+
136
+ let emitted: Record<string, unknown> | null = null;
137
+ fixture.componentInstance.edit.subscribe((rec) => {
138
+ emitted = rec as Record<string, unknown>;
139
+ });
140
+
141
+ const root = fixture.nativeElement as HTMLElement;
142
+ const editBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('button[app-button]')).find(
143
+ (b) => b.textContent?.trim().includes('Editar'),
144
+ );
145
+ expect(editBtn, 'debe existir el botón Editar').toBeDefined();
146
+ editBtn!.click();
147
+ fixture.detectChanges();
148
+
149
+ expect(emitted).toEqual(mockRecord);
150
+ });
151
+
152
+ it('4. emite el evento back al hacer clic en el botón Volver', () => {
153
+ fixture.componentRef.setInput('schema', mockSchema);
154
+ fixture.componentRef.setInput('record', mockRecord);
155
+ fixture.detectChanges();
156
+
157
+ let emitted = false;
158
+ fixture.componentInstance.back.subscribe(() => {
159
+ emitted = true;
160
+ });
161
+
162
+ const root = fixture.nativeElement as HTMLElement;
163
+ const backBtn = Array.from(root.querySelectorAll<HTMLButtonElement>('button[app-button]')).find(
164
+ (b) => b.textContent?.trim().includes('Volver'),
165
+ );
166
+ expect(backBtn, 'debe existir el botón Volver').toBeDefined();
167
+ backBtn!.click();
168
+ fixture.detectChanges();
169
+
170
+ expect(emitted).toBe(true);
171
+ });
172
+
173
+ it('5. renderiza secciones de historial (tipo list) cuando existen', async () => {
174
+ const listSchema: ResourceSchema = {
175
+ ...mockSchema,
176
+ fields: [
177
+ ...mockSchema.fields,
178
+ {
179
+ key: 'history',
180
+ label: 'Historial',
181
+ type: 'list',
182
+ itemFields: [
183
+ { key: 'action', label: 'Acción', type: 'text' },
184
+ { key: 'detail', label: 'Detalle', type: 'text' },
185
+ ],
186
+ },
187
+ ],
188
+ };
189
+ const recordWithHistory = {
190
+ ...mockRecord,
191
+ history: [{ action: 'PEDIDO_CREADO', detail: 'Pedido registrado' }],
192
+ };
193
+ fixture.componentRef.setInput('schema', listSchema);
194
+ fixture.componentRef.setInput('record', recordWithHistory);
195
+ await montar(fixture);
196
+
197
+ const root = fixture.nativeElement as HTMLElement;
198
+ expect(root.textContent).toContain('Pedido registrado');
199
+ });
200
+
201
+ it('6. R8.8: renderiza el botón de un trigger y lo dispara vía actionService', async () => {
202
+ const triggerSchema: ResourceSchema = {
203
+ ...mockSchema,
204
+ actions: [
205
+ {
206
+ id: 'regenerate_pin',
207
+ type: 'trigger',
208
+ name: 'regenerate-pin',
209
+ label: 'Regenerar PIN',
210
+ },
211
+ ],
212
+ };
213
+ fixture.componentRef.setInput('schema', triggerSchema);
214
+ fixture.componentRef.setInput('record', mockRecord);
215
+ await montar(fixture);
216
+
217
+ const root = fixture.nativeElement as HTMLElement;
218
+ const triggerBtn = Array.from(
219
+ root.querySelectorAll<HTMLButtonElement>('button[app-button]'),
220
+ ).find((b) => b.textContent?.trim() === 'Regenerar PIN');
221
+ expect(triggerBtn, 'debe existir el botón Regenerar PIN').toBeDefined();
222
+
223
+ // El clic delega en actionService.run (la confirmación y la ruta son
224
+ // cosa del descriptor, no de la vista — R3.11).
225
+ const runSpy = vi
226
+ .spyOn(TestBed.inject(ActionService), 'run')
227
+ .mockResolvedValue({ ok: true, reload: true });
228
+ triggerBtn!.click();
229
+ fixture.detectChanges();
230
+ await fixture.whenStable();
231
+
232
+ expect(runSpy).toHaveBeenCalledTimes(1);
233
+ const [action, request] = runSpy.mock.calls[0];
234
+ expect(action).toMatchObject({ id: 'regenerate_pin', name: 'regenerate-pin' });
235
+ expect(request.resource).toBe('orders');
236
+ expect(request.keyField).toBe('ref');
237
+ });
238
+
239
+ it('7. mientras viaja el get, reserva el espacio con skeleton (cargar ≠ vacío)', () => {
240
+ // Regresión del «renderizado completo pero vacío»: con el get lento
241
+ // (Apps Script ~1s) el detalle pintaba la fila parcial con los campos
242
+ // pesados en `—`. Ahora la región de secciones queda en busy con
243
+ // skeletons con la forma de las tarjetas.
244
+ getSpy.mockImplementation(() => NEVER);
245
+
246
+ fixture.componentRef.setInput('schema', mockSchema);
247
+ fixture.componentRef.setInput('record', mockRecord);
248
+ fixture.detectChanges();
249
+
250
+ const root = fixture.nativeElement as HTMLElement;
251
+ expect(root.querySelector('[aria-busy="true"] app-skeleton'), 'skeleton visible').toBeTruthy();
252
+ // Las etiquetas de las secciones NO se pintan hasta que llegue el registro.
253
+ expect(root.textContent).not.toContain('Fecha Retiro');
254
+ // La cabecera sí: son datos reales de la fila de la lista.
255
+ expect(root.textContent).toContain('ORD-42');
256
+ });
257
+
258
+ it('8. estado de error: ofrece reintento y al reintentar pinta el registro', async () => {
259
+ getSpy.mockImplementationOnce(() => throwError(() => new Error('500')));
260
+ getSpy.mockImplementationOnce(() => of({ ...mockRecord, pickup_date: '2026-10-12' }));
261
+
262
+ fixture.componentRef.setInput('schema', mockSchema);
263
+ fixture.componentRef.setInput('record', mockRecord);
264
+ await montar(fixture);
265
+
266
+ const root = fixture.nativeElement as HTMLElement;
267
+ expect(root.textContent).toContain('No se pudo cargar el registro');
268
+
269
+ const retryBtn = Array.from(
270
+ root.querySelectorAll<HTMLButtonElement>('button[app-button]'),
271
+ ).find((b) => b.textContent?.trim().includes('Reintentar'));
272
+ expect(retryBtn, 'debe existir el botón Reintentar').toBeDefined();
273
+ retryBtn!.click();
274
+ await montar(fixture);
275
+
276
+ // El date se pinta formateado por render-host, no en crudo.
277
+ expect(root.textContent).toContain('12 oct. 2026');
278
+ expect(root.textContent).not.toContain('No se pudo cargar el registro');
279
+ });
280
+
281
+ // ── R9.9 · D4 — target load error recovery ──
282
+
283
+ it('9. D4: el estado de error ofrece «Volver» junto a «Reintentar» y navega de vuelta', async () => {
284
+ getSpy.mockImplementationOnce(() => throwError(() => new Error('boom')));
285
+ fixture.componentRef.setInput('schema', mockSchema);
286
+ fixture.componentRef.setInput('record', mockRecord);
287
+ await montar(fixture);
288
+
289
+ const root = fixture.nativeElement as HTMLElement;
290
+ expect(root.textContent).toContain('No se pudo cargar el registro');
291
+ // El estado de error es un `app-empty-state` con ambas salidas DENTRO
292
+ // (el header también tiene «Volver», así que se busca dentro del slot).
293
+ const volver = Array.from(
294
+ root.querySelectorAll<HTMLButtonElement>('app-empty-state button[app-button]'),
295
+ ).find((b) => b.textContent?.trim().includes('Volver'));
296
+ expect(volver, 'debe existir el botón Volver en el estado de error').toBeDefined();
297
+
298
+ const router = TestBed.inject(Router);
299
+ const navegar = vi.spyOn(router, 'navigate');
300
+ volver!.click();
301
+ await fixture.whenStable();
302
+ expect(navegar).toHaveBeenCalled();
303
+ });
304
+
305
+ it('10. D4: la cabecera oculta Editar/Eliminar mientras el registro está en error', async () => {
306
+ getSpy.mockImplementationOnce(() => throwError(() => new Error('boom')));
307
+ fixture.componentRef.setInput('schema', mockSchema);
308
+ fixture.componentRef.setInput('record', mockRecord);
309
+ await montar(fixture);
310
+
311
+ const root = fixture.nativeElement as HTMLElement;
312
+ expect(root.textContent).toContain('No se pudo cargar el registro');
313
+ expect(root.textContent).not.toContain('Editar');
314
+ // Reintentar ⇒ éxito ⇒ la acción vuelve a existir
315
+ getSpy.mockImplementationOnce(() => of({ ...mockRecord }));
316
+ Array.from(root.querySelectorAll<HTMLButtonElement>('button[app-button]'))
317
+ .find((b) => b.textContent?.trim().includes('Reintentar'))!
318
+ .click();
319
+ await montar(fixture);
320
+ expect((fixture.nativeElement as HTMLElement).textContent).toContain('Editar');
321
+ });
322
+
323
+ it('11. D4: el error original queda retenido con identidad intacta (diagnóstico)', async () => {
324
+ const fallo = new Error('boom de red');
325
+ getSpy.mockImplementationOnce(() => throwError(() => fallo));
326
+ fixture.componentRef.setInput('schema', mockSchema);
327
+ fixture.componentRef.setInput('record', mockRecord);
328
+ await montar(fixture);
329
+
330
+ // Identity assert: el MISMO objeto, no una copia ni un string mapeado.
331
+ expect(fixture.componentInstance.errorDeCarga()).toBe(fallo);
332
+ });
333
+ });