mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
## What kind of change does this PR introduce? Feature that resolves DEPR-321. ## What is the current behavior? From @dshukertjr: > Users of free and pro orgs frequently struggle to figure out how to add teammates to a project. ## What is the new behavior? Project Settings → General now has a Project access section showing who can access the project (email + role), with a link to team management (_Manage members_ or _View team_ for limited users). Large member lists are truncated with +N more. | Permutations | | --- | | <img width="1462" height="544" alt="CleanShot 2026-03-06 at 17 00 00@2x" src="https://github.com/user-attachments/assets/68e5519f-3851-4ab8-a364-9fcb222fbcb7" /> | | <img width="1486" height="676" alt="CleanShot 2026-03-06 at 16 59 48@2x" src="https://github.com/user-attachments/assets/e1b85bb5-6fbd-46ec-9b13-15501defd030" /> | | <img width="1464" height="566" alt="CleanShot 2026-03-06 at 16 59 34@2x" src="https://github.com/user-attachments/assets/e9fdc188-cf79-4af9-8b3c-313e98256109" /> | | <img width="1438" height="654" alt="CleanShot 2026-03-06 at 17 11 25@2x-83D06149-E4AE-4AC0-98D9-FBBE10A58C8C" src="https://github.com/user-attachments/assets/8a6e1aa2-76bb-486e-999a-1df4f88c3692" /> | ## Additional context Behaviour is based on user role visibility, not a special plan-only toggle. Team+ orgs are more likely to hit limited/project-scoped cases; free/pro are usually org-wide access.
93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
import { renderHook } from '@testing-library/react'
|
|
import { useFlag } from 'common'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { useGenerateSettingsMenu } from './SettingsMenu.utils'
|
|
import { useIsPlatformWebhooksEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
|
|
|
vi.mock('lib/constants', async () => {
|
|
const actual = await vi.importActual<Record<string, unknown>>('lib/constants')
|
|
return {
|
|
...actual,
|
|
IS_PLATFORM: true,
|
|
}
|
|
})
|
|
|
|
vi.mock('common', () => ({
|
|
useFlag: vi.fn().mockReturnValue(false),
|
|
useParams: vi.fn().mockReturnValue({ ref: 'project-ref' }),
|
|
}))
|
|
|
|
vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
|
|
useSelectedOrganizationQuery: vi.fn().mockReturnValue({ data: { slug: 'my-org' } }),
|
|
}))
|
|
|
|
vi.mock('@/hooks/misc/useSelectedProject', () => ({
|
|
useSelectedProjectQuery: vi.fn().mockReturnValue({ data: { status: 'ACTIVE_HEALTHY' } }),
|
|
}))
|
|
|
|
vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({
|
|
useIsFeatureEnabled: vi
|
|
.fn()
|
|
.mockReturnValue({ projectSettingsLegacyJwtKeys: false, billingAll: true }),
|
|
}))
|
|
|
|
vi.mock('@/components/interfaces/App/FeaturePreview/FeaturePreviewContext', () => ({
|
|
useIsPlatformWebhooksEnabled: vi.fn().mockReturnValue(true),
|
|
}))
|
|
|
|
describe('useGenerateSettingsMenu', () => {
|
|
it('includes webhooks when platformWebhooks feature is enabled', () => {
|
|
vi.mocked(useIsPlatformWebhooksEnabled).mockReturnValue(true)
|
|
|
|
const { result } = renderHook(() => useGenerateSettingsMenu())
|
|
const configurationGroup = result.current.find((group) => group.title === 'Configuration')
|
|
const hasWebhooks = configurationGroup?.items.some(
|
|
(item) => item.name === 'Webhooks' && item.url === '/project/project-ref/settings/webhooks'
|
|
)
|
|
|
|
expect(hasWebhooks).toBe(true)
|
|
})
|
|
|
|
it('hides webhooks when platformWebhooks feature is disabled', () => {
|
|
vi.mocked(useIsPlatformWebhooksEnabled).mockReturnValue(false)
|
|
|
|
const { result } = renderHook(() => useGenerateSettingsMenu())
|
|
const configurationGroup = result.current.find((group) => group.title === 'Configuration')
|
|
const hasWebhooks = configurationGroup?.items.some((item) => item.name === 'Webhooks')
|
|
|
|
expect(hasWebhooks).toBe(false)
|
|
})
|
|
|
|
it('does not include members link in project settings navigation', () => {
|
|
const { result } = renderHook(() => useGenerateSettingsMenu())
|
|
const configurationGroup = result.current.find((group) => group.title === 'Configuration')
|
|
const hasMembers = configurationGroup?.items.some((item) => item.name === 'Members')
|
|
|
|
expect(hasMembers).toBe(false)
|
|
})
|
|
|
|
it('includes dashboard preferences when flag is enabled', () => {
|
|
vi.mocked(useFlag).mockReturnValue(true)
|
|
|
|
const { result } = renderHook(() => useGenerateSettingsMenu())
|
|
const preferencesGroup = result.current.find((group) => group.title === 'Preferences')
|
|
const hasDashboardPreferences = preferencesGroup?.items.some(
|
|
(item) =>
|
|
item.name === 'Dashboard preferences' &&
|
|
item.url === '/project/project-ref/settings/preferences'
|
|
)
|
|
|
|
expect(hasDashboardPreferences).toBe(true)
|
|
})
|
|
|
|
it('hides dashboard preferences when flag is disabled', () => {
|
|
vi.mocked(useFlag).mockReturnValue(false)
|
|
|
|
const { result } = renderHook(() => useGenerateSettingsMenu())
|
|
const preferencesGroup = result.current.find((group) => group.title === 'Preferences')
|
|
|
|
expect(preferencesGroup).toBeUndefined()
|
|
})
|
|
})
|