@jskit-ai/users-web 0.1.178-oldline.2 → 0.1.178

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 (124) hide show
  1. package/package.json +197 -45
  2. package/patterns/account-settings/PATTERN.md +61 -0
  3. package/src/client/bootstrap/user-bootstrap-handler.js +2 -12
  4. package/src/client/composables/account-settings/accountSettingsAvatarUploadRuntime.js +2 -2
  5. package/src/client/composables/useAccountSettingsRuntime.js +3 -3
  6. package/src/client/index.js +0 -16
  7. package/src/client/providers/UsersWebClientProvider.js +13 -14
  8. package/test/bootstrap.test.js +6 -41
  9. package/test/exportsContract.test.js +3 -29
  10. package/test/packageMetadataOwnership.test.js +19 -0
  11. package/test/settingsPlacementContract.test.js +93 -405
  12. package/fixtures/crud-list-date-filters/App.vue +0 -107
  13. package/fixtures/crud-list-date-filters/index.html +0 -12
  14. package/fixtures/crud-list-date-filters/main.js +0 -24
  15. package/fixtures/crud-list-date-filters/vite.config.mjs +0 -18
  16. package/package.descriptor.mjs +0 -468
  17. package/src/client/bulkActions.js +0 -47
  18. package/src/client/components/CrudAddEditScreen.vue +0 -186
  19. package/src/client/components/CrudListBulkActionSurface.vue +0 -126
  20. package/src/client/components/CrudListDateFilterControl.vue +0 -188
  21. package/src/client/components/CrudListFilterSurface.vue +0 -388
  22. package/src/client/components/CrudListRecordActionMenu.vue +0 -120
  23. package/src/client/components/CrudListScreen.vue +0 -501
  24. package/src/client/components/CrudViewScreen.vue +0 -197
  25. package/src/client/composables/crud/crudBindingSupport.js +0 -75
  26. package/src/client/composables/crud/crudHttpClientSupport.js +0 -57
  27. package/src/client/composables/crud/crudJsonApiTransportSupport.js +0 -145
  28. package/src/client/composables/crud/crudLookupFieldLabelSupport.js +0 -151
  29. package/src/client/composables/crud/crudLookupFieldRuntime.js +0 -251
  30. package/src/client/composables/crud/crudSchemaFormHelpers.js +0 -464
  31. package/src/client/composables/internal/crudListFilterLookupSupport.js +0 -161
  32. package/src/client/composables/internal/crudListParentTitleSupport.js +0 -171
  33. package/src/client/composables/internal/useOperationScope.js +0 -144
  34. package/src/client/composables/records/useAddEdit.js +0 -224
  35. package/src/client/composables/records/useCrudAddEdit.js +0 -227
  36. package/src/client/composables/records/useCrudList.js +0 -101
  37. package/src/client/composables/records/useCrudView.js +0 -47
  38. package/src/client/composables/records/useList.js +0 -500
  39. package/src/client/composables/records/useView.js +0 -164
  40. package/src/client/composables/runtime/addEditUiRuntime.js +0 -130
  41. package/src/client/composables/runtime/listUiRuntime.js +0 -123
  42. package/src/client/composables/runtime/modelStateHelpers.js +0 -90
  43. package/src/client/composables/runtime/operationAdapters.js +0 -34
  44. package/src/client/composables/runtime/operationUiHelpers.js +0 -125
  45. package/src/client/composables/runtime/operationValidationHelpers.js +0 -51
  46. package/src/client/composables/runtime/useAddEditCore.js +0 -112
  47. package/src/client/composables/runtime/useCommandCore.js +0 -123
  48. package/src/client/composables/runtime/useEndpointResource.js +0 -199
  49. package/src/client/composables/runtime/useFieldErrorBag.js +0 -61
  50. package/src/client/composables/runtime/useListCore.js +0 -112
  51. package/src/client/composables/runtime/useUiFeedback.js +0 -98
  52. package/src/client/composables/runtime/useViewCore.js +0 -93
  53. package/src/client/composables/runtime/viewUiRuntime.js +0 -96
  54. package/src/client/composables/support/errorMessageHelpers.js +0 -66
  55. package/src/client/composables/support/listQueryParamSupport.js +0 -473
  56. package/src/client/composables/support/listSearchSupport.js +0 -70
  57. package/src/client/composables/support/refValueHelpers.js +0 -19
  58. package/src/client/composables/support/requestQueryRuntimeSupport.js +0 -141
  59. package/src/client/composables/support/resourceLoadStateHelpers.js +0 -229
  60. package/src/client/composables/support/routeTemplateHelpers.js +0 -285
  61. package/src/client/composables/support/scopeHelpers.js +0 -145
  62. package/src/client/composables/useAccess.js +0 -109
  63. package/src/client/composables/useCommand.js +0 -118
  64. package/src/client/composables/useCrudAddEditScreen.js +0 -88
  65. package/src/client/composables/useCrudDeleteAction.js +0 -160
  66. package/src/client/composables/useCrudListBulkActions.js +0 -147
  67. package/src/client/composables/useCrudListFilterLookups.js +0 -146
  68. package/src/client/composables/useCrudListFilters.js +0 -339
  69. package/src/client/composables/useCrudListParentTitle.js +0 -151
  70. package/src/client/composables/useCrudListRowActions.js +0 -144
  71. package/src/client/composables/useCrudListScreen.js +0 -237
  72. package/src/client/composables/useCrudViewScreen.js +0 -77
  73. package/src/client/composables/usePagedCollection.js +0 -181
  74. package/src/client/composables/usePaths.js +0 -120
  75. package/src/client/composables/useRealtimeQueryInvalidation.js +0 -156
  76. package/src/client/composables/useScopeRuntime.js +0 -135
  77. package/src/client/composables/useSurfaceRouteContext.js +0 -25
  78. package/src/client/filters.js +0 -15
  79. package/src/client/lib/httpClient.js +0 -47
  80. package/src/client/lib/permissions.js +0 -34
  81. package/src/client/rowActions.js +0 -47
  82. package/src/client/support/contractGuards.js +0 -34
  83. package/src/client/support/crudListDateFilterSupport.js +0 -91
  84. package/test/addEditUiRuntime.test.js +0 -110
  85. package/test/crudBindingSupport.test.js +0 -110
  86. package/test/crudHttpClientSupport.test.js +0 -77
  87. package/test/crudJsonApiTransportSupport.test.js +0 -179
  88. package/test/crudListBulkActionSurface.test.js +0 -27
  89. package/test/crudListDateFilterSupport.test.js +0 -92
  90. package/test/crudListDateFilterSurface.browser.test.js +0 -273
  91. package/test/crudListFilterSurface.test.js +0 -90
  92. package/test/crudLookupFieldRuntime.test.js +0 -264
  93. package/test/crudScreenComponents.test.js +0 -108
  94. package/test/errorIntentContract.test.js +0 -31
  95. package/test/errorMessageHelpers.test.js +0 -28
  96. package/test/listQueryParamSupport.test.js +0 -223
  97. package/test/listUiRuntime.test.js +0 -102
  98. package/test/operationValidationHelpers.test.js +0 -64
  99. package/test/packageDescriptorOwnership.test.js +0 -18
  100. package/test/permissions.test.js +0 -35
  101. package/test/refValueHelpers.test.js +0 -14
  102. package/test/requestTransportOptions.test.js +0 -457
  103. package/test/resourceLoadStateHelpers.test.js +0 -189
  104. package/test/routeTemplateHelpers.test.js +0 -85
  105. package/test/scopeHelpers.test.js +0 -57
  106. package/test/useAddEditCore.test.js +0 -124
  107. package/test/useAddEditRequestQueryParams.test.js +0 -190
  108. package/test/useCrudAddEdit.test.js +0 -435
  109. package/test/useCrudDeleteAction.test.js +0 -139
  110. package/test/useCrudListBulkActions.test.js +0 -65
  111. package/test/useCrudListFilterLookups.test.js +0 -114
  112. package/test/useCrudListFilters.test.js +0 -293
  113. package/test/useCrudListParentTitle.test.js +0 -262
  114. package/test/useCrudListRowActions.test.js +0 -77
  115. package/test/useListRouteInitialization.test.js +0 -337
  116. package/test/useListSearchSupport.test.js +0 -61
  117. package/test/usePagedCollection.test.js +0 -53
  118. package/test/useViewRequestQueryParams.test.js +0 -20
  119. package/test/viewCoreLoading.test.js +0 -44
  120. package/test/viewUiRuntime.test.js +0 -95
  121. /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsNotificationsSection.vue +0 -0
  122. /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsPreferencesSection.vue +0 -0
  123. /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsProfileSection.vue +0 -0
  124. /package/{templates → patterns/account-settings/example}/src/pages/account/index.vue +0 -0
