From c15aaa833bb9048dfd0123db7089fddbebaac9ef Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 12 Oct 2020 12:45:07 +0200 Subject: [PATCH] Fix attribute empty option --- .../SingleAutocompleteSelectField.tsx | 19 +++- .../SingleAutocompleteSelectFieldContent.tsx | 27 ++++-- .../ProductAttributes/ProductAttributes.tsx | 13 +-- .../ProductUpdatePage.test.tsx | 92 +++++++++++++++++++ .../ProductVariantAttributes.tsx | 1 - src/products/fixtures.ts | 2 +- src/products/utils/handlers.ts | 2 +- .../stories/products/ProductUpdatePage.tsx | 1 + 8 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 src/products/components/ProductUpdatePage/ProductUpdatePage.test.tsx diff --git a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx index a7913a898..14698a75e 100644 --- a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx +++ b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx @@ -4,8 +4,13 @@ import TextField from "@material-ui/core/TextField"; import useStateFromProps from "@saleor/hooks/useStateFromProps"; import { FetchMoreProps } from "@saleor/types"; import classNames from "classnames"; -import Downshift from "downshift"; +import Downshift, { + ControllerStateAndHelpers, + DownshiftState, + StateChangeOptions +} from "downshift"; import { filter } from "fuzzaldrin"; +import { names } from "keycode"; import React from "react"; import ArrowDropdownIcon from "../../icons/ArrowDropdown"; @@ -76,20 +81,27 @@ const SingleAutocompleteSelectFieldComponent: React.FC + const handleChange = ( + item: string, + stateAndHelpers: ControllerStateAndHelpers + ) => { onChange({ target: { name, value: item } } as any); + stateAndHelpers.reset({ + inputValue: item + }); + }; return ( {debounceFn => ( displayValue} + itemToString={() => displayValue || ""} onInputValueChange={value => debounceFn(value)} onSelect={handleChange} selectedItem={value} @@ -190,6 +202,7 @@ const SingleAutocompleteSelectField: React.FC { const [query, setQuery] = React.useState(""); + if (fetchChoices) { return ( diff --git a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.tsx b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.tsx index 0b7075d13..98edcd956 100644 --- a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.tsx +++ b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectFieldContent.tsx @@ -35,7 +35,7 @@ export interface SingleAutocompleteSelectFieldContentProps choices: SingleAutocompleteChoiceType[]; displayCustomValue: boolean; emptyOption: boolean; - getItemProps: (options: GetItemPropsOptions) => void; + getItemProps: (options: GetItemPropsOptions) => any; highlightedIndex: number; inputValue: string; isCustomValueSelected: boolean; @@ -164,9 +164,11 @@ const SingleAutocompleteSelectFieldContent: React.FC { setSlice(sliceSize); - anchor.current.scrollTo({ - top: 0 - }); + if (anchor.current?.scrollTo) { + anchor.current.scrollTo({ + top: 0 + }); + } }, [choices?.length]); React.useEffect(() => { @@ -175,6 +177,10 @@ const SingleAutocompleteSelectFieldContent: React.FC
{choices.length > 0 || displayCustomValue ? ( <> - {emptyOption && ( + { - )} + } {add && ( @@ -222,6 +228,7 @@ const SingleAutocompleteSelectFieldContent: React.FC {suggestion.label} diff --git a/src/products/components/ProductAttributes/ProductAttributes.tsx b/src/products/components/ProductAttributes/ProductAttributes.tsx index 9bae09030..1f50a4daa 100644 --- a/src/products/components/ProductAttributes/ProductAttributes.tsx +++ b/src/products/components/ProductAttributes/ProductAttributes.tsx @@ -15,7 +15,6 @@ import SingleAutocompleteSelectField, { } from "@saleor/components/SingleAutocompleteSelectField"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; -import { maybe } from "@saleor/misc"; import { ProductDetails_product_attributes_attribute_values } from "@saleor/products/types/ProductDetails"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { getProductErrorMessage } from "@saleor/utils/errors"; @@ -194,13 +193,11 @@ const ProductAttributes: React.FC = ({ - attribute.data.values.find( - value => value.slug === attribute.value[0] - ).name, - attribute.value[0] - )} + displayValue={ + attribute.data.values.find( + value => value.slug === attribute.value[0] + )?.name || "" + } emptyOption={!attribute.data.isRequired} error={!!error} helperText={getProductErrorMessage(error, intl)} diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.test.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.test.tsx new file mode 100644 index 000000000..96854aaee --- /dev/null +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.test.tsx @@ -0,0 +1,92 @@ +import placeholderImage from "@assets/images/placeholder255x255.png"; +import { collections } from "@saleor/collections/fixtures"; +import { fetchMoreProps, listActionsProps } from "@saleor/fixtures"; +import { product as productFixture } from "@saleor/products/fixtures"; +import { taxTypes } from "@saleor/storybook/stories/taxes/fixtures"; +import { warehouseList } from "@saleor/warehouses/fixtures"; +import Wrapper from "@test/wrapper"; +import { configure, mount } from "enzyme"; +import React from "react"; + +import ProductUpdatePage, { ProductUpdatePageProps } from "./ProductUpdatePage"; + +const product = productFixture(placeholderImage); +import Adapter from "enzyme-adapter-react-16"; +configure({ adapter: new Adapter() }); + +const onSubmit = jest.fn(); + +const props: ProductUpdatePageProps = { + ...listActionsProps, + categories: [product.category], + collections, + defaultWeightUnit: "kg", + disabled: false, + errors: [], + fetchCategories: () => undefined, + fetchCollections: () => undefined, + fetchMoreCategories: fetchMoreProps, + fetchMoreCollections: fetchMoreProps, + header: product.name, + images: product.images, + onBack: () => undefined, + onDelete: () => undefined, + onImageDelete: () => undefined, + onImageUpload: () => undefined, + onSetDefaultVariant: () => undefined, + onSubmit, + onVariantAdd: () => undefined, + onVariantReorder: () => undefined, + onVariantShow: () => undefined, + onVariantsAdd: () => undefined, + onWarehouseConfigure: () => undefined, + placeholderImage, + product, + saveButtonBarState: "default", + taxTypes, + variants: product.variants, + warehouses: warehouseList +}; + +const selectors = { + dropdown: `[data-test="autocomplete-dropdown"]`, + empty: `[data-test-type="empty"]`, + input: `[data-test="product-attribute-value"] input` +}; + +describe("Product details page", () => { + it("can select empty option on attribute", () => { + const component = mount( + + + + ); + expect(component.find(selectors.dropdown).exists()).toBeFalsy(); + + component + .find(selectors.input) + .first() + .simulate("click"); + + expect(component.find(selectors.dropdown).exists()).toBeTruthy(); + + expect(component.find(selectors.empty).exists()); + + component + .find(selectors.empty) + .first() + .simulate("click"); + + expect( + component + .find(selectors.input) + .first() + .prop("value") + ).toEqual(""); + component + .find("form") + .first() + .simulate("submit"); + expect(onSubmit.mock.calls[0][0].attributes[0].value.length).toEqual(0); + }); +}); diff --git a/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx b/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx index a09714220..30c49c07d 100644 --- a/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx +++ b/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx @@ -98,7 +98,6 @@ const ProductVariantAttributes: React.FC = ({ attribute.value, attributes )} - emptyOption error={!!error} helperText={getProductVariantAttributeErrorMessage( error, diff --git a/src/products/fixtures.ts b/src/products/fixtures.ts index 1207f656c..fdd046a1e 100644 --- a/src/products/fixtures.ts +++ b/src/products/fixtures.ts @@ -24,7 +24,7 @@ export const product: ( inputType: AttributeInputTypeEnum.DROPDOWN, name: "Borders", slug: "Borders", - valueRequired: true, + valueRequired: false, values: [ { __typename: "AttributeValue", diff --git a/src/products/utils/handlers.ts b/src/products/utils/handlers.ts index e6ae599b5..39d3099a3 100644 --- a/src/products/utils/handlers.ts +++ b/src/products/utils/handlers.ts @@ -42,7 +42,7 @@ export function createAttributeChangeHandler( ]); triggerChange(); - changeAttributeData(attributeId, [value]); + changeAttributeData(attributeId, value === "" ? [] : [value]); }; } diff --git a/src/storybook/stories/products/ProductUpdatePage.tsx b/src/storybook/stories/products/ProductUpdatePage.tsx index 92506dfb0..4346e5f6d 100644 --- a/src/storybook/stories/products/ProductUpdatePage.tsx +++ b/src/storybook/stories/products/ProductUpdatePage.tsx @@ -92,6 +92,7 @@ storiesOf("Views / Products / Product edit", module) {...props} product={{ ...product, + productType: { ...product.productType, hasVariants: false