@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.
- package/package.json +197 -45
- package/patterns/account-settings/PATTERN.md +61 -0
- package/src/client/bootstrap/user-bootstrap-handler.js +2 -12
- package/src/client/composables/account-settings/accountSettingsAvatarUploadRuntime.js +2 -2
- package/src/client/composables/useAccountSettingsRuntime.js +3 -3
- package/src/client/index.js +0 -16
- package/src/client/providers/UsersWebClientProvider.js +13 -14
- package/test/bootstrap.test.js +6 -41
- package/test/exportsContract.test.js +3 -29
- package/test/packageMetadataOwnership.test.js +19 -0
- package/test/settingsPlacementContract.test.js +93 -405
- package/fixtures/crud-list-date-filters/App.vue +0 -107
- package/fixtures/crud-list-date-filters/index.html +0 -12
- package/fixtures/crud-list-date-filters/main.js +0 -24
- package/fixtures/crud-list-date-filters/vite.config.mjs +0 -18
- package/package.descriptor.mjs +0 -468
- package/src/client/bulkActions.js +0 -47
- package/src/client/components/CrudAddEditScreen.vue +0 -186
- package/src/client/components/CrudListBulkActionSurface.vue +0 -126
- package/src/client/components/CrudListDateFilterControl.vue +0 -188
- package/src/client/components/CrudListFilterSurface.vue +0 -388
- package/src/client/components/CrudListRecordActionMenu.vue +0 -120
- package/src/client/components/CrudListScreen.vue +0 -501
- package/src/client/components/CrudViewScreen.vue +0 -197
- package/src/client/composables/crud/crudBindingSupport.js +0 -75
- package/src/client/composables/crud/crudHttpClientSupport.js +0 -57
- package/src/client/composables/crud/crudJsonApiTransportSupport.js +0 -145
- package/src/client/composables/crud/crudLookupFieldLabelSupport.js +0 -151
- package/src/client/composables/crud/crudLookupFieldRuntime.js +0 -251
- package/src/client/composables/crud/crudSchemaFormHelpers.js +0 -464
- package/src/client/composables/internal/crudListFilterLookupSupport.js +0 -161
- package/src/client/composables/internal/crudListParentTitleSupport.js +0 -171
- package/src/client/composables/internal/useOperationScope.js +0 -144
- package/src/client/composables/records/useAddEdit.js +0 -224
- package/src/client/composables/records/useCrudAddEdit.js +0 -227
- package/src/client/composables/records/useCrudList.js +0 -101
- package/src/client/composables/records/useCrudView.js +0 -47
- package/src/client/composables/records/useList.js +0 -500
- package/src/client/composables/records/useView.js +0 -164
- package/src/client/composables/runtime/addEditUiRuntime.js +0 -130
- package/src/client/composables/runtime/listUiRuntime.js +0 -123
- package/src/client/composables/runtime/modelStateHelpers.js +0 -90
- package/src/client/composables/runtime/operationAdapters.js +0 -34
- package/src/client/composables/runtime/operationUiHelpers.js +0 -125
- package/src/client/composables/runtime/operationValidationHelpers.js +0 -51
- package/src/client/composables/runtime/useAddEditCore.js +0 -112
- package/src/client/composables/runtime/useCommandCore.js +0 -123
- package/src/client/composables/runtime/useEndpointResource.js +0 -199
- package/src/client/composables/runtime/useFieldErrorBag.js +0 -61
- package/src/client/composables/runtime/useListCore.js +0 -112
- package/src/client/composables/runtime/useUiFeedback.js +0 -98
- package/src/client/composables/runtime/useViewCore.js +0 -93
- package/src/client/composables/runtime/viewUiRuntime.js +0 -96
- package/src/client/composables/support/errorMessageHelpers.js +0 -66
- package/src/client/composables/support/listQueryParamSupport.js +0 -473
- package/src/client/composables/support/listSearchSupport.js +0 -70
- package/src/client/composables/support/refValueHelpers.js +0 -19
- package/src/client/composables/support/requestQueryRuntimeSupport.js +0 -141
- package/src/client/composables/support/resourceLoadStateHelpers.js +0 -229
- package/src/client/composables/support/routeTemplateHelpers.js +0 -285
- package/src/client/composables/support/scopeHelpers.js +0 -145
- package/src/client/composables/useAccess.js +0 -109
- package/src/client/composables/useCommand.js +0 -118
- package/src/client/composables/useCrudAddEditScreen.js +0 -88
- package/src/client/composables/useCrudDeleteAction.js +0 -160
- package/src/client/composables/useCrudListBulkActions.js +0 -147
- package/src/client/composables/useCrudListFilterLookups.js +0 -146
- package/src/client/composables/useCrudListFilters.js +0 -339
- package/src/client/composables/useCrudListParentTitle.js +0 -151
- package/src/client/composables/useCrudListRowActions.js +0 -144
- package/src/client/composables/useCrudListScreen.js +0 -237
- package/src/client/composables/useCrudViewScreen.js +0 -77
- package/src/client/composables/usePagedCollection.js +0 -181
- package/src/client/composables/usePaths.js +0 -120
- package/src/client/composables/useRealtimeQueryInvalidation.js +0 -156
- package/src/client/composables/useScopeRuntime.js +0 -135
- package/src/client/composables/useSurfaceRouteContext.js +0 -25
- package/src/client/filters.js +0 -15
- package/src/client/lib/httpClient.js +0 -47
- package/src/client/lib/permissions.js +0 -34
- package/src/client/rowActions.js +0 -47
- package/src/client/support/contractGuards.js +0 -34
- package/src/client/support/crudListDateFilterSupport.js +0 -91
- package/test/addEditUiRuntime.test.js +0 -110
- package/test/crudBindingSupport.test.js +0 -110
- package/test/crudHttpClientSupport.test.js +0 -77
- package/test/crudJsonApiTransportSupport.test.js +0 -179
- package/test/crudListBulkActionSurface.test.js +0 -27
- package/test/crudListDateFilterSupport.test.js +0 -92
- package/test/crudListDateFilterSurface.browser.test.js +0 -273
- package/test/crudListFilterSurface.test.js +0 -90
- package/test/crudLookupFieldRuntime.test.js +0 -264
- package/test/crudScreenComponents.test.js +0 -108
- package/test/errorIntentContract.test.js +0 -31
- package/test/errorMessageHelpers.test.js +0 -28
- package/test/listQueryParamSupport.test.js +0 -223
- package/test/listUiRuntime.test.js +0 -102
- package/test/operationValidationHelpers.test.js +0 -64
- package/test/packageDescriptorOwnership.test.js +0 -18
- package/test/permissions.test.js +0 -35
- package/test/refValueHelpers.test.js +0 -14
- package/test/requestTransportOptions.test.js +0 -457
- package/test/resourceLoadStateHelpers.test.js +0 -189
- package/test/routeTemplateHelpers.test.js +0 -85
- package/test/scopeHelpers.test.js +0 -57
- package/test/useAddEditCore.test.js +0 -124
- package/test/useAddEditRequestQueryParams.test.js +0 -190
- package/test/useCrudAddEdit.test.js +0 -435
- package/test/useCrudDeleteAction.test.js +0 -139
- package/test/useCrudListBulkActions.test.js +0 -65
- package/test/useCrudListFilterLookups.test.js +0 -114
- package/test/useCrudListFilters.test.js +0 -293
- package/test/useCrudListParentTitle.test.js +0 -262
- package/test/useCrudListRowActions.test.js +0 -77
- package/test/useListRouteInitialization.test.js +0 -337
- package/test/useListSearchSupport.test.js +0 -61
- package/test/usePagedCollection.test.js +0 -53
- package/test/useViewRequestQueryParams.test.js +0 -20
- package/test/viewCoreLoading.test.js +0 -44
- package/test/viewUiRuntime.test.js +0 -95
- /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsNotificationsSection.vue +0 -0
- /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsPreferencesSection.vue +0 -0
- /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsProfileSection.vue +0 -0
- /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
|
|
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
|
|
13
|
-
|
|
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
|
|
21
|
-
|
|
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
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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(
|
|
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("
|
|
106
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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("
|
|
164
|
-
const
|
|
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(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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("
|
|
177
|
-
|
|
178
|
-
path.join(
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
201
|
-
assert.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
111
|
+
placements?.topology?.placements?.map(({ id, owner = null }) => ({ id, owner })),
|
|
213
112
|
[
|
|
214
|
-
{
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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>
|