Files
supabase/apps/studio/tests/components/Settings/Infrastructure/AddReadReplicaDialog.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

100 lines
3.0 KiB
TypeScript

import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
import { describe, expect, test, vi } from 'vitest'
import { AddReadReplicaDialog } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/AddReadReplicaDialog'
import { customRender } from '@/tests/lib/custom-render'
const { mockReadReplicaForm } = vi.hoisted(() => ({ mockReadReplicaForm: vi.fn() }))
vi.mock('@/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicaForm', () => ({
ReadReplicaForm: ({
onClose,
onRecommendCompute,
}: {
onClose: () => void
onRecommendCompute: (size: 'ci_small') => void
}) => {
mockReadReplicaForm()
return (
<>
<button type="button" tabIndex={0} onClick={() => onRecommendCompute('ci_small')}>
Change compute
</button>
<button type="button" tabIndex={0}>
Change region
</button>
<button type="button" tabIndex={0} onClick={onClose}>
Cancel
</button>
</>
)
},
}))
const renderDialog = (onRecommendCompute = vi.fn()) => {
const TestDialog = () => {
const [open, setOpen] = useState(true)
return (
<AddReadReplicaDialog
open={open}
onOpenChange={setOpen}
onRecommendCompute={onRecommendCompute}
/>
)
}
return customRender(<TestDialog />)
}
describe('AddReadReplicaDialog', () => {
test('does not load form data while closed', () => {
customRender(
<AddReadReplicaDialog open={false} onOpenChange={vi.fn()} onRecommendCompute={vi.fn()} />
)
expect(mockReadReplicaForm).not.toHaveBeenCalled()
})
test('closes an unchanged dialog without confirmation', async () => {
const user = userEvent.setup()
renderDialog()
expect(screen.getByRole('dialog', { name: 'Add read replica' })).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Close' }))
await waitFor(() => {
expect(screen.queryByRole('dialog', { name: 'Add read replica' })).not.toBeInTheDocument()
})
expect(screen.queryByText('Unsaved changes')).not.toBeInTheDocument()
})
test('closes after a transient region selection without confirmation', async () => {
const user = userEvent.setup()
renderDialog()
await user.click(screen.getByRole('button', { name: 'Change region' }))
await user.click(screen.getByRole('button', { name: 'Cancel' }))
await waitFor(() => {
expect(screen.queryByRole('dialog', { name: 'Add read replica' })).not.toBeInTheDocument()
})
expect(screen.queryByText('Unsaved changes')).not.toBeInTheDocument()
})
test('hands the recommendation off after closing the dialog', async () => {
const user = userEvent.setup()
const onRecommendCompute = vi.fn()
renderDialog(onRecommendCompute)
await user.click(screen.getByRole('button', { name: 'Change compute' }))
await waitFor(() => expect(onRecommendCompute).toHaveBeenCalledWith('ci_small'))
})
})