@@ -3,455 +3,143 @@ import path from "node:path";
3
3
  import test from "node:test";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { fileURLToPath } from "node:url";
6
- import { assertGeneratedUiSourceContract } from "@jskit-ai/kernel/shared/support/generatedUiContract";
7
- import descriptor from "../package.descriptor.mjs";
6
+ import packageJson from "../package.json" with { type: "json" };
8
7
 
9
8
  const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
10
9
  const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
10
+ const placements = packageJson.jskit?.metadata?.ui?.placements;
11
11
 
12
- function readOutlets(host = "") {
13
- const outlets = descriptor?.metadata?.ui?.placements?.outlets;
14
- const normalizedTarget = String(host || "").trim();
15
- return Array.isArray(outlets)
16
- ? outlets.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
17
- : [];
12
+ function contributions() {
13
+ return Array.isArray(placements?.contributions) ? placements.contributions : [];
18
14
  }
19
15
 
20
- function findTopology(id, owner = "") {
21
- const placements = descriptor?.metadata?.ui?.placements?.topology?.placements;
22
- const normalizedId = String(id || "").trim();
23
- const normalizedOwner = String(owner || "").trim();
24
- return Array.isArray(placements)
25
- ? placements.find((entry) => {
26
- const entryId = String(entry?.id || "").trim();
27
- const entryOwner = String(entry?.owner || "").trim();
28
- return entryId === normalizedId && entryOwner === normalizedOwner;
29
- }) || null
30
- : null;
16
+ function contribution(id) {
17
+ return contributions().find((entry) => entry.id === id) || null;
31
18
  }
32
19
 
33
- function findContribution(id) {
34
- const contributions = descriptor?.metadata?.ui?.placements?.contributions;
35
- return Array.isArray(contributions)
36
- ? contributions.find((entry) => String(entry?.id || "").trim() === id) || null
37
- : null;
38
- }
39
-
40
- function findTextMutation(id) {
41
- const textMutations = descriptor?.mutations?.text;
42
- return Array.isArray(textMutations)
43
- ? textMutations.find((entry) => String(entry?.id || "").trim() === id) || null
44
- : null;
45
- }
46
-
47
- function findSourceMutation(id) {
48
- const sourceMutations = descriptor?.mutations?.source;
49
- return Array.isArray(sourceMutations)
50
- ? sourceMutations.find((entry) => String(entry?.id || "").trim() === id) || null
51
- : null;
52
- }
53
-
54
- function findFileMutation(id) {
55
- const fileMutations = descriptor?.mutations?.files;
56
- return Array.isArray(fileMutations)
57
- ? fileMutations.find((entry) => String(entry?.id || "").trim() === id) || null
58
- : null;
59
- }
60
-
61
- function expectContribution(id, expected = {}) {
62
- const contribution = findContribution(id);
63
- assert.ok(contribution, `Expected contribution "${id}".`);
64
-
65
- for (const [key, value] of Object.entries(expected)) {
66
- assert.deepEqual(contribution[key], value);
67
- }
68
- }
69
-
70
- function expectTextMutation(id, { reason = "", category = "", skipIfContains = "", snippets = [] } = {}) {
71
- const mutation = findTextMutation(id);
72
- assert.ok(mutation, `Expected text mutation "${id}".`);
73
- assert.equal(mutation.op, "append-text");
74
- assert.equal(mutation.file, "src/placement.js");
75
- assert.equal(mutation.position, "bottom");
76
- assert.equal(mutation.id, id);
77
-
78
- if (reason) {
79
- assert.equal(mutation.reason, reason);
80
- }
81
-
82
- if (category) {
83
- assert.equal(mutation.category, category);
84
- }
85
-
86
- if (skipIfContains) {
87
- assert.equal(mutation.skipIfContains, skipIfContains);
88
- }
20
+ test("account settings load state exposes a local retry action", async () => {
21
+ const source = await readFile(
22
+ path.join(PACKAGE_DIR, "src", "client", "components", "AccountSettingsClientElement.vue"),
23
+ "utf8"
24
+ );
89
25
 
90
- for (const snippet of snippets) {
91
- assert.ok(mutation.value.includes(snippet), `Expected mutation "${id}" to include "${snippet}".`);
92
- }
93
- }
26
+ assert.match(source, /settingsLoadError/);
27
+ assert.match(source, /@click="runtime\.refreshSettings"/);
28
+ });
94
29
 
95
- test("users-web home tools widget exposes home-cog outlet", async () => {
96
- const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "UsersHomeToolsWidget.vue"), "utf8");
30
+ test("users-web home tools widget exposes the semantic home-cog outlet", async () => {
31
+ const source = await readFile(
32
+ path.join(PACKAGE_DIR, "src", "client", "components", "UsersHomeToolsWidget.vue"),
33
+ "utf8"
34
+ );
97
35
 
98
36
  assert.match(source, /import \{ HOME_COG_OUTLET \} from "\.\.\/\.\.\/shared\/toolsOutletContracts\.js";/);
99
37
  assert.match(source, /<ShellOutletMenuWidget/);
100
38
  assert.match(source, /:target="HOME_COG_OUTLET\.target"/);
101
- assert.match(source, /:aria-label="HOME_COG_OUTLET\.ariaLabel"/);
102
39
  assert.doesNotMatch(source, /default-link-component-token/);
103
40
  });
104
41
 
105
- test("users-web account page template uses the package-owned account settings host", async () => {
106
- const source = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "account", "index.vue"), "utf8");
42
+ test("account settings pattern keeps the app-owned route and sections as adaptable examples", async () => {
43
+ const exampleRoot = path.join(PACKAGE_DIR, "patterns", "account-settings", "example");
44
+ const page = await readFile(path.join(exampleRoot, "src", "pages", "account", "index.vue"), "utf8");
107
45
 
108
46
  assert.match(
109
- source,
47
+ page,
110
48
  /import AccountSettingsClientElement from "@jskit-ai\/users-web\/client\/components\/AccountSettingsClientElement";/
111
49
  );
112
- assert.doesNotMatch(source, /components\/account\/settings\/AccountSettingsClientElement\.vue/);
113
- });
114
-
115
- test("users-web package-owned account settings host is fully placement-backed", async () => {
116
- const source = await readFile(
117
- path.join(PACKAGE_DIR, "src", "client", "components", "AccountSettingsClientElement.vue"),
118
- "utf8"
119
- );
120
-
121
- assertGeneratedUiSourceContract(source, {
122
- forbidCardShell: true,
123
- sourceName: "AccountSettingsClientElement.vue",
124
- requiredPatterns: [
125
- {
126
- id: "account-settings-header",
127
- pattern: /settings-panel__header/,
128
- message: "Account settings host needs a direct settings panel header."
129
- },
130
- {
131
- id: "account-settings-sections",
132
- pattern: /useAccountSettingsSections/,
133
- message: "Account settings host must remain placement-backed."
134
- }
135
- ]
136
- });
137
- assert.match(source, /useAccountSettingsSections/);
138
- assert.match(source, /settings-panel__header/);
139
- assert.doesNotMatch(source, /<v-card\b|v-card-title|v-card-subtitle/);
140
- assert.doesNotMatch(source, /AccountSettingsProfileSection/);
141
- assert.doesNotMatch(source, /AccountSettingsPreferencesSection/);
142
- assert.doesNotMatch(source, /AccountSettingsNotificationsSection/);
143
- });
144
50
 
145
- test("users-web profile form element uses a direct panel instead of card scaffolding", async () => {
146
- const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "ProfileClientElement.vue"), "utf8");
147
-
148
- assertGeneratedUiSourceContract(source, {
149
- forbidCardShell: true,
150
- sourceName: "ProfileClientElement.vue",
151
- requiredPatterns: [
152
- {
153
- id: "profile-panel-body",
154
- pattern: /profile-client-panel__body/,
155
- message: "Profile editor needs a direct panel body."
156
- }
157
- ]
158
- });
159
- assert.match(source, /profile-client-panel__body/);
160
- assert.doesNotMatch(source, /<v-card\b|v-card-title|v-card-subtitle|v-card-text|v-card-item/);
51
+ for (const filename of [
52
+ "AccountSettingsProfileSection.vue",
53
+ "AccountSettingsPreferencesSection.vue",
54
+ "AccountSettingsNotificationsSection.vue"
55
+ ]) {
56
+ const source = await readFile(
57
+ path.join(exampleRoot, "src", "components", "account", "settings", filename),
58
+ "utf8"
59
+ );
60
+ assert.match(source, /account-settings-section/);
61
+ assert.doesNotMatch(source, /<v-card\b|v-card-title|v-card-subtitle/);
62
+ }
161
63
  });
