mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
## Context PR here mainly breaks up the files under `ConnectSheet` to separate the functional logic so that we can write unit tests. No behavior changes intended beyond the bug fixes ## Changes involved - **Test organization:** moved all root-level `ConnectSheet` test files into `ConnectSheet/__tests__/` for consistency with other parts of the codebase that use this convention. - **Bug fix:** read replica label had a stray `}` / missing `)`, rendering as e.g. `Read Replica (us-east-1 - abc123})` instead of `Read Replica (us-east-1 - abc123)`. - **`ConnectSheet.tsx`:** extracted the "hydrate sheet state on open" `useEffect` logic (mode/field/URL param resolution from URL vs. localStorage) into a new `ConnectSheet.utils.ts`, with unit tests - **`useConnectServerEnv.ts`:** fixed two race conditions in the secret reveal/hide flow: - `toggle()` and `getValue()` could each fire a separate reveal request if triggered close together — now deduped to share one in-flight request. - `getValue()` could hide a secret that had just been explicitly revealed by a concurrent `toggle()`, due to reading a stale closure value — now reads the live state via `useLatest`. - Also stopped swallowing the original error on reveal failure (now attached via `cause`). - Added tests for the above, plus the 10s auto-hide timer (previously untested). - **`ConnectStepsSection.tsx`:** extracted `resolveContentPath` and the three inline "show notice" booleans (IPv4 addon, session pooler, self-hosted MCP) into `ConnectStepsSection.utils.ts`, matching the existing pattern for the Data API notice. Added unit tests for all of them. ## To test - [ ] Just a basic smoke test of the Connect sheet should do <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved connect setup hydration so saved preferences and URL values are applied more consistently when opening the sheet, including automatic URL backfilling where needed. * Refreshed connection guidance notices (IPv4 add-on, session pooler, and self-hosted MCP) with more consistent logic. * **Bug Fixes** * Fixed secret reveal behavior to keep concurrent reveal actions in sync, handle failures more safely, and ensure auto-hide works reliably. * Corrected the read-replica option label formatting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
229 lines
6.5 KiB
TypeScript
229 lines
6.5 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
|
|
import type { ConnectMode, ConnectState } from '../Connect.types'
|
|
import {
|
|
resolveContentPath,
|
|
shouldFetchDataApiConfig,
|
|
shouldShowDataApiDisabledWarning,
|
|
shouldShowIpv4AddonNotice,
|
|
shouldShowSelfHostedMcpNotice,
|
|
shouldShowSessionPoolerNotice,
|
|
} from '../ConnectStepsSection.utils'
|
|
|
|
const ALL_MODES: ConnectMode[] = ['framework', 'direct', 'orm', 'mcp', 'server']
|
|
const MCP_WITH_DATABASE_TOOLS = {
|
|
mode: 'mcp' as const,
|
|
mcpFeatures: ['database'],
|
|
}
|
|
|
|
describe('shouldFetchDataApiConfig', () => {
|
|
test('returns true for framework mode', () => {
|
|
expect(shouldFetchDataApiConfig({ mode: 'framework' })).toBe(true)
|
|
})
|
|
|
|
test.each(['direct', 'orm', 'server', 'mcp'] as const)('returns false for %s mode', (mode) => {
|
|
expect(shouldFetchDataApiConfig({ mode })).toBe(false)
|
|
})
|
|
|
|
test('returns false for mcp even when database tools are selected', () => {
|
|
expect(shouldFetchDataApiConfig(MCP_WITH_DATABASE_TOOLS)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('shouldShowDataApiDisabledWarning', () => {
|
|
test.each(ALL_MODES)('returns false while pending for %s mode', (mode) => {
|
|
expect(
|
|
shouldShowDataApiDisabledWarning({
|
|
mode,
|
|
isDataApiEnabled: false,
|
|
isPending: true,
|
|
isError: false,
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
test('returns false when config query errored', () => {
|
|
expect(
|
|
shouldShowDataApiDisabledWarning({
|
|
mode: 'framework',
|
|
isDataApiEnabled: false,
|
|
isPending: false,
|
|
isError: true,
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
test('returns true for framework when Data API is disabled', () => {
|
|
expect(
|
|
shouldShowDataApiDisabledWarning({
|
|
mode: 'framework',
|
|
isDataApiEnabled: false,
|
|
isPending: false,
|
|
isError: false,
|
|
})
|
|
).toBe(true)
|
|
})
|
|
|
|
test.each(['direct', 'orm', 'server', 'mcp'] as const)(
|
|
'returns false for %s when Data API is disabled',
|
|
(mode) => {
|
|
expect(
|
|
shouldShowDataApiDisabledWarning({
|
|
mode,
|
|
isDataApiEnabled: false,
|
|
isPending: false,
|
|
isError: false,
|
|
})
|
|
).toBe(false)
|
|
}
|
|
)
|
|
|
|
test('returns false for mcp with database tools selected when Data API is disabled', () => {
|
|
expect(
|
|
shouldShowDataApiDisabledWarning({
|
|
...MCP_WITH_DATABASE_TOOLS,
|
|
isDataApiEnabled: false,
|
|
isPending: false,
|
|
isError: false,
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
test('returns false when framework mode has Data API enabled', () => {
|
|
expect(
|
|
shouldShowDataApiDisabledWarning({
|
|
mode: 'framework',
|
|
isDataApiEnabled: true,
|
|
isPending: false,
|
|
isError: false,
|
|
})
|
|
).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('resolveContentPath', () => {
|
|
test('replaces multiple placeholders with state values', () => {
|
|
const state: ConnectState = {
|
|
mode: 'framework',
|
|
framework: 'nextjs',
|
|
frameworkVariant: 'app',
|
|
library: 'supabasejs',
|
|
}
|
|
expect(resolveContentPath('{{framework}}/{{frameworkVariant}}/{{library}}', state)).toBe(
|
|
'nextjs/app/supabasejs'
|
|
)
|
|
})
|
|
|
|
test('filters out segments that resolve to an empty/missing state value', () => {
|
|
const state: ConnectState = { mode: 'framework', framework: 'nextjs' }
|
|
expect(resolveContentPath('{{framework}}/{{frameworkVariant}}', state)).toBe('nextjs')
|
|
})
|
|
|
|
test('returns the template unchanged when it has no placeholders', () => {
|
|
const state: ConnectState = { mode: 'direct' }
|
|
expect(resolveContentPath('steps/install', state)).toBe('steps/install')
|
|
})
|
|
})
|
|
|
|
describe('shouldShowIpv4AddonNotice', () => {
|
|
const BASE = {
|
|
isPlatform: true,
|
|
mode: 'direct' as const,
|
|
connectionMethod: 'direct',
|
|
useSharedPooler: false,
|
|
hasIpv4Addon: false,
|
|
}
|
|
|
|
test('returns true for a direct connection with no IPv4 addon', () => {
|
|
expect(shouldShowIpv4AddonNotice(BASE)).toBe(true)
|
|
})
|
|
|
|
test('returns true for the transaction pooler when not using the shared pooler', () => {
|
|
expect(
|
|
shouldShowIpv4AddonNotice({
|
|
...BASE,
|
|
connectionMethod: 'transaction',
|
|
useSharedPooler: false,
|
|
})
|
|
).toBe(true)
|
|
})
|
|
|
|
test('returns false for the transaction pooler when using the shared pooler (already IPv4)', () => {
|
|
expect(
|
|
shouldShowIpv4AddonNotice({ ...BASE, connectionMethod: 'transaction', useSharedPooler: true })
|
|
).toBe(false)
|
|
})
|
|
|
|
test('returns false for the session pooler (has its own notice)', () => {
|
|
expect(shouldShowIpv4AddonNotice({ ...BASE, connectionMethod: 'session' })).toBe(false)
|
|
})
|
|
|
|
test('returns false when the IPv4 addon is already enabled', () => {
|
|
expect(shouldShowIpv4AddonNotice({ ...BASE, hasIpv4Addon: true })).toBe(false)
|
|
})
|
|
|
|
test('returns false when not in direct mode', () => {
|
|
expect(shouldShowIpv4AddonNotice({ ...BASE, mode: 'framework' })).toBe(false)
|
|
})
|
|
|
|
test('returns false when self-hosted (not platform)', () => {
|
|
expect(shouldShowIpv4AddonNotice({ ...BASE, isPlatform: false })).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('shouldShowSessionPoolerNotice', () => {
|
|
test('returns true for the session pooler on platform in direct mode', () => {
|
|
expect(
|
|
shouldShowSessionPoolerNotice({
|
|
isPlatform: true,
|
|
mode: 'direct',
|
|
connectionMethod: 'session',
|
|
})
|
|
).toBe(true)
|
|
})
|
|
|
|
test('returns false when self-hosted (not platform)', () => {
|
|
expect(
|
|
shouldShowSessionPoolerNotice({
|
|
isPlatform: false,
|
|
mode: 'direct',
|
|
connectionMethod: 'session',
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
test('returns false when not in direct mode', () => {
|
|
expect(
|
|
shouldShowSessionPoolerNotice({
|
|
isPlatform: true,
|
|
mode: 'framework',
|
|
connectionMethod: 'session',
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
test('returns false for a non-session connection method', () => {
|
|
expect(
|
|
shouldShowSessionPoolerNotice({
|
|
isPlatform: true,
|
|
mode: 'direct',
|
|
connectionMethod: 'transaction',
|
|
})
|
|
).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('shouldShowSelfHostedMcpNotice', () => {
|
|
test('returns true for mcp mode when self-hosted', () => {
|
|
expect(shouldShowSelfHostedMcpNotice({ isSelfHosted: true, mode: 'mcp' })).toBe(true)
|
|
})
|
|
|
|
test('returns false for mcp mode on platform', () => {
|
|
expect(shouldShowSelfHostedMcpNotice({ isSelfHosted: false, mode: 'mcp' })).toBe(false)
|
|
})
|
|
|
|
test('returns false when self-hosted but not in mcp mode', () => {
|
|
expect(shouldShowSelfHostedMcpNotice({ isSelfHosted: true, mode: 'direct' })).toBe(false)
|
|
})
|
|
})
|