@tinya-code/gas-admin-ui 0.2.0-alpha.13 → 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 (368) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +3 -15
  3. package/src/lib/actions/action-registry.spec.ts +52 -0
  4. package/src/lib/actions/action-registry.ts +126 -0
  5. package/src/lib/actions/action-service.spec.ts +215 -0
  6. package/src/lib/actions/action-service.ts +87 -0
  7. package/src/lib/actions/descriptors.spec.ts +155 -0
  8. package/src/lib/actions/descriptors.ts +154 -0
  9. package/src/lib/columns/column-cell.ts +33 -0
  10. package/src/lib/columns/column-defaults.spec.ts +99 -0
  11. package/src/lib/columns/column-defaults.ts +84 -0
  12. package/src/lib/columns/column-format.spec.ts +274 -0
  13. package/src/lib/columns/descriptors.ts +113 -0
  14. package/src/lib/columns/format-badge.ts +51 -0
  15. package/src/lib/columns/format-boolean.ts +58 -0
  16. package/src/lib/columns/format-currency.ts +30 -0
  17. package/src/lib/columns/format-date.ts +35 -0
  18. package/src/lib/columns/format-number.ts +32 -0
  19. package/src/lib/columns/format-text.ts +23 -0
  20. package/src/lib/columns/format-thumbnail.ts +63 -0
  21. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  22. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  23. package/src/lib/core/guards/auth.guard.ts +48 -0
  24. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  25. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  27. package/src/lib/core/models/api.model.ts +104 -0
  28. package/src/lib/core/models/schema-descriptions.ts +118 -0
  29. package/src/lib/core/models/schema.model.ts +1097 -0
  30. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  31. package/src/lib/core/registry/composition-validator.ts +270 -0
  32. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  33. package/src/lib/core/registry/field-validators.ts +440 -0
  34. package/src/lib/core/registry/render-host.spec.ts +208 -0
  35. package/src/lib/core/registry/render-host.ts +646 -0
  36. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  37. package/src/lib/core/registry/render-registry.ts +312 -0
  38. package/src/lib/core/registry/schema-catalog.ts +156 -0
  39. package/src/lib/core/services/api.service.spec.ts +1142 -0
  40. package/src/lib/core/services/api.service.ts +371 -0
  41. package/src/lib/core/services/auth.service.spec.ts +653 -0
  42. package/src/lib/core/services/auth.service.ts +503 -0
  43. package/src/lib/core/services/config.service.spec.ts +121 -0
  44. package/src/lib/core/services/config.service.ts +119 -0
  45. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  46. package/src/lib/core/services/data-source.service.ts +142 -0
  47. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  48. package/src/lib/core/services/error-mapper.service.ts +49 -0
  49. package/src/lib/core/services/notification.service.ts +73 -0
  50. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  51. package/src/lib/core/services/theme.service.spec.ts +106 -0
  52. package/src/lib/core/services/theme.service.ts +86 -0
  53. package/src/lib/core/services/transport.spec.ts +198 -0
  54. package/src/lib/core/services/transport.ts +164 -0
  55. package/src/lib/core/services/upload.service.ts +81 -0
  56. package/src/lib/core/testing/categories.schema.ts +125 -0
  57. package/src/lib/core/testing/orders.schema.ts +372 -0
  58. package/src/lib/core/testing/products.schema.ts +258 -0
  59. package/src/lib/core/testing/site.schema.ts +195 -0
  60. package/src/lib/core/testing/test-catalog.ts +21 -0
  61. package/src/lib/core/tokens.ts +112 -0
  62. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  63. package/src/lib/core/utils/clipboard.ts +90 -0
  64. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  65. package/src/lib/core/utils/context-resolver.ts +101 -0
  66. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  67. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  68. package/src/lib/core/utils/source-options.spec.ts +96 -0
  69. package/src/lib/core/utils/source-options.ts +54 -0
  70. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  71. package/src/lib/core/utils/template-interpolator.ts +56 -0
  72. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  74. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  75. package/src/lib/fields/boolean/boolean.ts +40 -0
  76. package/src/lib/fields/color/color.descriptor.ts +12 -0
  77. package/src/lib/fields/color/color.ts +34 -0
  78. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  79. package/src/lib/fields/currency/currency.ts +67 -0
  80. package/src/lib/fields/date/date.descriptor.ts +12 -0
  81. package/src/lib/fields/date/date.ts +36 -0
  82. package/src/lib/fields/descriptors.ts +122 -0
  83. package/src/lib/fields/display/display-badge.ts +65 -0
  84. package/src/lib/fields/display/display-date.ts +39 -0
  85. package/src/lib/fields/display/display-defaults.ts +84 -0
  86. package/src/lib/fields/display/display-file.ts +41 -0
  87. package/src/lib/fields/display/display-image.ts +54 -0
  88. package/src/lib/fields/display/display-list.ts +107 -0
  89. package/src/lib/fields/display/display-number.ts +41 -0
  90. package/src/lib/fields/display/display-text.ts +42 -0
  91. package/src/lib/fields/display/display.spec.ts +242 -0
  92. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  93. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  94. package/src/lib/fields/email/email.descriptor.ts +12 -0
  95. package/src/lib/fields/email/email.ts +28 -0
  96. package/src/lib/fields/field-aria.ts +60 -0
  97. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  98. package/src/lib/fields/field-host/field-host.ts +47 -0
  99. package/src/lib/fields/field-node.ts +88 -0
  100. package/src/lib/fields/file/file.descriptor.ts +14 -0
  101. package/src/lib/fields/file/file.spec.ts +134 -0
  102. package/src/lib/fields/file/file.ts +281 -0
  103. package/src/lib/fields/group/group.descriptor.ts +21 -0
  104. package/src/lib/fields/group/group.ts +110 -0
  105. package/src/lib/fields/image/image.descriptor.ts +12 -0
  106. package/src/lib/fields/image/image.spec.ts +117 -0
  107. package/src/lib/fields/image/image.ts +281 -0
  108. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  109. package/src/lib/fields/key-value/key-value.ts +283 -0
  110. package/src/lib/fields/list/list.descriptor.ts +21 -0
  111. package/src/lib/fields/list/list.ts +376 -0
  112. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  113. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  114. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  115. package/src/lib/fields/number/number.descriptor.ts +12 -0
  116. package/src/lib/fields/number/number.ts +40 -0
  117. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  118. package/src/lib/fields/phone/phone.ts +46 -0
  119. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  120. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  121. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  122. package/src/lib/fields/relation/relation.spec.ts +193 -0
  123. package/src/lib/fields/relation/relation.ts +224 -0
  124. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  125. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  126. package/src/lib/fields/select/radio-mode.ts +25 -0
  127. package/src/lib/fields/select/select.descriptor.ts +12 -0
  128. package/src/lib/fields/select/select.spec.ts +162 -0
  129. package/src/lib/fields/select/select.ts +210 -0
  130. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  131. package/src/lib/fields/slug/slug.ts +67 -0
  132. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  133. package/src/lib/fields/string-list/string-list.ts +222 -0
  134. package/src/lib/fields/text/text.descriptor.ts +12 -0
  135. package/src/lib/fields/text/text.ts +35 -0
  136. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  137. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  138. package/src/lib/fields/textarea/textarea.ts +126 -0
  139. package/src/lib/fields/time/time.descriptor.ts +12 -0
  140. package/src/lib/fields/time/time.ts +28 -0
  141. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  142. package/src/lib/fields/url/url.descriptor.ts +12 -0
  143. package/src/lib/fields/url/url.ts +53 -0
  144. package/src/lib/gas-admin-ui.spec.ts +21 -0
  145. package/src/lib/gas-admin-ui.ts +9 -0
  146. package/src/lib/layouts/descriptors.ts +9 -0
  147. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  148. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  149. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  150. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  151. package/src/lib/pages/form-view/form-model.ts +88 -0
  152. package/src/lib/pages/form-view/form-schema.ts +255 -0
  153. package/src/lib/pages/form-view/form-view.ts +1622 -0
  154. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  155. package/src/lib/pages/list-view/list-view.ts +882 -0
  156. package/src/lib/pages/login/login.spec.ts +109 -0
  157. package/src/lib/pages/login/login.ts +227 -0
  158. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  159. package/src/lib/shared/components/badge/badge.ts +35 -0
  160. package/src/lib/shared/components/button/button.ts +65 -0
  161. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  162. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  163. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  164. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  165. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  166. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  167. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  168. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  169. package/src/lib/shared/components/modal/modal.ts +145 -0
  170. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  172. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  173. package/src/lib/shared/components/toast/toast.ts +58 -0
  174. package/src/lib/shared/utils/col-span.ts +25 -0
  175. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  176. package/src/lib/shared/utils/depends-on.ts +48 -0
  177. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  178. package/src/lib/shared/utils/field-path.ts +49 -0
  179. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  180. package/src/lib/shared/utils/seed.spec.ts +64 -0
  181. package/src/lib/shared/utils/seed.ts +149 -0
  182. package/src/lib/shared/utils/slugify.ts +13 -0
  183. package/src/lib/shared/utils/validators.ts +41 -0
  184. package/src/lib/shell/layout/layout.ts +63 -0
  185. package/src/lib/shell/shell.routes.spec.ts +59 -0
  186. package/src/lib/shell/shell.routes.ts +122 -0
  187. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  188. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  189. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  190. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  191. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  192. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  193. package/src/lib/views/composer/composer-host.ts +248 -0
  194. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  195. package/src/lib/views/composer/view-context.spec.ts +117 -0
  196. package/src/lib/views/composer/view-context.ts +109 -0
  197. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  198. package/src/lib/views/composer/view-slot.ts +178 -0
  199. package/src/lib/views/descriptors.ts +46 -0
  200. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  201. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  202. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  203. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  206. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  207. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  208. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  209. package/src/lib/widgets/descriptors.ts +29 -0
  210. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  211. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  212. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  213. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  216. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  217. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  218. package/src/lib/widgets/record-list/record-list.ts +115 -0
  219. package/src/lib/widgets/series.spec.ts +42 -0
  220. package/src/lib/widgets/series.ts +56 -0
  221. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  222. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  223. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  224. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  225. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  226. package/src/public-api.ts +104 -0
  227. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  228. package/tsconfig.lib.json +13 -0
  229. package/tsconfig.lib.prod.json +11 -0
  230. package/tsconfig.spec.json +10 -0
  231. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  233. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  235. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  237. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  239. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  241. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  243. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  245. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  247. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  249. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  251. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  253. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  255. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  257. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  259. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  261. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  263. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  265. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  267. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  269. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  271. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  273. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  275. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  277. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  279. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  281. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  283. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  285. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  287. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  289. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  291. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  293. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  295. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  297. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  299. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  301. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  303. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  305. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  307. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  309. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  311. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  313. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  315. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  317. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  319. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  321. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  323. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  325. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  327. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  329. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  333. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  335. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  337. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  338. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  340. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  342. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  344. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  346. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  348. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  350. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  352. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  354. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  356. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  358. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  360. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  362. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  364. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  366. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  368. 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
+ });