@jskit-ai/workspaces-web 0.1.14 → 0.1.15
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 +61 -25
- package/package.json +13 -3
- package/src/client/account-settings/accountSettingsInvitesRuntime.js +88 -0
- package/src/client/account-settings/useAccountSettingsInvitesSectionRuntime.js +217 -0
- package/src/client/components/AccountSettingsInvitesSection.vue +72 -0
- package/src/client/components/MembersAdminClientElement.vue +400 -0
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +89 -0
- package/src/client/components/UsersWorkspaceSelector.vue +242 -0
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +12 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +657 -0
- package/src/client/components/WorkspaceProfileClientElement.vue +116 -0
- package/src/client/components/WorkspaceSettingsClientElement.vue +102 -0
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +265 -0
- package/src/client/components/WorkspacesClientElement.vue +540 -0
- package/src/client/composables/useBootstrapQuery.js +52 -0
- package/src/client/composables/useWorkspaceRouteContext.js +28 -0
- package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
- package/src/client/index.js +1 -0
- package/src/client/lib/bootstrap.js +59 -0
- package/src/client/lib/httpClient.js +10 -0
- package/src/client/lib/permissions.js +27 -0
- package/src/client/lib/profileSurfaceMenuLinks.js +163 -0
- package/src/client/lib/surfaceAccessPolicy.js +350 -0
- package/src/client/lib/theme.js +332 -0
- package/src/client/lib/workspaceLinkResolver.js +207 -0
- package/src/client/lib/workspaceSurfaceContext.js +82 -0
- package/src/client/lib/workspaceSurfacePaths.js +163 -0
- package/src/client/providers/WorkspacesWebClientProvider.js +59 -2
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
- package/src/client/runtime/bootstrapPlacementRuntime.js +463 -0
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +28 -0
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +147 -0
- package/src/client/support/realtimeWorkspace.js +21 -0
- package/src/client/support/runtimeNormalization.js +23 -0
- package/src/client/support/workspaceQueryKeys.js +15 -0
- package/src/shared/toolsOutletContracts.js +9 -0
- package/templates/src/pages/admin/members/index.vue +1 -1
- package/test/bootstrapPlacementRuntime.test.js +1095 -0
- package/test/exportsContract.test.js +2 -1
- package/test/profileSurfaceMenuLinks.test.js +208 -0
- package/test/settingsPlacementContract.test.js +19 -1
- package/test/surfaceAccessPolicy.test.js +129 -0
- package/test/theme.test.js +101 -0
- package/test/workspaceLinkResolver.test.js +61 -0
- package/test/workspaceSurfacePaths.test.js +39 -0
|
@@ -15,7 +15,8 @@ test("workspaces-web exports are explicit and aligned with production usage", ()
|
|
|
15
15
|
packageId: "@jskit-ai/workspaces-web",
|
|
16
16
|
requiredExports: [
|
|
17
17
|
"./client",
|
|
18
|
-
"./client/providers/WorkspacesWebClientProvider"
|
|
18
|
+
"./client/providers/WorkspacesWebClientProvider",
|
|
19
|
+
"./client/composables/useWorkspaceRouteContext"
|
|
19
20
|
]
|
|
20
21
|
});
|
|
21
22
|
|
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
opsOwner = false
|
|
15
|
+
} = {}) {
|
|
16
|
+
return {
|
|
17
|
+
auth: {
|
|
18
|
+
authenticated
|
|
19
|
+
},
|
|
20
|
+
workspace,
|
|
21
|
+
workspaces,
|
|
22
|
+
permissions: [],
|
|
23
|
+
surfaceAccess: {
|
|
24
|
+
opsowner: opsOwner
|
|
25
|
+
},
|
|
26
|
+
surfaceAccessPolicies: {
|
|
27
|
+
public: {},
|
|
28
|
+
workspace_member: {
|
|
29
|
+
requireAuth: true,
|
|
30
|
+
requireWorkspaceMembership: true
|
|
31
|
+
},
|
|
32
|
+
ops_owner: {
|
|
33
|
+
requireAuth: true,
|
|
34
|
+
requireFlagsAll: ["ops_owner"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
surfaceConfig: {
|
|
38
|
+
tenancyMode: "workspaces",
|
|
39
|
+
defaultSurfaceId: "app",
|
|
40
|
+
enabledSurfaceIds: ["home", "app", "admin", "ops"],
|
|
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
|
+
ops: {
|
|
70
|
+
id: "ops",
|
|
71
|
+
enabled: true,
|
|
72
|
+
pagesRoot: "ops",
|
|
73
|
+
routeBase: "/ops",
|
|
74
|
+
requiresAuth: true,
|
|
75
|
+
requiresWorkspace: false,
|
|
76
|
+
accessPolicyId: "ops_owner",
|
|
77
|
+
icon: "mdi-cog-outline"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const originalWindow = globalThis.window;
|
|
85
|
+
|
|
86
|
+
test.afterEach(() => {
|
|
87
|
+
if (typeof originalWindow === "undefined") {
|
|
88
|
+
delete globalThis.window;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
globalThis.window = originalWindow;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("hasWorkspaceMembership matches context.workspace slug", () => {
|
|
95
|
+
const context = createPlacementContext({
|
|
96
|
+
workspace: {
|
|
97
|
+
id: 1,
|
|
98
|
+
slug: "acme"
|
|
99
|
+
},
|
|
100
|
+
workspaces: []
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.equal(hasWorkspaceMembership(context, "acme"), true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("hasWorkspaceMembership matches context.workspaces slug", () => {
|
|
107
|
+
const context = createPlacementContext({
|
|
108
|
+
workspace: null,
|
|
109
|
+
workspaces: [
|
|
110
|
+
{
|
|
111
|
+
id: 1,
|
|
112
|
+
slug: "acme"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert.equal(hasWorkspaceMembership(context, "acme"), true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("resolvePrimarySurfaceSwitchLink shows Go to admin only for member workspace", () => {
|
|
121
|
+
const context = createPlacementContext({
|
|
122
|
+
workspace: {
|
|
123
|
+
id: 1,
|
|
124
|
+
slug: "acme"
|
|
125
|
+
},
|
|
126
|
+
workspaces: [
|
|
127
|
+
{
|
|
128
|
+
id: 1,
|
|
129
|
+
slug: "acme"
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const link = resolvePrimarySurfaceSwitchLink({
|
|
135
|
+
context,
|
|
136
|
+
surface: "app"
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
assert.deepEqual(link, {
|
|
140
|
+
id: "surface-switch.admin",
|
|
141
|
+
label: "Go to admin",
|
|
142
|
+
icon: mdiShieldCrownOutline,
|
|
143
|
+
to: "/w/acme/admin"
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("resolvePrimarySurfaceSwitchLink hides workspace switch when slug is only in URL and user is not member", () => {
|
|
148
|
+
globalThis.window = {
|
|
149
|
+
location: {
|
|
150
|
+
pathname: "/w/acme/admin",
|
|
151
|
+
search: "",
|
|
152
|
+
hash: ""
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const context = createPlacementContext({
|
|
157
|
+
workspace: null,
|
|
158
|
+
workspaces: []
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const link = resolvePrimarySurfaceSwitchLink({
|
|
162
|
+
context,
|
|
163
|
+
surface: "admin"
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
assert.equal(link, null);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("resolveProfileSurfaceMenuLinks includes gated non-workspace switches only for matching access flags", () => {
|
|
170
|
+
const nonOwnerContext = createPlacementContext({
|
|
171
|
+
workspace: {
|
|
172
|
+
id: 1,
|
|
173
|
+
slug: "acme"
|
|
174
|
+
},
|
|
175
|
+
workspaces: [
|
|
176
|
+
{
|
|
177
|
+
id: 1,
|
|
178
|
+
slug: "acme"
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
opsOwner: false
|
|
182
|
+
});
|
|
183
|
+
const ownerContext = createPlacementContext({
|
|
184
|
+
workspace: {
|
|
185
|
+
id: 1,
|
|
186
|
+
slug: "acme"
|
|
187
|
+
},
|
|
188
|
+
workspaces: [
|
|
189
|
+
{
|
|
190
|
+
id: 1,
|
|
191
|
+
slug: "acme"
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
opsOwner: true
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const nonOwnerLinks = resolveProfileSurfaceMenuLinks({
|
|
198
|
+
context: nonOwnerContext,
|
|
199
|
+
surface: "app"
|
|
200
|
+
});
|
|
201
|
+
const ownerLinks = resolveProfileSurfaceMenuLinks({
|
|
202
|
+
context: ownerContext,
|
|
203
|
+
surface: "app"
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
assert.equal(nonOwnerLinks.some((entry) => entry.id === "surface-switch.ops"), false);
|
|
207
|
+
assert.equal(ownerLinks.some((entry) => entry.id === "surface-switch.ops"), true);
|
|
208
|
+
});
|
|
@@ -62,7 +62,25 @@ test("workspaces-web descriptor metadata advertises admin settings outlets", ()
|
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
);
|
|
65
|
-
assert.equal(findContribution("
|
|
65
|
+
assert.equal(findContribution("workspaces.workspace.settings.general"), null);
|
|
66
|
+
assert.deepEqual(findContribution("workspaces.profile.menu.surface-switch"), {
|
|
67
|
+
id: "workspaces.profile.menu.surface-switch",
|
|
68
|
+
target: "auth-profile-menu:primary-menu",
|
|
69
|
+
surfaces: ["*"],
|
|
70
|
+
order: 100,
|
|
71
|
+
componentToken: "workspaces.web.profile.menu.surface-switch-item",
|
|
72
|
+
when: "auth.authenticated === true",
|
|
73
|
+
source: "mutations.text#workspaces-web-profile-surface-switch-placement"
|
|
74
|
+
});
|
|
75
|
+
assert.deepEqual(findContribution("workspaces.workspace.selector"), {
|
|
76
|
+
id: "workspaces.workspace.selector",
|
|
77
|
+
target: "shell-layout:top-left",
|
|
78
|
+
surfaces: ["*"],
|
|
79
|
+
order: 200,
|
|
80
|
+
componentToken: "workspaces.web.workspace.selector",
|
|
81
|
+
when: "auth.authenticated === true",
|
|
82
|
+
source: "mutations.text#workspaces-web-placement-block"
|
|
83
|
+
});
|
|
66
84
|
assert.deepEqual(findFileMutation("users-web-page-admin-workspace-settings"), {
|
|
67
85
|
from: "templates/src/pages/admin/workspace/settings/index.vue",
|
|
68
86
|
toSurface: "admin",
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
opsowner: false
|
|
19
|
+
},
|
|
20
|
+
surfaceAccessPolicies: {
|
|
21
|
+
public: {},
|
|
22
|
+
workspace_member: {
|
|
23
|
+
requireAuth: true,
|
|
24
|
+
requireWorkspaceMembership: true
|
|
25
|
+
},
|
|
26
|
+
ops_owner: {
|
|
27
|
+
requireAuth: true,
|
|
28
|
+
requireFlagsAll: ["ops_owner"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
surfaceConfig: {
|
|
32
|
+
defaultSurfaceId: "home",
|
|
33
|
+
enabledSurfaceIds: ["home", "app", "ops"],
|
|
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
|
+
ops: {
|
|
50
|
+
id: "ops",
|
|
51
|
+
enabled: true,
|
|
52
|
+
routeBase: "/ops",
|
|
53
|
+
requiresWorkspace: false,
|
|
54
|
+
accessPolicyId: "ops_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: "ops"
|
|
102
|
+
});
|
|
103
|
+
assert.equal(deniedDecision.allowed, false);
|
|
104
|
+
|
|
105
|
+
const allowedDecision = evaluateSurfaceAccess({
|
|
106
|
+
context: createContext({
|
|
107
|
+
surfaceAccess: {
|
|
108
|
+
opsowner: true
|
|
109
|
+
}
|
|
110
|
+
}),
|
|
111
|
+
surfaceId: "ops"
|
|
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
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { ThemeSymbol } from "vuetify/lib/composables/theme.js";
|
|
4
|
+
import { resolveWorkspaceThemePalette } from "@jskit-ai/workspaces-core/shared/settings";
|
|
5
|
+
import {
|
|
6
|
+
hexColorToRgb,
|
|
7
|
+
setVuetifyPrimaryColorOverride,
|
|
8
|
+
setVuetifyThemeName
|
|
9
|
+
} from "../src/client/lib/theme.js";
|
|
10
|
+
|
|
11
|
+
function createVuetifyThemeController(initialTheme = "light") {
|
|
12
|
+
return {
|
|
13
|
+
global: {
|
|
14
|
+
name: {
|
|
15
|
+
value: initialTheme
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
themes: {
|
|
19
|
+
value: {
|
|
20
|
+
light: {
|
|
21
|
+
dark: false,
|
|
22
|
+
colors: {
|
|
23
|
+
primary: "#0f6b54",
|
|
24
|
+
secondary: "#3f5150",
|
|
25
|
+
background: "#eef3ee",
|
|
26
|
+
surface: "#f7fbf6",
|
|
27
|
+
"surface-variant": "#dfe8df"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
dark: {
|
|
31
|
+
dark: true,
|
|
32
|
+
colors: {
|
|
33
|
+
primary: "#6fd0b5",
|
|
34
|
+
secondary: "#9db2af",
|
|
35
|
+
background: "#0f1715",
|
|
36
|
+
surface: "#16211e",
|
|
37
|
+
"surface-variant": "#253430"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
_context: {
|
|
43
|
+
provides: {
|
|
44
|
+
[ThemeSymbol]: null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test("hexColorToRgb returns Vuetify rgb tuple and rejects invalid values", () => {
|
|
51
|
+
assert.equal(hexColorToRgb("#0f6b54"), "15,107,84");
|
|
52
|
+
assert.equal(hexColorToRgb("#CC3344"), "204,51,68");
|
|
53
|
+
assert.equal(hexColorToRgb("invalid"), "");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("setVuetifyPrimaryColorOverride mutates workspace themes and restores base theme names", () => {
|
|
57
|
+
const themeController = createVuetifyThemeController("light");
|
|
58
|
+
const themeInput = {
|
|
59
|
+
lightPrimaryColor: "#CC3344",
|
|
60
|
+
lightSecondaryColor: "#884455",
|
|
61
|
+
lightSurfaceColor: "#F4F4F4",
|
|
62
|
+
lightSurfaceVariantColor: "#444444",
|
|
63
|
+
darkPrimaryColor: "#BB2233",
|
|
64
|
+
darkSecondaryColor: "#557799",
|
|
65
|
+
darkSurfaceColor: "#202020",
|
|
66
|
+
darkSurfaceVariantColor: "#A0A0A0"
|
|
67
|
+
};
|
|
68
|
+
const expectedLightPalette = resolveWorkspaceThemePalette(themeInput, {
|
|
69
|
+
mode: "light"
|
|
70
|
+
});
|
|
71
|
+
const expectedDarkPalette = resolveWorkspaceThemePalette(themeInput, {
|
|
72
|
+
mode: "dark"
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.equal(setVuetifyPrimaryColorOverride(themeController, themeInput), true);
|
|
76
|
+
assert.equal(themeController.global.name.value, "workspace-light");
|
|
77
|
+
assert.equal(themeController.themes.value["workspace-light"].colors.primary, expectedLightPalette.color);
|
|
78
|
+
assert.equal(themeController.themes.value["workspace-light"].colors.secondary, expectedLightPalette.secondaryColor);
|
|
79
|
+
assert.equal(themeController.themes.value["workspace-light"].colors.surface, expectedLightPalette.surfaceColor);
|
|
80
|
+
assert.equal(
|
|
81
|
+
themeController.themes.value["workspace-light"].colors["surface-variant"],
|
|
82
|
+
expectedLightPalette.surfaceVariantColor
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
assert.equal(setVuetifyPrimaryColorOverride(themeController, themeInput), false);
|
|
86
|
+
|
|
87
|
+
assert.equal(setVuetifyThemeName(themeController, "dark"), true);
|
|
88
|
+
assert.equal(setVuetifyPrimaryColorOverride(themeController, themeInput), true);
|
|
89
|
+
assert.equal(themeController.global.name.value, "workspace-dark");
|
|
90
|
+
assert.equal(themeController.themes.value["workspace-dark"].colors.primary, expectedDarkPalette.color);
|
|
91
|
+
assert.equal(themeController.themes.value["workspace-dark"].colors.secondary, expectedDarkPalette.secondaryColor);
|
|
92
|
+
assert.equal(themeController.themes.value["workspace-dark"].colors.surface, expectedDarkPalette.surfaceColor);
|
|
93
|
+
assert.equal(
|
|
94
|
+
themeController.themes.value["workspace-dark"].colors["surface-variant"],
|
|
95
|
+
expectedDarkPalette.surfaceVariantColor
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
assert.equal(setVuetifyPrimaryColorOverride(themeController, null), true);
|
|
99
|
+
assert.equal(themeController.global.name.value, "dark");
|
|
100
|
+
assert.equal(setVuetifyPrimaryColorOverride(themeController, null), false);
|
|
101
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
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 non-workspace surface ids", () => {
|
|
52
|
+
const context = createContext();
|
|
53
|
+
const path = resolveWorkspaceShellLinkPath({
|
|
54
|
+
context,
|
|
55
|
+
surface: "ops",
|
|
56
|
+
mode: "workspace",
|
|
57
|
+
workspaceSlug: "acme"
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.equal(path, "");
|
|
61
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
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", "ops"],
|
|
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
|
+
ops: { id: "ops", routeBase: "/ops", 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
|
+
});
|