Skip to content

feat(data-modeling): display errors when creating a diagram COMPASS-9307 #6892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 6, 2025
Merged
Prev Previous commit
Next Next commit
tests
  • Loading branch information
mabaasit committed May 5, 2025
commit 9a987c4616234abf2d0914ba7fd2de75b5a5688e
Original file line number Diff line number Diff line change
@@ -1,68 +1,257 @@
import React from 'react';
import type { ComponentProps } from 'react';
import { expect } from 'chai';
import { render, screen } from '@mongodb-js/testing-library-compass';
import { NewDiagramForm } from './new-diagram-form';

function noop() {
// no op
}
function renderNewDiagramForm(
props: Partial<ComponentProps<typeof NewDiagramForm>>
) {
return render(
<NewDiagramForm
collections={[]}
databases={[]}
diagramName=""
error={null}
formStep="enter-name"
isLoading={false}
isModalOpen={true}
selectedDatabase={null}
selectedCollections={[]}
selectedConnectionId={null}
onCancel={noop}
onCollectionsSelect={noop}
onCollectionsSelectionConfirm={noop}
onConnectionConfirmSelection={noop}
onConnectionSelect={noop}
onConnectionSelectCancel={noop}
onDatabaseConfirmSelection={noop}
onDatabaseSelect={noop}
onDatabaseSelectCancel={noop}
onNameChange={noop}
onNameConfirm={noop}
onNameConfirmCancel={noop}
{...props}
/>
);
}
import {
screen,
userEvent,
waitFor,
within,
} from '@mongodb-js/testing-library-compass';
import DiagramForm from './new-diagram-form';
import { changeName, createNewDiagram } from '../store/generate-diagram-wizard';
import { renderWithStore } from '../../tests/setup-store';
import type { DataModelingStore } from '../../tests/setup-store';

