mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 08:07:11 +00:00
feat: leave out the settings pages the organisation turns off
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { policyHiddenPages } from '../policy-pages';
|
||||||
|
import { sanitizePolicy } from '../../../lib/org-policy';
|
||||||
|
|
||||||
|
const screen = fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'screens', 'SettingsScreen.tsx'), 'utf8');
|
||||||
|
|
||||||
|
describe('policyHiddenPages', () => {
|
||||||
|
it('leaves every page without a policy or with the default gates', () => {
|
||||||
|
expect(policyHiddenPages(null)).toEqual([]);
|
||||||
|
expect(policyHiddenPages(sanitizePolicy({}))).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('takes away the pages of the gates the policy turns off', () => {
|
||||||
|
const policy = sanitizePolicy({
|
||||||
|
features: {
|
||||||
|
templatesEnabled: false,
|
||||||
|
customKeywordsEnabled: false,
|
||||||
|
sidebarAppsEnabled: false,
|
||||||
|
themesEnabled: false,
|
||||||
|
calendarEnabled: false,
|
||||||
|
contactsEnabled: false,
|
||||||
|
filesEnabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(policyHiddenPages(policy).sort()).toEqual(
|
||||||
|
['calendar', 'contacts', 'files', 'keywords', 'sidebar_apps', 'templates', 'themes'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('takes away Delegates and Recover deleted items when the policy hides them', () => {
|
||||||
|
const policy = sanitizePolicy({
|
||||||
|
restrictions: { delegation: { hidden: true }, deletedItemsRecovery: { hidden: true } },
|
||||||
|
});
|
||||||
|
expect(policyHiddenPages(policy).sort()).toEqual(['recovery', 'sharing']);
|
||||||
|
const locked = sanitizePolicy({ restrictions: { delegation: { locked: true } } });
|
||||||
|
expect(policyHiddenPages(locked)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('names pages the Settings list has', () => {
|
||||||
|
const policy = sanitizePolicy({
|
||||||
|
features: Object.fromEntries(['templatesEnabled', 'customKeywordsEnabled', 'sidebarAppsEnabled', 'themesEnabled', 'calendarEnabled', 'contactsEnabled', 'filesEnabled'].map((g) => [g, false])),
|
||||||
|
restrictions: { delegation: { hidden: true }, deletedItemsRecovery: { hidden: true } },
|
||||||
|
});
|
||||||
|
for (const page of policyHiddenPages(policy)) expect(screen).toContain(`{ id: '${page}',`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Settings list', () => {
|
||||||
|
it('leaves out the pages the active policy takes away', () => {
|
||||||
|
expect(screen).toContain('const policy = usePolicyStore((s) => s.effective);');
|
||||||
|
expect(screen).toContain('for (const page of policyHiddenPages(policy)) set.add(page as Tab);');
|
||||||
|
expect(screen).toContain('}, [recoveryAvailable, delegationAvailable, policy]);');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { isFeatureEnabled, type FeatureGate, type OrgPolicy } from '../../lib/org-policy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings pages a feature gate takes away, as the webmail's settings-app.tsx
|
||||||
|
* leaves their tabs out: Templates (templatesEnabled, in Composing there),
|
||||||
|
* Keywords (customKeywordsEnabled: Organize turns into Folders), Sidebar apps
|
||||||
|
* (sidebarAppsEnabled: the app has no plugins, which keep Add-ons there),
|
||||||
|
* Themes (themesEnabled, appearance-settings.tsx), and Calendar, Contacts and
|
||||||
|
* Files with their apps.
|
||||||
|
*/
|
||||||
|
const GATED_PAGES: ReadonlyArray<[FeatureGate, string]> = [
|
||||||
|
['templatesEnabled', 'templates'],
|
||||||
|
['customKeywordsEnabled', 'keywords'],
|
||||||
|
['sidebarAppsEnabled', 'sidebar_apps'],
|
||||||
|
['themesEnabled', 'themes'],
|
||||||
|
['calendarEnabled', 'calendar'],
|
||||||
|
['contactsEnabled', 'contacts'],
|
||||||
|
['filesEnabled', 'files'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pages a hidden restriction takes away: Delegates (`delegation`, webmail
|
||||||
|
* settings-app supportsSharing) and Recover deleted items
|
||||||
|
* (`deletedItemsRecovery`, webmail hooks/use-deleted-items-recovery.ts).
|
||||||
|
*/
|
||||||
|
const HIDDEN_PAGES: ReadonlyArray<[string, string]> = [
|
||||||
|
['delegation', 'sharing'],
|
||||||
|
['deletedItemsRecovery', 'recovery'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** The settings pages the active account's policy leaves out, by page id. */
|
||||||
|
export function policyHiddenPages(policy: OrgPolicy | null | undefined): string[] {
|
||||||
|
if (!policy) return [];
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const [gate, page] of GATED_PAGES) {
|
||||||
|
if (!isFeatureEnabled(policy, gate)) out.push(page);
|
||||||
|
}
|
||||||
|
for (const [key, page] of HIDDEN_PAGES) {
|
||||||
|
if (policy.restrictions[key]?.hidden === true) out.push(page);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -54,6 +54,8 @@ import { useAuthStore } from '../stores/auth-store';
|
|||||||
import { useManagedAccountStore } from '../stores/managed-account-store';
|
import { useManagedAccountStore } from '../stores/managed-account-store';
|
||||||
import { useAccountStore } from '../stores/account-store';
|
import { useAccountStore } from '../stores/account-store';
|
||||||
import { useSettingsStore } from '../stores/settings-store';
|
import { useSettingsStore } from '../stores/settings-store';
|
||||||
|
import { usePolicyStore } from '../stores/policy-store';
|
||||||
|
import { policyHiddenPages } from '../components/settings/policy-pages';
|
||||||
import { useUpdatesStore } from '../stores/updates-store';
|
import { useUpdatesStore } from '../stores/updates-store';
|
||||||
import { SUPPORTED_LOCALES } from '../i18n';
|
import { SUPPORTED_LOCALES } from '../i18n';
|
||||||
import { settingsSummary, type SummaryState } from '../lib/settings-summary';
|
import { settingsSummary, type SummaryState } from '../lib/settings-summary';
|
||||||
@@ -231,12 +233,15 @@ export default function SettingsScreen({ onLogout, onBack, onTabSelect }: Settin
|
|||||||
// list, not shown as "Not on this server", where the server lacks them.
|
// list, not shown as "Not on this server", where the server lacks them.
|
||||||
const recoveryAvailable = useDeletedItemsRecovery().available;
|
const recoveryAvailable = useDeletedItemsRecovery().available;
|
||||||
const delegationAvailable = useDelegationAvailable();
|
const delegationAvailable = useDelegationAvailable();
|
||||||
|
// Pages the organisation's policy takes away (feature gates, hidden pages).
|
||||||
|
const policy = usePolicyStore((s) => s.effective);
|
||||||
const hiddenTabs = React.useMemo(() => {
|
const hiddenTabs = React.useMemo(() => {
|
||||||
const set = new Set<Tab>();
|
const set = new Set<Tab>();
|
||||||
if (!recoveryAvailable) set.add('recovery');
|
if (!recoveryAvailable) set.add('recovery');
|
||||||
if (!delegationAvailable) set.add('sharing');
|
if (!delegationAvailable) set.add('sharing');
|
||||||
|
for (const page of policyHiddenPages(policy)) set.add(page as Tab);
|
||||||
return set;
|
return set;
|
||||||
}, [recoveryAvailable, delegationAvailable]);
|
}, [recoveryAvailable, delegationAvailable, policy]);
|
||||||
|
|
||||||
// A shared/group account picked under "Shared with me" scopes Settings to
|
// A shared/group account picked under "Shared with me" scopes Settings to
|
||||||
// the panes that account can be managed in (webmail: scoped settings).
|
// the panes that account can be managed in (webmail: scoped settings).
|
||||||
|
|||||||
Reference in New Issue
Block a user