Files
supabase/apps/studio/tests/components/Settings/Infrastructure/ReadReplicasSection.test.tsx
Danny White 34d6c2fbc5 feat(studio): move read replica creation to a dialog (#49516)
## What kind of change does this PR introduce?

Studio interface improvement.

## What is the current behavior?

Read replica creation uses an oversized sheet, with region selection,
eligibility guidance, and pricing all awkwardly competing for space. The
cost estimate can briefly show a compute-only subtotal while disk
pricing is still loading.

## What is the new behavior?

Read replica creation uses a focused, centred dialog with a vertical
region field, contextual eligibility guidance, and a separate cost
breakdown. Disabled forms omit redundant deployment-location text. The
additional monthly cost appears only after both compute and disk pricing
inputs are available.

| Before | After |
| --- | --- |
| <img width="1024" height="759" alt="Infrastructure Settings Chives
Pantry Supabase"
src="https://github.com/user-attachments/assets/afb0a9a6-3575-4d65-98a3-f21b23a032ac"
/> | <img width="1024" height="759" alt="Infrastructure Settings Chives
Pantry Supabase"
src="https://github.com/user-attachments/assets/89fd96f8-8f95-4b88-b1fd-505a261ff9a0"
/> |
| <img width="1024" height="759" alt="Infrastructure Settings Chisel
Toolshed Supabase"
src="https://github.com/user-attachments/assets/dd110b3a-6aca-4074-9a69-2dc711f4d1c7"
/> | <img width="1024" height="759" alt="Infrastructure Settings Chisel
Toolshed Supabase"
src="https://github.com/user-attachments/assets/894bbd81-01d5-411e-8279-1bd2e5839cff"
/> |
| <img width="1024" height="759" alt="Infrastructure Settings Chisel
Toolshed Supabase"
src="https://github.com/user-attachments/assets/09cbd315-c13d-4987-b274-daf4f91ea10d"
/> | <img width="1024" height="759" alt="Infrastructure Settings Chisel
Toolshed Supabase"
src="https://github.com/user-attachments/assets/a1effb33-1bf2-450a-8cd8-0de8675231c9"
/> |

## To test

- Open `/project/<ref>/settings/infrastructure` and select **Add read
replica** from the section header or empty state. Confirm the dialog
opens and closes using Close, Escape, backdrop, and Cancel.
- On an eligible project, confirm the pricing note initially reads
**Estimated additional cost**, then adds **of $X/month** once pricing
loads. **View breakdown** should remain disabled until then.
- Change the region and open **View breakdown**. Confirm the monthly
cost table has standard row borders and an estimated total.
- On a project below Small compute, confirm the region field is
disabled, its deployment-location description is hidden, and **Change
compute** returns to the compute controls.


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

* **New Features**
* Replaced the add read replica sheet with a dialog-based setup
experience.
* Added region details, eligibility guidance, compute recommendations,
and estimated pricing.
  * Added retry options when pricing information fails to load.

* **UI Improvements**
  * Updated warning messages, documentation links, and action labels.
  * Improved dialog behavior and deferred data loading until opened.

* **Tests**
* Expanded coverage for dialog behavior, eligibility warnings, pricing
errors, retries, and recommendations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-26 15:17:37 +08:00

96 lines
3.4 KiB
TypeScript

import { screen } from '@testing-library/react'
import { HttpResponse } from 'msw'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { ReadReplicasSection } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicasSection'
import type { components } from '@/data/api'
import { customRender } from '@/tests/lib/custom-render'
import { addAPIMock } from '@/tests/lib/msw'
type DatabaseDetailResponse = components['schemas']['DatabaseDetailResponse']
type DatabaseStatusResponse = components['schemas']['DatabaseStatusResponse']
type LoadBalancerDetailResponse = components['schemas']['LoadBalancerDetailResponse']
const { mockUseIsFeatureEnabled } = vi.hoisted(() => ({
mockUseIsFeatureEnabled: vi.fn(() => ({ infrastructureReadReplicas: true })),
}))
vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({
useIsFeatureEnabled: mockUseIsFeatureEnabled,
}))
vi.mock(
'@/components/interfaces/Settings/Infrastructure/ReadReplicas/AddReadReplicaDialog',
() => ({ AddReadReplicaDialog: () => null })
)
const addReplicaListMocks = () => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref/databases',
response: () =>
HttpResponse.json<DatabaseDetailResponse[]>([
{
cloud_provider: 'AWS',
connectionString: 'postgresql://postgres:password@db.default.supabase.co:5432/postgres',
db_host: 'db.default.supabase.co',
db_name: 'postgres',
db_port: 5432,
db_user: 'postgres',
identifier: 'default',
inserted_at: '2026-01-01T00:00:00.000Z',
region: 'us-east-1',
restUrl: 'https://default.supabase.co',
size: 't4g.small',
status: 'ACTIVE_HEALTHY',
},
]),
})
addAPIMock({
method: 'get',
path: '/platform/projects/:ref/databases-statuses',
response: () => HttpResponse.json<DatabaseStatusResponse[]>([]),
})
addAPIMock({
method: 'get',
path: '/platform/projects/:ref/load-balancers',
response: () => HttpResponse.json<LoadBalancerDetailResponse[]>([]),
})
}
describe('ReadReplicasSection', () => {
beforeEach(() => {
mockUseIsFeatureEnabled.mockReturnValue({ infrastructureReadReplicas: true })
})
test('renders the read replicas section with add CTA and empty state', async () => {
mockUseIsFeatureEnabled.mockReturnValue({ infrastructureReadReplicas: true })
addReplicaListMocks()
customRender(<ReadReplicasSection onRecommendCompute={vi.fn()} />)
expect(await screen.findByText('Read replicas')).toBeInTheDocument()
expect(await screen.findByText('No read replicas')).toBeInTheDocument()
expect(screen.getAllByRole('button', { name: /Add read replica/i }).length).toBeGreaterThan(0)
})
test('does not fetch replicas when the feature is disabled', async () => {
mockUseIsFeatureEnabled.mockReturnValue({ infrastructureReadReplicas: false })
let fetchedReplicas = false
addAPIMock({
method: 'get',
path: '/platform/projects/:ref/databases',
response: () => {
fetchedReplicas = true
return HttpResponse.json<DatabaseDetailResponse[]>([])
},
})
customRender(<ReadReplicasSection onRecommendCompute={vi.fn()} />)
expect(screen.queryByText('Read replicas')).not.toBeInTheDocument()
await new Promise((resolve) => setTimeout(resolve, 50))
expect(fetchedReplicas).toBe(false)
})
})