mirror of
https://github.com/supabase/supabase.git
synced 2026-06-29 03:50:30 -04:00
9660b0075c
## What kind of change does this PR introduce? Code cleanup. Follow-up to #45774. ## What is the current behavior? The organisation invite interstitial derives invite states, titles, and descriptions from nested conditional logic in the component. That makes the component harder to scan and pushes too much state coverage into render tests. ## What is the new behavior? See #45774 for screenshots of the general UI before-and-after (which this one builds upon). That PR also contains testing instructions. Extracts the invite status and content decisions into small pure helpers, then covers those helpers with focused unit tests. The component keeps the user-facing render and interaction coverage, including the invalid lookup regression where a 404 should render the invalid invite state instead of raw backend copy. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Improved organization invite flow with enhanced error state handling for expired, invalid, and wrong-account scenarios. * Better consistency in error messages and user guidance throughout the invite process. [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45813) <!-- end of auto-generated comment: release notes by coderabbit.ai -->
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
|
|
import {
|
|
getOrganizationInviteContent,
|
|
getOrganizationInviteStatus,
|
|
type OrganizationInviteStatus,
|
|
} from '@/components/interfaces/OrganizationInvite/OrganizationInvite.utils'
|
|
import type { OrganizationInviteByToken } from '@/data/organization-members/organization-invitation-token-query'
|
|
import type { ResponseError } from '@/types'
|
|
|
|
const READY_INVITE: OrganizationInviteByToken = {
|
|
authorized_user: true,
|
|
email_match: true,
|
|
expired_token: false,
|
|
invite_id: 42,
|
|
organization_name: 'Acme Corp',
|
|
sso_mismatch: false,
|
|
token_does_not_exist: false,
|
|
}
|
|
|
|
const responseError = (message: string, code = 500) => ({ message, code }) as ResponseError
|
|
type StatusOverrides = Partial<Parameters<typeof getOrganizationInviteStatus>[0]>
|
|
|
|
const getStatus = (overrides: StatusOverrides = {}) =>
|
|
getOrganizationInviteStatus({
|
|
data: READY_INVITE,
|
|
error: null,
|
|
isErrorInvitation: false,
|
|
isLoadingInvitation: false,
|
|
isLoadingProfile: false,
|
|
isLoggedIn: true,
|
|
isRouterReady: true,
|
|
isSuccessInvitation: true,
|
|
profileExists: true,
|
|
...overrides,
|
|
})
|
|
|
|
describe('OrganizationInvite utils', () => {
|
|
test.each<[string, OrganizationInviteStatus, StatusOverrides]>([
|
|
['signed out when there is no current user', 'signed-out', { isLoggedIn: false }],
|
|
['loading while the profile is loading', 'loading', { isLoadingProfile: true }],
|
|
['loading while the invite is loading', 'loading', { isLoadingInvitation: true }],
|
|
[
|
|
'no longer valid for accepted or declined invites',
|
|
'no-longer-valid',
|
|
{
|
|
data: undefined,
|
|
error: responseError('Failed to retrieve organization', 401),
|
|
isErrorInvitation: true,
|
|
isSuccessInvitation: false,
|
|
},
|
|
],
|
|
[
|
|
'invalid when the API returns a missing token response',
|
|
'invalid',
|
|
{
|
|
data: { ...READY_INVITE, token_does_not_exist: true },
|
|
},
|
|
],
|
|
[
|
|
'invalid when the invite lookup 404s',
|
|
'invalid',
|
|
{
|
|
data: undefined,
|
|
error: responseError('Not Found', 404),
|
|
isErrorInvitation: true,
|
|
isSuccessInvitation: false,
|
|
},
|
|
],
|
|
[
|
|
'error for other API failures',
|
|
'error',
|
|
{
|
|
data: undefined,
|
|
error: responseError('Failed to retrieve token', 500),
|
|
isErrorInvitation: true,
|
|
isSuccessInvitation: false,
|
|
},
|
|
],
|
|
[
|
|
'expired when the token has expired',
|
|
'expired',
|
|
{
|
|
data: { ...READY_INVITE, expired_token: true },
|
|
},
|
|
],
|
|
[
|
|
'wrong account when the invite email does not match',
|
|
'wrong-account',
|
|
{
|
|
data: { ...READY_INVITE, email_match: false },
|
|
},
|
|
],
|
|
['ready when the invite can be accepted', 'ready', {}],
|
|
])('returns %s', (_name, expected, overrides) => {
|
|
expect(getStatus(overrides)).toBe(expected)
|
|
})
|
|
|
|
test.each<[OrganizationInviteStatus, string, string | undefined]>([
|
|
['signed-out', 'View invitation', 'Sign in or create an account to view this invitation'],
|
|
['ready', 'Join Acme Corp', 'You have been invited to join this Supabase organization'],
|
|
['wrong-account', 'Wrong account', undefined],
|
|
['expired', 'Invite expired', undefined],
|
|
['invalid', 'Invite invalid', undefined],
|
|
['no-longer-valid', 'Invite no longer available', undefined],
|
|
['error', 'Unable to load invitation', undefined],
|
|
])('returns content for %s', (status, title, description) => {
|
|
expect(
|
|
getOrganizationInviteContent({
|
|
data: READY_INVITE,
|
|
isSignUpEnabled: true,
|
|
status,
|
|
})
|
|
).toEqual({ title, ...(description ? { description } : {}) })
|
|
})
|
|
|
|
test('omits sign-up copy when sign-up is disabled', () => {
|
|
expect(
|
|
getOrganizationInviteContent({
|
|
data: READY_INVITE,
|
|
isSignUpEnabled: false,
|
|
status: 'signed-out',
|
|
}).description
|
|
).toBe('Sign in to view this invitation')
|
|
})
|
|
})
|