InstallIntegrationSheet.test.tsx129 lines · main
1import { safeSql } from '@supabase/pg-meta'
2import { fireEvent, screen, waitFor } from '@testing-library/dom'
3import userEvent from '@testing-library/user-event'
4import { mockAnimationsApi } from 'jsdom-testing-mocks'
5import { beforeEach, describe, expect, it, vi } from 'vitest'
6
7import { IntegrationDefinition } from '../../Landing/Integrations.constants'
8import { InstallIntegrationSheet } from './InstallIntegrationSheet/InstallIntegrationSheet'
9import { customRender } from '@/tests/lib/custom-render'
10import { routerMock } from '@/tests/lib/route-mock'
11
12mockAnimationsApi()
13
14vi.mock('@/hooks/misc/useSelectedProject', () => ({
15 useSelectedProjectQuery: () => ({
16 data: { ref: 'default', connectionString: 'postgres://localhost' },
17 }),
18}))
19
20vi.mock('@/hooks/useProtectedSchemas', () => ({
21 useProtectedSchemas: () => ({ data: [] }),
22}))
23
24const mockExtensions = vi.fn()
25vi.mock('@/data/database-extensions/database-extensions-query', () => ({
26 useDatabaseExtensionsQuery: () => ({ data: mockExtensions(), isSuccess: true }),
27}))
28
29vi.mock('@/data/database/schemas-query', () => ({
30 useSchemasQuery: () => ({ data: [{ id: 1, name: 'public' }] }),
31}))
32
33const mockExecuteSql = vi.fn()
34vi.mock('@/data/sql/execute-sql-mutation', () => ({
35 useExecuteSqlMutation: () => ({ mutateAsync: mockExecuteSql }),
36}))
37
38vi.mock('@/data/database-extensions/database-extension-enable-mutation', () => ({
39 useDatabaseExtensionEnableMutation: () => ({ mutateAsync: vi.fn() }),
40}))
41
42vi.mock('@/components/interfaces/Database/Extensions/Extensions.constants', () => ({
43 extensionsWithRecommendedSchemas: {},
44}))
45
46vi.mock('./IntegrationOverviewTabV2.utils', () => ({
47 getEnableExtensionsSQL: () => safeSql`CREATE EXTENSION IF NOT EXISTS pg_net;`,
48 getExtensionDefaultSchema: () => 'extensions',
49}))
50
51const createIntegration = (overrides: Partial<IntegrationDefinition> = {}): IntegrationDefinition =>
52 ({
53 id: 'test-integration',
54 type: 'postgres_extension',
55 name: 'Test Integration',
56 requiredExtensions: ['pg_net'],
57 icon: () => null,
58 description: 'Test description',
59 docsUrl: null,
60 author: { name: 'Test', websiteUrl: 'https://test.com' },
61 navigate: () => null,
62 ...overrides,
63 }) as unknown as any
64
65const getInstallButton = () => {
66 const buttons = screen.getAllByRole('button', { name: 'Install integration' })
67 return buttons[buttons.length - 1]
68}
69
70describe('InstallIntegrationSheet', () => {
71 beforeEach(() => {
72 routerMock.setCurrentUrl('/project/default/integrations/test-integration/overview')
73 mockExecuteSql.mockReset()
74 })
75
76 it('install button is disabled when extensions are missing even if installationCommand exists', async () => {
77 mockExtensions.mockReturnValue([])
78
79 customRender(
80 <InstallIntegrationSheet
81 integration={createIntegration({
82 installationCommand: vi.fn().mockResolvedValue(undefined),
83 })}
84 />
85 )
86
87 await userEvent.click(screen.getByRole('button', { name: 'Install integration' }))
88 expect(getInstallButton()).toBeDisabled()
89 })
90
91 it('install button is disabled when extensions are missing and no installationCommand', async () => {
92 mockExtensions.mockReturnValue([])
93
94 customRender(
95 <InstallIntegrationSheet
96 integration={createIntegration({ installationCommand: undefined })}
97 />
98 )
99
100 await userEvent.click(screen.getByRole('button', { name: 'Install integration' }))
101 expect(getInstallButton()).toBeDisabled()
102 })
103
104 it('uses installationCommand instead of SQL when provided', async () => {
105 mockExtensions.mockReturnValue([
106 { name: 'pg_net', installed_version: null, default_version: '0.6.0' },
107 ])
108
109 const mockCommand = vi.fn().mockResolvedValue(undefined)
110 customRender(
111 <InstallIntegrationSheet
112 integration={createIntegration({ installationCommand: mockCommand })}
113 />
114 )
115
116 await userEvent.click(screen.getByRole('button', { name: 'Install integration' }))
117
118 // SheetContent renders via a Radix portal, placing the submit button outside
119 // the <form> in the DOM. jsdom doesn't support the HTML `form` attribute on
120 // buttons, so we submit the form directly instead of clicking the button.
121 const form = document.getElementById('installation-settings')!
122 fireEvent.submit(form)
123
124 await waitFor(() => {
125 expect(mockCommand).toHaveBeenCalledWith(expect.objectContaining({ ref: 'default' }))
126 })
127 expect(mockExecuteSql).not.toHaveBeenCalled()
128 })
129})