mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 18:11:51 +08:00
## 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 -->
100 lines
3.0 KiB
TypeScript
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'))
|
|
})
|
|
})
|