diff --git a/packages/compass-data-modeling/src/components/new-diagram-form.spec.tsx b/packages/compass-data-modeling/src/components/new-diagram-form.spec.tsx
new file mode 100644
index 00000000000..571c377db0c
--- /dev/null
+++ b/packages/compass-data-modeling/src/components/new-diagram-form.spec.tsx
@@ -0,0 +1,431 @@
+import React from 'react';
+import { expect } from 'chai';
+import {
+ screen,
+ userEvent,
+ waitFor,
+ within,
+} from '@mongodb-js/testing-library-compass';
+import NewDiagramForm from './new-diagram-form';
+import { changeName, createNewDiagram } from '../store/generate-diagram-wizard';
+import { renderWithStore } from '../../test/setup-store';
+import type { DataModelingStore } from '../../test/setup-store';
+
+describe('NewDiagramForm', function () {
+ context('enter-name step', function () {
+ let store: DataModelingStore;
+ let modal: HTMLElement;
+
+ beforeEach(() => {
+ const { store: setupStore } = renderWithStore();
+ 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', function () {
+ const { store } = renderWithStore(, {
+ connections: [],
+ });
+ 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('shows list of connections and allows user to select one', async function () {
+ const { store } = renderWithStore();
+
+ {
+ // 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(store.getState().generateDiagramWizard.step).to.equal(
+ 'SELECT_DATABASE'
+ );
+ });
+
+ it('shows error if it fails to connect', async function () {
+ const { store } = renderWithStore(, {
+ services: {
+ connections: {
+ connect() {
+ throw new Error('Can not connect');
+ },
+ getConnectionById(id: string) {
+ return {
+ info: { id },
+ };
+ },
+ } as any,
+ },
+ });
+
+ {
+ // 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'));
+ userEvent.click(screen.getByText('Conn2'));
+ userEvent.click(
+ screen.getByRole('button', {
+ name: /next/i,
+ })
+ );
+
+ await waitFor(() => {
+ // As it fails to connect, we are closing the modal and the toast is shown
+ // to the user with an error (which is outside of this component).
+ expect(store.getState().generateDiagramWizard.inProgress).to.be.false;
+ });
+ });
+ });
+
+ context('select-database step', function () {
+ it('shows list of databases and allows user to select one', async function () {
+ const { store } = renderWithStore();
+
+ {
+ // 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'
+ );
+ });
+
+ it('shows error if it fails to fetch list of databases', async function () {
+ const { store } = renderWithStore(, {
+ services: {
+ connections: {
+ connect() {
+ return Promise.resolve();
+ },
+ getConnectionById(id: string) {
+ return {
+ info: { id },
+ };
+ },
+ getDataServiceForConnection() {
+ return {
+ listDatabases() {
+ throw new Error('Can not list databases');
+ },
+ };
+ },
+ } as any,
+ },
+ });
+
+ {
+ // 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'));
+ userEvent.click(screen.getByText('Conn2'));
+ userEvent.click(
+ screen.getByRole('button', {
+ name: /next/i,
+ })
+ );
+
+ await waitFor(() => {
+ expect(store.getState().generateDiagramWizard.step).to.equal(
+ 'SELECT_DATABASE'
+ );
+ });
+
+ expect(screen.getByText(/select database/i)).to.exist;
+ expect(store.getState().generateDiagramWizard.error?.message).to.equal(
+ 'Can not list databases'
+ );
+ const alert = screen.getByRole('alert');
+ expect(alert.textContent).to.contain('Can not list databases');
+ });
+ });
+
+ context('select-collections step', function () {
+ it('shows list of collections', async function () {
+ const { store } = renderWithStore();
+
+ {
+ // 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;
+ });
+ });
+
+ it('shows error if it fails to fetch list of collections', async function () {
+ const { store } = renderWithStore(, {
+ services: {
+ connections: {
+ connect() {
+ return Promise.resolve();
+ },
+ getConnectionById(id: string) {
+ return {
+ info: { id },
+ };
+ },
+ getDataServiceForConnection() {
+ return {
+ listDatabases() {
+ return [
+ {
+ _id: 'sample_airbnb',
+ name: 'sample_airbnb',
+ },
+ ];
+ },
+ listCollections() {
+ throw new Error('Can not list collections');
+ },
+ };
+ },
+ } as any,
+ },
+ });
+
+ {
+ // Navigate to connections step
+ store.dispatch(createNewDiagram());
+ store.dispatch(changeName('diagram1'));
+ userEvent.click(
+ screen.getByRole('button', {
+ name: /next/i,
+ })
+ );
+ }
+
+ {
+ // Navigate to databases list
+ 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 collections
+ userEvent.click(screen.getByTestId('new-diagram-database-selector'));
+ userEvent.click(screen.getByText('sample_airbnb'));
+ userEvent.click(
+ screen.getByRole('button', {
+ name: /next/i,
+ })
+ );
+ // When it fails to load collections, we are back at SELECT_DATABASE
+ await waitFor(() => {
+ expect(store.getState().generateDiagramWizard.step).to.equal(
+ 'SELECT_DATABASE'
+ );
+ });
+ }
+
+ expect(screen.getByText(/select database/i)).to.exist;
+ expect(store.getState().generateDiagramWizard.error?.message).to.equal(
+ 'Can not list collections'
+ );
+ const alert = screen.getByRole('alert');
+ expect(alert.textContent).to.contain('Can not list collections');
+ });
+ });
+});
diff --git a/packages/compass-data-modeling/src/components/new-diagram-form.tsx b/packages/compass-data-modeling/src/components/new-diagram-form.tsx
index b4d90061433..bac68927711 100644
--- a/packages/compass-data-modeling/src/components/new-diagram-form.tsx
+++ b/packages/compass-data-modeling/src/components/new-diagram-form.tsx
@@ -21,6 +21,7 @@ import {
Banner,
Button,
css,
+ ErrorSummary,
FormFieldContainer,
Modal,
ModalBody,
@@ -29,9 +30,14 @@ import {
Option,
Select,
SelectTable,
+ spacing,
TextInput,
} from '@mongodb-js/compass-components';
+const footerStyles = css({
+ gap: spacing[200],
+});
+
const FormStepContainer: React.FunctionComponent<{
title: string;
description?: string;
@@ -56,7 +62,7 @@ const FormStepContainer: React.FunctionComponent<{
<>
{children}
-
+