mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 14:03:33 +09:00
A Free project owner could not switch to Content mode directly, but could open a free Builder share link and use Content mode there. Owners can now use the switch on any plan. Share-link visitors can use it only when the project plan includes Content mode. ## Changes - Keep Content visible in the mode menu. When it is unavailable, disable it and show a Pro badge instead of the shortcut. - Let project owners use Content mode on any plan. - On Free projects, disable Content mode for every share-link permission. A Content-only link opens in Preview; a Builder link still opens in Design. ## Checklist - [x] Keep the Content menu row visible and test its badge and shortcut. - [x] Update the permission rule and tests for Free owners, Free share links, and Pro share links. - [x] Verify a Free Content-only link falls back to Preview mode. - [x] Review Builder docs. The Modes guide already says owners can try Content mode, and the Share links guide says Content-only links require Pro. No copy change is needed. - [x] Run focused tests, Builder typecheck, and lint after the permission change. - [x] Review the non-test diff for redundant code. - [x] Set a paid plan only in E2E tests that open Content mode through an Editor share link. Keep unrelated fixtures on their original plan. - [x] Fix the Design-mode fallback for Free Content-only links and add a regression test. - [x] Confirm required CI checks pass on the latest commit. ## Verification - Builder typecheck: passed. - Oxlint on changed code: passed. - Focused tests: 66 passed across mode, permission, and state suites. The two new Design-entry cases failed before the fix. - Latest-commit CI: Main workflow, Visual regression, Check submodules, and Lint PR passed. Every E2E shard passed. - Manual Storybook screenshots were not run because approval was not received. ## Known limit The Share dialog blocks Content-only links on Free plans. Token creation and update do not enforce that plan limit on the server. This PR controls which Builder mode a share-link visitor can enter; it does not change token issuance. Closes #6618
816 lines
22 KiB
TypeScript
816 lines
22 KiB
TypeScript
import { describe, test, expect } from "vitest";
|
|
import { getPermissions } from "./permissions";
|
|
|
|
// Free plan permission values
|
|
const defaultPlanPermissions = {
|
|
canDownloadAssets: false,
|
|
canRestoreBackups: false,
|
|
allowDynamicData: false,
|
|
allowAuth: false,
|
|
allowContentMode: false,
|
|
allowStagingPublish: false,
|
|
allowAdditionalPermissions: false,
|
|
maxContactEmailsPerProject: 0,
|
|
maxDomainsAllowedPerUser: 0,
|
|
maxDailyPublishesPerUser: 10,
|
|
maxWorkspaces: 0,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
};
|
|
|
|
const proPlan = {
|
|
canDownloadAssets: true,
|
|
canRestoreBackups: true,
|
|
allowAdditionalPermissions: true,
|
|
allowDynamicData: true,
|
|
allowAuth: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
maxContactEmailsPerProject: 5,
|
|
maxDomainsAllowedPerUser: 100,
|
|
maxDailyPublishesPerUser: 100,
|
|
maxWorkspaces: 0,
|
|
maxProjectsAllowedPerUser: 100,
|
|
maxAssetsPerProject: 500,
|
|
seatsIncluded: 0,
|
|
maxSeatsPerWorkspace: 0,
|
|
};
|
|
|
|
const freePlan = {
|
|
canDownloadAssets: false,
|
|
canRestoreBackups: false,
|
|
allowAdditionalPermissions: false,
|
|
allowDynamicData: false,
|
|
allowAuth: false,
|
|
allowContentMode: false,
|
|
allowStagingPublish: false,
|
|
maxContactEmailsPerProject: 0,
|
|
maxDomainsAllowedPerUser: 0,
|
|
maxDailyPublishesPerUser: 10,
|
|
maxWorkspaces: 0,
|
|
maxProjectsAllowedPerUser: 2,
|
|
maxAssetsPerProject: 50,
|
|
seatsIncluded: 0,
|
|
maxSeatsPerWorkspace: 0,
|
|
};
|
|
|
|
describe("getPermissions", () => {
|
|
describe("role-based permissions", () => {
|
|
test("owner has full role permissions", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
})
|
|
).toEqual({
|
|
canCreateProject: true,
|
|
canDuplicate: true,
|
|
canRename: true,
|
|
canShare: true,
|
|
canDelete: true,
|
|
canTransfer: true,
|
|
canEditTags: true,
|
|
canOpenSettings: true,
|
|
canPublishToStagingOnly: false,
|
|
...defaultPlanPermissions,
|
|
});
|
|
});
|
|
|
|
test("administrators can create, duplicate, rename, and edit tags", () => {
|
|
const result = getPermissions({
|
|
role: "administrators",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result).toEqual({
|
|
canCreateProject: true,
|
|
canDuplicate: true,
|
|
canRename: true,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: true,
|
|
canEditTags: true,
|
|
canOpenSettings: false,
|
|
canPublishToStagingOnly: false,
|
|
...defaultPlanPermissions,
|
|
});
|
|
});
|
|
|
|
test("builders can create, duplicate, rename, and edit tags", () => {
|
|
const result = getPermissions({
|
|
role: "builders",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result).toEqual({
|
|
canCreateProject: true,
|
|
canDuplicate: true,
|
|
canRename: true,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: false,
|
|
canEditTags: true,
|
|
canOpenSettings: false,
|
|
canPublishToStagingOnly: true,
|
|
...defaultPlanPermissions,
|
|
});
|
|
});
|
|
|
|
test("editors can rename and edit tags only", () => {
|
|
const result = getPermissions({
|
|
role: "editors",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result).toEqual({
|
|
canCreateProject: false,
|
|
canDuplicate: false,
|
|
canRename: true,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: false,
|
|
canEditTags: true,
|
|
canOpenSettings: false,
|
|
canPublishToStagingOnly: false,
|
|
...defaultPlanPermissions,
|
|
});
|
|
});
|
|
|
|
test("viewers have no role-based permissions", () => {
|
|
const result = getPermissions({
|
|
role: "viewers",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result).toEqual({
|
|
canCreateProject: false,
|
|
canDuplicate: false,
|
|
canRename: false,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: false,
|
|
canEditTags: false,
|
|
canOpenSettings: false,
|
|
canPublishToStagingOnly: false,
|
|
...defaultPlanPermissions,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("canCreateProject by role", () => {
|
|
test("own → true", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canCreateProject
|
|
).toBe(true);
|
|
});
|
|
|
|
test("administrators → true", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "administrators",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canCreateProject
|
|
).toBe(true);
|
|
});
|
|
|
|
test("builders → true", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "builders",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canCreateProject
|
|
).toBe(true);
|
|
});
|
|
|
|
test("editors → false", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "editors",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canCreateProject
|
|
).toBe(false);
|
|
});
|
|
|
|
test("viewers → false", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "viewers",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canCreateProject
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("plan-based permissions", () => {
|
|
test("pro plan enables all plan features", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result).toMatchObject({
|
|
allowDynamicData: true,
|
|
allowAuth: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
allowAdditionalPermissions: true,
|
|
maxContactEmailsPerProject: 5,
|
|
maxDomainsAllowedPerUser: 100,
|
|
maxDailyPublishesPerUser: 100,
|
|
maxWorkspaces: 0,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
|
|
test("free plan restricts plan features", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result).toMatchObject({
|
|
allowDynamicData: false,
|
|
allowAuth: false,
|
|
allowContentMode: false,
|
|
allowStagingPublish: false,
|
|
allowAdditionalPermissions: false,
|
|
maxContactEmailsPerProject: 0,
|
|
maxDomainsAllowedPerUser: 0,
|
|
maxDailyPublishesPerUser: 10,
|
|
maxWorkspaces: 0,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("allowContentMode with authPermit", () => {
|
|
test("content mode denied for free shared project (view permit)", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
authPermit: "view",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result.allowContentMode).toBe(false);
|
|
});
|
|
|
|
test("content mode denied for free shared project (edit permit)", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
authPermit: "edit",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result.allowContentMode).toBe(false);
|
|
});
|
|
|
|
test("content mode denied for free shared project (build permit)", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
authPermit: "build",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result.allowContentMode).toBe(false);
|
|
});
|
|
|
|
test("content mode denied for free shared project (admin permit)", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
authPermit: "admin",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result.allowContentMode).toBe(false);
|
|
});
|
|
|
|
test("content mode allowed for shared project with Pro plan", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
authPermit: "edit",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result.allowContentMode).toBe(true);
|
|
});
|
|
|
|
test("content mode is allowed for own project on any plan", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
authPermit: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).allowContentMode
|
|
).toBe(true);
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
authPermit: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
}).allowContentMode
|
|
).toBe(true);
|
|
});
|
|
|
|
test("content mode denied without authPermit on free plan", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).allowContentMode
|
|
).toBe(false);
|
|
});
|
|
|
|
test("content mode allowed without authPermit on pro plan", () => {
|
|
const result = getPermissions({
|
|
role: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
});
|
|
expect(result.allowContentMode).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("combined role and plan permissions", () => {
|
|
test("viewer on free plan has minimal permissions", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "viewers",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
})
|
|
).toEqual({
|
|
canCreateProject: false,
|
|
canDuplicate: false,
|
|
canRename: false,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: false,
|
|
canEditTags: false,
|
|
canOpenSettings: false,
|
|
canDownloadAssets: false,
|
|
canRestoreBackups: false,
|
|
allowDynamicData: false,
|
|
allowAuth: false,
|
|
allowContentMode: false,
|
|
allowStagingPublish: false,
|
|
allowAdditionalPermissions: false,
|
|
maxContactEmailsPerProject: 0,
|
|
maxDomainsAllowedPerUser: 0,
|
|
maxDailyPublishesPerUser: 10,
|
|
maxWorkspaces: 0,
|
|
canPublishToStagingOnly: false,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
|
|
test("builder on pro plan has builder role + all plan features", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "builders",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
})
|
|
).toEqual({
|
|
canCreateProject: true,
|
|
canDuplicate: true,
|
|
canRename: true,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: false,
|
|
canEditTags: true,
|
|
canOpenSettings: false,
|
|
canDownloadAssets: true,
|
|
canRestoreBackups: true,
|
|
allowDynamicData: true,
|
|
allowAuth: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
allowAdditionalPermissions: true,
|
|
maxContactEmailsPerProject: 5,
|
|
maxDomainsAllowedPerUser: 100,
|
|
maxDailyPublishesPerUser: 100,
|
|
maxWorkspaces: 0,
|
|
canPublishToStagingOnly: true,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
|
|
test("owner on pro plan has all permissions", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
})
|
|
).toEqual({
|
|
canCreateProject: true,
|
|
canDuplicate: true,
|
|
canRename: true,
|
|
canShare: true,
|
|
canDelete: true,
|
|
canTransfer: true,
|
|
canEditTags: true,
|
|
canOpenSettings: true,
|
|
canDownloadAssets: true,
|
|
canRestoreBackups: true,
|
|
allowDynamicData: true,
|
|
allowAuth: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
allowAdditionalPermissions: true,
|
|
maxContactEmailsPerProject: 5,
|
|
maxDomainsAllowedPerUser: 100,
|
|
maxDailyPublishesPerUser: 100,
|
|
maxWorkspaces: 0,
|
|
canPublishToStagingOnly: false,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
|
|
test("administrator on free plan has admin role but limited plan features", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "administrators",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
})
|
|
).toEqual({
|
|
canCreateProject: true,
|
|
canDuplicate: true,
|
|
canRename: true,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: true,
|
|
canEditTags: true,
|
|
canOpenSettings: false,
|
|
canDownloadAssets: false,
|
|
canRestoreBackups: false,
|
|
allowDynamicData: false,
|
|
allowAuth: false,
|
|
allowContentMode: false,
|
|
allowStagingPublish: false,
|
|
allowAdditionalPermissions: false,
|
|
maxContactEmailsPerProject: 0,
|
|
maxDomainsAllowedPerUser: 0,
|
|
maxDailyPublishesPerUser: 10,
|
|
maxWorkspaces: 0,
|
|
canPublishToStagingOnly: false,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
|
|
test("editor on pro plan has editor role + all plan features", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "editors",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
})
|
|
).toEqual({
|
|
canCreateProject: false,
|
|
canDuplicate: false,
|
|
canRename: true,
|
|
canShare: false,
|
|
canDelete: false,
|
|
canTransfer: false,
|
|
canEditTags: true,
|
|
canOpenSettings: false,
|
|
canDownloadAssets: true,
|
|
canRestoreBackups: true,
|
|
allowDynamicData: true,
|
|
allowAuth: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
allowAdditionalPermissions: true,
|
|
maxContactEmailsPerProject: 5,
|
|
maxDomainsAllowedPerUser: 100,
|
|
maxDailyPublishesPerUser: 100,
|
|
maxWorkspaces: 0,
|
|
canPublishToStagingOnly: false,
|
|
canInviteMembers: false,
|
|
canCreateWorkspace: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("all three parameters combined", () => {
|
|
test("viewer + free plan + shared project does not enable content mode", () => {
|
|
const result = getPermissions({
|
|
role: "viewers",
|
|
planFeatures: freePlan,
|
|
authPermit: "edit",
|
|
workspaces: [],
|
|
});
|
|
// role: all false
|
|
expect(result.canCreateProject).toBe(false);
|
|
expect(result.canDuplicate).toBe(false);
|
|
expect(result.canRename).toBe(false);
|
|
expect(result.canShare).toBe(false);
|
|
expect(result.canDelete).toBe(false);
|
|
expect(result.canEditTags).toBe(false);
|
|
expect(result.canOpenSettings).toBe(false);
|
|
// plan: all false
|
|
expect(result.allowDynamicData).toBe(false);
|
|
expect(result.allowContentMode).toBe(false);
|
|
expect(result.allowStagingPublish).toBe(false);
|
|
expect(result.allowAdditionalPermissions).toBe(false);
|
|
});
|
|
|
|
test("builder + pro plan + shared project has builder role + all plan features", () => {
|
|
const result = getPermissions({
|
|
role: "builders",
|
|
planFeatures: proPlan,
|
|
authPermit: "build",
|
|
workspaces: [],
|
|
});
|
|
expect(result.canCreateProject).toBe(true);
|
|
expect(result.canDuplicate).toBe(true);
|
|
expect(result.canRename).toBe(true);
|
|
expect(result.canShare).toBe(false);
|
|
expect(result.canDelete).toBe(false);
|
|
expect(result.canEditTags).toBe(true);
|
|
expect(result.canOpenSettings).toBe(false);
|
|
expect(result.allowDynamicData).toBe(true);
|
|
expect(result.allowContentMode).toBe(true);
|
|
expect(result.allowStagingPublish).toBe(true);
|
|
expect(result.allowAdditionalPermissions).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("maxContactEmailsPerProject", () => {
|
|
test("defaults to 0 on free plan", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).maxContactEmailsPerProject
|
|
).toBe(0);
|
|
});
|
|
|
|
test("reflects plan value", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
}).maxContactEmailsPerProject
|
|
).toBe(5);
|
|
});
|
|
});
|
|
|
|
describe("maxDomainsAllowedPerUser", () => {
|
|
test("defaults to 0 on free plan", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).maxDomainsAllowedPerUser
|
|
).toBe(0);
|
|
});
|
|
|
|
test("reflects plan value", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
}).maxDomainsAllowedPerUser
|
|
).toBe(100);
|
|
});
|
|
|
|
test("free plan has 0", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).maxDomainsAllowedPerUser
|
|
).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("maxDailyPublishesPerUser", () => {
|
|
test("defaults to 10 on free plan", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).maxDailyPublishesPerUser
|
|
).toBe(10);
|
|
});
|
|
|
|
test("reflects plan value for pro", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: proPlan,
|
|
workspaces: [],
|
|
}).maxDailyPublishesPerUser
|
|
).toBe(100);
|
|
});
|
|
|
|
test("free plan allows limited publishes", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).maxDailyPublishesPerUser
|
|
).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe("canInviteMembers", () => {
|
|
test("false when maxWorkspaces is 0", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canInviteMembers
|
|
).toBe(false);
|
|
});
|
|
|
|
test("false when maxWorkspaces is 1", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 1 },
|
|
workspaces: [],
|
|
}).canInviteMembers
|
|
).toBe(false);
|
|
});
|
|
|
|
test("true when maxWorkspaces is greater than 1", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 5 },
|
|
workspaces: [],
|
|
}).canInviteMembers
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("canCreateWorkspace", () => {
|
|
test("false when owned count equals maxWorkspaces", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 1 },
|
|
workspaces: [{ role: "own" }],
|
|
}).canCreateWorkspace
|
|
).toBe(false);
|
|
});
|
|
|
|
test("false when owned count exceeds maxWorkspaces", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 1 },
|
|
workspaces: [{ role: "own" }, { role: "own" }],
|
|
}).canCreateWorkspace
|
|
).toBe(false);
|
|
});
|
|
|
|
test("true when owned count is below maxWorkspaces", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 3 },
|
|
workspaces: [{ role: "own" }],
|
|
}).canCreateWorkspace
|
|
).toBe(true);
|
|
});
|
|
|
|
test("counts only owned workspaces, not shared", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 2 },
|
|
workspaces: [
|
|
{ role: "own" },
|
|
{ role: "editors" },
|
|
{ role: "viewers" },
|
|
],
|
|
}).canCreateWorkspace
|
|
).toBe(true);
|
|
});
|
|
|
|
test("false when maxWorkspaces is 0", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canCreateWorkspace
|
|
).toBe(false);
|
|
});
|
|
|
|
test("defaults to empty workspaces", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: { ...freePlan, maxWorkspaces: 5 },
|
|
workspaces: [],
|
|
}).canCreateWorkspace
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("canPublishToStagingOnly", () => {
|
|
test("own → false (full publish access)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(false);
|
|
});
|
|
|
|
test("administrators → false (full publish access)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "administrators",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(false);
|
|
});
|
|
|
|
test("builders → true (staging only)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "builders",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(true);
|
|
});
|
|
|
|
test("editors → false (no publish access)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "editors",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(false);
|
|
});
|
|
|
|
test("viewers → false (no publish access)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "viewers",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(false);
|
|
});
|
|
|
|
test("authPermit 'build' → true (share-link builder, staging only)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
authPermit: "build",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(true);
|
|
});
|
|
|
|
test("authPermit 'admin' → false (share-link admin, full access through canPublish)", () => {
|
|
expect(
|
|
getPermissions({
|
|
role: "own",
|
|
authPermit: "admin",
|
|
planFeatures: freePlan,
|
|
workspaces: [],
|
|
}).canPublishToStagingOnly
|
|
).toBe(false);
|
|
});
|
|
});
|
|
});
|