Files
supabase/apps/studio/tests/components/SQLEditor/QuerySourceMenu.test.tsx
Charis 0ed49231b7 refactor(studio): unify CellSource and the SQL editor's QuerySource into QuerySourceBinding (#49072)
Third of a stack. **Stacked on #49070** (which is stacked on #49069) —
review those first. Base retargets automatically as each merges.

Mechanical throughout; no behavior change.

## The problem

Three types described where a query runs, and no two agreed:

| | shape |
|---|---|
| `CellSource` (registry) | `{ id, type, parameters: { … } }` — `id` and
`type` always held the same literal |
| `QuerySource` (SQL editor) | `{ type: 'database' } \| { type: 'logs',
dateRange }` |
| notebook cells | flat per-backend fields, neither of the above |

Anything crossing between them needed a translation that dropped fields
on the way — which is how a notebook cell's replica selection had
nowhere to go.

## What changed

One `QuerySourceBinding`: a backend `_tag` with that backend's
parameters spread flat beside it, borrowed from the wire schema (#49069)
so the binding and the persisted cell agree by construction.

- **`QuerySource` is deleted.** `useRunSource` returns the shared
binding, so `runSource.type`/`dateRange` become `_tag`/`time_range`
across the SQL editor — that is most of the file count here.
- **`getQuerySourceBinding`** projects a notebook cell onto a binding;
**`toQuerySourceBinding`** does the same for any backend-tagged carrier.
Both overloaded so an already-narrowed caller gets the matching binding
back rather than the union, which keeps the result spreadable without
re-narrowing.
- **`ExplorerQuerySourceMenu`** drops its inline copy of the
custom-range and upgrade-prompt logic in favor of `useLogsCustomRange`,
which the SQL editor menu already used.

The registry keeps only what is genuinely runtime: endpoints, labels,
icons, availability, defaults. What a query *is* stays in the wire
schema.

## Verification

Typecheck, Prettier, and the lint ratchet clean. 405 tests pass across
the notebook schema, query sources, the logs components, the SQL editor,
and the Explorer surfaces.

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

* **Improvements**
* Updated query source handling across Explorer and SQL Editor for a
more consistent selection experience.
* Database and log sources now preserve identifiers and time ranges more
reliably when switching or editing queries.
* Source menus, labels, icons, validation, and query execution now
reflect the selected source more accurately.

* **Bug Fixes**
* Invalid or outdated saved source settings now safely fall back to a
database source.
* Improved log-source detection and time-range handling throughout query
editing and execution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-08-14 15:09:29 +07:00

88 lines
3.2 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 { mockAnimationsApi } from 'jsdom-testing-mocks'
import { beforeEach, describe, expect, it } from 'vitest'
import { QuerySourceMenu } from '@/components/interfaces/SQLEditor/UtilityPanel/QuerySourceMenu/QuerySourceMenu'
import { DEFAULT_LOG_TIME_RANGE } from '@/data/query-sources/query-source-registry'
import { customRender } from '@/tests/lib/custom-render'
import { addAPIMock } from '@/tests/lib/msw'
// QuerySourceMenu renders a Radix dropdown (+ nested dialog), both of which use Web Animations.
mockAnimationsApi()
beforeEach(() => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: {
id: 1,
ref: 'default',
organization_id: 1,
name: 'Test Project',
status: 'ACTIVE_HEALTHY',
cloud_provider: 'AWS',
region: 'us-east-1',
db_host: 'db.default.supabase.co',
restUrl: 'https://default.supabase.co/rest/v1/',
inserted_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
subscription_id: 'sub_123',
is_branch_enabled: false,
is_physical_backups_enabled: false,
high_availability: false,
integration_source: null,
connectionString: 'postgresql://postgres@localhost:5432/postgres',
is_hibernating: false,
},
})
})
describe('QuerySourceMenu', () => {
it('hides logs when creating logs queries is unavailable', async () => {
customRender(
<QuerySourceMenu
id="database-snippet"
runSource={{ _tag: 'database' }}
canCreateLogsSnippet={false}
/>
)
await userEvent.click(screen.getByRole('button', { name: 'Query source: Database' }))
expect(screen.queryByText('Logs')).not.toBeInTheDocument()
})
it('keeps the dropdown open across a source switch, so the new sources controls appear without reopening it', async () => {
// Selecting a source doesn't mutate `runSource` in place — it navigates to a
// fresh tab, and the parent re-renders this component with the new source once
// the route lands. Rerendering with the switched-to prop below stands in for
// that navigation, so the test observes exactly what the user does: does the
// dropdown have to be reopened to see the newly-available controls?
const { rerender } = customRender(
<QuerySourceMenu id="new-snippet" runSource={{ _tag: 'database' }} canCreateLogsSnippet />
)
await userEvent.click(screen.getByRole('button', { name: 'Query source: Database' }))
expect(await screen.findByText('Run as')).toBeInTheDocument()
expect(screen.queryByText('Time range')).not.toBeInTheDocument()
await userEvent.click(screen.getByText('Logs'))
rerender(
<QuerySourceMenu
id="new-snippet"
runSource={{ _tag: 'logs', time_range: DEFAULT_LOG_TIME_RANGE }}
canCreateLogsSnippet
/>
)
// The dropdown never closed, so the logs-only "Time range" control is visible
// immediately, and the database-only controls are gone — without the user
// having to reopen the menu.
expect(screen.getByText('Time range')).toBeInTheDocument()
expect(screen.queryByText('Run as')).not.toBeInTheDocument()
})
})