Files
supabase/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.test.ts
Danny White b2b5cba287 feat(studio): add organization shortcuts (#46356)
## What kind of change does this PR introduce?

Feature. Resolves FE-3470.

## What is the current behavior?

Organization surfaces have a `G then ,` shortcut to enter org settings,
but once inside there is no keyboard navigation, sidebar tooltips, or
action shortcuts for the various org pages.

| Area | Current behaviour |
| --- | --- |
| Org Settings sidebar | Routes are click-only once users are inside
Settings. |
| OAuth Apps | Publish / confirm actions have no keyboard shortcuts. |
| Private Apps | Create app has no keyboard shortcut. |
| Team | Invite / send actions have no keyboard shortcuts. |
| Integrations | Add project connection has no keyboard shortcut. |
| Org Projects | New project and search have no keyboard shortcuts. |
| Audit Logs | Refresh has no keyboard shortcut. |

## What is the new behavior?

Mirrors the Project Settings shortcut pattern (#46352) across all
Organization surfaces.

| Area | New shortcut coverage |
| --- | --- |
| Org Settings sidebar | `S then G/C/S/A/P/W/L/D` for General, Security,
SSO, OAuth apps, Private apps, Webhooks, Audit logs, Legal documents.
Shortcut badge appears on hover in the sidebar. |
| Org Settings entry | `G then ,` (remapped from `G then O`) to match
the Project Settings chord. |
| OAuth Apps | `Shift+N` opens Publish app panel; `Mod+Enter` confirms
the open panel. |
| Private Apps | `Shift+N` opens Create app sheet (works in both
empty-state and list-state). |
| Team | `Shift+N` opens Invite members dialog; `Mod+Enter` sends the
invitation(s). |
| Integrations | `Shift+N` triggers Add project connection when
permitted. |
| Org Projects | `Shift+N` navigates to new project; `Shift+F` focuses
the search input. |
| Audit Logs | `Shift+R` refreshes the log list. |

### Implementation notes

- Threads `shortcutId` through the `WithSidebar` pipeline (`SidebarLink`
→ `SubMenuSection` → `ProductMenuGroup`) so tooltip display is automatic
— no new rendering logic.
- Layout-scoped chords mount only while `OrganizationSettingsLayout` is
active, so `S then G` in org settings does not conflict with `S then G`
in project settings.
- Cheatsheet reference groups promoted to typed constants with readable
labels (was: bare strings like `'org-oauth-apps'`).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* System-wide keyboard shortcuts for org areas: project search & new
project, private app creation, OAuth app publish/confirm, add GitHub
integration, invite members (open/submit), and refresh audit logs.
* Sidebar and product menu now show assigned shortcuts for faster
navigation; org settings navigation shortcut remapped.

* **Tests**
* Added coverage for org shortcut registry behavior, sequences, and
ordering.

* **Chores**
* New shortcut reference groups and ordering for improved
discoverability.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46356?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-05-28 15:48:32 +00:00

146 lines
4.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
generateOrganizationSettingsMenuItems,
generateOrganizationSettingsSections,
normalizeOrganizationSettingsPath,
} from './OrganizationSettingsLayout'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
describe('generateOrganizationSettingsMenuItems', () => {
it('includes webhooks entry for organization settings nav', () => {
const items = generateOrganizationSettingsMenuItems({
slug: 'my-org',
showSecuritySettings: true,
showSsoSettings: true,
showLegalDocuments: true,
})
expect(items.some((item) => item.label === 'Webhooks')).toBe(true)
expect(items.some((item) => item.href === '/org/my-org/webhooks')).toBe(true)
})
})
describe('OrganizationSettingsLayout helpers', () => {
it('returns expected organization settings sections and links', () => {
const sections = generateOrganizationSettingsSections({
slug: 'my-org',
currentPath: '/org/my-org/general',
showSecuritySettings: true,
showSsoSettings: true,
showLegalDocuments: true,
})
expect(sections.map((section) => section.heading)).toEqual([
'Configuration',
'Connections',
'Compliance',
])
expect(sections.flatMap((section) => section.links.map((item) => item.label))).toEqual([
'General',
'Security',
'SSO',
'OAuth Apps',
'Webhooks',
'Audit Logs',
'Legal Documents',
])
expect(
sections.flatMap((section) => section.links).find((item) => item.label === 'General')
?.isActive
).toBe(true)
})
it('hides feature-flagged items when flags are disabled', () => {
const sections = generateOrganizationSettingsSections({
slug: 'my-org',
currentPath: '/org/my-org/general',
showSecuritySettings: false,
showSsoSettings: false,
showLegalDocuments: false,
})
expect(sections.map((section) => section.heading)).toEqual([
'Configuration',
'Connections',
'Compliance',
])
expect(sections.flatMap((section) => section.links.map((item) => item.label))).toEqual([
'General',
'OAuth Apps',
'Webhooks',
'Audit Logs',
])
})
it('normalizes hash paths for active state checks', () => {
const currentPath = normalizeOrganizationSettingsPath('/org/my-org/security#sso')
const sections = generateOrganizationSettingsSections({
slug: 'my-org',
currentPath,
showSecuritySettings: true,
showSsoSettings: true,
showLegalDocuments: true,
})
expect(
sections.flatMap((section) => section.links).find((item) => item.label === 'Security')
?.isActive
).toBe(true)
})
it('attaches shortcutId to each settings link', () => {
const sections = generateOrganizationSettingsSections({
slug: 'my-org',
currentPath: '/org/my-org/general',
showSecuritySettings: true,
showSsoSettings: true,
showLegalDocuments: true,
showPlatformWebhooks: true,
})
const allLinks = sections.flatMap((s) => s.links)
const linkByKey = (key: string) => allLinks.find((l) => l.key === key)
expect(linkByKey('general')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_GENERAL)
expect(linkByKey('security')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_SECURITY)
expect(linkByKey('sso')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_SSO)
expect(linkByKey('apps')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_APPS)
expect(linkByKey('webhooks')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_WEBHOOKS)
expect(linkByKey('audit')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_AUDIT)
expect(linkByKey('documents')?.shortcutId).toBe(SHORTCUT_IDS.NAV_ORG_SETTINGS_DOCUMENTS)
})
it('omits feature-flagged links (and their shortcutIds) when flags are off', () => {
const sections = generateOrganizationSettingsSections({
slug: 'my-org',
currentPath: '/org/my-org/general',
showSecuritySettings: false,
showSsoSettings: false,
showLegalDocuments: false,
})
const allLinks = sections.flatMap((s) => s.links)
const keys = allLinks.map((l) => l.key)
expect(keys).not.toContain('security')
expect(keys).not.toContain('sso')
expect(keys).not.toContain('documents')
})
it('keeps webhooks nav item active for nested endpoint routes', () => {
const sections = generateOrganizationSettingsSections({
slug: 'my-org',
currentPath: '/org/my-org/webhooks/org-endpoint-1',
showSecuritySettings: true,
showSsoSettings: true,
showLegalDocuments: true,
})
expect(
sections.flatMap((section) => section.links).find((item) => item.label === 'Webhooks')
?.isActive
).toBe(true)
})
})