From 689b4310a7ce20c7a9b7ce0dd60a8ac352a8fb71 Mon Sep 17 00:00:00 2001 From: Alexander Lesnenko <alxnddr@users.noreply.github.com> Date: Fri, 24 Dec 2021 23:36:42 +0200 Subject: [PATCH] Dashboard and question list filter with checkboxes (#19467) * Dashboard and table list filter * fix native question filters tey to render list of values * Fix specs * fix specs --- .../metabase/components/FieldValuesWidget.jsx | 130 ++++++++++-------- .../components/ListField/ListField.styled.tsx | 36 +++++ .../components/ListField/ListField.tsx | 122 ++++++++++++++++ .../metabase/components/ListField/index.ts | 1 + .../filters/pickers/DefaultPicker.jsx | 8 +- .../components/FieldValuesWidget.unit.spec.js | 9 +- ...board-filters-sql-text-category.cy.spec.js | 6 +- ...dashboard-filters-text-category.cy.spec.js | 7 +- .../old-parameters.cy.spec.js | 4 +- ...ata-should-not-use-dash-filters.cy.spec.js | 8 +- ...15689-allow-multiple-selections.cy.spec.js | 15 +- .../dashboard/parameters-embedded.cy.spec.js | 1 - .../scenarios/dashboard/x-rays.cy.spec.js | 3 +- .../helpers/e2e-field-filter-helpers.js | 28 ++++ ...ng-filter-name-hides-close-icon.cy.spec.js | 48 ------- .../scenarios/question/saved.cy.spec.js | 1 + .../scenarios/question/view.cy.spec.js | 14 +- 17 files changed, 302 insertions(+), 139 deletions(-) create mode 100644 frontend/src/metabase/components/ListField/ListField.styled.tsx create mode 100644 frontend/src/metabase/components/ListField/ListField.tsx create mode 100644 frontend/src/metabase/components/ListField/index.ts delete mode 100644 frontend/test/metabase/scenarios/native-filters/reproductions/17751-long-filter-name-hides-close-icon.cy.spec.js diff --git a/frontend/src/metabase/components/FieldValuesWidget.jsx b/frontend/src/metabase/components/FieldValuesWidget.jsx index 9f875552b46..3304ad9349c 100644 --- a/frontend/src/metabase/components/FieldValuesWidget.jsx +++ b/frontend/src/metabase/components/FieldValuesWidget.jsx @@ -6,6 +6,7 @@ import { t, jt } from "ttag"; import _ from "underscore"; import TokenField from "metabase/components/TokenField"; +import { ListField } from "metabase/components/ListField"; import ValueComponent from "metabase/components/Value"; import LoadingSpinner from "metabase/components/LoadingSpinner"; @@ -100,7 +101,7 @@ export class FieldValuesWidget extends Component { return this.props.parameter && this.props.parameter.id; } - UNSAFE_componentWillMount() { + componentDidMount() { if (this.shouldList()) { if (this.useChainFilterEndpoints()) { this.fetchDashboardParamValues(); @@ -418,6 +419,10 @@ export class FieldValuesWidget extends Component { options = []; } + const isLoading = loadingState === "LOADING"; + const isFetchingList = this.shouldList() && isLoading; + const hasListData = this.hasList(); + return ( <div style={{ @@ -426,63 +431,78 @@ export class FieldValuesWidget extends Component { maxWidth: this.props.maxWidth, }} > - <TokenField - value={value.filter(v => v != null)} - onChange={onChange} - placeholder={placeholder} - updateOnInputChange - // forwarded props - multi={multi} - autoFocus={autoFocus} - color={color} - style={{ ...style, minWidth: "inherit" }} - className={className} - parameter={this.props.parameter} - optionsStyle={!parameter ? { maxHeight: "none" } : {}} - // end forwarded props - options={options} - valueKey={0} - valueRenderer={value => - this.renderValue(value, { autoLoad: true, compact: false }) - } - optionRenderer={option => - this.renderValue(option[0], { autoLoad: false }) - } - layoutRenderer={props => ( - <div> - {props.valuesList} - {this.renderOptions(props)} - </div> - )} - filterOption={(option, filterString) => { - const lowerCaseFilterString = filterString.toLowerCase(); - return option.some( - value => - value != null && - String(value) - .toLowerCase() - .includes(lowerCaseFilterString), - ); - }} - onInputChange={this.onInputChange} - parseFreeformValue={v => { - // trim whitespace - v = String(v || "").trim(); - // empty string is not valid - if (!v) { - return null; + {isFetchingList && <LoadingState />} + {hasListData && ( + <ListField + isDashboardFilter={parameter} + placeholder={this.getTokenFieldPlaceholder()} + value={value.filter(v => v != null)} + onChange={onChange} + options={options} + optionRenderer={option => + this.renderValue(option[0], { autoLoad: false }) + } + /> + )} + {!hasListData && !isFetchingList && ( + <TokenField + value={value.filter(v => v != null)} + onChange={onChange} + placeholder={placeholder} + updateOnInputChange + // forwarded props + multi={multi} + autoFocus={autoFocus} + color={color} + style={{ ...style, minWidth: "inherit" }} + className={className} + parameter={this.props.parameter} + optionsStyle={!parameter ? { maxHeight: "none" } : {}} + // end forwarded props + options={options} + valueKey={0} + valueRenderer={value => + this.renderValue(value, { autoLoad: true, compact: false }) + } + optionRenderer={option => + this.renderValue(option[0], { autoLoad: false }) } - // if the field is numeric we need to parse the string into an integer - if (fields[0].isNumeric()) { - if (/^-?\d+(\.\d+)?$/.test(v)) { - return parseFloat(v); - } else { + layoutRenderer={props => ( + <div> + {props.valuesList} + {this.renderOptions(props)} + </div> + )} + filterOption={(option, filterString) => { + const lowerCaseFilterString = filterString.toLowerCase(); + return option.some( + value => + value != null && + String(value) + .toLowerCase() + .includes(lowerCaseFilterString), + ); + }} + onInputChange={this.onInputChange} + parseFreeformValue={v => { + // trim whitespace + v = String(v || "").trim(); + // empty string is not valid + if (!v) { return null; } - } - return v; - }} - /> + // if the field is numeric we need to parse the string into an integer + if (fields[0].isNumeric()) { + if (/^-?\d+(\.\d+)?$/.test(v)) { + return parseFloat(v); + } else { + return null; + } + } + return v; + }} + /> + )} </div> ); } diff --git a/frontend/src/metabase/components/ListField/ListField.styled.tsx b/frontend/src/metabase/components/ListField/ListField.styled.tsx new file mode 100644 index 00000000000..218ececdee6 --- /dev/null +++ b/frontend/src/metabase/components/ListField/ListField.styled.tsx @@ -0,0 +1,36 @@ +import styled from "styled-components"; +import TextInput from "metabase/components/TextInput"; +import { color } from "metabase/lib/colors"; + +export const EmptyStateContainer = styled.div` + padding: 2rem 2rem 0 2rem; +`; + +interface FilterInputProps { + isDashboardFilter?: boolean; +} + +export const FilterInput = styled<FilterInputProps>(TextInput as any)` + margin-bottom: ${props => (props.isDashboardFilter ? "0" : "0.5rem")}; + border: ${props => + props.isDashboardFilter ? `1px solid ${color("border")}` : "none"}; +` as any; + +interface OptionListProps { + isDashboardFilter?: boolean; +} + +export const OptionsList = styled.ul<OptionListProps>` + overflow: auto; + list-style: none; + max-height: ${props => (props.isDashboardFilter ? "300px" : "none")}; + padding: ${props => (props.isDashboardFilter ? "0.5rem" : "0")}; +`; + +export const OptionContainer = styled.li` + padding: 0.5rem 0.125rem; +`; + +export const LabelWrapper = styled.div` + padding-left: 0.5rem; +`; diff --git a/frontend/src/metabase/components/ListField/ListField.tsx b/frontend/src/metabase/components/ListField/ListField.tsx new file mode 100644 index 00000000000..c347c016c72 --- /dev/null +++ b/frontend/src/metabase/components/ListField/ListField.tsx @@ -0,0 +1,122 @@ +import React, { useMemo, useState } from "react"; +import _ from "underscore"; +import { t } from "ttag"; +import { useDebouncedValue } from "metabase/hooks/use-debounced-value"; +import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants"; +import _EmptyState from "metabase/components/EmptyState"; +import _Checkbox from "../CheckBox"; + +import { + OptionContainer, + LabelWrapper, + OptionsList, + EmptyStateContainer, + FilterInput, +} from "./ListField.styled"; + +const SEARCH_THRESHOLD = 10; + +const Checkbox = _Checkbox as any; +const EmptyState = _EmptyState as any; + +type Option = any[]; + +interface ListFieldProps { + onChange: (value: string[]) => void; + value: string[]; + options: Option; + optionRenderer: (option: any) => JSX.Element; + placeholder: string; + isDashboardFilter?: boolean; +} + +export const ListField = ({ + onChange, + value, + options, + optionRenderer, + placeholder, + isDashboardFilter, +}: ListFieldProps) => { + const [selectedValues, setSelectedValues] = useState(new Set(value)); + const sortedOptions = useMemo(() => { + if (selectedValues.size === 0) { + return options; + } + + const [selected, unselected] = _.partition(options, option => + selectedValues.has(option[0]), + ); + + return [...selected, ...unselected]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [options.length]); + + const [filter, setFilter] = useState(""); + const debouncedFilter = useDebouncedValue(filter, SEARCH_DEBOUNCE_DURATION); + + const filteredOptions = useMemo(() => { + const trimmedFilter = debouncedFilter.trim().toLowerCase(); + + if (trimmedFilter.length === 0) { + return sortedOptions; + } + + return options.filter(option => + option[0] + .toString() + .toLowerCase() + .startsWith(trimmedFilter), + ); + }, [options, debouncedFilter, sortedOptions]); + + const handleToggleOption = (option: any) => { + const newSelectedValues = selectedValues.has(option) + ? Array.from(selectedValues).filter(value => value !== option) + : [...selectedValues, option]; + + setSelectedValues(new Set(newSelectedValues)); + onChange?.(newSelectedValues); + }; + + const shouldShowFilter = options.length > SEARCH_THRESHOLD; + const shouldShowEmptyState = + options.length > 0 && filteredOptions.length === 0; + + return ( + <> + {shouldShowFilter && ( + <FilterInput + isDashboardFilter={isDashboardFilter} + padding={isDashboardFilter ? "md" : "sm"} + borderRadius={isDashboardFilter ? "md" : "sm"} + colorScheme={isDashboardFilter ? "transparent" : "admin"} + placeholder={placeholder} + value={filter} + onChange={setFilter} + hasClearButton + /> + )} + + {shouldShowEmptyState && ( + <EmptyStateContainer> + <EmptyState message={t`Didn't find anything`} icon="search" /> + </EmptyStateContainer> + )} + + <OptionsList isDashboardFilter={isDashboardFilter}> + {filteredOptions.map(option => ( + <OptionContainer key={option[0]}> + <Checkbox + data-testid={`${option[0]}-filter-value`} + checkedColor={isDashboardFilter ? "brand" : "accent7"} + checked={selectedValues.has(option[0])} + label={<LabelWrapper>{optionRenderer(option)}</LabelWrapper>} + onChange={() => handleToggleOption(option[0])} + /> + </OptionContainer> + ))} + </OptionsList> + </> + ); +}; diff --git a/frontend/src/metabase/components/ListField/index.ts b/frontend/src/metabase/components/ListField/index.ts new file mode 100644 index 00000000000..044d0d4b14e --- /dev/null +++ b/frontend/src/metabase/components/ListField/index.ts @@ -0,0 +1 @@ +export * from "./ListField"; diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/DefaultPicker.jsx b/frontend/src/metabase/query_builder/components/filters/pickers/DefaultPicker.jsx index dc0ebbf7a57..dd5ae45fe33 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/DefaultPicker.jsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/DefaultPicker.jsx @@ -141,13 +141,9 @@ export default function DefaultPicker({ let layout = null; if (isBetweenLayout) { - layout = ( - <BetweenLayout className={className} fieldWidgets={fieldWidgets} /> - ); + layout = <BetweenLayout fieldWidgets={fieldWidgets} />; } else if (fieldWidgets.length > 0) { - layout = ( - <DefaultLayout className={className} fieldWidgets={fieldWidgets} /> - ); + layout = <DefaultLayout fieldWidgets={fieldWidgets} />; } return ( diff --git a/frontend/test/metabase/components/FieldValuesWidget.unit.spec.js b/frontend/test/metabase/components/FieldValuesWidget.unit.spec.js index 984e0166f4f..2bd98d24463 100644 --- a/frontend/test/metabase/components/FieldValuesWidget.unit.spec.js +++ b/frontend/test/metabase/components/FieldValuesWidget.unit.spec.js @@ -47,8 +47,15 @@ describe("FieldValuesWidget", () => { expect(fetchFieldValues).toHaveBeenCalledWith(PRODUCTS.CATEGORY.id); }); - it("should have 'Search the list' as the placeholder text", () => { + it("should not have 'Search the list' as the placeholder text for fields with less or equal than 10 values", () => { renderFieldValuesWidget({ ...props }); + expect(screen.queryByLabelText("Search the list")).toBeNull(); + }); + + it("should have 'Search the list' as the placeholder text for fields with less than 10 values", () => { + renderFieldValuesWidget({ + fields: [mock(PRODUCTS.TITLE, { has_field_values: "list" })], + }); screen.findByLabelText("Search the list"); }); }); diff --git a/frontend/test/metabase/scenarios/dashboard-filters-sql/dashboard-filters-sql-text-category.cy.spec.js b/frontend/test/metabase/scenarios/dashboard-filters-sql/dashboard-filters-sql-text-category.cy.spec.js index 923385ac7fe..c0697076e52 100644 --- a/frontend/test/metabase/scenarios/dashboard-filters-sql/dashboard-filters-sql-text-category.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard-filters-sql/dashboard-filters-sql-text-category.cy.spec.js @@ -8,7 +8,7 @@ import { } from "__support__/e2e/cypress"; import { DASHBOARD_SQL_TEXT_FILTERS } from "./helpers/e2e-dashboard-filter-sql-data-objects"; -import { addWidgetStringFilter } from "../native-filters/helpers/e2e-field-filter-helpers"; +import { applyFilterByType } from "../native-filters/helpers/e2e-field-filter-helpers"; import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset"; @@ -48,7 +48,7 @@ Object.entries(DASHBOARD_SQL_TEXT_FILTERS).forEach( saveDashboard(); filterWidget().click(); - addWidgetStringFilter(value); + applyFilterByType(filter, value); cy.get(".Card").within(() => { cy.contains(representativeResult); @@ -60,7 +60,7 @@ Object.entries(DASHBOARD_SQL_TEXT_FILTERS).forEach( .next() .click(); - addWidgetStringFilter(value); + applyFilterByType(filter, value); saveDashboard(); diff --git a/frontend/test/metabase/scenarios/dashboard-filters/dashboard-filters-text-category.cy.spec.js b/frontend/test/metabase/scenarios/dashboard-filters/dashboard-filters-text-category.cy.spec.js index 39de6bdd54b..8277df43515 100644 --- a/frontend/test/metabase/scenarios/dashboard-filters/dashboard-filters-text-category.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard-filters/dashboard-filters-text-category.cy.spec.js @@ -8,7 +8,7 @@ import { } from "__support__/e2e/cypress"; import { DASHBOARD_TEXT_FILTERS } from "./helpers/e2e-dashboard-filter-data-objects"; -import { addWidgetStringFilter } from "../native-filters/helpers/e2e-field-filter-helpers"; +import { applyFilterByType } from "../native-filters/helpers/e2e-field-filter-helpers"; Object.entries(DASHBOARD_TEXT_FILTERS).forEach( ([filter, { value, representativeResult }]) => { @@ -32,7 +32,8 @@ Object.entries(DASHBOARD_TEXT_FILTERS).forEach( saveDashboard(); filterWidget().click(); - addWidgetStringFilter(value); + + applyFilterByType(filter, value); cy.get(".Card").within(() => { cy.contains(representativeResult); @@ -44,7 +45,7 @@ Object.entries(DASHBOARD_TEXT_FILTERS).forEach( .next() .click(); - addWidgetStringFilter(value); + applyFilterByType(filter, value); saveDashboard(); diff --git a/frontend/test/metabase/scenarios/dashboard-filters/old-parameters.cy.spec.js b/frontend/test/metabase/scenarios/dashboard-filters/old-parameters.cy.spec.js index 4e01f57e689..ceafd260c43 100644 --- a/frontend/test/metabase/scenarios/dashboard-filters/old-parameters.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard-filters/old-parameters.cy.spec.js @@ -64,7 +64,7 @@ describe("scenarios > dashboard > OLD parameters", () => { cy.contains("Category").click(); popover().within(() => { - cy.get("input").type("Gadget{enter}"); + cy.findByText("Gadget").click(); cy.findByText("Add filter").click(); }); @@ -193,7 +193,7 @@ describe("scenarios > dashboard > OLD parameters", () => { cy.contains("Category").click(); popover().within(() => { - cy.get("input").type("Gadget{enter}"); + cy.findByText("Gadget").click(); cy.findByText("Add filter").click(); }); diff --git a/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15119-nodata-should-not-use-dash-filters.cy.spec.js b/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15119-nodata-should-not-use-dash-filters.cy.spec.js index d35ff83a97a..0662f757602 100644 --- a/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15119-nodata-should-not-use-dash-filters.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15119-nodata-should-not-use-dash-filters.cy.spec.js @@ -1,4 +1,4 @@ -import { restore, filterWidget } from "__support__/e2e/cypress"; +import { restore, filterWidget, popover } from "__support__/e2e/cypress"; import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset"; const { PRODUCTS, PRODUCTS_ID } = SAMPLE_DATASET; @@ -57,8 +57,10 @@ describe("issue 15119", () => { .contains("Category") .click(); - cy.findByPlaceholderText("Search the list").type("Gizmo"); - cy.button("Add filter").click(); + popover().within(() => { + cy.findByText("Gizmo").click(); + cy.button("Add filter").click(); + }); cy.contains("Rustic Paper Wallet"); }); diff --git a/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15689-allow-multiple-selections.cy.spec.js b/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15689-allow-multiple-selections.cy.spec.js index a3d271393a7..d3a625ec4da 100644 --- a/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15689-allow-multiple-selections.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard-filters/reproductions/15689-allow-multiple-selections.cy.spec.js @@ -58,18 +58,7 @@ describe("issue 15689", () => { cy.findByText("AK").click(); cy.findByText("CA").click(); - cy.icon("close") - .as("close") - .should("have.length", 2); - - cy.get("@close") - .first() - .closest("li") - .contains("AK"); - - cy.get("@close") - .last() - .closest("li") - .contains("CA"); + cy.findByTestId("AK-filter-value").should("be.checked"); + cy.findByTestId("CA-filter-value").should("be.checked"); }); }); diff --git a/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js index 796a481c60d..837d6d81ab0 100644 --- a/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard/parameters-embedded.cy.spec.js @@ -196,7 +196,6 @@ function sharedParametersTests(visitUrl) { it("should show values for PEOPLE.SOURCE", () => { visitUrl(); cy.contains("Source").click(); - popover().find('[placeholder="Search the list"]'); popover().contains("Affiliate"); }); diff --git a/frontend/test/metabase/scenarios/dashboard/x-rays.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/x-rays.cy.spec.js index 0f7c00a09a5..fc93dac35d3 100644 --- a/frontend/test/metabase/scenarios/dashboard/x-rays.cy.spec.js +++ b/frontend/test/metabase/scenarios/dashboard/x-rays.cy.spec.js @@ -173,7 +173,8 @@ describe("scenarios > x-rays", () => { // add a parameter filter to the auto dashboard cy.findByText("State", timeout).click(); popover().within(() => { - cy.get("input").type("GA{enter}"); + cy.findByPlaceholderText("Search the list").type("GA{enter}"); + cy.findByText("GA").click(); cy.findByText("Add filter").click(); }); diff --git a/frontend/test/metabase/scenarios/native-filters/helpers/e2e-field-filter-helpers.js b/frontend/test/metabase/scenarios/native-filters/helpers/e2e-field-filter-helpers.js index 53826df07d2..5c9316e170c 100644 --- a/frontend/test/metabase/scenarios/native-filters/helpers/e2e-field-filter-helpers.js +++ b/frontend/test/metabase/scenarios/native-filters/helpers/e2e-field-filter-helpers.js @@ -32,6 +32,34 @@ export function addWidgetStringFilter(value) { cy.button("Add filter").click(); } +/** + * Selectes value from the field values list filter widget + * + * @param {string} value + */ + +export function selectFilterValueFromList(value) { + popover().within(() => { + cy.findByText(value).click(); + cy.button("Add filter").click(); + }); +} + +/** + * Applies filter value by filter type + * + * @param {string} filter + * @param {string} value + */ + +export function applyFilterByType(filter, value) { + if (["Dropdown", "Is not"].includes(filter)) { + selectFilterValueFromList(value); + } else { + addWidgetStringFilter(value); + } +} + /** * Adds default string filter value when the filter is marked as required. * diff --git a/frontend/test/metabase/scenarios/native-filters/reproductions/17751-long-filter-name-hides-close-icon.cy.spec.js b/frontend/test/metabase/scenarios/native-filters/reproductions/17751-long-filter-name-hides-close-icon.cy.spec.js deleted file mode 100644 index 6679cdb0edd..00000000000 --- a/frontend/test/metabase/scenarios/native-filters/reproductions/17751-long-filter-name-hides-close-icon.cy.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { restore, filterWidget, popover } from "__support__/e2e/cypress"; - -import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset"; - -const { PRODUCTS } = SAMPLE_DATASET; - -const inputValue = - "A long value that hides the close icon after reopening the filter widget"; - -const questionDetails = { - native: { - query: "{{filter}}", - "template-tags": { - filter: { - id: "3ae0f2d7-c78e-a474-77b3-c3a827d8b919", - name: "filter", - "display-name": "Filter", - type: "dimension", - dimension: ["field", PRODUCTS.CATEGORY, null], - "widget-type": "string/=", - default: inputValue, - }, - }, - }, -}; - -describe("issue 17751", () => { - beforeEach(() => { - restore(); - cy.signInAsAdmin(); - - cy.createNativeQuestion(questionDetails, { visitQuestion: true }); - }); - - it("should handle long filter values correctly with the visible 'close' icon (metabase#17751)", () => { - filterWidget() - .find(".Icon-close") - .should("be.visible"); - - cy.findByText(inputValue).click(); - - popover() - .contains(inputValue) - .closest("li") - .find(".Icon-close") - .should("be.visible"); - }); -}); diff --git a/frontend/test/metabase/scenarios/question/saved.cy.spec.js b/frontend/test/metabase/scenarios/question/saved.cy.spec.js index 0e3a359dc1c..faebf6db5aa 100644 --- a/frontend/test/metabase/scenarios/question/saved.cy.spec.js +++ b/frontend/test/metabase/scenarios/question/saved.cy.spec.js @@ -66,6 +66,7 @@ describe("scenarios > question > saved", () => { popover().within(() => cy.findByText("Filter by this column").click()); popover().within(() => { cy.findByPlaceholderText("Search the list").type("100"); + cy.findByText("100").click(); cy.findByText("Update filter").click(); }); cy.findByText("Quantity is equal to 100"); diff --git a/frontend/test/metabase/scenarios/question/view.cy.spec.js b/frontend/test/metabase/scenarios/question/view.cy.spec.js index c6c537803fc..baa62562146 100644 --- a/frontend/test/metabase/scenarios/question/view.cy.spec.js +++ b/frontend/test/metabase/scenarios/question/view.cy.spec.js @@ -146,14 +146,19 @@ describe("scenarios > question > view", () => { it("should give everyone view permissions", () => {}); - it("should show filters by list for Category", () => { + it("should show filters by list for Category without a search box", () => { cy.visit("/question/4"); cy.findAllByText("CATEGORY") .first() .click(); popover().within(() => { - cy.findByPlaceholderText("Search the list"); + cy.findByText("Doohickey"); + cy.findByText("Gizmo"); + cy.findByText("Gadget"); + cy.findByText("Widget"); + + cy.findByPlaceholderText("Search the list").should("not.exist"); cy.findByPlaceholderText("Search by Category").should("not.exist"); }); }); @@ -213,7 +218,10 @@ describe("scenarios > question > view", () => { .first() .click(); popover().within(() => { - cy.findByPlaceholderText("Search by Vendor").type("Balistreri-Muller"); + cy.findByPlaceholderText("Search by Vendor") + .focus() + .clear() + .type("Balistreri-Muller"); cy.findByText("Add filter").click(); }); cy.get(".RunButton") -- GitLab