@jskit-ai/workspaces-web 0.1.139-oldline.2 → 0.1.140
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 +247 -9
- package/patterns/workspace-surfaces/PATTERN.md +63 -0
- package/src/client/account-settings/accountSettingsInvitesRuntime.js +2 -2
- package/src/client/account-settings/useAccountSettingsInvitesSectionRuntime.js +3 -3
- package/src/client/components/MembersAdminClientElement.vue +1 -1
- package/src/client/components/WorkspaceInviteLanding.vue +4 -4
- package/src/client/components/WorkspaceMembersClientElement.vue +6 -6
- package/src/client/components/{UsersWorkspaceMembersMenuItem.vue → WorkspaceMembersMenuItem.vue} +2 -2
- package/src/client/components/{UsersWorkspacePermissionMenuItem.vue → WorkspacePermissionMenuItem.vue} +3 -3
- package/src/client/components/{UsersProfileSurfaceSwitchMenuItem.vue → WorkspaceProfileSurfaceSwitchMenuItem.vue} +1 -1
- package/src/client/components/{UsersWorkspaceSelector.vue → WorkspaceSelector.vue} +1 -1
- package/src/client/components/{UsersWorkspaceSettingsMenuItem.vue → WorkspaceSettingsMenuItem.vue} +2 -2
- package/src/client/components/WorkspacesClientElement.vue +13 -13
- package/src/client/composables/useWorkspaceRouteContext.js +1 -1
- package/src/client/index.js +0 -5
- package/src/client/lib/surfaceAccessPolicy.js +4 -1
- package/src/client/providers/WorkspacesWebClientProvider.js +54 -50
- package/src/client/runtime/bootstrapPlacementRuntime.js +18 -32
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +4 -4
- package/test/bootstrapPlacementRuntime.test.js +40 -125
- package/test/packageMetadataOwnership.test.js +19 -0
- package/test/settingsPlacementContract.test.js +87 -389
- package/test/{usersWorkspaceSelectorContract.test.js → workspaceSelectorContract.test.js} +3 -3
- package/package.descriptor.mjs +0 -492
- package/src/client/lib/httpClient.js +0 -10
- package/src/client/lib/permissions.js +0 -27
- package/test/packageDescriptorOwnership.test.js +0 -16
- /package/{templates → patterns/workspace-surfaces/example}/packages/main/src/client/components/AccountPendingInvitesCue.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/packages/main/src/client/components/AccountSettingsInvitesSection.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/components/WorkspaceNotFoundCard.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/composables/useWorkspaceNotFoundState.js +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/pages/admin/members/index.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/pages/admin/workspace/settings/index.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/pages/admin/workspace/settings.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/pages/invite/[token].vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/surfaces/admin/index.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/surfaces/admin/root.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/surfaces/app/index.vue +0 -0
- /package/{templates → patterns/workspace-surfaces/example}/src/surfaces/app/root.vue +0 -0
- /package/src/client/components/{UsersWorkspaceToolsWidget.vue → WorkspaceToolsWidget.vue} +0 -0
|
@@ -3,449 +3,147 @@ 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
|
-
const
|
|
10
|
-
const
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const exampleRoot = path.join(packageRoot, "patterns", "workspace-surfaces", "example");
|
|
10
|
+
const placements = packageJson.jskit?.metadata?.ui?.placements;
|
|
11
11
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
const normalizedTarget = String(target || "").trim();
|
|
15
|
-
return Array.isArray(outlets)
|
|
16
|
-
? outlets.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
|
|
17
|
-
: [];
|
|
12
|
+
function contribution(id) {
|
|
13
|
+
return placements?.contributions?.find((entry) => entry.id === id) || null;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
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;
|
|
31
|
-
}
|
|
32
|
-
|
|
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
|
-
test("workspaces-web admin settings template exposes surface-derived settings outlets", async () => {
|
|
62
|
-
const source = await readFile(
|
|
63
|
-
path.join(PACKAGE_DIR, "templates", "src", "pages", "admin", "workspace", "settings.vue"),
|
|
16
|
+
test("workspace surfaces pattern includes settings, invitations, and app-owned integration examples", async () => {
|
|
17
|
+
const settings = await readFile(
|
|
18
|
+
path.join(exampleRoot, "src", "pages", "admin", "workspace", "settings.vue"),
|
|
64
19
|
"utf8"
|
|
65
20
|
);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
forbidCardShell: true,
|
|
69
|
-
sourceName: "admin/workspace/settings.vue",
|
|
70
|
-
requiredPatterns: [
|
|
71
|
-
{
|
|
72
|
-
id: "admin-settings-outlet",
|
|
73
|
-
pattern: /target="admin-settings:primary-menu"/,
|
|
74
|
-
message: "Admin settings shell needs the semantic settings outlet host."
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
id: "admin-settings-router-view",
|
|
78
|
-
pattern: /<RouterView \/>/,
|
|
79
|
-
message: "Admin settings shell needs to host child settings routes."
|
|
80
|
-
}
|
|
81
|
-
]
|
|
82
|
-
});
|
|
83
|
-
assert.match(source, /target="admin-settings:primary-menu"/);
|
|
84
|
-
assert.doesNotMatch(source, /default-link-component-token/);
|
|
85
|
-
assert.doesNotMatch(source, /<v-card\b/);
|
|
86
|
-
assert.match(source, /<RouterView \/>/);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("workspaces-web installs an app-owned account invites section wrapper", async () => {
|
|
90
|
-
const source = await readFile(
|
|
91
|
-
path.join(PACKAGE_DIR, "templates", "packages", "main", "src", "client", "components", "AccountSettingsInvitesSection.vue"),
|
|
21
|
+
const invites = await readFile(
|
|
22
|
+
path.join(exampleRoot, "packages", "main", "src", "client", "components", "AccountSettingsInvitesSection.vue"),
|
|
92
23
|
"utf8"
|
|
93
24
|
);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
source,
|
|
97
|
-
/@jskit-ai\/workspaces-web\/client\/components\/AccountSettingsInvitesSection/
|
|
98
|
-
);
|
|
99
|
-
assert.deepEqual(findFileMutation("users-web-main-component-account-settings-invites-section"), {
|
|
100
|
-
from: "templates/packages/main/src/client/components/AccountSettingsInvitesSection.vue",
|
|
101
|
-
to: "packages/main/src/client/components/AccountSettingsInvitesSection.vue",
|
|
102
|
-
ownership: "app",
|
|
103
|
-
reason: "Install app-owned account invites section scaffold for multihoming account settings.",
|
|
104
|
-
category: "workspaces-web",
|
|
105
|
-
id: "users-web-main-component-account-settings-invites-section"
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("workspaces-web installs a public workspace invite route scaffold", async () => {
|
|
110
|
-
const source = await readFile(
|
|
111
|
-
path.join(PACKAGE_DIR, "templates", "src", "pages", "invite", "[token].vue"),
|
|
25
|
+
const invitationPage = await readFile(
|
|
26
|
+
path.join(exampleRoot, "src", "pages", "invite", "[token].vue"),
|
|
112
27
|
"utf8"
|
|
113
28
|
);
|
|
114
29
|
|
|
115
|
-
assert.
|
|
116
|
-
assert.match(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
120
|
-
assert.match(
|
|
121
|
-
assert.
|
|
122
|
-
from: "templates/src/pages/invite/[token].vue",
|
|
123
|
-
to: "src/pages/invite/[token].vue",
|
|
124
|
-
ownership: "app",
|
|
125
|
-
reason: "Install public workspace invite acceptance route scaffold.",
|
|
126
|
-
category: "workspaces-web",
|
|
127
|
-
id: "workspaces-web-page-public-invite"
|
|
128
|
-
});
|
|
30
|
+
assert.match(settings, /target="admin-settings:primary-menu"/);
|
|
31
|
+
assert.match(settings, /<RouterView \/>/);
|
|
32
|
+
assert.doesNotMatch(settings, /<v-card\b|default-link-component-token/);
|
|
33
|
+
assert.match(invites, /@jskit-ai\/workspaces-web\/client\/components\/AccountSettingsInvitesSection/);
|
|
34
|
+
assert.match(invitationPage, /@jskit-ai\/workspaces-web\/client\/components\/WorkspaceInviteLanding/);
|
|
35
|
+
assert.match(invitationPage, /<WorkspaceInviteLanding \/>/);
|
|
36
|
+
assert.doesNotMatch(invitationPage, /\bfetch\s*\(/);
|
|
129
37
|
});
|
|
130
38
|
|
|
131
|
-
test("
|
|
132
|
-
for (const
|
|
133
|
-
path.join(
|
|
134
|
-
path.join("src", "client", "components", "WorkspacesClientElement.vue"),
|
|
135
|
-
path.join("src", "client", "components", "AccountSettingsInvitesSection.vue")
|
|
39
|
+
test("workspace settings and not-found examples use direct panels", async () => {
|
|
40
|
+
for (const sourcePath of [
|
|
41
|
+
path.join(exampleRoot, "src", "components", "WorkspaceNotFoundCard.vue"),
|
|
42
|
+
path.join(packageRoot, "src", "client", "components", "WorkspacesClientElement.vue"),
|
|
43
|
+
path.join(packageRoot, "src", "client", "components", "AccountSettingsInvitesSection.vue")
|
|
136
44
|
]) {
|
|
137
|
-
const source = await readFile(
|
|
138
|
-
|
|
139
|
-
assertGeneratedUiSourceContract(source, {
|
|
140
|
-
forbidCardShell: true,
|
|
141
|
-
sourceName: relativePath
|
|
142
|
-
});
|
|
45
|
+
const source = await readFile(sourcePath, "utf8");
|
|
143
46
|
assert.doesNotMatch(source, /<v-card\b|v-card-title|v-card-subtitle/);
|
|
144
47
|
}
|
|
145
48
|
});
|
|
146
49
|
|
|
147
|
-
test("
|
|
50
|
+
test("workspace resource load states expose local retry actions", async () => {
|
|
148
51
|
const expectations = new Map([
|
|
149
52
|
["src/client/components/WorkspacesClientElement.vue", /bootstrapLoadError[\s\S]*@click="refreshBootstrap"/],
|
|
150
53
|
["src/client/components/WorkspaceMembersClientElement.vue", /canRetryLoad[\s\S]*@click="refreshLoad"/]
|
|
151
54
|
]);
|
|
152
55
|
|
|
153
56
|
for (const [relativePath, pattern] of expectations) {
|
|
154
|
-
const source = await readFile(path.join(
|
|
155
|
-
assert.match(source, pattern
|
|
57
|
+
const source = await readFile(path.join(packageRoot, relativePath), "utf8");
|
|
58
|
+
assert.match(source, pattern);
|
|
156
59
|
}
|
|
157
60
|
});
|
|
158
61
|
|
|
159
|
-
test("
|
|
62
|
+
test("workspace command loading follows the http-web proxyRefs contract", async () => {
|
|
160
63
|
const expectations = new Map([
|
|
161
64
|
[
|
|
162
65
|
"src/client/components/WorkspaceMembersClientElement.vue",
|
|
163
|
-
[
|
|
164
|
-
/isCreatingInvite: Boolean\(inviteCreateCommand\.isRunning\)/,
|
|
165
|
-
/if \(inviteCreateCommand\.isRunning \|\| !canInviteMembers\.value\)/
|
|
166
|
-
]
|
|
66
|
+
[/isCreatingInvite: Boolean\(inviteCreateCommand\.isRunning\)/, /inviteCreateCommand\.isRunning \|\| !canInviteMembers\.value/]
|
|
167
67
|
],
|
|
168
68
|
[
|
|
169
69
|
"src/client/components/WorkspacesClientElement.vue",
|
|
170
|
-
[
|
|
171
|
-
/const isCreatingWorkspace = computed\(\(\) => Boolean\(createWorkspaceCommand\.isRunning\)\)/
|
|
172
|
-
]
|
|
70
|
+
[/const isCreatingWorkspace = computed\(\(\) => Boolean\(createWorkspaceCommand\.isRunning\)\)/]
|
|
173
71
|
],
|
|
174
72
|
[
|
|
175
73
|
"src/client/account-settings/useAccountSettingsInvitesSectionRuntime.js",
|
|
176
|
-
[
|
|
177
|
-
/const isResolvingInvite = computed\(\(\) => Boolean\(redeemInviteCommand\.isRunning\)\)/
|
|
178
|
-
]
|
|
74
|
+
[/const isResolvingInvite = computed\(\(\) => Boolean\(redeemInviteCommand\.isRunning\)\)/]
|
|
179
75
|
]
|
|
180
76
|
]);
|
|
181
77
|
|
|
182
78
|
for (const [relativePath, patterns] of expectations) {
|
|
183
|
-
const source = await readFile(path.join(
|
|
184
|
-
assert.doesNotMatch(
|
|
185
|
-
source,
|
|
186
|
-
/\.isRunning\.value/,
|
|
187
|
-
`${relativePath} must not read useCommand().isRunning.value; useCommand returns proxyRefs.`
|
|
188
|
-
);
|
|
79
|
+
const source = await readFile(path.join(packageRoot, relativePath), "utf8");
|
|
80
|
+
assert.doesNotMatch(source, /\.isRunning\.value/);
|
|
189
81
|
for (const pattern of patterns) {
|
|
190
|
-
assert.match(source, pattern
|
|
82
|
+
assert.match(source, pattern);
|
|
191
83
|
}
|
|
192
84
|
}
|
|
193
85
|
});
|
|
194
86
|
|
|
195
|
-
test("
|
|
196
|
-
const
|
|
197
|
-
path.join(
|
|
87
|
+
test("workspace pattern includes pending-invite context and product-ready empty surfaces", async () => {
|
|
88
|
+
const cue = await readFile(
|
|
89
|
+
path.join(exampleRoot, "packages", "main", "src", "client", "components", "AccountPendingInvitesCue.vue"),
|
|
198
90
|
"utf8"
|
|
199
91
|
);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
assert.doesNotMatch(source, /\buseQuery\b/);
|
|
203
|
-
assert.match(source, /placementContext\.value\?\.pendingInvitesCount/);
|
|
204
|
-
assert.match(source, /placementContext\.value\?\.workspaceInvitesEnabled/);
|
|
205
|
-
assert.deepEqual(findFileMutation("users-web-main-component-account-pending-invites-cue"), {
|
|
206
|
-
from: "templates/packages/main/src/client/components/AccountPendingInvitesCue.vue",
|
|
207
|
-
to: "packages/main/src/client/components/AccountPendingInvitesCue.vue",
|
|
208
|
-
ownership: "app",
|
|
209
|
-
reason: "Install app-owned account pending invites cue component scaffold.",
|
|
210
|
-
category: "workspaces-web",
|
|
211
|
-
id: "users-web-main-component-account-pending-invites-cue"
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("workspaces-web admin settings index template is an app-owned landing slot", async () => {
|
|
216
|
-
const source = await readFile(
|
|
217
|
-
path.join(PACKAGE_DIR, "templates", "src", "pages", "admin", "workspace", "settings", "index.vue"),
|
|
92
|
+
const settingsLanding = await readFile(
|
|
93
|
+
path.join(exampleRoot, "src", "pages", "admin", "workspace", "settings", "index.vue"),
|
|
218
94
|
"utf8"
|
|
219
95
|
);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
assert.doesNotMatch(source, /@jskit-ai\/workspaces-web\/client\/components\/WorkspaceSettingsClientElement/);
|
|
233
|
-
assert.doesNotMatch(source, /<WorkspaceSettingsClientElement \/>/);
|
|
234
|
-
assert.doesNotMatch(source, /your_child_segment|To redirect this settings shell/);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("workspaces-web starter surfaces avoid instructional placeholder copy", async () => {
|
|
238
|
-
const appSource = await readFile(
|
|
239
|
-
path.join(PACKAGE_DIR, "templates", "src", "surfaces", "app", "index.vue"),
|
|
240
|
-
"utf8"
|
|
241
|
-
);
|
|
242
|
-
const adminSource = await readFile(
|
|
243
|
-
path.join(PACKAGE_DIR, "templates", "src", "surfaces", "admin", "index.vue"),
|
|
244
|
-
"utf8"
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
assertGeneratedUiSourceContract(appSource, {
|
|
248
|
-
forbidCardShell: true,
|
|
249
|
-
sourceName: "workspaces app surface",
|
|
250
|
-
requiredPatterns: [
|
|
251
|
-
{
|
|
252
|
-
id: "workspace-app-empty-state",
|
|
253
|
-
pattern: /No workspace activity yet/,
|
|
254
|
-
message: "Workspace app surface needs a product-shaped empty state."
|
|
255
|
-
}
|
|
256
|
-
]
|
|
257
|
-
});
|
|
258
|
-
assertGeneratedUiSourceContract(adminSource, {
|
|
259
|
-
forbidCardShell: true,
|
|
260
|
-
sourceName: "workspaces admin surface",
|
|
261
|
-
requiredPatterns: [
|
|
262
|
-
{
|
|
263
|
-
id: "workspace-admin-member-link",
|
|
264
|
-
pattern: /:to="adminChildPath\('members'\)"/,
|
|
265
|
-
message: "Workspace admin surface needs a direct members action."
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
id: "workspace-admin-settings-link",
|
|
269
|
-
pattern: /:to="adminChildPath\('workspace\/settings'\)"/,
|
|
270
|
-
message: "Workspace admin surface needs a direct settings action."
|
|
271
|
-
}
|
|
272
|
-
]
|
|
273
|
-
});
|
|
274
|
-
assert.match(appSource, /No workspace activity yet/);
|
|
275
|
-
assert.match(adminSource, /Manage members and workspace settings/);
|
|
276
|
-
assert.match(adminSource, /const route = useRoute\(\)/);
|
|
277
|
-
assert.match(adminSource, /function adminChildPath/);
|
|
278
|
-
assert.match(adminSource, /:to="adminChildPath\('members'\)"/);
|
|
279
|
-
assert.match(adminSource, /:to="adminChildPath\('workspace\/settings'\)"/);
|
|
280
|
-
assert.doesNotMatch(adminSource, /to="\.\//);
|
|
281
|
-
assert.doesNotMatch(appSource, /Replace this page|Primary in-workspace surface/);
|
|
282
|
-
assert.doesNotMatch(adminSource, /Use this area|Privileged workspace workflows/);
|
|
96
|
+
const appSurface = await readFile(path.join(exampleRoot, "src", "surfaces", "app", "index.vue"), "utf8");
|
|
97
|
+
const adminSurface = await readFile(path.join(exampleRoot, "src", "surfaces", "admin", "index.vue"), "utf8");
|
|
98
|
+
|
|
99
|
+
assert.match(cue, /placementContext\.value\?\.pendingInvitesCount/);
|
|
100
|
+
assert.match(cue, /placementContext\.value\?\.workspaceInvitesEnabled/);
|
|
101
|
+
assert.doesNotMatch(cue, /\bfetch\s*\(|\buseQuery\b/);
|
|
102
|
+
assert.match(settingsLanding, /No settings sections yet/);
|
|
103
|
+
assert.match(appSurface, /No workspace activity yet/);
|
|
104
|
+
assert.match(adminSurface, /Manage members and workspace settings/);
|
|
105
|
+
assert.doesNotMatch(appSurface, /Replace this page|Primary in-workspace surface/);
|
|
106
|
+
assert.doesNotMatch(adminSurface, /Use this area|Privileged workspace workflows/);
|
|
283
107
|
});
|
|
284
108
|
|
|
285
|
-
test("workspaces-web
|
|
109
|
+
test("workspaces-web metadata declares semantic outlets without source mutations", () => {
|
|
110
|
+
assert.equal(packageJson.jskit?.mutations, undefined);
|
|
111
|
+
assert.deepEqual(placements?.outlets, [
|
|
112
|
+
{
|
|
113
|
+
target: "admin-settings:primary-menu",
|
|
114
|
+
surfaces: ["admin"],
|
|
115
|
+
source: "patterns/workspace-surfaces/example/src/pages/admin/workspace/settings.vue"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
target: "admin-cog:primary-menu",
|
|
119
|
+
surfaces: ["admin"],
|
|
120
|
+
source: "src/client/components/WorkspaceToolsWidget.vue"
|
|
121
|
+
}
|
|
122
|
+
]);
|
|
286
123
|
assert.deepEqual(
|
|
287
|
-
|
|
124
|
+
placements?.topology?.placements?.map(({ id, owner = null }) => ({ id, owner })),
|
|
288
125
|
[
|
|
289
|
-
{
|
|
290
|
-
|
|
291
|
-
surfaces: ["admin"],
|
|
292
|
-
source: "templates/src/pages/admin/workspace/settings.vue"
|
|
293
|
-
}
|
|
126
|
+
{ id: "page.section-nav", owner: "admin-settings" },
|
|
127
|
+
{ id: "admin.tools-menu", owner: null }
|
|
294
128
|
]
|
|
295
129
|
);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("workspace contributions identify live package source or reusable pattern source", () => {
|
|
133
|
+
assert.equal(contribution("workspaces.workspace.menu.app")?.source, "patterns/workspace-surfaces/example/src/surfaces/app/index.vue");
|
|
134
|
+
assert.equal(contribution("workspaces.workspace.menu.admin")?.source, "patterns/workspace-surfaces/example/src/surfaces/admin/index.vue");
|
|
135
|
+
assert.equal(contribution("workspaces.profile.menu.surface-switch")?.source, "src/client/components/WorkspaceProfileSurfaceSwitchMenuItem.vue");
|
|
136
|
+
assert.equal(contribution("workspaces.workspace.selector")?.source, "src/client/components/WorkspaceSelector.vue");
|
|
137
|
+
assert.equal(
|
|
138
|
+
contribution("workspaces.account.invites.cue")?.source,
|
|
139
|
+
"patterns/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue"
|
|
305
140
|
);
|
|
306
|
-
assert.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
renderers: {
|
|
315
|
-
link: "local.main.ui.surface-aware-menu-link-item"
|
|
316
|
-
}
|
|
317
|
-
},
|
|
318
|
-
medium: {
|
|
319
|
-
outlet: "admin-settings:primary-menu",
|
|
320
|
-
renderers: {
|
|
321
|
-
link: "local.main.ui.surface-aware-menu-link-item"
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
expanded: {
|
|
325
|
-
outlet: "admin-settings:primary-menu",
|
|
326
|
-
renderers: {
|
|
327
|
-
link: "local.main.ui.surface-aware-menu-link-item"
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
assert.deepEqual(findTopology("admin.tools-menu"), {
|
|
333
|
-
id: "admin.tools-menu",
|
|
334
|
-
description: "Admin surface tools menu actions.",
|
|
335
|
-
surfaces: ["admin"],
|
|
336
|
-
variants: {
|
|
337
|
-
compact: {
|
|
338
|
-
outlet: "admin-cog:primary-menu",
|
|
339
|
-
renderers: {
|
|
340
|
-
link: "local.main.ui.surface-aware-menu-link-item"
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
medium: {
|
|
344
|
-
outlet: "admin-cog:primary-menu",
|
|
345
|
-
renderers: {
|
|
346
|
-
link: "local.main.ui.surface-aware-menu-link-item"
|
|
347
|
-
}
|
|
348
|
-
},
|
|
349
|
-
expanded: {
|
|
350
|
-
outlet: "admin-cog:primary-menu",
|
|
351
|
-
renderers: {
|
|
352
|
-
link: "local.main.ui.surface-aware-menu-link-item"
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
assert.equal(findContribution("workspaces.workspace.settings.general"), null);
|
|
358
|
-
assert.deepEqual(findContribution("workspaces.workspace.menu.app"), {
|
|
359
|
-
id: "workspaces.workspace.menu.app",
|
|
360
|
-
target: "shell.primary-nav",
|
|
361
|
-
kind: "link",
|
|
362
|
-
surfaces: ["app"],
|
|
363
|
-
order: 50,
|
|
364
|
-
when: "auth.authenticated === true",
|
|
365
|
-
source: "mutations.text#workspaces-web-placement-block"
|
|
366
|
-
});
|
|
367
|
-
assert.deepEqual(findContribution("workspaces.workspace.menu.admin"), {
|
|
368
|
-
id: "workspaces.workspace.menu.admin",
|
|
369
|
-
target: "shell.primary-nav",
|
|
370
|
-
kind: "link",
|
|
371
|
-
surfaces: ["admin"],
|
|
372
|
-
order: 60,
|
|
373
|
-
when: "auth.authenticated === true",
|
|
374
|
-
source: "mutations.text#workspaces-web-placement-block"
|
|
375
|
-
});
|
|
376
|
-
assert.match(findTextMutation("workspaces-web-placement-block")?.value || "", /id: "workspaces\.workspace\.menu\.app"[\s\S]*surfaces: \["app"\][\s\S]*label: "Home"/);
|
|
377
|
-
assert.match(findTextMutation("workspaces-web-placement-block")?.value || "", /id: "workspaces\.workspace\.menu\.admin"[\s\S]*surfaces: \["admin"\][\s\S]*label: "Home"/);
|
|
378
|
-
assert.deepEqual(findContribution("workspaces.profile.menu.surface-switch"), {
|
|
379
|
-
id: "workspaces.profile.menu.surface-switch",
|
|
380
|
-
target: "auth.profile-menu",
|
|
381
|
-
kind: "component",
|
|
382
|
-
surfaces: ["*"],
|
|
383
|
-
order: 100,
|
|
384
|
-
componentToken: "workspaces.web.profile.menu.surface-switch-item",
|
|
385
|
-
when: "auth.authenticated === true",
|
|
386
|
-
source: "mutations.text#workspaces-web-profile-surface-switch-placement"
|
|
387
|
-
});
|
|
388
|
-
assert.deepEqual(findContribution("workspaces.workspace.selector"), {
|
|
389
|
-
id: "workspaces.workspace.selector",
|
|
390
|
-
target: "shell.identity",
|
|
391
|
-
kind: "component",
|
|
392
|
-
surfaces: ["*"],
|
|
393
|
-
order: 200,
|
|
394
|
-
componentToken: "workspaces.web.workspace.selector",
|
|
395
|
-
when: "auth.authenticated === true",
|
|
396
|
-
source: "mutations.text#workspaces-web-placement-block"
|
|
397
|
-
});
|
|
398
|
-
assert.deepEqual(findContribution("workspaces.account.settings.invites"), {
|
|
399
|
-
id: "workspaces.account.settings.invites",
|
|
400
|
-
target: "settings.sections",
|
|
401
|
-
owner: "account-settings",
|
|
402
|
-
kind: "component",
|
|
403
|
-
surfaces: ["account"],
|
|
404
|
-
order: 400,
|
|
405
|
-
componentToken: "local.main.account-settings.section.invites",
|
|
406
|
-
when: "auth.authenticated === true && workspaceInvitesEnabled === true",
|
|
407
|
-
source: "mutations.text#workspaces-web-account-settings-placement"
|
|
408
|
-
});
|
|
409
|
-
assert.match(findTextMutation("workspaces-web-account-settings-placement")?.value || "", /id: "workspaces\.account\.settings\.invites"/);
|
|
410
|
-
assert.match(findTextMutation("workspaces-web-account-settings-placement")?.value || "", /target: "settings\.sections"/);
|
|
411
|
-
assert.match(findTextMutation("workspaces-web-account-settings-placement")?.value || "", /owner: "account-settings"/);
|
|
412
|
-
assert.match(findTextMutation("workspaces-web-account-settings-placement")?.value || "", /componentToken: "local\.main\.account-settings\.section\.invites"/);
|
|
413
|
-
assert.equal(findTextMutation("workspaces-web-admin-placement-topology")?.file, "src/placementTopology.js");
|
|
414
|
-
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /id: "page\.section-nav"/);
|
|
415
|
-
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /owner: "admin-settings"/);
|
|
416
|
-
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /outlet: "admin-settings:primary-menu"/);
|
|
417
|
-
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /id: "admin\.tools-menu"/);
|
|
418
|
-
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /outlet: "admin-cog:primary-menu"/);
|
|
419
|
-
assert.deepEqual(findFileMutation("users-web-page-admin-workspace-settings"), {
|
|
420
|
-
from: "templates/src/pages/admin/workspace/settings/index.vue",
|
|
421
|
-
toSurface: "admin",
|
|
422
|
-
toSurfacePath: "workspace/settings/index.vue",
|
|
423
|
-
ownership: "app",
|
|
424
|
-
reason: "Install workspace settings index stub scaffold for app-owned landing or redirect behavior.",
|
|
425
|
-
category: "workspaces-web",
|
|
426
|
-
id: "users-web-page-admin-workspace-settings",
|
|
427
|
-
when: {
|
|
428
|
-
config: "tenancyMode",
|
|
429
|
-
in: ["personal", "workspaces"]
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-section-import"), {
|
|
433
|
-
op: "ensure-import",
|
|
434
|
-
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
435
|
-
defaultImport: "AccountSettingsInvitesSection",
|
|
436
|
-
from: "../components/AccountSettingsInvitesSection.vue",
|
|
437
|
-
reason: "Bind app-owned account invites section component into local main client provider imports.",
|
|
438
|
-
category: "workspaces-web",
|
|
439
|
-
id: "users-web-main-client-provider-account-settings-section-import"
|
|
440
|
-
});
|
|
441
|
-
assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-section-register"), {
|
|
442
|
-
op: "ensure-call",
|
|
443
|
-
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
444
|
-
callee: "registerMainClientComponent",
|
|
445
|
-
args: ["\"local.main.account-settings.section.invites\"", "() => AccountSettingsInvitesSection"],
|
|
446
|
-
beforeClass: "MainClientProvider",
|
|
447
|
-
reason: "Bind app-owned account invites section component token into local main client provider registry.",
|
|
448
|
-
category: "workspaces-web",
|
|
449
|
-
id: "users-web-main-client-provider-account-settings-section-register"
|
|
450
|
-
});
|
|
141
|
+
assert.equal(
|
|
142
|
+
contribution("workspaces.account.settings.invites")?.source,
|
|
143
|
+
"patterns/workspace-surfaces/example/packages/main/src/client/components/AccountSettingsInvitesSection.vue"
|
|
144
|
+
);
|
|
145
|
+
assert.equal(contribution("workspaces.workspace.tools.widget")?.source, "src/client/components/WorkspaceToolsWidget.vue");
|
|
146
|
+
assert.equal(contribution("workspaces.workspace.menu.workspace-settings")?.source, "src/client/components/WorkspaceSettingsMenuItem.vue");
|
|
147
|
+
assert.equal(contribution("workspaces.workspace.menu.members")?.source, "src/client/components/WorkspaceMembersMenuItem.vue");
|
|
148
|
+
assert.equal(contribution("workspaces.workspace.settings.general"), null);
|
|
451
149
|
});
|
|
@@ -6,9 +6,9 @@ import { readFile } from "node:fs/promises";
|
|
|
6
6
|
|
|
7
7
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
9
|
-
const COMPONENT_PATH = path.join(PACKAGE_DIR, "src", "client", "components", "
|
|
9
|
+
const COMPONENT_PATH = path.join(PACKAGE_DIR, "src", "client", "components", "WorkspaceSelector.vue");
|
|
10
10
|
|
|
11
|
-
test("
|
|
11
|
+
test("WorkspaceSelector passes workspaceSlug through generic page params", async () => {
|
|
12
12
|
const source = await readFile(COMPONENT_PATH, "utf8");
|
|
13
13
|
|
|
14
14
|
assert.match(
|
|
@@ -18,7 +18,7 @@ test("UsersWorkspaceSelector passes workspaceSlug through generic page params",
|
|
|
18
18
|
assert.doesNotMatch(source, /workspaceSlug:\s*normalizedSlug,\s*mode:\s*"workspace"/s);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test("
|
|
21
|
+
test("WorkspaceSelector activator preserves the generated tap-target contract", async () => {
|
|
22
22
|
const source = await readFile(COMPONENT_PATH, "utf8");
|
|
23
23
|
|
|
24
24
|
assert.match(source, /class="users-workspace-selector__activator text-none"/);
|