From d060850ec0661f0a369e35a2b86aebb1e5827e48 Mon Sep 17 00:00:00 2001 From: Anton Kulyk <kuliks.anton@gmail.com> Date: Tue, 28 Mar 2023 12:24:31 +0100 Subject: [PATCH] Fix notebook editor shows the remove button on the data step (#29575) * Add basic tests to `NotebookStep` * Fix irreversible steps have "remove" button * Use `aria-label` instead of a test ID * Use `IconButtonWrapper` --- ...nested-query-remove-expressions.cy.spec.js | 2 +- .../reproductions/17514-ui-overlay.cy.spec.js | 2 +- .../notebook/NotebookStep/NotebookStep.tsx | 21 ++-- .../NotebookStep/NotebookStep.unit.spec.tsx | 108 ++++++++++++++++++ 4 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.unit.spec.tsx diff --git a/e2e/test/scenarios/custom-column/reproductions/19745-cc-nested-query-remove-expressions.cy.spec.js b/e2e/test/scenarios/custom-column/reproductions/19745-cc-nested-query-remove-expressions.cy.spec.js index b50f98ffd31..ed8ef366a05 100644 --- a/e2e/test/scenarios/custom-column/reproductions/19745-cc-nested-query-remove-expressions.cy.spec.js +++ b/e2e/test/scenarios/custom-column/reproductions/19745-cc-nested-query-remove-expressions.cy.spec.js @@ -97,7 +97,7 @@ function removeExpression(name) { function removeAllExpressions() { getNotebookStep("expression", { stage: 1 }).within(() => { - cy.findByTestId("remove-step").click({ force: true }); + cy.findByLabelText("Remove step").click({ force: true }); }); } diff --git a/e2e/test/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js b/e2e/test/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js index 0ca2f08448f..331663bf241 100644 --- a/e2e/test/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js +++ b/e2e/test/scenarios/question/reproductions/17514-ui-overlay.cy.spec.js @@ -172,7 +172,7 @@ function removeJoinedTable() { cy.findAllByText("Join data") .first() .parent() - .findByTestId("remove-step") + .findByLabelText("Remove step") .click({ force: true }); } diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.tsx b/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.tsx index 2c53c1f2ed9..773ec5f8226 100644 --- a/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.tsx +++ b/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.tsx @@ -5,6 +5,7 @@ import { color as c } from "metabase/lib/colors"; import { useToggle } from "metabase/hooks/use-toggle"; import Icon from "metabase/components/Icon"; +import IconButtonWrapper from "metabase/components/IconButtonWrapper"; import ExpandingContent from "metabase/components/ExpandingContent"; import type Question from "metabase-lib/Question"; @@ -104,6 +105,7 @@ function NotebookStep({ const color = getColor(); const canPreview = step?.previewQuery?.isValid?.(); const hasPreviewButton = !isPreviewOpen && canPreview; + const canRevert = typeof step.revert === "function"; return ( <ExpandingContent isInitiallyOpen={!isLastOpened} isOpen> @@ -113,13 +115,18 @@ function NotebookStep({ > <StepHeader color={color}> {title} - <Icon - name="close" - className="ml-auto cursor-pointer text-light text-medium-hover hover-child" - tooltip={t`Remove`} - onClick={handleClickRevert} - data-testid="remove-step" - /> + {canRevert && ( + <IconButtonWrapper + className="ml-auto text-light text-medium-hover hover-child" + onClick={handleClickRevert} + > + <Icon + name="close" + tooltip={t`Remove`} + aria-label={t`Remove step`} + /> + </IconButtonWrapper> + )} </StepHeader> {NotebookStepComponent && ( diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.unit.spec.tsx b/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.unit.spec.tsx new file mode 100644 index 00000000000..c21a2f9cc2b --- /dev/null +++ b/frontend/src/metabase/query_builder/components/notebook/NotebookStep/NotebookStep.unit.spec.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import { renderWithProviders, screen } from "__support__/ui"; +import { + setupDatabasesEndpoints, + setupSearchEndpoints, +} from "__support__/server-mocks"; + +import { createSampleDatabase } from "metabase-types/api/mocks/presets"; + +import { getSavedStructuredQuestion } from "metabase-lib/mocks"; +import type Question from "metabase-lib/Question"; +import type StructuredQuery from "metabase-lib/queries/StructuredQuery"; + +import { + NotebookStep as INotebookStep, + NotebookStepType, +} from "../lib/steps.types"; +import NotebookStep from "./NotebookStep"; + +type SetupOpts = { + step?: INotebookStep; + question?: Question; +}; + +const DEFAULT_QUESTION = getSavedStructuredQuestion(); +const DEFAULT_QUERY = DEFAULT_QUESTION.query() as StructuredQuery; + +function createNotebookStep(opts = {}): INotebookStep { + return { + id: "test-step", + type: "data", + stageIndex: 0, + itemIndex: 0, + query: DEFAULT_QUERY, + valid: true, + active: true, + visible: true, + actions: [], + previewQuery: null, + next: null, + previous: null, + revert: jest.fn(), + clean: jest.fn(), + update: jest.fn(), + ...opts, + }; +} + +function setup({ + step = createNotebookStep(), + question = DEFAULT_QUESTION, +}: SetupOpts = {}) { + const openStep = jest.fn(); + const updateQuery = jest.fn(); + + setupDatabasesEndpoints([createSampleDatabase()]); + setupSearchEndpoints([]); + + renderWithProviders( + <NotebookStep + step={step} + sourceQuestion={question} + isLastStep={false} + isLastOpened={false} + openStep={openStep} + updateQuery={updateQuery} + />, + ); + + return { + openStep, + updateQuery, + }; +} + +const STEP_TYPES: NotebookStepType[] = [ + "data", + "join", + "expression", + "filter", + "summarize", + "aggregate", + "breakout", + "sort", + "limit", +]; + +describe("NotebookStep", () => { + test.each(STEP_TYPES)(`renders a %s step correctly`, type => { + const step = createNotebookStep({ type }); + const testId = `step-${type}-${step.stageIndex}-${step.itemIndex}`; + setup({ step }); + + expect(screen.getByTestId(testId)).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Remove step" }), + ).toBeInTheDocument(); + }); + + it("doesn't render the remove button if step revert isn't implemented", () => { + const step = createNotebookStep({ type: "data", revert: null }); + setup({ step }); + + expect( + screen.queryByRole("button", { name: "Remove step" }), + ).not.toBeInTheDocument(); + }); +}); -- GitLab