mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Right now our tests for API mocking is using vi.mock and mocking that query or fetch handler. This is not the right approach IMO, 2 years ago @jordienr added MSW with some very powerful helpers. The idea is to move component test that rely on API using MSW within ViteTest. Principles are simple: - Mock API responses - Mount your component that uses API responses - Tests and assert on UI - Added Skill for Clanker This pattern is 100 times better than what we have <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded and strengthened test suites for secrets, org lookup, support flows, OAuth auth, and onboarding; mocks now use contract-backed responses for more realistic coverage. * **Documentation** * Added a comprehensive guide describing a standardized pattern for component tests that mock network requests. * **Chores** * Improved test helpers, typing for API mocks, and test runner configuration for more reliable and maintainable tests. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46439?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <alaister@users.noreply.github.com> Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import { fireEvent, getByText, render as originalRender, screen } from '@testing-library/react'
|
|
import { platformComponents as components } from 'api-types'
|
|
import type React from 'react'
|
|
import { useState } from 'react'
|
|
import { TooltipProvider } from 'ui'
|
|
import { CommandProvider } from 'ui-patterns/CommandMenu'
|
|
|
|
import { ProjectInfoInfinite } from '@/data/projects/projects-infinite-query'
|
|
import type { Organization } from '@/types'
|
|
|
|
type OrganizationResponse = components['schemas']['OrganizationResponse']
|
|
|
|
interface SelectorOptions {
|
|
container?: HTMLElement
|
|
}
|
|
|
|
/**
|
|
* Returns the toggle button given a text matcher
|
|
*
|
|
* Defaults to screen if container option is not provided
|
|
*/
|
|
export const getToggleByText = (
|
|
text: string | RegExp,
|
|
options: SelectorOptions = {}
|
|
): HTMLElement | null => {
|
|
const container = options?.container
|
|
let textNode
|
|
if (container) {
|
|
textNode = getByText(container as HTMLElement, text)
|
|
} else {
|
|
textNode = screen.getByText(text)
|
|
}
|
|
if (textNode && textNode.parentElement) {
|
|
return textNode.parentElement.querySelector('button')
|
|
} else {
|
|
return textNode
|
|
}
|
|
}
|
|
|
|
export const clickDropdown = (elem: HTMLElement) => {
|
|
fireEvent.pointerDown(
|
|
elem,
|
|
new window.PointerEvent('pointerdown', {
|
|
ctrlKey: false,
|
|
button: 0,
|
|
})
|
|
)
|
|
}
|
|
|
|
export const createMockOrganization = (details: Partial<Organization>): Organization => {
|
|
const base: Organization = {
|
|
id: 1,
|
|
name: 'Organization 1',
|
|
slug: 'abcdefghijklmnopqrst',
|
|
plan: { id: 'free', name: 'Free' },
|
|
managed_by: 'supabase',
|
|
is_owner: true,
|
|
billing_email: 'billing@example.com',
|
|
billing_partner: null,
|
|
integration_source: null,
|
|
usage_billing_enabled: false,
|
|
stripe_customer_id: 'stripe-1',
|
|
subscription_id: 'subscription-1',
|
|
organization_requires_mfa: false,
|
|
opt_in_tags: [],
|
|
restriction_status: null,
|
|
restriction_data: null,
|
|
organization_missing_address: false,
|
|
organization_missing_tax_id: false,
|
|
}
|
|
|
|
return Object.assign(base, details)
|
|
}
|
|
|
|
/**
|
|
* Returns the raw OpenAPI `OrganizationResponse` shape — without the
|
|
* frontend-derived fields (`managed_by`, `partner_id`) that
|
|
* `castOrganizationResponseToOrganization` attaches in the query layer.
|
|
*
|
|
* Use this for MSW mocks at the network boundary (e.g. `/platform/organizations`).
|
|
* Use `createMockOrganization` for in-app fixtures that need the cast shape.
|
|
*/
|
|
export const createMockOrganizationResponse = (
|
|
details: Partial<OrganizationResponse> = {}
|
|
): OrganizationResponse => {
|
|
const base: OrganizationResponse = {
|
|
id: 1,
|
|
name: 'Organization 1',
|
|
slug: 'abcdefghijklmnopqrst',
|
|
plan: { id: 'free', name: 'Free' },
|
|
is_owner: true,
|
|
billing_email: 'billing@example.com',
|
|
billing_partner: null,
|
|
integration_source: null,
|
|
usage_billing_enabled: false,
|
|
stripe_customer_id: 'stripe-1',
|
|
subscription_id: 'subscription-1',
|
|
organization_requires_mfa: false,
|
|
opt_in_tags: [],
|
|
restriction_status: null,
|
|
restriction_data: null,
|
|
organization_missing_address: false,
|
|
organization_missing_tax_id: false,
|
|
}
|
|
|
|
return Object.assign(base, details)
|
|
}
|
|
|
|
export const createMockProject = (details: Partial<ProjectInfoInfinite>): ProjectInfoInfinite => {
|
|
const base: ProjectInfoInfinite = {
|
|
id: 1,
|
|
ref: 'abcdefghijklmnopqrst',
|
|
name: 'Project 1',
|
|
status: 'ACTIVE_HEALTHY',
|
|
organization_id: 1,
|
|
cloud_provider: 'AWS',
|
|
region: 'us-east-1',
|
|
inserted_at: new Date().toISOString(),
|
|
subscription_id: 'subscription-1',
|
|
is_branch_enabled: false,
|
|
is_physical_backups_enabled: false,
|
|
organization_slug: 'slug',
|
|
preview_branch_refs: [],
|
|
}
|
|
|
|
return Object.assign(base, details)
|
|
}
|
|
|
|
/**
|
|
* A custom render function for react testing library
|
|
* https://testing-library.com/docs/react-testing-library/setup/#custom-render
|
|
*/
|
|
const ReactQueryTestConfig: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|
const [queryClient] = useState(
|
|
() =>
|
|
new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: false,
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
return (
|
|
<TooltipProvider>
|
|
<QueryClientProvider client={queryClient}>
|
|
<CommandProvider openKey="">{children}</CommandProvider>
|
|
</QueryClientProvider>
|
|
</TooltipProvider>
|
|
)
|
|
}
|
|
type renderParams = Parameters<typeof originalRender>
|
|
export const render = ((ui: renderParams[0], options: renderParams[1]) =>
|
|
originalRender(ui, {
|
|
wrapper: ReactQueryTestConfig,
|
|
...options,
|
|
})) as typeof originalRender
|