diff --git a/packages/compass-data-modeling/src/components/diagram-editor.tsx b/packages/compass-data-modeling/src/components/diagram-editor.tsx index 00a05969f28..a0a60db4a59 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor.tsx @@ -159,8 +159,11 @@ const DiagramEditor: React.FunctionComponent<{ if (step === 'EDITING') { content = ( -
-
+
+
-
+
{ onApplyClick(JSON.parse(applyInput)); }} + data-testid="apply-button" disabled={!isEditValid} > Apply 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 20f8da45810..b4d90061433 100644 --- a/packages/compass-data-modeling/src/components/new-diagram-form.tsx +++ b/packages/compass-data-modeling/src/components/new-diagram-form.tsx @@ -61,6 +61,7 @@ const FormStepContainer: React.FunctionComponent<{ onClick={onNextClick} disabled={isNextDisabled} isLoading={isLoading} + data-testid="new-diagram-confirm-button" variant="primary" > {nextLabel} @@ -202,6 +203,7 @@ const NewDiagramForm: React.FunctionComponent = ({ { onNameChange(e.currentTarget.value); }} @@ -214,6 +216,7 @@ const NewDiagramForm: React.FunctionComponent = ({ {databases.map((db) => { @@ -259,6 +263,7 @@ const NewDiagramForm: React.FunctionComponent = ({ return { id: collName, selected: selectedCollections.includes(collName), + 'data-testid': `new-diagram-collection-checkbox-${collName}`, }; })} columns={[['id', 'Collection Name']]} @@ -294,6 +299,7 @@ const NewDiagramForm: React.FunctionComponent = ({ return ( { if (!open) { onCancel(); diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx index 27c261ef262..45d471f1aac 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx @@ -45,6 +45,8 @@ const SavedDiagramsList: React.FunctionComponent<{ onClick={() => { onOpenDiagramClick(diagram); }} + data-testid="saved-diagram-card" + data-diagram-name={diagram.name} > {diagram.name} } callToAction={ - } @@ -98,6 +104,7 @@ const SavedDiagramsList: React.FunctionComponent<{ onClick={onCreateDiagramClick} variant="primary" size="xsmall" + data-testid="create-diagram-button" > Create diagram diff --git a/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts b/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts index 29b49f5694d..068d24e7dd8 100644 --- a/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts +++ b/packages/compass-e2e-tests/helpers/commands/workspace-tabs.ts @@ -1,3 +1,4 @@ +import { Key } from 'webdriverio'; import type { CompassBrowser } from '../compass-browser'; import * as Selectors from '../selectors'; import type { WorkspaceTabSelectorOptions } from '../selectors'; @@ -11,6 +12,13 @@ export async function navigateToMyQueries(browser: CompassBrowser) { .waitForDisplayed(); } +export async function navigateToDataModeling(browser: CompassBrowser) { + await browser.clickVisible(Selectors.SidebarDataModelingTab); + await browser + .$(Selectors.workspaceTab({ type: 'Data Modeling', active: true })) + .waitForDisplayed(); +} + async function closeTab( browser: CompassBrowser, selectorOptions: WorkspaceTabSelectorOptions, @@ -87,3 +95,15 @@ export async function closeWorkspaceTab( ): Promise { await closeTab(browser, selectorOptions, true); } + +export async function openNewTab(browser: CompassBrowser): Promise { + const countTabs = async () => { + return await browser.$$(Selectors.workspaceTab()).length; + }; + const tabsBefore = await countTabs(); + await browser.keys([Key.Ctrl, 't']); + await browser.waitUntil(async () => { + const tabsAfter = await countTabs(); + return tabsAfter === tabsBefore + 1; + }); +} diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index 92d2af1a24a..f2ec0640933 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1424,3 +1424,27 @@ export const AutoUpdateDownloadLink = '[data-testid="auto-update-download-link"]'; export const AutoUpdateReleaseNotesLink = '[data-testid="auto-update-release-notes-link"]'; + +// Data Modeling +export const SidebarDataModelingTab = `${Sidebar} [aria-label="Data Modeling"]`; +export const CreateNewDataModelButton = '[data-testid="create-diagram-button"]'; +export const CreateDataModelModal = '[data-testid="new-diagram-modal"]'; +export const CreateDataModelConfirmButton = `${CreateDataModelModal} [data-testid="new-diagram-confirm-button"]`; +export const CreateDataModelNameInput = `${CreateDataModelModal} [data-testid="new-diagram-name-input"]`; +export const CreateDataModelConnectionSelector = `${CreateDataModelModal} [data-testid="new-diagram-connection-selector"]`; +export const CreateDataModelDatabaseSelector = `${CreateDataModelModal} [data-testid="new-diagram-database-selector"]`; +export const CreateDataModelCollectionCheckbox = ( + collectionName: string +): string => + `${CreateDataModelModal} [data-testid="new-diagram-collection-checkbox-${collectionName}"]`; +export const DataModelEditor = '[data-testid="diagram-editor-container"]'; +export const DataModelPreview = `${DataModelEditor} [data-testid="model-preview"]`; +export const DataModelApplyEditor = `${DataModelEditor} [data-testid="apply-editor"]`; +export const DataModelEditorApplyButton = `${DataModelApplyEditor} [data-testid="apply-button"]`; +export const DataModelUndoButton = 'button[aria-label="Undo"]'; +export const DataModelRedoButton = 'button[aria-label="Redo"]'; +export const DataModelsListItem = (diagramName: string) => + `[data-testid="saved-diagram-card"][data-diagram-name="${diagramName}"]`; +export const DataModelsListItemActions = (diagramName: string) => + `${DataModelsListItem(diagramName)} [aria-label="Show actions"]`; +export const DataModelsListItemDeleteButton = `[data-action="delete"]`; diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts new file mode 100644 index 00000000000..3b4947d6329 --- /dev/null +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -0,0 +1,156 @@ +import { expect } from 'chai'; +import type { CompassBrowser } from '../helpers/compass-browser'; +import { + init, + cleanup, + screenshotIfFailed, + skipForWeb, + DEFAULT_CONNECTION_NAME_1, +} from '../helpers/compass'; +import type { Compass } from '../helpers/compass'; +import * as Selectors from '../helpers/selectors'; +import { + createNestedDocumentsCollection, + createNumbersCollection, +} from '../helpers/insert-data'; + +describe('Data Modeling tab', function () { + let compass: Compass; + let browser: CompassBrowser; + + before(async function () { + skipForWeb(this, 'data modeling not yet available in compass-web'); + + compass = await init(this.test?.fullTitle()); + browser = compass.browser; + await browser.setFeature('enableDataModeling', true); + await browser.setupDefaultConnections(); + }); + + beforeEach(async function () { + await createNestedDocumentsCollection('testCollection1'); + await createNumbersCollection('testCollection2'); + await browser.disconnectAll(); + await browser.connectToDefaults(); + }); + + after(async function () { + if (compass) { + await cleanup(compass); + } + }); + + afterEach(async function () { + await screenshotIfFailed(compass, this.currentTest); + }); + + it('creates a new data model using an existing connection', async function () { + await browser.navigateToDataModeling(); + + // Click on create new data model button + await browser.clickVisible(Selectors.CreateNewDataModelButton); + + // Fill in model details + const dataModelName = 'Test Data Model'; + await browser.setValueVisible( + Selectors.CreateDataModelNameInput, + dataModelName + ); + await browser.clickVisible(Selectors.CreateDataModelConfirmButton); + + // Select existing connection + await browser.selectOption( + Selectors.CreateDataModelConnectionSelector, + DEFAULT_CONNECTION_NAME_1 + ); + await browser.clickVisible(Selectors.CreateDataModelConfirmButton); + + // Select a database + await browser.selectOption( + Selectors.CreateDataModelDatabaseSelector, + 'test' + ); + await browser.clickVisible(Selectors.CreateDataModelConfirmButton); + + // TODO: Confirm all collections are selected by default (COMPASS-9309) + // Note: We'll need to change the UI, right now the labels are disconnected from the checkboxes + await browser.clickVisible(Selectors.CreateDataModelConfirmButton); + + // Wait for the diagram editor to load + const dataModelEditor = browser.$(Selectors.DataModelEditor); + await dataModelEditor.waitForDisplayed(); + + // Verify that the diagram is displayed and contains both collections + const text = await browser.getCodemirrorEditorText( + Selectors.DataModelPreview + ); + expect(text).to.include('"test.testCollection1": '); + expect(text).to.include('"test.testCollection2": '); + + // Apply change to the model + const newModel = { + type: 'SetModel', + model: { schema: { coll1: {}, coll2: {} } }, + }; + const newPreview = JSON.stringify(newModel.model, null, 2); + await browser.setCodemirrorEditorValue( + Selectors.DataModelApplyEditor, + JSON.stringify(newModel) + ); + await browser.clickVisible(Selectors.DataModelEditorApplyButton); + + // Verify that the model is updated + const updatedText = await browser.getCodemirrorEditorText( + Selectors.DataModelPreview + ); + expect(updatedText).to.equal(newPreview); + + // Undo the change + await browser.clickVisible(Selectors.DataModelUndoButton); + await browser.waitUntil(async () => { + const textAfterUndo = await browser.getCodemirrorEditorText( + Selectors.DataModelPreview + ); + return ( + textAfterUndo.includes('"test.testCollection1": ') && + textAfterUndo.includes('"test.testCollection2": ') + ); + }); + + // Redo the change + await browser.waitForAriaDisabled(Selectors.DataModelRedoButton, false); + await browser.clickVisible(Selectors.DataModelRedoButton); + await browser.waitUntil(async () => { + const redoneText = await browser.getCodemirrorEditorText( + Selectors.DataModelPreview + ); + return redoneText === newPreview; + }); + + // Open a new tab + await browser.openNewTab(); + + // Open the saved diagram + await browser.clickVisible(Selectors.DataModelsListItem(dataModelName)); + await browser.$(Selectors.DataModelEditor).waitForDisplayed(); + + // Verify that the diagram has the latest changes + const savedText = await browser.getCodemirrorEditorText( + Selectors.DataModelPreview + ); + expect(savedText).to.equal(newPreview); + + // Open a new tab + await browser.openNewTab(); + + // Delete the saved diagram + await browser.clickVisible( + Selectors.DataModelsListItemActions(dataModelName) + ); + await browser.clickVisible(Selectors.DataModelsListItemDeleteButton); + await browser.clickVisible(Selectors.confirmationModalConfirmButton()); + await browser + .$(Selectors.DataModelsListItem(dataModelName)) + .waitForDisplayed({ reverse: true }); + }); +});