UNPKG

datajunction-ui

Version:
494 lines (408 loc) 14.7 kB
import React from 'react'; import { render, fireEvent, waitFor, screen, act, } from '@testing-library/react'; import { useFormikContext } from 'formik'; import ManageDimensionLinksDialog from '../ManageDimensionLinksDialog'; import DJClientContext from '../../../providers/djclient'; // Stub FormikSelect with a plain multi-select so tests can drive the field // without wrestling react-select's pointer-event quirks. vi.mock('../../AddEditNodePage/FormikSelect', () => ({ FormikSelect: ({ selectOptions, formikFieldName }) => { const { values, setFieldValue } = useFormikContext(); return ( <select data-testid={`mock-select-${formikFieldName}`} multiple value={values[formikFieldName] || []} onChange={e => setFieldValue( formikFieldName, Array.from(e.target.selectedOptions, opt => opt.value), ) } > {selectOptions.map(opt => ( <option key={opt.value} value={opt.value}> {opt.label} </option> ))} </select> ); }, })); // Mock window.location.reload delete window.location; window.location = { reload: vi.fn() }; // Mock window.confirm window.confirm = vi.fn(() => true); const mockDjClient = { DataJunctionAPI: { linkDimension: vi.fn(), unlinkDimension: vi.fn(), addReferenceDimensionLink: vi.fn(), removeReferenceDimensionLink: vi.fn(), }, node: vi.fn().mockResolvedValue({ name: 'default.test_dimension', columns: [{ name: 'id', type: 'int' }], }), }; describe('<ManageDimensionLinksDialog />', () => { beforeEach(() => { vi.clearAllMocks(); window.alert = vi.fn(); window.confirm.mockReturnValue(true); }); const defaultProps = { column: { name: 'test_column', type: 'int' }, node: { name: 'default.node1' }, dimensions: [ { value: 'default.dim1', label: 'dim1 (5 links)' }, { value: 'default.dim2', label: 'dim2 (3 links)' }, ], fkLinks: [], onSubmit: vi.fn(), }; it('renders the toggle button', () => { const { getByLabelText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); expect(getByLabelText('ManageDimensionLinksToggle')).toBeInTheDocument(); }); it('opens modal when toggle button is clicked', async () => { const { getByLabelText, getByRole } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect( getByRole('dialog', { name: 'ManageDimensionLinksDialog' }), ).toBeInTheDocument(); }); }); it('displays column name and type in header', async () => { const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog column={{ name: 'test_column', type: 'varchar' }} node={defaultProps.node} dimensions={defaultProps.dimensions} fkLinks={[]} onSubmit={defaultProps.onSubmit} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByText('test_column')).toBeInTheDocument(); expect(getByText('varchar')).toBeInTheDocument(); }); }); it('shows two tabs: FK Links and Reference Links', async () => { const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByText('FK Links')).toBeInTheDocument(); expect(getByText('Reference Links')).toBeInTheDocument(); }); }); it('switches to reference links tab when clicked', async () => { const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByText('Reference Links')).toBeInTheDocument(); }); fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Dimension Node *')).toBeInTheDocument(); expect(getByText('Dimension Column *')).toBeInTheDocument(); }); }); it('displays FK links when provided', async () => { const propsWithFkLinks = { ...defaultProps, fkLinks: ['default.dim1', 'default.dim2'], }; const { getByLabelText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...propsWithFkLinks} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { // The FK Links tab should be displayed by default expect(getByLabelText('ManageDimensionLinksToggle')).toBeInTheDocument(); }); }); it('shows FK Links form with select dimensions field', async () => { const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByText('Select Dimensions')).toBeInTheDocument(); expect(getByText('Save')).toBeInTheDocument(); }); }); it('displays reference link form fields', async () => { const { getByLabelText, getByText, getByPlaceholderText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByText('Reference Links')).toBeInTheDocument(); }); // Switch to reference link tab fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Dimension Node *')).toBeInTheDocument(); expect(getByText('Dimension Column *')).toBeInTheDocument(); expect( getByPlaceholderText('e.g., birth_date, registration_date'), ).toBeInTheDocument(); expect(getByText('Add Link')).toBeInTheDocument(); }); }); it('handles removing reference link', async () => { mockDjClient.DataJunctionAPI.removeReferenceDimensionLink.mockResolvedValue( { status: 200, }, ); const propsWithReferenceLink = { ...defaultProps, referenceLink: { dimension: 'default.dim1', dimension_column: 'id', }, }; const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...propsWithReferenceLink} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByText('Reference Links')).toBeInTheDocument(); }); // Switch to reference link tab fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Remove Link')).toBeInTheDocument(); }); // Remove the link const removeButton = getByText('Remove Link'); await act(async () => { fireEvent.click(removeButton); }); await waitFor(() => { expect(window.confirm).toHaveBeenCalledWith( 'Are you sure you want to remove this reference link?', ); expect( mockDjClient.DataJunctionAPI.removeReferenceDimensionLink, ).toHaveBeenCalledWith('default.node1', 'test_column'); expect(window.location.reload).toHaveBeenCalled(); }); }); it('does not remove reference link when user cancels confirm dialog', async () => { window.confirm.mockReturnValueOnce(false); const propsWithReferenceLink = { ...defaultProps, referenceLink: { dimension: 'default.dim1', dimension_column: 'id', }, }; const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...propsWithReferenceLink} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Remove Link')).toBeInTheDocument(); }); const removeButton = getByText('Remove Link'); await act(async () => { fireEvent.click(removeButton); }); expect( mockDjClient.DataJunctionAPI.removeReferenceDimensionLink, ).not.toHaveBeenCalled(); }); it('handles failed reference link removal', async () => { mockDjClient.DataJunctionAPI.removeReferenceDimensionLink.mockResolvedValue( { status: 500, json: { message: 'Server error' }, }, ); const propsWithReferenceLink = { ...defaultProps, referenceLink: { dimension: 'default.dim1', dimension_column: 'id', }, }; const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...propsWithReferenceLink} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Remove Link')).toBeInTheDocument(); }); const removeButton = getByText('Remove Link'); await act(async () => { fireEvent.click(removeButton); }); await waitFor(() => { expect(window.alert).toHaveBeenCalled(); expect(window.location.reload).not.toHaveBeenCalled(); }); }); it('shows Update Link button when reference link exists', async () => { const propsWithReferenceLink = { ...defaultProps, referenceLink: { dimension: 'default.dim1', dimension_column: 'user_id', }, }; const { getByLabelText, getByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...propsWithReferenceLink} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Update Link')).toBeInTheDocument(); expect(getByText('Remove Link')).toBeInTheDocument(); }); }); it('shows validation error when submitting reference link without required fields', async () => { const { getByLabelText, getByText, getAllByText } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); fireEvent.click(getByText('Reference Links')); await waitFor(() => { expect(getByText('Add Link')).toBeInTheDocument(); }); // Try to submit without filling required fields const saveButton = getByText('Add Link'); await act(async () => { fireEvent.click(saveButton); }); await waitFor(() => { const requiredErrors = getAllByText('Required'); expect(requiredErrors.length).toBeGreaterThan(0); }); }); it('shows success message when FK link save succeeds', async () => { mockDjClient.DataJunctionAPI.linkDimension.mockResolvedValue({ status: 201, json: { message: 'ok' }, }); const { getByLabelText, getByText, getByTestId } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByTestId('mock-select-fkDimensions')).toBeInTheDocument(); }); const select = getByTestId('mock-select-fkDimensions'); await act(async () => { fireEvent.change(select, { target: { value: 'default.dim1' } }); }); await act(async () => { fireEvent.click(getByText('Save')); }); await waitFor(() => { expect(getByTestId('success')).toHaveTextContent( 'FK links updated successfully!', ); }); expect(mockDjClient.DataJunctionAPI.linkDimension).toHaveBeenCalledWith( 'default.node1', 'test_column', 'default.dim1', ); }); it('surfaces server error message when FK link save fails', async () => { mockDjClient.DataJunctionAPI.linkDimension.mockResolvedValue({ status: 422, json: { message: 'Dimension default.dim1 has a compound primary key (a, b); a single-column FK link cannot join it correctly.', }, }); const { getByLabelText, getByText, getByTestId, queryByTestId } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByTestId('mock-select-fkDimensions')).toBeInTheDocument(); }); const select = getByTestId('mock-select-fkDimensions'); await act(async () => { fireEvent.change(select, { target: { value: 'default.dim1' } }); }); await act(async () => { fireEvent.click(getByText('Save')); }); await waitFor(() => { expect(getByTestId('failure')).toHaveTextContent('compound primary key'); }); expect(queryByTestId('success')).not.toBeInTheDocument(); }); it('closes modal when clicking backdrop', async () => { const { getByLabelText, getByRole, queryByRole } = render( <DJClientContext.Provider value={mockDjClient}> <ManageDimensionLinksDialog {...defaultProps} /> </DJClientContext.Provider>, ); fireEvent.click(getByLabelText('ManageDimensionLinksToggle')); await waitFor(() => { expect(getByRole('dialog')).toBeInTheDocument(); }); // Click the backdrop const backdrop = getByRole('dialog').parentElement; fireEvent.click(backdrop); await waitFor(() => { expect(queryByRole('dialog')).not.toBeInTheDocument(); }); }); });