@jskit-ai/users-web 0.1.52 → 0.1.54
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.descriptor.mjs +14 -96
- package/package.json +16 -11
- package/src/client/account-settings/sections.js +74 -0
- package/src/client/composables/account-settings/accountSettingsRuntimeHelpers.js +2 -38
- package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -2
- package/src/client/composables/internal/crudListParentTitleSupport.js +1 -1
- package/src/client/composables/internal/useOperationScope.js +12 -12
- package/src/client/composables/records/useAddEdit.js +2 -2
- package/src/client/composables/records/useList.js +3 -3
- package/src/client/composables/records/useView.js +2 -2
- package/src/client/composables/support/scopeHelpers.js +19 -19
- package/src/client/composables/useAccess.js +3 -3
- package/src/client/composables/useAccountSettingsRuntime.js +8 -156
- package/src/client/composables/useCommand.js +2 -2
- package/src/client/composables/useCrudListParentTitle.js +2 -2
- package/src/client/composables/usePaths.js +50 -38
- package/src/client/composables/useScopeRuntime.js +55 -27
- package/src/client/composables/useSurfaceRouteContext.js +1 -7
- package/src/client/index.js +0 -1
- package/src/client/lib/bootstrap.js +0 -63
- package/src/client/lib/httpClient.js +2 -59
- package/src/client/lib/theme.js +12 -189
- package/src/client/providers/UsersWebClientProvider.js +2 -25
- package/src/client/providers/bootUsersWebClientProvider.js +28 -0
- package/src/shared/toolsOutletContracts.js +1 -8
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +33 -21
- package/test/accountSettingsSections.test.js +79 -0
- package/test/exportsContract.test.js +2 -2
- package/test/scopeHelpers.test.js +6 -6
- package/test/settingsPlacementContract.test.js +4 -49
- package/test/theme.test.js +0 -56
- package/src/client/components/ConsoleSettingsClientElement.vue +0 -24
- package/src/client/components/MembersAdminClientElement.vue +0 -400
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +0 -39
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +0 -36
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +0 -90
- package/src/client/components/UsersWorkspaceSelector.vue +0 -248
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +0 -39
- package/src/client/components/UsersWorkspaceToolsWidget.vue +0 -12
- package/src/client/components/WorkspaceMembersClientElement.vue +0 -655
- package/src/client/components/WorkspaceProfileClientElement.vue +0 -116
- package/src/client/components/WorkspaceSettingsClientElement.vue +0 -102
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +0 -265
- package/src/client/components/WorkspacesClientElement.vue +0 -509
- package/src/client/composables/account-settings/accountSettingsInvitesRuntime.js +0 -88
- package/src/client/composables/useBootstrapQuery.js +0 -52
- package/src/client/composables/useWorkspaceRouteContext.js +0 -28
- package/src/client/composables/useWorkspaceSurfaceId.js +0 -43
- package/src/client/lib/menuIcons.js +0 -210
- package/src/client/lib/profileSurfaceMenuLinks.js +0 -142
- package/src/client/lib/surfaceAccessPolicy.js +0 -350
- package/src/client/lib/workspaceLinkResolver.js +0 -207
- package/src/client/lib/workspaceSurfaceContext.js +0 -82
- package/src/client/lib/workspaceSurfacePaths.js +0 -163
- package/src/client/providers/UsersWorkspacesClientProvider.js +0 -24
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +0 -371
- package/src/client/runtime/bootstrapPlacementRuntime.js +0 -463
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +0 -28
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +0 -147
- package/src/client/support/menuLinkTarget.js +0 -93
- package/src/client/support/realtimeWorkspace.js +0 -21
- package/src/client/support/runtimeNormalization.js +0 -27
- package/src/client/support/workspaceQueryKeys.js +0 -15
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +0 -77
- package/templates/src/pages/console/settings/index.vue +0 -8
- package/templates/src/pages/console/settings.vue +0 -32
- package/test/bootstrapPlacementRuntime.test.js +0 -1095
- package/test/menuIcons.test.js +0 -35
- package/test/menuLinkTarget.test.js +0 -116
- package/test/profileSurfaceMenuLinks.test.js +0 -207
- package/test/surfaceAccessPolicy.test.js +0 -129
- package/test/workspaceLinkResolver.test.js +0 -61
- package/test/workspaceSurfacePaths.test.js +0 -39
package/test/menuIcons.test.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
mdiAccountCogOutline,
|
|
5
|
-
mdiConsoleNetworkOutline,
|
|
6
|
-
mdiCogOutline,
|
|
7
|
-
mdiFolderOutline,
|
|
8
|
-
mdiLogin,
|
|
9
|
-
mdiShieldCrownOutline,
|
|
10
|
-
mdiViewDashboardOutline,
|
|
11
|
-
mdiViewListOutline
|
|
12
|
-
} from "@mdi/js";
|
|
13
|
-
import {
|
|
14
|
-
resolveMenuLinkIcon,
|
|
15
|
-
resolveSurfaceSwitchIcon
|
|
16
|
-
} from "../src/client/lib/menuIcons.js";
|
|
17
|
-
|
|
18
|
-
test("resolveSurfaceSwitchIcon prefers explicit icon and maps known surfaces", () => {
|
|
19
|
-
assert.equal(resolveSurfaceSwitchIcon("admin"), mdiShieldCrownOutline);
|
|
20
|
-
assert.equal(resolveSurfaceSwitchIcon("console"), mdiConsoleNetworkOutline);
|
|
21
|
-
assert.equal(resolveSurfaceSwitchIcon("admin", "custom-icon"), "custom-icon");
|
|
22
|
-
assert.equal(resolveSurfaceSwitchIcon("admin", "mdi-cog-outline"), mdiCogOutline);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("resolveMenuLinkIcon resolves settings/login fallbacks and generic default", () => {
|
|
26
|
-
assert.equal(resolveMenuLinkIcon({ icon: "mdi-cog-outline" }), mdiCogOutline);
|
|
27
|
-
assert.equal(resolveMenuLinkIcon({ to: "/account" }), mdiAccountCogOutline);
|
|
28
|
-
assert.equal(resolveMenuLinkIcon({ label: "Sign in" }), mdiLogin);
|
|
29
|
-
assert.equal(resolveMenuLinkIcon({ label: "Go to admin" }), mdiShieldCrownOutline);
|
|
30
|
-
assert.equal(resolveMenuLinkIcon({ label: "Workspace", to: "/w/acme" }), mdiViewDashboardOutline);
|
|
31
|
-
assert.equal(resolveMenuLinkIcon({ to: "/projects" }), mdiFolderOutline);
|
|
32
|
-
assert.equal(resolveMenuLinkIcon({ label: "Settings", to: "/workspace/settings" }), mdiCogOutline);
|
|
33
|
-
assert.equal(resolveMenuLinkIcon({ label: "Anything else", to: "/foo" }), mdiFolderOutline);
|
|
34
|
-
assert.equal(resolveMenuLinkIcon({ label: "Anything else" }), mdiViewListOutline);
|
|
35
|
-
});
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
normalizeMenuLinkPathname,
|
|
5
|
-
resolveMenuLinkTarget
|
|
6
|
-
} from "../src/client/support/menuLinkTarget.js";
|
|
7
|
-
|
|
8
|
-
const WORKSPACE_PLACEMENT_CONTEXT = Object.freeze({
|
|
9
|
-
surfaceConfig: {
|
|
10
|
-
enabledSurfaceIds: ["admin"],
|
|
11
|
-
surfacesById: {
|
|
12
|
-
admin: {
|
|
13
|
-
id: "admin",
|
|
14
|
-
requiresWorkspace: true
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const MIXED_PLACEMENT_CONTEXT = Object.freeze({
|
|
21
|
-
surfaceConfig: {
|
|
22
|
-
enabledSurfaceIds: ["app", "admin"],
|
|
23
|
-
surfacesById: {
|
|
24
|
-
app: {
|
|
25
|
-
id: "app",
|
|
26
|
-
requiresWorkspace: false
|
|
27
|
-
},
|
|
28
|
-
admin: {
|
|
29
|
-
id: "admin",
|
|
30
|
-
requiresWorkspace: true
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
function createPageResolver() {
|
|
37
|
-
return function resolvePagePath(relativePath = "", options = {}) {
|
|
38
|
-
return `page:${String(options.surface || "")}:${String(relativePath || "")}`;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
test("resolveMenuLinkTarget resolves suffix targets when no explicit to is provided", () => {
|
|
43
|
-
assert.equal(
|
|
44
|
-
resolveMenuLinkTarget({
|
|
45
|
-
surface: "admin",
|
|
46
|
-
placementContext: WORKSPACE_PLACEMENT_CONTEXT,
|
|
47
|
-
workspaceSuffix: "/practice/vets",
|
|
48
|
-
nonWorkspaceSuffix: "/practice/vets",
|
|
49
|
-
resolvePagePath: createPageResolver()
|
|
50
|
-
}),
|
|
51
|
-
"page:admin:/practice/vets"
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("resolveMenuLinkTarget resolves relative targets through suffix templates", () => {
|
|
56
|
-
assert.equal(
|
|
57
|
-
resolveMenuLinkTarget({
|
|
58
|
-
to: "./notes",
|
|
59
|
-
surface: "admin",
|
|
60
|
-
placementContext: WORKSPACE_PLACEMENT_CONTEXT,
|
|
61
|
-
workspaceSuffix: "/contacts/[contactId]/notes",
|
|
62
|
-
nonWorkspaceSuffix: "/contacts/[contactId]/notes",
|
|
63
|
-
routeParams: {
|
|
64
|
-
contactId: 42
|
|
65
|
-
},
|
|
66
|
-
resolvePagePath: createPageResolver()
|
|
67
|
-
}),
|
|
68
|
-
"page:admin:/contacts/42/notes"
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("resolveMenuLinkTarget returns empty string for unresolved relative targets", () => {
|
|
73
|
-
assert.equal(
|
|
74
|
-
resolveMenuLinkTarget({
|
|
75
|
-
to: "./notes",
|
|
76
|
-
surface: "admin",
|
|
77
|
-
placementContext: WORKSPACE_PLACEMENT_CONTEXT,
|
|
78
|
-
workspaceSuffix: "/contacts/[contactId]/notes",
|
|
79
|
-
nonWorkspaceSuffix: "/contacts/[contactId]/notes",
|
|
80
|
-
resolvePagePath: createPageResolver()
|
|
81
|
-
}),
|
|
82
|
-
""
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("resolveMenuLinkTarget keeps absolute targets unchanged", () => {
|
|
87
|
-
assert.equal(
|
|
88
|
-
resolveMenuLinkTarget({
|
|
89
|
-
to: "/practice/vets",
|
|
90
|
-
surface: "admin",
|
|
91
|
-
placementContext: WORKSPACE_PLACEMENT_CONTEXT,
|
|
92
|
-
workspaceSuffix: "/ignored",
|
|
93
|
-
nonWorkspaceSuffix: "/ignored",
|
|
94
|
-
resolvePagePath: createPageResolver()
|
|
95
|
-
}),
|
|
96
|
-
"/practice/vets"
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("resolveMenuLinkTarget uses non-workspace suffix for non-workspace surfaces", () => {
|
|
101
|
-
assert.equal(
|
|
102
|
-
resolveMenuLinkTarget({
|
|
103
|
-
surface: "app",
|
|
104
|
-
currentSurfaceId: "admin",
|
|
105
|
-
placementContext: MIXED_PLACEMENT_CONTEXT,
|
|
106
|
-
workspaceSuffix: "/workspace-only",
|
|
107
|
-
nonWorkspaceSuffix: "/public-page",
|
|
108
|
-
resolvePagePath: createPageResolver()
|
|
109
|
-
}),
|
|
110
|
-
"page:app:/public-page"
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("normalizeMenuLinkPathname removes query strings and hashes", () => {
|
|
115
|
-
assert.equal(normalizeMenuLinkPathname("/practice/vets?tab=all#section"), "/practice/vets");
|
|
116
|
-
});
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { mdiShieldCrownOutline } from "@mdi/js";
|
|
4
|
-
import {
|
|
5
|
-
hasWorkspaceMembership,
|
|
6
|
-
resolvePrimarySurfaceSwitchLink,
|
|
7
|
-
resolveProfileSurfaceMenuLinks
|
|
8
|
-
} from "../src/client/lib/profileSurfaceMenuLinks.js";
|
|
9
|
-
|
|
10
|
-
function createPlacementContext({
|
|
11
|
-
workspace = null,
|
|
12
|
-
workspaces = [],
|
|
13
|
-
authenticated = true,
|
|
14
|
-
consoleOwner = false
|
|
15
|
-
} = {}) {
|
|
16
|
-
return {
|
|
17
|
-
auth: {
|
|
18
|
-
authenticated
|
|
19
|
-
},
|
|
20
|
-
workspace,
|
|
21
|
-
workspaces,
|
|
22
|
-
permissions: [],
|
|
23
|
-
surfaceAccess: {
|
|
24
|
-
consoleowner: consoleOwner
|
|
25
|
-
},
|
|
26
|
-
surfaceAccessPolicies: {
|
|
27
|
-
public: {},
|
|
28
|
-
workspace_member: {
|
|
29
|
-
requireAuth: true,
|
|
30
|
-
requireWorkspaceMembership: true
|
|
31
|
-
},
|
|
32
|
-
console_owner: {
|
|
33
|
-
requireAuth: true,
|
|
34
|
-
requireFlagsAll: ["console_owner"]
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
surfaceConfig: {
|
|
38
|
-
tenancyMode: "workspaces",
|
|
39
|
-
defaultSurfaceId: "app",
|
|
40
|
-
enabledSurfaceIds: ["home", "app", "admin", "console"],
|
|
41
|
-
surfacesById: {
|
|
42
|
-
home: {
|
|
43
|
-
id: "home",
|
|
44
|
-
enabled: true,
|
|
45
|
-
pagesRoot: "",
|
|
46
|
-
routeBase: "/",
|
|
47
|
-
requiresAuth: false,
|
|
48
|
-
requiresWorkspace: false,
|
|
49
|
-
accessPolicyId: "public"
|
|
50
|
-
},
|
|
51
|
-
app: {
|
|
52
|
-
id: "app",
|
|
53
|
-
enabled: true,
|
|
54
|
-
pagesRoot: "w/[workspaceSlug]",
|
|
55
|
-
routeBase: "/w/:workspaceSlug",
|
|
56
|
-
requiresAuth: true,
|
|
57
|
-
requiresWorkspace: true,
|
|
58
|
-
accessPolicyId: "workspace_member"
|
|
59
|
-
},
|
|
60
|
-
admin: {
|
|
61
|
-
id: "admin",
|
|
62
|
-
enabled: true,
|
|
63
|
-
pagesRoot: "w/[workspaceSlug]/admin",
|
|
64
|
-
routeBase: "/w/:workspaceSlug/admin",
|
|
65
|
-
requiresAuth: true,
|
|
66
|
-
requiresWorkspace: true,
|
|
67
|
-
accessPolicyId: "workspace_member"
|
|
68
|
-
},
|
|
69
|
-
console: {
|
|
70
|
-
id: "console",
|
|
71
|
-
enabled: true,
|
|
72
|
-
pagesRoot: "console",
|
|
73
|
-
routeBase: "/console",
|
|
74
|
-
requiresAuth: true,
|
|
75
|
-
requiresWorkspace: false,
|
|
76
|
-
accessPolicyId: "console_owner"
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const originalWindow = globalThis.window;
|
|
84
|
-
|
|
85
|
-
test.afterEach(() => {
|
|
86
|
-
if (typeof originalWindow === "undefined") {
|
|
87
|
-
delete globalThis.window;
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
globalThis.window = originalWindow;
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("hasWorkspaceMembership matches context.workspace slug", () => {
|
|
94
|
-
const context = createPlacementContext({
|
|
95
|
-
workspace: {
|
|
96
|
-
id: 1,
|
|
97
|
-
slug: "acme"
|
|
98
|
-
},
|
|
99
|
-
workspaces: []
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
assert.equal(hasWorkspaceMembership(context, "acme"), true);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("hasWorkspaceMembership matches context.workspaces slug", () => {
|
|
106
|
-
const context = createPlacementContext({
|
|
107
|
-
workspace: null,
|
|
108
|
-
workspaces: [
|
|
109
|
-
{
|
|
110
|
-
id: 1,
|
|
111
|
-
slug: "acme"
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
assert.equal(hasWorkspaceMembership(context, "acme"), true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("resolvePrimarySurfaceSwitchLink shows Go to admin only for member workspace", () => {
|
|
120
|
-
const context = createPlacementContext({
|
|
121
|
-
workspace: {
|
|
122
|
-
id: 1,
|
|
123
|
-
slug: "acme"
|
|
124
|
-
},
|
|
125
|
-
workspaces: [
|
|
126
|
-
{
|
|
127
|
-
id: 1,
|
|
128
|
-
slug: "acme"
|
|
129
|
-
}
|
|
130
|
-
]
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const link = resolvePrimarySurfaceSwitchLink({
|
|
134
|
-
context,
|
|
135
|
-
surface: "app"
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
assert.deepEqual(link, {
|
|
139
|
-
id: "surface-switch.admin",
|
|
140
|
-
label: "Go to admin",
|
|
141
|
-
icon: mdiShieldCrownOutline,
|
|
142
|
-
to: "/w/acme/admin"
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("resolvePrimarySurfaceSwitchLink hides workspace switch when slug is only in URL and user is not member", () => {
|
|
147
|
-
globalThis.window = {
|
|
148
|
-
location: {
|
|
149
|
-
pathname: "/w/acme/admin",
|
|
150
|
-
search: "",
|
|
151
|
-
hash: ""
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const context = createPlacementContext({
|
|
156
|
-
workspace: null,
|
|
157
|
-
workspaces: []
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const link = resolvePrimarySurfaceSwitchLink({
|
|
161
|
-
context,
|
|
162
|
-
surface: "admin"
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
assert.equal(link, null);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("resolveProfileSurfaceMenuLinks includes console switch only for console owners", () => {
|
|
169
|
-
const nonOwnerContext = createPlacementContext({
|
|
170
|
-
workspace: {
|
|
171
|
-
id: 1,
|
|
172
|
-
slug: "acme"
|
|
173
|
-
},
|
|
174
|
-
workspaces: [
|
|
175
|
-
{
|
|
176
|
-
id: 1,
|
|
177
|
-
slug: "acme"
|
|
178
|
-
}
|
|
179
|
-
],
|
|
180
|
-
consoleOwner: false
|
|
181
|
-
});
|
|
182
|
-
const ownerContext = createPlacementContext({
|
|
183
|
-
workspace: {
|
|
184
|
-
id: 1,
|
|
185
|
-
slug: "acme"
|
|
186
|
-
},
|
|
187
|
-
workspaces: [
|
|
188
|
-
{
|
|
189
|
-
id: 1,
|
|
190
|
-
slug: "acme"
|
|
191
|
-
}
|
|
192
|
-
],
|
|
193
|
-
consoleOwner: true
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const nonOwnerLinks = resolveProfileSurfaceMenuLinks({
|
|
197
|
-
context: nonOwnerContext,
|
|
198
|
-
surface: "app"
|
|
199
|
-
});
|
|
200
|
-
const ownerLinks = resolveProfileSurfaceMenuLinks({
|
|
201
|
-
context: ownerContext,
|
|
202
|
-
surface: "app"
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
assert.equal(nonOwnerLinks.some((entry) => entry.id === "surface-switch.console"), false);
|
|
206
|
-
assert.equal(ownerLinks.some((entry) => entry.id === "surface-switch.console"), true);
|
|
207
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { evaluateSurfaceAccess } from "../src/client/lib/surfaceAccessPolicy.js";
|
|
4
|
-
|
|
5
|
-
function createContext(overrides = {}) {
|
|
6
|
-
return {
|
|
7
|
-
auth: {
|
|
8
|
-
authenticated: true
|
|
9
|
-
},
|
|
10
|
-
workspaces: [
|
|
11
|
-
{
|
|
12
|
-
id: 1,
|
|
13
|
-
slug: "acme"
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
permissions: [],
|
|
17
|
-
surfaceAccess: {
|
|
18
|
-
consoleowner: false
|
|
19
|
-
},
|
|
20
|
-
surfaceAccessPolicies: {
|
|
21
|
-
public: {},
|
|
22
|
-
workspace_member: {
|
|
23
|
-
requireAuth: true,
|
|
24
|
-
requireWorkspaceMembership: true
|
|
25
|
-
},
|
|
26
|
-
console_owner: {
|
|
27
|
-
requireAuth: true,
|
|
28
|
-
requireFlagsAll: ["console_owner"]
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
surfaceConfig: {
|
|
32
|
-
defaultSurfaceId: "home",
|
|
33
|
-
enabledSurfaceIds: ["home", "app", "console"],
|
|
34
|
-
surfacesById: {
|
|
35
|
-
home: {
|
|
36
|
-
id: "home",
|
|
37
|
-
enabled: true,
|
|
38
|
-
routeBase: "/home",
|
|
39
|
-
requiresWorkspace: false,
|
|
40
|
-
accessPolicyId: "public"
|
|
41
|
-
},
|
|
42
|
-
app: {
|
|
43
|
-
id: "app",
|
|
44
|
-
enabled: true,
|
|
45
|
-
routeBase: "/w/:workspaceSlug",
|
|
46
|
-
requiresWorkspace: true,
|
|
47
|
-
accessPolicyId: "workspace_member"
|
|
48
|
-
},
|
|
49
|
-
console: {
|
|
50
|
-
id: "console",
|
|
51
|
-
enabled: true,
|
|
52
|
-
routeBase: "/console",
|
|
53
|
-
requiresWorkspace: false,
|
|
54
|
-
accessPolicyId: "console_owner"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
...overrides
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
test("evaluateSurfaceAccess allows workspace member surfaces for accessible workspace", () => {
|
|
63
|
-
const decision = evaluateSurfaceAccess({
|
|
64
|
-
context: createContext(),
|
|
65
|
-
surfaceId: "app",
|
|
66
|
-
workspaceSlug: "acme"
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
assert.equal(decision.allowed, true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("evaluateSurfaceAccess denies workspace member surfaces for inaccessible workspace", () => {
|
|
73
|
-
const decision = evaluateSurfaceAccess({
|
|
74
|
-
context: createContext(),
|
|
75
|
-
surfaceId: "app",
|
|
76
|
-
workspaceSlug: "missing"
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
assert.equal(decision.allowed, false);
|
|
80
|
-
assert.equal(decision.reason, "surface-access-workspace-membership-required");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("evaluateSurfaceAccess allows unknown workspace membership when allowOnUnknown=true", () => {
|
|
84
|
-
const context = createContext();
|
|
85
|
-
delete context.workspaces;
|
|
86
|
-
|
|
87
|
-
const decision = evaluateSurfaceAccess({
|
|
88
|
-
context,
|
|
89
|
-
surfaceId: "app",
|
|
90
|
-
workspaceSlug: "acme",
|
|
91
|
-
allowOnUnknown: true
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
assert.equal(decision.allowed, true);
|
|
95
|
-
assert.equal(decision.pending, true);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("evaluateSurfaceAccess enforces bootstrap surface access flags", () => {
|
|
99
|
-
const deniedDecision = evaluateSurfaceAccess({
|
|
100
|
-
context: createContext(),
|
|
101
|
-
surfaceId: "console"
|
|
102
|
-
});
|
|
103
|
-
assert.equal(deniedDecision.allowed, false);
|
|
104
|
-
|
|
105
|
-
const allowedDecision = evaluateSurfaceAccess({
|
|
106
|
-
context: createContext({
|
|
107
|
-
surfaceAccess: {
|
|
108
|
-
consoleowner: true
|
|
109
|
-
}
|
|
110
|
-
}),
|
|
111
|
-
surfaceId: "console"
|
|
112
|
-
});
|
|
113
|
-
assert.equal(allowedDecision.allowed, true);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("evaluateSurfaceAccess denies workspace route with not_found status", () => {
|
|
117
|
-
const decision = evaluateSurfaceAccess({
|
|
118
|
-
context: createContext({
|
|
119
|
-
workspaceBootstrapStatuses: {
|
|
120
|
-
acme: "not_found"
|
|
121
|
-
}
|
|
122
|
-
}),
|
|
123
|
-
surfaceId: "app",
|
|
124
|
-
workspaceSlug: "acme"
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
assert.equal(decision.allowed, false);
|
|
128
|
-
assert.equal(decision.reason, "surface-access-workspace-not-found");
|
|
129
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { resolveWorkspaceShellLinkPath } from "../src/client/lib/workspaceLinkResolver.js";
|
|
4
|
-
|
|
5
|
-
function createContext({ defaultSurfaceId = "dashboard", enabledSurfaceIds = ["dashboard"], surfacesById = {} } = {}) {
|
|
6
|
-
return {
|
|
7
|
-
surfaceConfig: {
|
|
8
|
-
defaultSurfaceId,
|
|
9
|
-
enabledSurfaceIds,
|
|
10
|
-
surfacesById
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
test("resolveWorkspaceShellLinkPath resolves workspace path for known workspace surfaces", () => {
|
|
16
|
-
const context = {
|
|
17
|
-
surfaceConfig: {
|
|
18
|
-
defaultSurfaceId: "dashboard",
|
|
19
|
-
enabledSurfaceIds: ["dashboard"],
|
|
20
|
-
surfacesById: {
|
|
21
|
-
dashboard: {
|
|
22
|
-
routeBase: "/dashboard",
|
|
23
|
-
requiresWorkspace: true
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const path = resolveWorkspaceShellLinkPath({
|
|
30
|
-
context,
|
|
31
|
-
surface: "dashboard",
|
|
32
|
-
mode: "workspace",
|
|
33
|
-
workspaceSlug: "acme"
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
assert.equal(path, "/w/acme");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("resolveWorkspaceShellLinkPath returns empty path for unknown non-default workspace surface", () => {
|
|
40
|
-
const context = createContext();
|
|
41
|
-
const path = resolveWorkspaceShellLinkPath({
|
|
42
|
-
context,
|
|
43
|
-
surface: "admin",
|
|
44
|
-
mode: "workspace",
|
|
45
|
-
workspaceSlug: "acme"
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
assert.equal(path, "");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("resolveWorkspaceShellLinkPath returns empty path for unknown console-like surface ids", () => {
|
|
52
|
-
const context = createContext();
|
|
53
|
-
const path = resolveWorkspaceShellLinkPath({
|
|
54
|
-
context,
|
|
55
|
-
surface: "console",
|
|
56
|
-
mode: "workspace",
|
|
57
|
-
workspaceSlug: "acme"
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
assert.equal(path, "");
|
|
61
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
resolveAccountSettingsPathFromPlacementContext,
|
|
6
|
-
resolveSurfaceWorkspacePathFromPlacementContext
|
|
7
|
-
} from "../src/client/lib/workspaceSurfacePaths.js";
|
|
8
|
-
|
|
9
|
-
test("resolveSurfaceWorkspacePathFromPlacementContext returns empty path without workspace slug", () => {
|
|
10
|
-
const context = {
|
|
11
|
-
surfaceConfig: {
|
|
12
|
-
defaultSurfaceId: "home",
|
|
13
|
-
enabledSurfaceIds: ["home", "app", "admin", "console"],
|
|
14
|
-
surfacesById: {
|
|
15
|
-
home: { id: "home", routeBase: "/home", requiresWorkspace: false },
|
|
16
|
-
app: { id: "app", routeBase: "/w/:workspaceSlug", requiresWorkspace: true },
|
|
17
|
-
admin: { id: "admin", routeBase: "/w/:workspaceSlug/admin", requiresWorkspace: true },
|
|
18
|
-
console: { id: "console", routeBase: "/console", requiresWorkspace: false }
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
assert.equal(resolveSurfaceWorkspacePathFromPlacementContext(context, "app", "", "/"), "");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("resolveAccountSettingsPathFromPlacementContext uses account surface route when available", () => {
|
|
27
|
-
const context = {
|
|
28
|
-
surfaceConfig: {
|
|
29
|
-
defaultSurfaceId: "home",
|
|
30
|
-
enabledSurfaceIds: ["home", "account"],
|
|
31
|
-
surfacesById: {
|
|
32
|
-
home: { id: "home", routeBase: "/home", requiresWorkspace: false },
|
|
33
|
-
account: { id: "account", routeBase: "/profile", requiresWorkspace: false }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
assert.equal(resolveAccountSettingsPathFromPlacementContext(context), "/profile");
|
|
39
|
-
});
|