@antelopejs/dms 0.6.2 → 0.7.1

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 (444) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/db/models/notification-feed-filter.d.ts +0 -1
  3. package/dist/db/models/notification-feed-filter.js +0 -4
  4. package/dist/db/models/notification-feed-filter.js.map +1 -1
  5. package/dist/db/models/signInAttempts.model.d.ts +18 -0
  6. package/dist/db/models/signInAttempts.model.js +83 -12
  7. package/dist/db/models/signInAttempts.model.js.map +1 -1
  8. package/dist/db/models/userEmailChanges.model.d.ts +8 -1
  9. package/dist/db/models/userEmailChanges.model.js +27 -2
  10. package/dist/db/models/userEmailChanges.model.js.map +1 -1
  11. package/dist/db/models/userNotificationPreferences.model.d.ts +0 -2
  12. package/dist/db/models/userNotificationPreferences.model.js +0 -13
  13. package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
  14. package/dist/db/models/userNotifications.model.d.ts +5 -2
  15. package/dist/db/models/userNotifications.model.js +19 -2
  16. package/dist/db/models/userNotifications.model.js.map +1 -1
  17. package/dist/db/tables/signInAttempts.table.d.ts +2 -1
  18. package/dist/db/tables/signInAttempts.table.js +4 -0
  19. package/dist/db/tables/signInAttempts.table.js.map +1 -1
  20. package/dist/db/tables/userEmailChanges.table.d.ts +2 -1
  21. package/dist/db/tables/userEmailChanges.table.js +6 -1
  22. package/dist/db/tables/userEmailChanges.table.js.map +1 -1
  23. package/dist/db/tables/userNotificationPreferences.table.d.ts +0 -1
  24. package/dist/db/tables/userNotificationPreferences.table.js +0 -4
  25. package/dist/db/tables/userNotificationPreferences.table.js.map +1 -1
  26. package/dist/implementations/dms/nav-badges.d.ts +6 -4
  27. package/dist/implementations/dms/nav-badges.js +6 -5
  28. package/dist/implementations/dms/nav-badges.js.map +1 -1
  29. package/dist/implementations/dms/page.d.ts +1 -0
  30. package/dist/implementations/dms/page.js +46 -8
  31. package/dist/implementations/dms/page.js.map +1 -1
  32. package/dist/implementations/dms/permission-preview.d.ts +1 -1
  33. package/dist/implementations/dms/permission-preview.js +9 -4
  34. package/dist/implementations/dms/permission-preview.js.map +1 -1
  35. package/dist/implementations/dms-auth/index.d.ts +3 -2
  36. package/dist/implementations/dms-auth/index.js +39 -35
  37. package/dist/implementations/dms-auth/index.js.map +1 -1
  38. package/dist/implementations/dms-auth/token-session.d.ts +3 -0
  39. package/dist/implementations/dms-auth/token-session.js +22 -0
  40. package/dist/implementations/dms-auth/token-session.js.map +1 -0
  41. package/dist/implementations/dms-notifications/index.d.ts +0 -1
  42. package/dist/implementations/dms-notifications/index.js +0 -7
  43. package/dist/implementations/dms-notifications/index.js.map +1 -1
  44. package/dist/pages/settings/settings-index.js +3 -2
  45. package/dist/pages/settings/settings-index.js.map +1 -1
  46. package/dist/pages/settings/users/account-activity-feed.d.ts +1 -1
  47. package/dist/pages/settings/users/account-activity-feed.js +1 -1
  48. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  49. package/dist/pages/settings/users/account-activity.d.ts +1 -1
  50. package/dist/pages/settings/users/account-activity.js +1 -1
  51. package/dist/pages/settings/users/account-activity.js.map +1 -1
  52. package/dist/pages/settings/users/account-credentials.d.ts +3 -0
  53. package/dist/pages/settings/users/account-credentials.js +2 -0
  54. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  55. package/dist/pages/settings/users/current-password.d.ts +1 -1
  56. package/dist/pages/settings/users/current-password.js +23 -2
  57. package/dist/pages/settings/users/current-password.js.map +1 -1
  58. package/dist/pages/settings/users/email-change.d.ts +1 -1
  59. package/dist/pages/settings/users/email-change.js +33 -26
  60. package/dist/pages/settings/users/email-change.js.map +1 -1
  61. package/dist/pages/settings/users/invite-resend.js +0 -1
  62. package/dist/pages/settings/users/invite-resend.js.map +1 -1
  63. package/dist/pages/settings/users/invites.d.ts +1 -2
  64. package/dist/pages/settings/users/invites.js +21 -19
  65. package/dist/pages/settings/users/invites.js.map +1 -1
  66. package/dist/pages/settings/users/member-invite-batch.js +0 -1
  67. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  68. package/dist/pages/settings/users/member-invite-form.js +10 -22
  69. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  70. package/dist/pages/settings/users/member-role-options.d.ts +6 -4
  71. package/dist/pages/settings/users/member-role-options.js +15 -4
  72. package/dist/pages/settings/users/member-role-options.js.map +1 -1
  73. package/dist/pages/settings/users/members.d.ts +2 -3
  74. package/dist/pages/settings/users/members.js +7 -12
  75. package/dist/pages/settings/users/members.js.map +1 -1
  76. package/dist/pages/settings/users/notifications.d.ts +1 -8
  77. package/dist/pages/settings/users/notifications.js +6 -38
  78. package/dist/pages/settings/users/notifications.js.map +1 -1
  79. package/dist/pages/settings/users/permission-tree-nodes.d.ts +1 -1
  80. package/dist/pages/settings/users/permission-tree-nodes.js +23 -9
  81. package/dist/pages/settings/users/permission-tree-nodes.js.map +1 -1
  82. package/dist/pages/settings/users/permission-warnings.d.ts +7 -0
  83. package/dist/pages/settings/users/permission-warnings.js +42 -0
  84. package/dist/pages/settings/users/permission-warnings.js.map +1 -0
  85. package/dist/pages/settings/users/profile-helpers.d.ts +1 -3
  86. package/dist/pages/settings/users/profile-helpers.js +0 -16
  87. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  88. package/dist/pages/settings/users/role-editor-store.js +6 -3
  89. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  90. package/dist/pages/settings/users/role-editor.d.ts +2 -1
  91. package/dist/pages/settings/users/role-editor.js +8 -4
  92. package/dist/pages/settings/users/role-editor.js.map +1 -1
  93. package/dist/pages/settings/users/role-preview.js +2 -1
  94. package/dist/pages/settings/users/role-preview.js.map +1 -1
  95. package/dist/pages/settings/users/roles.js +5 -1
  96. package/dist/pages/settings/users/roles.js.map +1 -1
  97. package/dist/pages/settings/users/security-attention.d.ts +4 -0
  98. package/dist/pages/settings/users/security-attention.js +23 -7
  99. package/dist/pages/settings/users/security-attention.js.map +1 -1
  100. package/dist/pages/settings/users/security.d.ts +2 -1
  101. package/dist/pages/settings/users/security.js +13 -2
  102. package/dist/pages/settings/users/security.js.map +1 -1
  103. package/dist/pages/settings/users/two-factor-operations.d.ts +2 -1
  104. package/dist/pages/settings/users/two-factor-operations.js +28 -29
  105. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  106. package/dist/routes/auth/forgot-password.js +3 -0
  107. package/dist/routes/auth/forgot-password.js.map +1 -1
  108. package/dist/routes/auth/index.d.ts +1 -0
  109. package/dist/routes/auth/index.js +1 -0
  110. package/dist/routes/auth/index.js.map +1 -1
  111. package/dist/routes/auth/invite.js +28 -4
  112. package/dist/routes/auth/invite.js.map +1 -1
  113. package/dist/routes/auth/login-throttle.d.ts +2 -0
  114. package/dist/routes/auth/login-throttle.js +5 -4
  115. package/dist/routes/auth/login-throttle.js.map +1 -1
  116. package/dist/routes/auth/request-2fa-email.js +6 -18
  117. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  118. package/dist/routes/auth/reset-code.d.ts +3 -0
  119. package/dist/routes/auth/reset-code.js +37 -0
  120. package/dist/routes/auth/reset-code.js.map +1 -0
  121. package/dist/routes/auth/reset-password.d.ts +2 -2
  122. package/dist/routes/auth/reset-password.js +5 -10
  123. package/dist/routes/auth/reset-password.js.map +1 -1
  124. package/dist/routes/auth/signup.js +10 -32
  125. package/dist/routes/auth/signup.js.map +1 -1
  126. package/dist/routes/auth/two-factor-throttle.d.ts +5 -0
  127. package/dist/routes/auth/two-factor-throttle.js +81 -0
  128. package/dist/routes/auth/two-factor-throttle.js.map +1 -0
  129. package/dist/routes/auth/validate-forgot-password-token.js +2 -8
  130. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  131. package/dist/routes/auth/validate-invite-token.d.ts +1 -0
  132. package/dist/routes/auth/validate-invite-token.js +11 -0
  133. package/dist/routes/auth/validate-invite-token.js.map +1 -0
  134. package/dist/routes/auth/verify-2fa.js +10 -13
  135. package/dist/routes/auth/verify-2fa.js.map +1 -1
  136. package/dist/routes/auth.d.ts +1 -0
  137. package/dist/routes/auth.js +11 -1
  138. package/dist/routes/auth.js.map +1 -1
  139. package/dist/test/helpers/auth.d.ts +0 -1
  140. package/dist/test/helpers/auth.js +0 -1
  141. package/dist/test/helpers/auth.js.map +1 -1
  142. package/dist/test/helpers/emails.d.ts +10 -0
  143. package/dist/test/helpers/emails.js +36 -0
  144. package/dist/test/helpers/emails.js.map +1 -0
  145. package/dist/test/helpers/fixtures.d.ts +0 -1
  146. package/dist/test/helpers/fixtures.js +1 -2
  147. package/dist/test/helpers/fixtures.js.map +1 -1
  148. package/dist/test/helpers/page-access.d.ts +1 -0
  149. package/dist/test/helpers/page-access.js +6 -1
  150. package/dist/test/helpers/page-access.js.map +1 -1
  151. package/dist/test/integration/auth/current-password.test.d.ts +1 -0
  152. package/dist/test/integration/auth/current-password.test.js +65 -0
  153. package/dist/test/integration/auth/current-password.test.js.map +1 -0
  154. package/dist/test/integration/auth/password-reset.test.d.ts +1 -0
  155. package/dist/test/integration/auth/password-reset.test.js +126 -0
  156. package/dist/test/integration/auth/password-reset.test.js.map +1 -0
  157. package/dist/test/integration/auth/session-revocation.test.d.ts +1 -0
  158. package/dist/test/integration/auth/session-revocation.test.js +45 -0
  159. package/dist/test/integration/auth/session-revocation.test.js.map +1 -0
  160. package/dist/test/integration/auth/two-factor-codes.test.d.ts +1 -0
  161. package/dist/test/integration/auth/two-factor-codes.test.js +335 -0
  162. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -0
  163. package/dist/test/integration/auth/two-factor-password.test.js +5 -1
  164. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  165. package/dist/test/integration/auth/two-factor-token.test.d.ts +1 -0
  166. package/dist/test/integration/auth/two-factor-token.test.js +92 -0
  167. package/dist/test/integration/auth/two-factor-token.test.js.map +1 -0
  168. package/dist/test/integration/email-change.test.js +36 -0
  169. package/dist/test/integration/email-change.test.js.map +1 -1
  170. package/dist/test/integration/invite-extension-edit.test.js +0 -1
  171. package/dist/test/integration/invite-extension-edit.test.js.map +1 -1
  172. package/dist/test/integration/invite-link-status.test.d.ts +1 -0
  173. package/dist/test/integration/invite-link-status.test.js +110 -0
  174. package/dist/test/integration/invite-link-status.test.js.map +1 -0
  175. package/dist/test/integration/invite-resolution.test.js +0 -3
  176. package/dist/test/integration/invite-resolution.test.js.map +1 -1
  177. package/dist/test/integration/member-settings-access.test.js +17 -0
  178. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  179. package/dist/test/integration/notification-unread-badge.test.d.ts +1 -0
  180. package/dist/test/integration/notification-unread-badge.test.js +92 -0
  181. package/dist/test/integration/notification-unread-badge.test.js.map +1 -0
  182. package/dist/test/integration/permission-warnings.test.d.ts +1 -0
  183. package/dist/test/integration/permission-warnings.test.js +71 -0
  184. package/dist/test/integration/permission-warnings.test.js.map +1 -0
  185. package/dist/test/integration/removed-member-notifications.test.d.ts +1 -0
  186. package/dist/test/integration/removed-member-notifications.test.js +80 -0
  187. package/dist/test/integration/removed-member-notifications.test.js.map +1 -0
  188. package/dist/test/integration/role-permission-ancestors.test.d.ts +1 -0
  189. package/dist/test/integration/role-permission-ancestors.test.js +146 -0
  190. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -0
  191. package/dist/test/integration/tenant-lifecycle.test.js +0 -4
  192. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  193. package/dist/test/unit/auth-session-revocation.test.d.ts +1 -0
  194. package/dist/test/unit/auth-session-revocation.test.js +105 -0
  195. package/dist/test/unit/auth-session-revocation.test.js.map +1 -0
  196. package/dist/test/unit/db/email-normalization.test.js +0 -1
  197. package/dist/test/unit/db/email-normalization.test.js.map +1 -1
  198. package/dist/test/unit/db/notification-feed-filter.test.js +0 -16
  199. package/dist/test/unit/db/notification-feed-filter.test.js.map +1 -1
  200. package/dist/test/unit/db/notification-inbox.test.js +63 -13
  201. package/dist/test/unit/db/notification-inbox.test.js.map +1 -1
  202. package/dist/test/unit/db/notification-preferences.test.js +0 -17
  203. package/dist/test/unit/db/notification-preferences.test.js.map +1 -1
  204. package/dist/test/unit/implementations/dms/empty-menu-group.test.d.ts +1 -0
  205. package/dist/test/unit/implementations/dms/empty-menu-group.test.js +133 -0
  206. package/dist/test/unit/implementations/dms/empty-menu-group.test.js.map +1 -0
  207. package/dist/test/unit/implementations/dms/layout-banners.test.js +2 -1
  208. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
  209. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.d.ts +1 -0
  210. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js +149 -0
  211. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js.map +1 -0
  212. package/dist/test/unit/implementations/dms/nav-badges.test.js +55 -4
  213. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  214. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.d.ts +1 -0
  215. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js +118 -0
  216. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js.map +1 -0
  217. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js +16 -0
  218. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js.map +1 -1
  219. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js +5 -3
  220. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js.map +1 -1
  221. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js +29 -3
  222. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js.map +1 -1
  223. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +4 -2
  224. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -1
  225. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js +2 -1
  226. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js.map +1 -1
  227. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js +2 -1
  228. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js.map +1 -1
  229. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js +7 -2
  230. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js.map +1 -1
  231. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.d.ts +1 -0
  232. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js +133 -0
  233. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js.map +1 -0
  234. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.d.ts +1 -0
  235. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js +149 -0
  236. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js.map +1 -0
  237. package/dist/test/unit/interfaces/dms/invite-extension.test.js +13 -0
  238. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  239. package/dist/test/unit/interfaces/dms/member-access.test.js +27 -1
  240. package/dist/test/unit/interfaces/dms/member-access.test.js.map +1 -1
  241. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.d.ts +1 -0
  242. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js +31 -0
  243. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js.map +1 -0
  244. package/dist/test/unit/interfaces/dms/page-extension.test.js +4 -1
  245. package/dist/test/unit/interfaces/dms/page-extension.test.js.map +1 -1
  246. package/dist/test/unit/interfaces/dms/permission-requirements.test.d.ts +1 -0
  247. package/dist/test/unit/interfaces/dms/permission-requirements.test.js +144 -0
  248. package/dist/test/unit/interfaces/dms/permission-requirements.test.js.map +1 -0
  249. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +6 -2
  250. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
  251. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js +1 -1
  252. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js.map +1 -1
  253. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js +2 -1
  254. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js.map +1 -1
  255. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -1
  256. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  257. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js +2 -1
  258. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js.map +1 -1
  259. package/dist/test/unit/pages/settings/account-activity-feed.test.js +2 -2
  260. package/dist/test/unit/pages/settings/account-activity-feed.test.js.map +1 -1
  261. package/dist/test/unit/pages/settings/account-activity.test.js +11 -2
  262. package/dist/test/unit/pages/settings/account-activity.test.js.map +1 -1
  263. package/dist/test/unit/pages/settings/invite-email-delivery.test.js +0 -1
  264. package/dist/test/unit/pages/settings/invite-email-delivery.test.js.map +1 -1
  265. package/dist/test/unit/pages/settings/invite-email-validation-option.test.d.ts +1 -0
  266. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js +33 -0
  267. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js.map +1 -0
  268. package/dist/test/unit/pages/settings/member-lists-page-size.test.d.ts +1 -0
  269. package/dist/test/unit/pages/settings/member-lists-page-size.test.js +22 -0
  270. package/dist/test/unit/pages/settings/member-lists-page-size.test.js.map +1 -0
  271. package/dist/test/unit/pages/settings/member-table-widths.test.js +2 -2
  272. package/dist/test/unit/pages/settings/member-table-widths.test.js.map +1 -1
  273. package/dist/test/unit/pages/settings/members-page.test.js +7 -10
  274. package/dist/test/unit/pages/settings/members-page.test.js.map +1 -1
  275. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +3 -10
  276. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -1
  277. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js +57 -0
  278. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js.map +1 -1
  279. package/dist/test/unit/pages/settings/permission-warnings.test.d.ts +1 -0
  280. package/dist/test/unit/pages/settings/permission-warnings.test.js +49 -0
  281. package/dist/test/unit/pages/settings/permission-warnings.test.js.map +1 -0
  282. package/dist/test/unit/pages/settings/role-editor.test.js +16 -0
  283. package/dist/test/unit/pages/settings/role-editor.test.js.map +1 -1
  284. package/dist/test/unit/pages/settings/role-preview.test.js +7 -0
  285. package/dist/test/unit/pages/settings/role-preview.test.js.map +1 -1
  286. package/dist/test/unit/pages/settings/security-attention.test.d.ts +1 -0
  287. package/dist/test/unit/pages/settings/security-attention.test.js +74 -0
  288. package/dist/test/unit/pages/settings/security-attention.test.js.map +1 -0
  289. package/dist/test/unit/sanitize-user.test.d.ts +1 -0
  290. package/dist/test/unit/sanitize-user.test.js +21 -0
  291. package/dist/test/unit/sanitize-user.test.js.map +1 -0
  292. package/dist/test/unit/signup-email-validation.test.d.ts +1 -0
  293. package/dist/test/unit/signup-email-validation.test.js +79 -0
  294. package/dist/test/unit/signup-email-validation.test.js.map +1 -0
  295. package/dist/test/unit/validation/member-invite.schema.test.js +14 -52
  296. package/dist/test/unit/validation/member-invite.schema.test.js.map +1 -1
  297. package/dist/utils/account-notifications.d.ts +5 -0
  298. package/dist/utils/account-notifications.js +8 -0
  299. package/dist/utils/account-notifications.js.map +1 -1
  300. package/dist/utils/notification-emitter.d.ts +1 -1
  301. package/dist/utils/notification-emitter.js +1 -1
  302. package/dist/utils/notification-emitter.js.map +1 -1
  303. package/dist/utils/notification-tones.d.ts +2 -1
  304. package/dist/utils/notification-tones.js +5 -1
  305. package/dist/utils/notification-tones.js.map +1 -1
  306. package/dist/utils/one-time-code.d.ts +3 -0
  307. package/dist/utils/one-time-code.js +21 -0
  308. package/dist/utils/one-time-code.js.map +1 -0
  309. package/dist/utils/sign-in-monitor.d.ts +1 -0
  310. package/dist/utils/sign-in-monitor.js +3 -2
  311. package/dist/utils/sign-in-monitor.js.map +1 -1
  312. package/dist/utils/two-factor-codes.d.ts +10 -0
  313. package/dist/utils/two-factor-codes.js +76 -0
  314. package/dist/utils/two-factor-codes.js.map +1 -0
  315. package/dist/validation/auth.schema.d.ts +17 -0
  316. package/dist/validation/auth.schema.js +17 -6
  317. package/dist/validation/auth.schema.js.map +1 -1
  318. package/dist/validation/issue-message.d.ts +1 -0
  319. package/dist/validation/issue-message.js +9 -0
  320. package/dist/validation/issue-message.js.map +1 -0
  321. package/dist/validation/member-invite.schema.d.ts +1 -25
  322. package/dist/validation/member-invite.schema.js +2 -23
  323. package/dist/validation/member-invite.schema.js.map +1 -1
  324. package/frontend-vue/dms.frontend.ts +2 -0
  325. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +3 -0
  326. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +105 -5
  327. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +20 -4
  328. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +20 -4
  329. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +3 -2
  330. package/frontend-vue/layers/dms-core/app/types/menu.ts +15 -0
  331. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -1
  332. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -1
  333. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +20 -0
  334. package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +75 -0
  335. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +22 -0
  336. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +24 -10
  337. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +139 -0
  338. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +4 -2
  339. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +90 -0
  340. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidePanelHost.vue +17 -0
  341. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +25 -43
  342. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +24 -2
  343. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationMatrixCategory.vue +70 -25
  344. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +25 -10
  345. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay.ts +41 -5
  346. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePreferencesSummary.vue +2 -1
  347. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +20 -4
  348. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +21 -0
  349. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionArea.vue +16 -2
  350. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionBranch.vue +1 -0
  351. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionLeaf.vue +13 -1
  352. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionTree.vue +9 -0
  353. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionWarning.vue +46 -0
  354. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +108 -29
  355. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +2 -0
  356. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityDialogFrame.vue +6 -6
  357. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -1
  358. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +5 -4
  359. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +4 -3
  360. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +54 -18
  361. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/shell/SettingsNav.vue +13 -8
  362. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/assistantGroups.ts +143 -0
  363. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/useCommandPaletteAssistantMode.ts +249 -0
  364. package/frontend-vue/layers/dms-layout/app/build/composables/general/useGithubStarPrompt.ts +178 -0
  365. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +191 -0
  366. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +51 -0
  367. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +113 -0
  368. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useCategoryNavCards.ts +17 -10
  369. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +45 -0
  370. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +8 -1
  371. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +50 -3
  372. package/frontend-vue/layers/dms-layout/app/build/composables/settings/useSettingsNavigation.ts +3 -0
  373. package/frontend-vue/layers/dms-layout/app/build/utils/categoryPages.ts +1 -2
  374. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +5 -0
  375. package/frontend-vue/layers/dms-layout/app/components/layout/DashboardSearch.vue +109 -7
  376. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +19 -9
  377. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +22 -40
  378. package/frontend-vue/layers/dms-layout/app/composables/useAppSidePanels.ts +96 -0
  379. package/frontend-vue/layers/dms-layout/app/composables/useCommandPaletteAssistant.ts +129 -0
  380. package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +33 -11
  381. package/frontend-vue/layers/dms-layout/app/composables/useSidePanel.ts +41 -0
  382. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +15 -0
  383. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +1 -7
  384. package/frontend-vue/layers/dms-layout/app/plugins/side-panel-host.ts +13 -0
  385. package/frontend-vue/layers/dms-layout/app/types/page.ts +4 -0
  386. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +43 -13
  387. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +43 -13
  388. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +45 -0
  389. package/frontend-vue/layers/dms-ui/app/build/components/modal/ModalEscHint.vue +28 -0
  390. package/frontend-vue/layers/dms-ui/app/build/composables/form/floatingSaveBars.ts +38 -0
  391. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +66 -0
  392. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +50 -9
  393. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +7 -1
  394. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/chrome.ts +2 -2
  395. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +14 -10
  396. package/frontend-vue/layers/dms-ui/app/build/utils/navBadge.ts +12 -0
  397. package/frontend-vue/layers/dms-ui/app/build/utils/tone.ts +22 -0
  398. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +117 -82
  399. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +12 -1
  400. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +16 -1
  401. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +7 -8
  402. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +28 -6
  403. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
  404. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +4 -0
  405. package/frontend-vue/layers/dms-ui/app/components/status-pill/StatusPill.vue +7 -2
  406. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +7 -2
  407. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +48 -34
  408. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +13 -3
  409. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -14
  410. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -14
  411. package/frontend-vue/package.json +2 -2
  412. package/frontend-vue/pnpm-lock.yaml +5 -5
  413. package/frontend-vue/tests/activity-feed-fill-height.test.ts +107 -0
  414. package/frontend-vue/tests/category-nav-cards.test.ts +21 -13
  415. package/frontend-vue/tests/command-palette-assistant.test.ts +344 -0
  416. package/frontend-vue/tests/fixtures/dashboard-search-stub.ts +91 -0
  417. package/frontend-vue/tests/forgot-validation-resend.test.ts +234 -0
  418. package/frontend-vue/tests/form-display-options.test.ts +64 -0
  419. package/frontend-vue/tests/form-load-error.test.ts +160 -0
  420. package/frontend-vue/tests/form-saved-before-success.test.ts +110 -0
  421. package/frontend-vue/tests/github-star-prompt.test.ts +328 -0
  422. package/frontend-vue/tests/header-actions.test.ts +191 -0
  423. package/frontend-vue/tests/invites-edit-modal.test.ts +157 -0
  424. package/frontend-vue/tests/member-role-picker.test.ts +26 -0
  425. package/frontend-vue/tests/menu-hidden-page.test.ts +93 -0
  426. package/frontend-vue/tests/nav-badge-tones.test.ts +274 -0
  427. package/frontend-vue/tests/nav-card-badge.test.ts +69 -0
  428. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +163 -0
  429. package/frontend-vue/tests/notification-popover-errors.test.ts +71 -37
  430. package/frontend-vue/tests/notification-preferences-groups.test.ts +268 -0
  431. package/frontend-vue/tests/notification-tone-contract.test.ts +70 -0
  432. package/frontend-vue/tests/page-fill-height.test.ts +9 -1
  433. package/frontend-vue/tests/page-no-visible-blocks.test.ts +202 -0
  434. package/frontend-vue/tests/profile-preferences-summary.test.ts +26 -2
  435. package/frontend-vue/tests/profile-security-summary.test.ts +121 -0
  436. package/frontend-vue/tests/role-permission-tree.test.ts +102 -8
  437. package/frontend-vue/tests/role-permissions.test.ts +178 -56
  438. package/frontend-vue/tests/side-panels.test.ts +470 -0
  439. package/frontend-vue/tests/signup-invitation-link.test.ts +71 -2
  440. package/frontend-vue/tests/table-actions-column-width.test.ts +51 -1
  441. package/frontend-vue/tests/table-view-chrome.test.ts +2 -2
  442. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  443. package/package.json +2 -2
  444. package/skills/dms/REFERENCE.md +1 -1
