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

66 lines
2.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, test, vi } from 'vitest'
import { ReadReplicaPricingDialog } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicaForm/ReadReplicaPricingDialog'
import { customRender } from '@/tests/lib/custom-render'
vi.mock('@/hooks/misc/useSelectedProject', () => ({
useSelectedProjectQuery: () => ({ data: { cloud_provider: 'AWS' } }),
}))
const replicaCost = {
isLoading: false,
isError: false,
retry: vi.fn(),
totalCost: '$85.94',
compute: { label: 'Small', cost: '$15.00', priceDescription: '$15/month' },
disk: { type: 'gp3', label: '125 GB (gp3)', cost: '$15.63' },
iops: { label: '3,000 IOPS', cost: '$30.00' },
throughput: { label: '125 MB/s', cost: '$25.31' },
} as const
describe('ReadReplicaPricingDialog', () => {
test('does not show a partial estimate while pricing data loads', () => {
customRender(<ReadReplicaPricingDialog replicaCost={{ ...replicaCost, isLoading: true }} />)
expect(screen.getByText('Estimated additional cost')).toBeInTheDocument()
expect(screen.queryByText(/Estimated additional cost of/)).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: 'View breakdown' })).toBeDisabled()
})
test('shows the estimate once all pricing data is available', () => {
customRender(<ReadReplicaPricingDialog replicaCost={replicaCost} />)
expect(screen.getByText('Estimated additional cost of $85.94/month')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'View breakdown' })).toBeEnabled()
})
test('offers to retry when pricing data cannot be loaded', async () => {
const user = userEvent.setup()
const retry = vi.fn()
customRender(
<ReadReplicaPricingDialog replicaCost={{ ...replicaCost, isError: true, retry }} />
)
expect(screen.getByText('Unable to estimate additional cost')).toBeInTheDocument()
expect(screen.getByText('We couldn’t load the required pricing data.')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Retry' }))
expect(retry).toHaveBeenCalledOnce()
})
test('keeps the error state stable while retrying', () => {
customRender(
<ReadReplicaPricingDialog replicaCost={{ ...replicaCost, isError: true, isLoading: true }} />
)
expect(screen.getByText('Unable to estimate additional cost')).toBeInTheDocument()
expect(screen.queryByText('Estimated additional cost')).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Retry' })).toBeDisabled()
expect(screen.queryByRole('button', { name: 'View breakdown' })).not.toBeInTheDocument()
})
})