162
64
 
163
- test("users-web profile email ownership copy is provider-neutral", async () => {
164
- const sourcePaths = [
65
+ test("profile copy remains authentication-provider neutral", async () => {
66
+ for (const sourcePath of [
165
67
  path.join("src", "client", "components", "ProfileClientElement.vue"),
166
- path.join("templates", "src", "components", "account", "settings", "AccountSettingsProfileSection.vue")
167
- ];
168
-
169
- for (const sourcePath of sourcePaths) {
68
+ path.join(
69
+ "patterns",
70
+ "account-settings",
71
+ "example",
72
+ "src",
73
+ "components",
74
+ "account",
75
+ "settings",
76
+ "AccountSettingsProfileSection.vue"
77
+ )
78
+ ]) {
170
79
  const source = await readFile(path.join(PACKAGE_DIR, sourcePath), "utf8");
171
80
  assert.match(source, /Managed by your sign-in account/);
172
81
  assert.doesNotMatch(source, /Managed by Supabase Auth/);
173
82
  }
174
83
  });
175
84
 
176
- test("users-web account settings section templates use direct settings panels", async () => {
177
- for (const relativePath of [
178
- path.join("templates", "src", "components", "account", "settings", "AccountSettingsProfileSection.vue"),
179
- path.join("templates", "src", "components", "account", "settings", "AccountSettingsPreferencesSection.vue"),
180
- path.join("templates", "src", "components", "account", "settings", "AccountSettingsNotificationsSection.vue")
181
- ]) {
182
- const source = await readFile(path.join(PACKAGE_DIR, relativePath), "utf8");
85
+ test("package-owned account settings host is placement-backed rather than scaffold-backed", async () => {
86
+ const source = await readFile(
87
+ path.join(PACKAGE_DIR, "src", "client", "components", "AccountSettingsClientElement.vue"),
88
+ "utf8"
89
+ );
183
90
 
184
- assertGeneratedUiSourceContract(source, {
185
- forbidCardShell: true,
186
- sourceName: relativePath,
187
- requiredPatterns: [
188
- {
189
- id: "account-settings-section",
190
- pattern: /account-settings-section/,
191
- message: "Account settings sections need the direct section panel primitive."
192
- }
193
- ]
194
- });
195
- assert.match(source, /account-settings-section/);
196
- assert.doesNotMatch(source, /<v-card\b|v-card-title|v-card-subtitle/);
197
- }
91
+ assert.match(source, /useAccountSettingsSections/);
92
+ assert.match(source, /settings-panel__header/);
93
+ assert.doesNotMatch(source, /AccountSettingsProfileSection|AccountSettingsPreferencesSection|AccountSettingsNotificationsSection/);
198
94
  });
199
95
 
200
- test("users-web descriptor metadata advertises home cog outlet and standard home settings placements", () => {
201
- assert.deepEqual(
202
- readOutlets("home-cog:primary-menu"),
203
- [
204
- {
205
- target: "home-cog:primary-menu",
206
- surfaces: ["home"],
207
- source: "src/client/components/UsersHomeToolsWidget.vue"
208
- }
209
- ]
210
- );
96
+ test("users-web declares semantic outlets and topology without source mutations", () => {
97
+ assert.equal(packageJson.jskit?.mutations, undefined);
98
+ assert.deepEqual(placements?.outlets, [
99
+ {
100
+ target: "home-cog:primary-menu",
101
+ surfaces: ["home"],
102
+ source: "src/client/components/UsersHomeToolsWidget.vue"
103
+ },
104
+ {
105
+ target: "account-settings:sections",
106
+ surfaces: ["account"],
107
+ source: "src/client/components/AccountSettingsClientElement.vue"
108
+ }
109
+ ]);
211
110
  assert.deepEqual(
212
- readOutlets("account-settings:sections"),
111
+ placements?.topology?.placements?.map(({ id, owner = null }) => ({ id, owner })),
213
112
  [
214
- {
215
- target: "account-settings:sections",
216
- surfaces: ["account"],
217
- source: "src/client/components/AccountSettingsClientElement.vue"
218
- }
113
+ { id: "home.tools-menu", owner: null },
114
+ { id: "settings.sections", owner: "account-settings" }
219
115
  ]
220
116
  );
221
- assert.deepEqual(findTopology("home.tools-menu"), {
222
- id: "home.tools-menu",
223
- description: "Home surface tools menu actions.",
224
- surfaces: ["home"],
225
- variants: {
226
- compact: {
227
- outlet: "home-cog:primary-menu",
228
- renderers: {
229
- link: "local.main.ui.surface-aware-menu-link-item"
230
- }
231
- },
232
- medium: {
233
- outlet: "home-cog:primary-menu",
234
- renderers: {
235
- link: "local.main.ui.surface-aware-menu-link-item"
236
- }
237
- },
238
- expanded: {
239
- outlet: "home-cog:primary-menu",
240
- renderers: {
241
- link: "local.main.ui.surface-aware-menu-link-item"
242
- }
243
- }
244
- }
245
- });
246
- assert.deepEqual(findTopology("settings.sections", "account-settings"), {
247
- id: "settings.sections",
248
- owner: "account-settings",
249
- description: "Account settings content sections.",
250
- surfaces: ["account"],
251
- variants: {
252
- compact: {
253
- outlet: "account-settings:sections"
254
- },
255
- medium: {
256
- outlet: "account-settings:sections"
257
- },
258
- expanded: {
259
- outlet: "account-settings:sections"
260
- }
261
- }
262
- });
263
-
264
- expectContribution("users.profile.menu.settings", {
265
- target: "auth.profile-menu",
266
- kind: "link",
267
- surfaces: ["*"],
268
- order: 500,
269
- when: "auth.authenticated === true",
270
- source: "mutations.text#users-web-profile-settings-placement"
271
- });
117
+ });
272
118
 
273
- expectContribution("users.home.tools.widget", {
119
+ test("semantic contributions identify live package source or reusable pattern source", () => {
120
+ assert.deepEqual(contribution("users.home.tools.widget"), {
121
+ id: "users.home.tools.widget",
274
122
  target: "shell.status",
275
123
  kind: "component",
276
124
  surfaces: ["home"],
277
125
  order: 900,
278
126
  componentToken: "users.web.home.tools.widget",
279
127
  when: "auth.authenticated === true",
280
- source: "mutations.text#users-web-home-tools-placement"
281
- });
282
-
283
- expectContribution("users.home.menu.settings", {
284
- target: "home.tools-menu",
285
- kind: "link",
286
- surfaces: ["home"],
287
- order: 100,
288
- when: "auth.authenticated === true",
289
- source: "mutations.text#users-web-home-tools-placement"
290
- });
291
- assert.equal(findContribution("users.home.settings.general"), null);
292
- expectContribution("users.account.settings.profile", {
293
- target: "settings.sections",
294
- owner: "account-settings",
295
- kind: "component",
296
- surfaces: ["account"],
297
- order: 100,
298
- componentToken: "local.main.account-settings.section.profile",
299
- source: "mutations.text#users-web-account-settings-sections-placement"
128
+ source: "src/client/components/UsersHomeToolsWidget.vue"
300
129
  });
301
- expectContribution("users.account.settings.preferences", {
302
- target: "settings.sections",
303
- owner: "account-settings",
304
- kind: "component",
305
- surfaces: ["account"],
306
- order: 200,
307
- componentToken: "local.main.account-settings.section.preferences",
308
- source: "mutations.text#users-web-account-settings-sections-placement"
309
- });
310
- expectContribution("users.account.settings.notifications", {
311
- target: "settings.sections",
312
- owner: "account-settings",
313
- kind: "component",
314
- surfaces: ["account"],
315
- order: 300,
316
- componentToken: "local.main.account-settings.section.notifications",
317
- source: "mutations.text#users-web-account-settings-sections-placement"
318
- });
319
-
320
- expectTextMutation("users-web-home-tools-placement", {
321
- reason: "Append users-web home tools widget and settings menu placements into app-owned placement registry.",
322
- category: "users-web",
323
- skipIfContains: 'id: "users.home.tools.widget"',
324
- snippets: [
325
- 'id: "users.home.tools.widget"',
326
- 'componentToken: "users.web.home.tools.widget"',
327
- 'id: "users.home.menu.settings"',
328
- 'target: "home.tools-menu"',
329
- 'kind: "link"',
330
- 'scopedSuffix: "/settings"',
331
- 'unscopedSuffix: "/settings"'
332
- ]
333
- });
334
- assert.equal(findTextMutation("users-web-home-tools-topology")?.file, "src/placementTopology.js");
335
- assert.match(findTextMutation("users-web-home-tools-topology")?.value || "", /id: "home\.tools-menu"/);
336
- assert.match(findTextMutation("users-web-home-tools-topology")?.value || "", /outlet: "home-cog:primary-menu"/);
337
- expectTextMutation("users-web-account-settings-sections-placement", {
338
- reason: "Append users-web account settings section placements into the app-owned placement registry.",
339
- category: "users-web",
340
- skipIfContains: 'id: "users.account.settings.profile"',
341
- snippets: [
342
- 'id: "users.account.settings.profile"',
343
- 'target: "settings.sections"',
344
- 'owner: "account-settings"',
345
- 'componentToken: "local.main.account-settings.section.profile"',
346
- 'value: "profile"',
347
- 'id: "users.account.settings.preferences"',
348
- 'componentToken: "local.main.account-settings.section.preferences"',
349
- 'value: "preferences"',
350
- 'id: "users.account.settings.notifications"',
351
- 'componentToken: "local.main.account-settings.section.notifications"',
352
- 'value: "notifications"'
353
- ]
354
- });
355
- assert.equal(findTextMutation("users-web-account-settings-topology")?.file, "src/placementTopology.js");
356
- assert.match(findTextMutation("users-web-account-settings-topology")?.value || "", /id: "settings\.sections"/);
357
- assert.match(findTextMutation("users-web-account-settings-topology")?.value || "", /owner: "account-settings"/);
358
- assert.match(findTextMutation("users-web-account-settings-topology")?.value || "", /outlet: "account-settings:sections"/);
359
-
360
- expectTextMutation("users-web-profile-settings-placement", {
361
- reason: "Append users-web profile settings menu placement into app-owned placement registry.",
362
- category: "users-web",
363
- skipIfContains: 'id: "users.profile.menu.settings"',
364
- snippets: [
365
- 'id: "users.profile.menu.settings"',
366
- 'target: "auth.profile-menu"',
367
- 'kind: "link"',
368
- 'label: "Settings"',
369
- 'to: "/account"'
370
- ]
371
- });
372
-
373
- assert.equal(findFileMutation("users-web-component-account-settings-root"), null);
374
- assert.equal(findFileMutation("users-web-component-account-settings-invites"), null);
375
- assert.deepEqual(findFileMutation("users-web-component-account-settings-profile"), {
376
- from: "templates/src/components/account/settings/AccountSettingsProfileSection.vue",
377
- to: "src/components/account/settings/AccountSettingsProfileSection.vue",
378
- ownership: "app",
379
- reason: "Install app-owned account settings profile section scaffold.",
380
- category: "users-web",
381
- id: "users-web-component-account-settings-profile"
382
- });
383
- assert.deepEqual(findFileMutation("users-web-component-account-settings-preferences"), {
384
- from: "templates/src/components/account/settings/AccountSettingsPreferencesSection.vue",
385
- to: "src/components/account/settings/AccountSettingsPreferencesSection.vue",
386
- ownership: "app",
387
- reason: "Install app-owned account settings preferences section scaffold.",
388
- category: "users-web",
389
- id: "users-web-component-account-settings-preferences"
390
- });
391
- assert.deepEqual(findFileMutation("users-web-component-account-settings-notifications"), {
392
- from: "templates/src/components/account/settings/AccountSettingsNotificationsSection.vue",
393
- to: "src/components/account/settings/AccountSettingsNotificationsSection.vue",
394
- ownership: "app",
395
- reason: "Install app-owned account settings notifications section scaffold.",
396
- category: "users-web",
397
- id: "users-web-component-account-settings-notifications"
398
- });
399
- assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-profile-import"), {
400
- op: "ensure-import",
401
- file: "packages/main/src/client/providers/MainClientProvider.js",
402
- defaultImport: "AccountSettingsProfileSection",
403
- from: "/src/components/account/settings/AccountSettingsProfileSection.vue",
404
- reason: "Bind the app-owned account profile settings section into local main client provider imports.",
405
- category: "users-web",
406
- id: "users-web-main-client-provider-account-settings-profile-import"
407
- });
408
- assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-preferences-import"), {
409
- op: "ensure-import",
410
- file: "packages/main/src/client/providers/MainClientProvider.js",
411
- defaultImport: "AccountSettingsPreferencesSection",
412
- from: "/src/components/account/settings/AccountSettingsPreferencesSection.vue",
413
- reason: "Bind the app-owned account preferences settings section into local main client provider imports.",
414
- category: "users-web",
415
- id: "users-web-main-client-provider-account-settings-preferences-import"
416
- });
417
- assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-notifications-import"), {
418
- op: "ensure-import",
419
- file: "packages/main/src/client/providers/MainClientProvider.js",
420
- defaultImport: "AccountSettingsNotificationsSection",
421
- from: "/src/components/account/settings/AccountSettingsNotificationsSection.vue",
422
- reason: "Bind the app-owned account notifications settings section into local main client provider imports.",
423
- category: "users-web",
424
- id: "users-web-main-client-provider-account-settings-notifications-import"
425
- });
426
- assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-profile-register"), {
427
- op: "ensure-call",
428
- file: "packages/main/src/client/providers/MainClientProvider.js",
429
- callee: "registerMainClientComponent",
430
- args: ["\"local.main.account-settings.section.profile\"", "() => AccountSettingsProfileSection"],
431
- beforeClass: "MainClientProvider",
432
- reason: "Bind the app-owned account profile settings section token into local main client provider registry.",
433
- category: "users-web",
434
- id: "users-web-main-client-provider-account-settings-profile-register"
435
- });
436
- assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-preferences-register"), {
437
- op: "ensure-call",
438
- file: "packages/main/src/client/providers/MainClientProvider.js",
439
- callee: "registerMainClientComponent",
440
- args: ["\"local.main.account-settings.section.preferences\"", "() => AccountSettingsPreferencesSection"],
441
- beforeClass: "MainClientProvider",
442
- reason: "Bind the app-owned account preferences settings section token into local main client provider registry.",
443
- category: "users-web",
444
- id: "users-web-main-client-provider-account-settings-preferences-register"
445
- });
446
- assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-notifications-register"), {
447
- op: "ensure-call",
448
- file: "packages/main/src/client/providers/MainClientProvider.js",
449
- callee: "registerMainClientComponent",
450
- args: ["\"local.main.account-settings.section.notifications\"", "() => AccountSettingsNotificationsSection"],
451
- beforeClass: "MainClientProvider",
452
- reason: "Bind the app-owned account notifications settings section token into local main client provider registry.",
453
- category: "users-web",
454
- id: "users-web-main-client-provider-account-settings-notifications-register"
455
- });
456
-
130
+ assert.equal(contribution("users.profile.menu.settings")?.source, "patterns/account-settings/PATTERN.md");
131
+ assert.equal(contribution("users.home.menu.settings")?.source, "patterns/account-settings/PATTERN.md");
132
+ assert.equal(
133
+ contribution("users.account.settings.profile")?.source,
134
+ "patterns/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue"
135
+ );
136
+ assert.equal(
137
+ contribution("users.account.settings.preferences")?.source,
138
+ "patterns/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue"
139
+ );
140
+ assert.equal(
141
+ contribution("users.account.settings.notifications")?.source,
142
+ "patterns/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue"
143
+ );
144
+ assert.equal(contribution("users.home.settings.general"), null);
457
145
  });