@@ -0,0 +1,64 @@
1
+ // @vitest-environment jsdom
2
+ import { computed, createApp, h, useAttrs } from "vue";
3
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
4
+ import Display from "../layers/dms-ui/app/components/form/components/Display.vue";
5
+
6
+ interface StatusOptions {
7
+ onlineLabel?: string;
8
+ offlineLabel?: string;
9
+ }
10
+
11
+ // A status type worded from its options, as the registered one is: the
12
+ // fallback labels show when the options do not reach the formatter.
13
+ const statusType = {
14
+ id: "status",
15
+ formatter: {
16
+ default: (value: unknown, _locale: string, options?: unknown) => {
17
+ const opts = options as StatusOptions | undefined;
18
+ return value
19
+ ? (opts?.onlineLabel ?? "Online")
20
+ : (opts?.offlineLabel ?? "Offline");
21
+ },
22
+ },
23
+ };
24
+
25
+ /** A read-only field as a form renders it: its options spread on it. */
26
+ function mountDisplay(modelValue: unknown, options: Record<string, unknown>) {
27
+ const app = createApp({
28
+ render: () => h(Display, { modelValue, type: "status", ...options }),
29
+ });
30
+ const container = document.createElement("div");
31
+ app.mount(container);
32
+ return { app, container };
33
+ }
34
+
35
+ beforeEach(() => {
36
+ vi.stubGlobal("computed", computed);
37
+ vi.stubGlobal("useAttrs", useAttrs);
38
+ vi.stubGlobal("useI18n", () => ({
39
+ locale: { value: "en" },
40
+ t: (key: string) => key,
41
+ }));
42
+ vi.stubGlobal("useDataTypes", () => ({ getDataType: () => statusType }));
43
+ });
44
+
45
+ afterEach(() => {
46
+ vi.unstubAllGlobals();
47
+ });
48
+
49
+ it("words a read-only status with the labels its column sets", () => {
50
+ const options = {
51
+ onlineLabel: "Pending",
52
+ offlineLabel: "Expired",
53
+ onlineColor: "warning",
54
+ offlineColor: "neutral",
55
+ };
56
+
57
+ const pending = mountDisplay(true, options);
58
+ const expired = mountDisplay(false, options);
59
+
60
+ expect(pending.container.textContent).toBe("Pending");
61
+ expect(expired.container.textContent).toBe("Expired");
62
+ pending.app.unmount();
63
+ expired.app.unmount();
64
+ });
@@ -0,0 +1,160 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createSSRApp, defineComponent, h, ref } from "vue";
3
+ import { renderToString } from "vue/server-renderer";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import FormLoadError from "../layers/dms-ui/app/build/components/form/FormLoadError.vue";
6
+ import {
7
+ type FormLoadSource,
8
+ trackFormRecordLoad,
9
+ } from "../layers/dms-ui/app/build/composables/form/formRecordLoad";
10
+
11
+ // A role that may edit a table's rows but not view them opened the edit form
12
+ // empty: the row's read was refused, the form showed its blank fields, and a
13
+ // value picked there replaced the row's own. A form whose record does not
14
+ // load shows why, with a retry, and cannot be saved.
15
+
16
+ const ROW = { values: { email: "zz-invitee@example.com" }, initial: {} };
17
+ const FORBIDDEN = Object.assign(new Error("Forbidden"), { statusCode: 403 });
18
+
19
+ interface Payload {
20
+ values: Record<string, unknown>;
21
+ initial: Record<string, unknown>;
22
+ }
23
+
24
+ function loadOf(
25
+ data: Payload | null,
26
+ error: unknown,
27
+ next?: { data: Payload; error: unknown },
28
+ ): FormLoadSource<Payload> {
29
+ const load: FormLoadSource<Payload> = {
30
+ data: ref(data),
31
+ error: ref(error),
32
+ refresh: vi.fn(async () => {
33
+ if (!next) return;
34
+ load.data.value = next.data;
35
+ load.error.value = next.error;
36
+ }),
37
+ };
38
+ return load;
39
+ }
40
+
41
+ describe("a form's record load", () => {
42
+ it("reports a refused read as forbidden and applies nothing", () => {
43
+ const apply = vi.fn();
44
+ const load = trackFormRecordLoad(loadOf(null, FORBIDDEN), apply);
45
+ expect(load.failure.value).toBe("forbidden");
46
+ expect(apply).not.toHaveBeenCalled();
47
+ });
48
+
49
+ it("never applies the values an earlier opening left in the cache", () => {
50
+ const apply = vi.fn();
51
+ const load = trackFormRecordLoad(loadOf(ROW, new Error("network")), apply);
52
+ expect(load.failure.value).toBe("error");
53
+ expect(apply).not.toHaveBeenCalled();
54
+ });
55
+
56
+ it("opens on the record once a retry loads it", async () => {
57
+ const apply = vi.fn();
58
+ const source = loadOf(null, FORBIDDEN, { data: ROW, error: null });
59
+ const load = trackFormRecordLoad(source, apply);
60
+ await load.retry();
61
+ expect(source.refresh).toHaveBeenCalledOnce();
62
+ expect(load.failure.value).toBe(null);
63
+ expect(load.retrying.value).toBe(false);
64
+ expect(apply).toHaveBeenCalledWith(ROW);
65
+ });
66
+
67
+ it("applies a record that loaded", () => {
68
+ const apply = vi.fn();
69
+ const load = trackFormRecordLoad(loadOf(ROW, null), apply);
70
+ expect(load.failure.value).toBe(null);
71
+ expect(apply).toHaveBeenCalledWith(ROW);
72
+ });
73
+ });
74
+
75
+ describe("the load error a form shows", () => {
76
+ const Slot = defineComponent({
77
+ props: {
78
+ title: { type: String, default: "" },
79
+ description: { type: String, default: "" },
80
+ label: { type: String, default: "" },
81
+ },
82
+ setup:
83
+ (props, { slots }) =>
84
+ () =>
85
+ h("div", [
86
+ props.title,
87
+ props.description,
88
+ props.label,
89
+ slots.default?.(),
90
+ slots.actions?.(),
91
+ ]),
92
+ });
93
+
94
+ async function render(failure: "forbidden" | "error"): Promise<string> {
95
+ const app = createSSRApp({
96
+ render: () => h(FormLoadError, { failure }),
97
+ });
98
+ for (const name of ["UButton", "UIcon"]) app.component(name, Slot);
99
+ return renderToString(app);
100
+ }
101
+
102
+ beforeEach(() => {
103
+ vi.stubGlobal("useI18n", () => ({ t: String }));
104
+ });
105
+
106
+ afterEach(() => vi.unstubAllGlobals());
107
+
108
+ it("says the read was refused, with a retry", async () => {
109
+ const html = await render("forbidden");
110
+ expect(html).toContain("dms.form.load_error.forbidden_title");
111
+ expect(html).toContain("dms.form.load_error.forbidden_description");
112
+ expect(html).toContain("dms.form.load_error.retry");
113
+ });
114
+
115
+ it("says the load failed, with a retry", async () => {
116
+ const html = await render("error");
117
+ expect(html).toContain("dms.form.load_error.error_title");
118
+ expect(html).toContain("dms.form.load_error.retry");
119
+ });
120
+
121
+ it("has its texts in English and French", () => {
122
+ for (const file of ["ui-en-GB.json", "ui-fr-FR.json"]) {
123
+ const locale = JSON.parse(
124
+ readFileSync(
125
+ new URL(`../layers/dms-ui/i18n/locales/${file}`, import.meta.url),
126
+ "utf8",
127
+ ),
128
+ );
129
+ expect(Object.keys(locale.dms.form.load_error).sort()).toEqual([
130
+ "error_description",
131
+ "error_title",
132
+ "forbidden_description",
133
+ "forbidden_title",
134
+ "retry",
135
+ ]);
136
+ }
137
+ });
138
+ });
139
+
140
+ describe("a form whose record did not load", () => {
141
+ const formSource = readFileSync(
142
+ new URL("../layers/dms-ui/app/components/form/Form.vue", import.meta.url),
143
+ "utf8",
144
+ );
145
+
146
+ it("shows the load error instead of its fields", () => {
147
+ expect(formSource).toMatch(
148
+ /<div v-if="loadFailure"[^>]*>\s*<DmsFormLoadError[\s\S]*?<\/div>\s*<div v-else :class="surfaceClasses\.body">/,
149
+ );
150
+ });
151
+
152
+ it("cannot be saved: no save bar, and a submit is refused", () => {
153
+ expect(formSource).toMatch(
154
+ /<DmsSaveBar\s+v-if="canSave && !isInstant && !loadFailure"/,
155
+ );
156
+ expect(formSource).toMatch(
157
+ /function onSubmit\([^)]*\) \{\s*if \(loadFailure\.value \|\| !runValidators\(\)\) return;/,
158
+ );
159
+ });
160
+ });
@@ -0,0 +1,110 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { computed, effectScope, ref, watch, type EffectScope } from "vue";
3
+ import { HttpMethod } from "../layers/dms-core/app/types/http";
4
+ import { useForm } from "../layers/dms-ui/app/composables/form/useForm";
5
+ import { useEventedAction } from "../layers/dms-core/app/composables/useEventedAction";
6
+ import { FormEvents } from "../layers/dms-ui/app/composables/form/types/events";
7
+
8
+ const authFetch = vi.fn();
9
+ const sendComponentEvent = vi.fn();
10
+ let scope: EffectScope;
11
+
12
+ const FIELDS = [
13
+ { id: "roles", label: "Roles", component: { componentName: "DmsInputText" } },
14
+ ];
15
+
16
+ function setupForm(onSaved: () => void) {
17
+ return scope.run(() =>
18
+ useForm(
19
+ {
20
+ componentId: "form-1",
21
+ pageId: "page-1",
22
+ fields: FIELDS,
23
+ fetchUrl: "/api/invites/get?id=invite-1",
24
+ submitUrl: "/api/invites/edit?id=invite-1",
25
+ } as never,
26
+ { onSaved },
27
+ ),
28
+ )!;
29
+ }
30
+
31
+ beforeEach(() => {
32
+ scope = effectScope();
33
+ authFetch.mockResolvedValue({ success: true });
34
+ vi.stubGlobal("computed", computed);
35
+ vi.stubGlobal("ref", ref);
36
+ vi.stubGlobal("watch", watch);
37
+ vi.stubGlobal("HttpMethod", HttpMethod);
38
+ vi.stubGlobal("isString", (value: unknown) => typeof value === "string");
39
+ vi.stubGlobal("Color", { error: "error", success: "success", info: "info" });
40
+ vi.stubGlobal("CONTENT_LANGUAGE_HEADER", "Content-Language");
41
+ vi.stubGlobal("useToast", () => ({ add: vi.fn() }));
42
+ vi.stubGlobal("useAuthFetch", () => ({ $authFetch: authFetch }));
43
+ vi.stubGlobal("useDataTypes", () => ({ getDataType: () => undefined }));
44
+ vi.stubGlobal("useTranslation", () => ({
45
+ processI18n: (key: string) => key,
46
+ processApiMessage: (message: string) => message,
47
+ }));
48
+ vi.stubGlobal("useI18n", () => ({ t: (key: string) => key }));
49
+ vi.stubGlobal("useDmsRoute", () => ({ query: {} }));
50
+ vi.stubGlobal("useComponentEvent", () => ({ sendComponentEvent }));
51
+ // The real one: it sends the submit events the watch rules react to.
52
+ vi.stubGlobal("useEventedAction", useEventedAction);
53
+ vi.stubGlobal(
54
+ "useWatch",
55
+ (_actions: unknown, _id: unknown, initial: Record<string, unknown>) => ({
56
+ isLoading: ref(false),
57
+ state: ref({ ...initial }),
58
+ }),
59
+ );
60
+ vi.stubGlobal("navigateDms", vi.fn());
61
+ });
62
+
63
+ afterEach(() => {
64
+ scope.stop();
65
+ vi.unstubAllGlobals();
66
+ vi.resetAllMocks();
67
+ });
68
+
69
+ describe("DMS form announcing a successful submit", () => {
70
+ // A form page goes back to its list on the success event: announced while
71
+ // the form still held its changes, the leave guard asked to discard them
72
+ // although they were saved, and the page stayed open behind its dialog.
73
+ it("holds nothing unsaved once the success event goes out", async () => {
74
+ const steps: string[] = [];
75
+ const form = setupForm(() => steps.push("saved"));
76
+ let savedValues: unknown;
77
+ sendComponentEvent.mockImplementation((event: string) => {
78
+ if (event !== FormEvents.SUBMIT_SUCCESS) return;
79
+ steps.push("success");
80
+ savedValues = { ...form.initialValues.value };
81
+ });
82
+ form.initialValues.value = { roles: ["auditor"] };
83
+ const edited = { roles: ["auditor", "finance"] };
84
+ Object.assign(form.state.value, edited);
85
+
86
+ await form.onSubmit({ data: edited } as never);
87
+
88
+ expect(steps).toEqual(["saved", "success"]);
89
+ expect(savedValues).toEqual(edited);
90
+ expect(form.submitSucceeded.value).toBe(true);
91
+ });
92
+
93
+ it("announces no success, and saves nothing, when the submit is refused", async () => {
94
+ authFetch.mockRejectedValue({ statusCode: 400, data: "refused" });
95
+ const onSaved = vi.fn();
96
+ const form = setupForm(onSaved);
97
+ form.initialValues.value = { roles: ["auditor"] };
98
+ Object.assign(form.state.value, { roles: [] });
99
+
100
+ await form.onSubmit({ data: { roles: [] } } as never);
101
+
102
+ expect(onSaved).not.toHaveBeenCalled();
103
+ expect(sendComponentEvent).not.toHaveBeenCalledWith(
104
+ FormEvents.SUBMIT_SUCCESS,
105
+ expect.anything(),
106
+ expect.anything(),
107
+ );
108
+ expect(form.initialValues.value).toEqual({ roles: ["auditor"] });
109
+ });
110
+ });
@@ -0,0 +1,328 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import {
4
+ computed,
5
+ createApp,
6
+ defineComponent,
7
+ h,
8
+ nextTick,
9
+ ref,
10
+ type App,
11
+ type Component,
12
+ type Ref,
13
+ } from "vue";
14
+ import GithubStarPrompt from "../layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue";
15
+ import EmptyLayout from "../layers/dms-layout/app/custom-layouts/EmptyLayout.vue";
16
+ import SaveBar from "../layers/dms-ui/app/components/save-bar/SaveBar.vue";
17
+ import {
18
+ GITHUB_STAR_PROMPT_DELAY_MS,
19
+ GITHUB_STAR_PROMPT_KEYS,
20
+ GITHUB_STAR_PROMPT_SNOOZE_MS,
21
+ GITHUB_STAR_PROMPT_TICK_MS,
22
+ } from "../layers/dms-layout/app/build/composables/general/useGithubStarPrompt";
23
+ import { GITHUB_REPOSITORY_URL } from "../layers/dms-layout/app/build/utils/github-repository";
24
+
25
+ vi.mock("../layers/dms-layout/app/build/components/layout/Footer.vue", () => ({
26
+ default: { render: () => null },
27
+ }));
28
+
29
+ const MINUTE_MS = 60 * 1000;
30
+ const HOUR_MS = 60 * MINUTE_MS;
31
+
32
+ let storage: Map<string, string>;
33
+ let user: Ref<object | null>;
34
+ let toasts: Ref<object[]>;
35
+ let visibility: DocumentVisibilityState;
36
+ let app: App | undefined;
37
+ let host: HTMLDivElement;
38
+
39
+ const ButtonStub = defineComponent({
40
+ inheritAttrs: false,
41
+ props: { label: { type: String, default: undefined } },
42
+ emits: ["click"],
43
+ setup:
44
+ (props, { attrs, emit }) =>
45
+ () =>
46
+ h(
47
+ "button",
48
+ {
49
+ type: "button",
50
+ "data-action": attrs["data-action"],
51
+ "aria-label": attrs["aria-label"],
52
+ onClick: () => emit("click"),
53
+ },
54
+ props.label,
55
+ ),
56
+ });
57
+
58
+ const memoryStorage = {
59
+ getItem: (key: string) => storage.get(key) ?? null,
60
+ setItem: (key: string, value: string) => void storage.set(key, value),
61
+ removeItem: (key: string) => void storage.delete(key),
62
+ };
63
+
64
+ const throwingStorage = {
65
+ getItem: () => {
66
+ throw new Error("SecurityError");
67
+ },
68
+ setItem: () => {
69
+ throw new Error("QuotaExceededError");
70
+ },
71
+ };
72
+
73
+ async function mount(component: Component = GithubStarPrompt): Promise<void> {
74
+ host = document.createElement("div");
75
+ document.body.append(host);
76
+ app = createApp(component);
77
+ app.component("UButton", ButtonStub);
78
+ app.component("UIcon", { render: () => null });
79
+ app.component("DmsIconWell", { render: () => null });
80
+ app.component("DmsAppLogo", { render: () => null });
81
+ app.mount(host);
82
+ await nextTick();
83
+ }
84
+
85
+ function prompt(): HTMLElement | null {
86
+ return host.querySelector('[role="dialog"]');
87
+ }
88
+
89
+ async function click(action: string): Promise<void> {
90
+ host.querySelector<HTMLButtonElement>(`[data-action="${action}"]`)!.click();
91
+ await nextTick();
92
+ }
93
+
94
+ async function advance(ms: number): Promise<void> {
95
+ vi.advanceTimersByTime(ms);
96
+ await nextTick();
97
+ }
98
+
99
+ async function setVisibility(state: DocumentVisibilityState): Promise<void> {
100
+ visibility = state;
101
+ document.dispatchEvent(new Event("visibilitychange"));
102
+ await nextTick();
103
+ }
104
+
105
+ function seedActiveTime(ms: number): void {
106
+ storage.set(GITHUB_STAR_PROMPT_KEYS.activeMs, String(ms));
107
+ }
108
+
109
+ beforeEach(() => {
110
+ vi.useFakeTimers();
111
+ vi.stubEnv("DEV", true);
112
+ storage = new Map();
113
+ user = ref({ email: "user@example.com" });
114
+ toasts = ref([]);
115
+ visibility = "visible";
116
+ vi.spyOn(document, "visibilityState", "get").mockImplementation(
117
+ () => visibility,
118
+ );
119
+ vi.stubGlobal("localStorage", memoryStorage);
120
+ vi.stubGlobal("computed", computed);
121
+ vi.stubGlobal("ref", ref);
122
+ vi.stubGlobal("useI18n", () => ({ t: (key: string) => key }));
123
+ vi.stubGlobal("useCurrentUser", () => ({ user }));
124
+ vi.stubGlobal("useToast", () => ({ toasts }));
125
+ vi.stubGlobal("useColorModePreference", () => ref("system"));
126
+ vi.stubGlobal("open", vi.fn());
127
+ });
128
+
129
+ afterEach(() => {
130
+ app?.unmount();
131
+ app = undefined;
132
+ host?.remove();
133
+ vi.useRealTimers();
134
+ vi.restoreAllMocks();
135
+ vi.unstubAllGlobals();
136
+ vi.unstubAllEnvs();
137
+ });
138
+
139
+ describe("GithubStarPrompt", () => {
140
+ it("stays hidden before 15 minutes of use", async () => {
141
+ await mount();
142
+ await advance(GITHUB_STAR_PROMPT_DELAY_MS - GITHUB_STAR_PROMPT_TICK_MS);
143
+
144
+ expect(prompt()).toBeNull();
145
+ expect(Number(storage.get(GITHUB_STAR_PROMPT_KEYS.activeMs))).toBe(
146
+ GITHUB_STAR_PROMPT_DELAY_MS - GITHUB_STAR_PROMPT_TICK_MS,
147
+ );
148
+ });
149
+
150
+ it("shows in dev mode after 15 minutes of visible time, added up across sessions", async () => {
151
+ seedActiveTime(10 * MINUTE_MS);
152
+ await mount();
153
+ await advance(4 * MINUTE_MS);
154
+ expect(prompt()).toBeNull();
155
+
156
+ await advance(MINUTE_MS);
157
+
158
+ expect(prompt()).not.toBeNull();
159
+ expect(prompt()!.getAttribute("aria-labelledby")).toBeTruthy();
160
+ });
161
+
162
+ it("does not count the time the tab is hidden", async () => {
163
+ seedActiveTime(14 * MINUTE_MS);
164
+ await mount();
165
+ await setVisibility("hidden");
166
+ await advance(HOUR_MS);
167
+ expect(prompt()).toBeNull();
168
+
169
+ await setVisibility("visible");
170
+ await advance(MINUTE_MS);
171
+
172
+ expect(prompt()).not.toBeNull();
173
+ });
174
+
175
+ it("hides on Later and shows again 48 hours after", async () => {
176
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
177
+ await mount();
178
+ expect(prompt()).not.toBeNull();
179
+
180
+ await click("later");
181
+
182
+ expect(prompt()).toBeNull();
183
+ expect(Number(storage.get(GITHUB_STAR_PROMPT_KEYS.snoozedUntil))).toBe(
184
+ Date.now() + GITHUB_STAR_PROMPT_SNOOZE_MS,
185
+ );
186
+ await advance(GITHUB_STAR_PROMPT_SNOOZE_MS - HOUR_MS);
187
+ expect(prompt()).toBeNull();
188
+
189
+ await advance(HOUR_MS);
190
+ expect(prompt()).not.toBeNull();
191
+ });
192
+
193
+ it("opens the repository on star, and never shows again", async () => {
194
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
195
+ await mount();
196
+
197
+ await click("star");
198
+
199
+ expect(window.open).toHaveBeenCalledWith(
200
+ GITHUB_REPOSITORY_URL,
201
+ "_blank",
202
+ "noopener,noreferrer",
203
+ );
204
+ expect(prompt()).toBeNull();
205
+ expect(storage.get(GITHUB_STAR_PROMPT_KEYS.dismissed)).toBe("1");
206
+
207
+ app!.unmount();
208
+ host.remove();
209
+ await mount();
210
+ await advance(GITHUB_STAR_PROMPT_SNOOZE_MS);
211
+ expect(prompt()).toBeNull();
212
+ });
213
+
214
+ it("treats the close button as never show again", async () => {
215
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
216
+ await mount();
217
+
218
+ await click("close");
219
+
220
+ expect(prompt()).toBeNull();
221
+ expect(storage.get(GITHUB_STAR_PROMPT_KEYS.dismissed)).toBe("1");
222
+ expect(window.open).not.toHaveBeenCalled();
223
+ });
224
+
225
+ it("follows a choice made in another tab", async () => {
226
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
227
+ await mount();
228
+
229
+ storage.set(GITHUB_STAR_PROMPT_KEYS.dismissed, "1");
230
+ await advance(GITHUB_STAR_PROMPT_TICK_MS);
231
+
232
+ expect(prompt()).toBeNull();
233
+ });
234
+
235
+ // The time and the choices then last as long as the page.
236
+ it("does not crash when storage throws", async () => {
237
+ vi.stubGlobal("localStorage", throwingStorage);
238
+ await mount();
239
+ await advance(GITHUB_STAR_PROMPT_DELAY_MS - GITHUB_STAR_PROMPT_TICK_MS);
240
+ expect(prompt()).toBeNull();
241
+ await advance(GITHUB_STAR_PROMPT_TICK_MS);
242
+ expect(prompt()).not.toBeNull();
243
+
244
+ await click("later");
245
+ await advance(GITHUB_STAR_PROMPT_TICK_MS);
246
+ expect(prompt()).toBeNull();
247
+
248
+ await advance(GITHUB_STAR_PROMPT_SNOOZE_MS);
249
+ await click("close");
250
+ await advance(GITHUB_STAR_PROMPT_SNOOZE_MS);
251
+ expect(prompt()).toBeNull();
252
+ });
253
+
254
+ it("steps aside while a toast is up", async () => {
255
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
256
+ toasts.value = [{ id: 1 }];
257
+ await mount();
258
+ expect(prompt()).toBeNull();
259
+
260
+ toasts.value = [];
261
+ await nextTick();
262
+ expect(prompt()).not.toBeNull();
263
+ });
264
+
265
+ it("steps aside while a floating save bar is shown", async () => {
266
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
267
+ vi.stubGlobal("useTranslation", () => ({
268
+ processI18n: (text: string) => text,
269
+ }));
270
+ const dirty = ref(false);
271
+ const variant = ref<"floating" | "band">("floating");
272
+ await mount(
273
+ defineComponent({
274
+ setup: () => () => [
275
+ h(GithubStarPrompt),
276
+ h(SaveBar, { dirty: dirty.value, variant: variant.value }),
277
+ ],
278
+ }),
279
+ );
280
+ // Nothing to save: the bar keeps its place, hidden.
281
+ expect(prompt()).not.toBeNull();
282
+
283
+ dirty.value = true;
284
+ await nextTick();
285
+ expect(prompt()).toBeNull();
286
+
287
+ // A form card's footer band is not the bar sticking to the bottom.
288
+ variant.value = "band";
289
+ await nextTick();
290
+ expect(prompt()).not.toBeNull();
291
+
292
+ variant.value = "floating";
293
+ await nextTick();
294
+ expect(prompt()).toBeNull();
295
+
296
+ dirty.value = false;
297
+ await nextTick();
298
+ expect(prompt()).not.toBeNull();
299
+ });
300
+
301
+ it("renders nothing without a signed-in user", async () => {
302
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
303
+ user.value = null;
304
+ await mount();
305
+
306
+ expect(prompt()).toBeNull();
307
+ });
308
+
309
+ it("is not part of the auth pages' layout", async () => {
310
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
311
+ await mount(EmptyLayout);
312
+
313
+ expect(prompt()).toBeNull();
314
+ });
315
+
316
+ it("never shows outside dev mode, nor counts or stores anything", async () => {
317
+ vi.stubEnv("DEV", false);
318
+ const setItem = vi.spyOn(memoryStorage, "setItem");
319
+ await mount();
320
+ await advance(GITHUB_STAR_PROMPT_DELAY_MS);
321
+ seedActiveTime(GITHUB_STAR_PROMPT_DELAY_MS);
322
+ await advance(GITHUB_STAR_PROMPT_TICK_MS);
323
+
324
+ expect(prompt()).toBeNull();
325
+ expect(setItem).not.toHaveBeenCalled();
326
+ expect(vi.getTimerCount()).toBe(0);
327
+ });
328
+ });