diff --git a/frontend/src/metabase/components/FieldValuesWidget.jsx b/frontend/src/metabase/components/FieldValuesWidget.jsx index 9f875552b4660547574100413ee6b51c8039a7f2..3304ad9349c1a09ffc178f4a17f4ff5674acb3b5 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 0000000000000000000000000000000000000000..218ececdee6bb636b9381ab3a903e5755d274a52 --- /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 0000000000000000000000000000000000000000..c347c016c72fbf08929d9f8bc626ddd710129f84 --- /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 0000000000000000000000000000000000000000..044d0d4b14ef3fdb9a6f7080bfb67c4109fc7a24 --- /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 dc0ebbf7a5731d15c92a08c21c21fd065965fee8..dd5ae45fe334c6d248e9902ba16097447a896b74 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 984e0166f4fcc878a4390fb8a993ae9c4ece6663..2bd98d24463d9f4e14361b08fafc91ca3ed5fad7 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 923385ac7fea7524b67bc504b666ead98a5e79ff..c0697076e52105fe8b18b329b2485762ada5ab0d 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 39de6bdd54bf2945b4e00077226d4316dc72f102..8277df43515e005de87feb3bda888ff7516b44e0 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 4e01f57e689b7415669fb7ba39c4e571a42a680c..ceafd260c43731c13677572caf097ef991cf101a 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 d35ff83a97a64375fe7594ce802d1faec2f4c0fc..0662f757602f00df225d310cbe88018939a7f6ce 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 a3d271393a73bbabe69caa23884d9fba2429ad12..d3a625ec4da4ff70a9d476120d51311ce393c527 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 796a481c60d2b96e3375a7cd9c6320da7c1135c0..837d6d81ab0686b13fb3d8b8ba9e5ad1cde8e03a 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 0f7c00a09a53a1fb942c744639c365e2daf27609..fc93dac35d3c68b012bcc6a4add86eade52b73c7 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 53826df07d2f0895083681226558c4a5e4be6228..5c9316e170c9867bd0d5ae0e658a5dd1d5413279 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 6679cdb0eddc88b214f47450efb960acc57904ee..0000000000000000000000000000000000000000 --- 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 0e3a359dc1c2b04adb7f19405f7e991502257c88..faebf6db5aaae7952c126f63198fd939ce7aeaf8 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 c6c537803fc1d32eca928613e22b93b1a9a8b2ce..baa6256214675dc453d37b18f255f0217ebcfcca 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")