@@ -1,107 +0,0 @@
1
- <script setup>
2
- import { computed } from "vue";
3
- import CrudListFilterSurface from "../../src/client/components/CrudListFilterSurface.vue";
4
- import { useCrudListFilters } from "../../src/client/composables/useCrudListFilters.js";
5
-
6
- const filters = {
7
- status: {
8
- key: "status",
9
- queryKey: "status",
10
- type: "enum",
11
- label: "Status",
12
- options: [
13
- { value: "active", label: "Active" },
14
- { value: "archived", label: "Archived" }
15
- ]
16
- },
17
- submittedOn: {
18
- key: "submittedOn",
19
- queryKey: "submittedOn",
20
- type: "date",
21
- label: "Submitted date"
22
- },
23
- arrivalDate: {
24
- key: "arrivalDate",
25
- queryKey: "arrivalDate",
26
- type: "dateRange",
27
- label: "Arrival"
28
- },
29
- assignment: {
30
- key: "assignment",
31
- queryKey: "assignment",
32
- type: "presence",
33
- label: "Assignment",
34
- options: [
35
- { value: "present", label: "Assigned" },
36
- { value: "missing", label: "Unassigned" }
37
- ]
38
- }
39
- };
40
- const runtime = useCrudListFilters(filters);
41
- const query = new URLSearchParams(window.location.search);
42
-
43
- for (const [key, param] of Object.entries(runtime.queryParams)) {
44
- if (query.has(key)) {
45
- param.value = query.get(key);
46
- }
47
- }
48
-
49
- const contractSnapshot = computed(() => JSON.stringify({
50
- values: {
51
- submittedOn: runtime.values.submittedOn,
52
- arrivalDate: {
53
- from: runtime.values.arrivalDate.from,
54
- to: runtime.values.arrivalDate.to
55
- }
56
- },
57
- query: {
58
- submittedOn: runtime.queryParams.submittedOn.value,
59
- arrivalDate: runtime.queryParams.arrivalDate.value
60
- }
61
- }));
62
- </script>
63
-
64
- <template>
65
- <v-app>
66
- <v-main>
67
- <v-container class="date-filter-fixture py-8">
68
- <div class="mb-6">
69
- <div class="text-overline text-primary">JSKIT users-web</div>
70
- <h1 class="text-h4 mb-2">Date filter controls</h1>
71
- <p class="text-body-1 text-medium-emphasis mb-0">
72
- Date and date-range filters beside ordinary Vuetify selects.
73
- </p>
74
- </div>
75
-
76
- <CrudListFilterSurface :filters="filters" :runtime="runtime" />
77
-
78
- <output data-testid="date-filter-contract" class="date-filter-fixture__contract">
79
- {{ contractSnapshot }}
80
- </output>
81
- </v-container>
82
- </v-main>
83
- </v-app>
84
- </template>
85
-
86
- <style>
87
- html {
88
- overflow-x: hidden;
89
- }
90
-
91
- body {
92
- background: rgb(var(--v-theme-background));
93
- }
94
-
95
- .date-filter-fixture {
96
- max-width: 90rem;
97
- }
98
-
99
- .date-filter-fixture__contract {
100
- display: block;
101
- font-family: monospace;
102
- height: 1px;
103
- overflow: hidden;
104
- position: absolute;
105
- width: 1px;
106
- }
107
- </style>
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>CRUD date filter visual regression</title>
7
- </head>
8
- <body>
9
- <div id="app"></div>
10
- <script type="module" src="/main.js"></script>
11
- </body>
12
- </html>