describe('NewDiagramForm', function () {
context('it renders errors at the step', function () {
it('renders error when selecting a connection', function () {
renderNewDiagramForm({
formStep: 'select-connection',
error: new Error('Can not connect.'),
context('enter-name step', function () {
let store: DataModelingStore;
let modal: HTMLElement;

beforeEach(async () => {
const { store: setupStore } = await renderWithStore(<DiagramForm />);
store = setupStore;
store.dispatch(createNewDiagram());
modal = screen.getByTestId('new-diagram-modal');
expect(modal).to.be.visible;
});

it('allows user to enter name for the model', function () {
userEvent.type(
within(modal).getByTestId('new-diagram-name-input'),
'diagram-1'
);
expect(store.getState().generateDiagramWizard.diagramName).to.equal(
'diagram-1'
);
});

it('keeps next button disabled if diagram name is empty', function () {
userEvent.clear(within(modal).getByTestId('new-diagram-name-input'));
expect(store.getState().generateDiagramWizard.diagramName).to.equal('');
const button = within(modal).getByRole('button', {
name: /next/i,
});
expect(button.getAttribute('aria-disabled')).to.equal('true');
});

it('cancels process when cancel is clicked', function () {
userEvent.click(
within(modal).getByRole('button', {
name: /cancel/i,
})
);
expect(store.getState().generateDiagramWizard.inProgress).to.be.false;
});
});

context('select-connection step', function () {
it('shows warning if there are no connections', async function () {
const { store } = await renderWithStore(<DiagramForm />, {
connections: [],
});
expect(screen.getByText('Can not connect.')).to.exist;
store.dispatch(createNewDiagram());

store.dispatch(changeName('diagram1'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);

const alert = screen.getByRole('alert');
expect(alert.textContent).to.contain(
'You do not have any connections, create a new connection first'
);
});

it('renders error when selecting a database', function () {
renderNewDiagramForm({
formStep: 'select-database',
error: new Error('Can not fetch databases.'),
it('shows list of connections and allows user to select one', async function () {
const { store } = await renderWithStore(<DiagramForm />);

{
// Navigate to connections step
store.dispatch(createNewDiagram());
store.dispatch(changeName('diagram1'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);
}

userEvent.click(screen.getByTestId('new-diagram-connection-selector'));
expect(screen.getByText('Conn1')).to.exist;
expect(screen.getByText('Conn2')).to.exist;

userEvent.click(screen.getByText('Conn2'));
expect(store.getState().generateDiagramWizard.selectedConnectionId).to.eq(
'two'
);

userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);

expect(store.getState().generateDiagramWizard.step).to.equal(
'CONNECTING'
);
await waitFor(() => {
expect(
store.getState().generateDiagramWizard.connectionDatabases
).to.deep.equal(['berlin', 'sample_airbnb']);
});
expect(screen.getByText('Can not fetch databases.')).to.exist;

expect(store.getState().generateDiagramWizard.step).to.equal(
'SELECT_DATABASE'
);
});

it('renders error when selecting collections', function () {
renderNewDiagramForm({
formStep: 'select-collections',
error: new Error('Can not fetch collections.'),
// TODO
it.skip('shows error if it fails to connect');
});

context('select-database step', function () {
it('shows list of databases and allows user to select one', async function () {
const { store } = await renderWithStore(<DiagramForm />);

{
// Navigate to connections step
store.dispatch(createNewDiagram());
store.dispatch(changeName('diagram1'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);
}

{
// Navigate to select db
userEvent.click(screen.getByTestId('new-diagram-connection-selector'));
userEvent.click(screen.getByText('Conn2'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);
await waitFor(() => {
expect(store.getState().generateDiagramWizard.step).to.equal(
'SELECT_DATABASE'
);
});
}

userEvent.click(screen.getByTestId('new-diagram-database-selector'));
expect(screen.getByText('berlin')).to.exist;
expect(screen.getByText('sample_airbnb')).to.exist;

userEvent.click(screen.getByText('sample_airbnb'));
expect(store.getState().generateDiagramWizard.selectedDatabase).to.eq(
'sample_airbnb'
);

userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);

expect(store.getState().generateDiagramWizard.step).to.equal(
'LOADING_COLLECTIONS'
);
await waitFor(() => {
expect(
store.getState().generateDiagramWizard.selectedCollections
).to.deep.equal(['listings', 'listingsAndReviews', 'reviews']);
});

expect(store.getState().generateDiagramWizard.step).to.equal(
'SELECT_COLLECTIONS'
);
});
// TODO
it.skip('shows error if it fails to fetch list of databases');
});

context('select-collections step', function () {
it('shows list of collections', async function () {
const { store } = await renderWithStore(<DiagramForm />);

{
// Navigate to connections step
store.dispatch(createNewDiagram());
store.dispatch(changeName('diagram1'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);
}

{
// Navigate to select db
userEvent.click(screen.getByTestId('new-diagram-connection-selector'));
userEvent.click(screen.getByText('Conn2'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);
await waitFor(() => {
expect(store.getState().generateDiagramWizard.step).to.equal(
'SELECT_DATABASE'
);
});
}

{
// Navigate to select colls
userEvent.click(screen.getByTestId('new-diagram-database-selector'));
userEvent.click(screen.getByText('sample_airbnb'));
userEvent.click(
screen.getByRole('button', {
name: /next/i,
})
);
await waitFor(() => {
expect(store.getState().generateDiagramWizard.step).to.equal(
'SELECT_COLLECTIONS'
);
});
}

expect(screen.getByText('listings')).to.exist;
expect(screen.getByText('listingsAndReviews')).to.exist;
expect(screen.getByText('reviews')).to.exist;

expect(store.getState().generateDiagramWizard.step).to.equal(
'SELECT_COLLECTIONS'
);
expect(
store.getState().generateDiagramWizard.selectedCollections
).to.deep.equal(['listings', 'listingsAndReviews', 'reviews']);

userEvent.click(
screen.getByRole('button', {
name: /generate/i,
})
);

await waitFor(() => {
expect(store.getState().generateDiagramWizard.inProgress).to.be.false;
});
expect(screen.getByText('Can not fetch collections.')).to.exist;
});
// TODO
it.skip('shows error if it fails to fetch list of collections');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ type NewDiagramFormProps = {
onCollectionsSelectionConfirm: () => void;
};

export const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
isModalOpen,
formStep: currentStep,
isLoading,
Expand Down Expand Up @@ -182,7 +182,7 @@ export const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
onConfirmAction: onCollectionsSelectionConfirm,
confirmActionLabel: 'Generate',
isConfirmDisabled:
!selectedCollections || selectCollections.length === 0,
!selectedCollections || selectedCollections.length === 0,
onCancelAction: onDatabaseSelectCancel,
cancelLabel: 'Back',
};
Expand Down Expand Up @@ -211,7 +211,7 @@ export const NewDiagramForm: React.FunctionComponent<NewDiagramFormProps> = ({
<TextInput
label="New data model name"
value={diagramName}
data-testId="new-diagram-name-input"
data-testid="new-diagram-name-input"
onChange={(e) => {
onNameChange(e.currentTarget.value);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,14 @@ export const generateDiagramWizardReducer: Reducer<
step: 'SELECT_DATABASE',
};
}
if (
isAction(action, GenerateDiagramWizardActionTypes.CONFIRM_SELECT_DATABASE)
) {
return {
...state,
step: 'LOADING_COLLECTIONS',
};
}
return state;
};

Expand Down
Loading
Loading