import { aColumn, anIndex, aTable } from '@liam-hq/schema' import { render, screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ReactFlowProvider } from '@xyflow/react' import { Command } from 'cmdk' import { NuqsTestingAdapter } from 'nuqs/adapters/testing' import type { ReactNode } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { SchemaProvider, type SchemaProviderValue, UserEditingProvider, } from '../../../../../../stores' import * as UseTableSelection from '../../../../hooks' import { CommandPaletteProvider } from '../CommandPaletteProvider' import * as UseCommandPalette from '../CommandPaletteProvider/hooks' import type { CommandPaletteSuggestion } from '../types' import { TableDetailOptions } from './TableDetailOptions' beforeEach(() => { window.location.hash = '' }) afterEach(() => { vi.clearAllMocks() }) const mockSetCommandPaletteDialogOpen = vi.fn() const mockSelectTable = vi.fn() const mockWindowOpen = vi.fn() const originalUseCommandPaletteOrThrow = UseCommandPalette.useCommandPaletteOrThrow vi.spyOn(UseCommandPalette, 'useCommandPaletteOrThrow').mockImplementation( () => { const original = originalUseCommandPaletteOrThrow() return { ...original, setOpen: mockSetCommandPaletteDialogOpen, } }, ) const originalUseTableSelection = UseTableSelection.useTableSelection vi.spyOn(UseTableSelection, 'useTableSelection').mockImplementation(() => { const original = originalUseTableSelection() return { ...original, selectTable: mockSelectTable, } }) vi.spyOn(window, 'open').mockImplementation(mockWindowOpen) const schema: SchemaProviderValue = { current: { tables: { users: aTable({ name: 'users', columns: { id: aColumn({ name: 'id' }), created_at: aColumn({ name: 'created_at', type: 'timestamp' }), }, indexes: { users_on_status_id: anIndex({ name: 'users_on_status_id' }), users_on_created_at: anIndex({ name: 'users_on_created_at' }), }, }), posts: aTable({ name: 'posts' }), }, enums: {}, extensions: {}, }, } const wrapper = ({ children }: { children: ReactNode }) => ( {children} ) it('displays selected table option and its columns', () => { render(, { wrapper, }) // table option const userTableOption = screen.getByRole('option', { name: 'users' }) expect(within(userTableOption).getByRole('link')).toHaveAttribute( 'href', '?active=users', ) // column options const idColumnOption = screen.getByRole('option', { name: 'id' }) expect(within(idColumnOption).getByRole('link')).toHaveAttribute( 'href', '?active=users#users__columns__id', ) const createdAtColumnOption = screen.getByRole('option', { name: 'created_at', }) expect(within(createdAtColumnOption).getByRole('link')).toHaveAttribute( 'href', '?active=users#users__columns__created_at', ) // index options const usersOnStatusIdIndexColumnOption = screen.getByRole('option', { name: 'users_on_status_id', }) expect( within(usersOnStatusIdIndexColumnOption).getByRole('link'), ).toHaveAttribute('href', '?active=users#users__indexes__users_on_status_id') const usersOnCreatedAtIndexColumnOption = screen.getByRole('option', { name: 'users_on_created_at', }) expect( within(usersOnCreatedAtIndexColumnOption).getByRole('link'), ).toHaveAttribute('href', '?active=users#users__indexes__users_on_created_at') // other tables are not displayed expect(screen.queryByRole('link', { name: 'posts' })).not.toBeInTheDocument() }) describe('mouse interactions', () => { describe.each<{ kind: string; elementName: string; hash: string }>([ { kind: 'table', elementName: 'users', hash: '' }, { kind: 'column', elementName: 'created_at', hash: '#users__columns__created_at', }, { kind: 'index', elementName: 'users_on_status_id', hash: '#users__indexes__users_on_status_id', }, ])('$kind option', ({ elementName, hash }) => { it('moves to clicked option in ERD and closes the dialog', async () => { const user = userEvent.setup() render(, { wrapper, }) await user.click(screen.getByRole('link', { name: elementName })) expect(mockSelectTable).toHaveBeenCalled() expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false) expect(window.location.hash).toBe(hash) }) it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => { const user = userEvent.setup() render(, { wrapper, }) await user.keyboard('{Meta>}') await user.click(screen.getByRole('link', { name: elementName })) await user.keyboard('{/Meta}') expect(mockSelectTable).not.toHaveBeenCalled() expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled() // FIXME: jsdom doesn't implement behavior of ⌘ + click to open a link in a new tab, but it changes the URL of the current window // So, the following assertion should pass but doesn't // expect(window.location.hash).toBe('') }) }) }) describe('keyboard interactions', () => { describe.each<{ kind: string suggestion: CommandPaletteSuggestion hash: string }>([ { kind: 'table', suggestion: { type: 'table', name: 'users' }, hash: '', }, { kind: 'column', suggestion: { type: 'column', tableName: 'users', columnName: 'created_at', }, hash: '#users__columns__created_at', }, { kind: 'index', suggestion: { type: 'index', tableName: 'users', indexName: 'users_on_status_id', }, hash: '#users__indexes__users_on_status_id', }, ])('$kind option', ({ suggestion, hash }) => { it('moves to suggested option in ERD and closes the dialog on Enter', async () => { const user = userEvent.setup() render(, { wrapper, }) await user.keyboard('{Enter}') expect(mockSelectTable).toHaveBeenCalledWith({ displayArea: 'main', tableId: 'users', }) expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false) expect(window.location.hash).toBe(hash) // other functions are not called expect(mockWindowOpen).not.toHaveBeenCalled() }) it('opens suggested element in another tab on ⌘Enter', async () => { const user = userEvent.setup() render(, { wrapper, }) await user.keyboard('{Meta>}{Enter}{/Meta}') expect(mockWindowOpen).toHaveBeenCalledWith(`?active=users${hash}`) // other functions are not called expect(mockSelectTable).not.toHaveBeenCalled() expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled() }) }) it('does nothing on Enter when suggestion is neither table, column nor index', async () => { const user = userEvent.setup() render( , { wrapper }, ) await user.keyboard('{Meta>}{Enter}{/Meta}') expect(mockWindowOpen).not.toHaveBeenCalled() expect(mockSelectTable).not.toHaveBeenCalled() expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled() }) })