Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/visualBuilder/components/FieldToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function FieldToolbarComponent(
let fieldType = null;
let isWholeMultipleField = false;
let isMultipleSelect = false;
let isTaxonomyField = false;
const APP_LIST_MIN_WIDTH = 230;

let disableFieldActions = false;
Expand Down Expand Up @@ -196,10 +197,12 @@ function FieldToolbarComponent(
isCustomFieldWholeMultiple =
fieldType === FieldDataType.CUSTOM_FIELD && isMultiple && isWholeMultipleField;

// A multiple Select edits as one field, so a value click gets the whole-field toolbar.
// A multiple Select or Taxonomy edits as one value, so a value click gets the whole-field toolbar.
isMultipleSelect = fieldType === FieldDataType.SELECT && isMultiple;
// Taxonomy is always multiple, so unlike Select no isMultiple check is needed.
isTaxonomyField = fieldType === FieldDataType.TAXONOMY;
Comment thread
altsh-cs marked this conversation as resolved.
Resolved

if (isCustomFieldWholeMultiple || isMultipleSelect) {
if (isCustomFieldWholeMultiple || isMultipleSelect || isTaxonomyField) {
isModalEditable = true;
} else {
isModalEditable = ALLOWED_MODAL_EDITABLE_FIELD.includes(fieldType) && !isWholeMultipleField;
Expand Down Expand Up @@ -263,9 +266,9 @@ function FieldToolbarComponent(
// propagation to be stopped, should ideally only attach onClick to fieldpath dropdown
e.preventDefault();
e.stopPropagation();
// Multiple Select is edited as one value; a value click must not bind to its index
// Multiple Select and Taxonomy are edited as one value; a value click must not bind to its index
handleEdit(
isMultipleSelect
isMultipleSelect || isTaxonomyField
Comment thread
altsh-cs marked this conversation as resolved.
Resolved
? {
...fieldMetadata,
instance: {
Expand Down Expand Up @@ -514,7 +517,7 @@ function FieldToolbarComponent(
disabled={disableFieldActions}
/>
) : null}
{isMultiple && !isWholeMultipleField && !isMultipleSelect ? (
{isMultiple && !isWholeMultipleField && !isMultipleSelect && !isTaxonomyField ? (
Comment thread
altsh-cs marked this conversation as resolved.
Resolved
<>
<button
data-testid="visual-builder__focused-toolbar__multiple-field-toolbar__move-left-button"
Expand Down
160 changes: 160 additions & 0 deletions src/visualBuilder/components/__test__/fieldToolbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -796,4 +796,164 @@ describe("FieldToolbarComponent", () => {
).toBeDisabled();
});
});

describe("Taxonomy field toolbar", () => {
const taxonomySchema = {
data_type: "taxonomy",
display_name: "Taxonomies",
uid: "taxonomies",
multiple: true,
mandatory: false,
non_localizable: false,
unique: false,
taxonomies: [{ taxonomy_uid: "colors", non_localizable: false }],
} as unknown as ISchemaFieldMap;

const termMetadata: CslpData = {
...mockMultipleFieldMetadata,
fieldPath: "taxonomies",
fieldPathWithIndex: "taxonomies",
multipleFieldMetadata: {
index: 1,
parentDetails: {
parentPath: "taxonomies",
parentCslpValue: "entry.ct.en-us",
},
},
instance: { fieldPathWithIndex: "taxonomies.1" },
};

const TAX_EDIT_ID =
"visual-builder__focused-toolbar__multiple-field-toolbar__edit-button";
const TAX_PREFIX =
"visual-builder__focused-toolbar__multiple-field-toolbar";

const renderTaxonomy = async (fieldMetadata: CslpData) => {
vi.mocked(FieldSchemaMap.getFieldSchema).mockImplementation(() =>
Promise.resolve(taxonomySchema)
);
const view = render(
<FieldToolbarComponent
eventDetails={{ ...mockEventDetails, fieldMetadata }}
hideOverlay={vi.fn()}
/>
);
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
});
return view;
};

const expectNoMoveOrDelete = (container: HTMLElement) => {
for (const id of ["move-left", "move-right", "delete"]) {
expect(
container.querySelector(
`[data-testid="${TAX_PREFIX}__${id}-button"]`
)
).not.toBeInTheDocument();
}
};

test("term click shows edit without move or delete", async () => {
const { container } = await renderTaxonomy(termMetadata);

expect(
await findByTestId(container, TAX_EDIT_ID, {}, { timeout: 1000 })
).toBeInTheDocument();
expectNoMoveOrDelete(container);
});

test("term click inside a modular block keeps the parent index on edit", async () => {
const blockTermMetadata: CslpData = {
...termMetadata,
fieldPath: "blocks.section.taxonomies",
fieldPathWithIndex: "blocks.0.section.taxonomies",
multipleFieldMetadata: {
index: 2,
parentDetails: {
parentPath: "blocks.0.section.taxonomies",
parentCslpValue: "entry.ct.en-us",
},
},
instance: {
fieldPathWithIndex: "blocks.0.section.taxonomies.2",
},
};
const { container } = await renderTaxonomy(blockTermMetadata);

fireEvent.click(
await findByTestId(container, TAX_EDIT_ID, {}, { timeout: 1000 })
);

expect(visualBuilderPostMessage?.send).toHaveBeenCalledWith(
VisualBuilderPostMessageEvents.OPEN_FIELD_EDIT_MODAL,
{
fieldMetadata: {
...blockTermMetadata,
instance: {
fieldPathWithIndex: "blocks.0.section.taxonomies",
},
multipleFieldMetadata: {
...blockTermMetadata.multipleFieldMetadata,
index: -1,
},
},
}
);
expect(blockTermMetadata.instance.fieldPathWithIndex).toBe(
"blocks.0.section.taxonomies.2"
);
expect(blockTermMetadata.multipleFieldMetadata.index).toBe(2);
});

test("whole-list click shows edit without move or delete", async () => {
const { container } = await renderTaxonomy({
...termMetadata,
multipleFieldMetadata: { index: -1, parentDetails: null },
instance: { fieldPathWithIndex: "taxonomies" },
});

expect(
await findByTestId(container, TAX_EDIT_ID, {}, { timeout: 1000 })
).toBeInTheDocument();
expectNoMoveOrDelete(container);
});

test("term click sends whole-field metadata on edit", async () => {
Comment thread
altsh-cs marked this conversation as resolved.
Resolved
const { container } = await renderTaxonomy(termMetadata);

fireEvent.click(
await findByTestId(container, TAX_EDIT_ID, {}, { timeout: 1000 })
);

expect(visualBuilderPostMessage?.send).toHaveBeenCalledWith(
VisualBuilderPostMessageEvents.OPEN_FIELD_EDIT_MODAL,
{
fieldMetadata: {
...termMetadata,
instance: { fieldPathWithIndex: "taxonomies" },
multipleFieldMetadata: {
...termMetadata.multipleFieldMetadata,
index: -1,
},
},
}
);
expect(termMetadata.instance.fieldPathWithIndex).toBe(
"taxonomies.1"
);
});

test("edit button is disabled when the taxonomy field is disabled", async () => {
vi.mocked(isFieldDisabled).mockReturnValue({
isDisabled: true,
reason: "",
} as ReturnType<typeof isFieldDisabled>);
const { container } = await renderTaxonomy(termMetadata);

expect(
await findByTestId(container, TAX_EDIT_ID, {}, { timeout: 1000 })
).toBeDisabled();
});
});
});
1 change: 1 addition & 0 deletions src/visualBuilder/components/icons/fields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export const fieldIcons: Partial<Record<FieldDataType, React.FC>> = {
isodate: EditIcon,
url: EditIcon,
select: EditIcon,
taxonomy: EditIcon,
};
17 changes: 17 additions & 0 deletions src/visualBuilder/utils/__test__/handleIndividualFields.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,23 @@ describe("handleIndividualFields", () => {
expect(handleAddButtonsForMultiple).not.toHaveBeenCalled();
});

it("still adds instance buttons for a multiple Taxonomy field", async () => {
const fieldSchema = {
data_type: "taxonomy",
multiple: true,
taxonomies: [{ taxonomy_uid: "colors" }],
};

(FieldSchemaMap.getFieldSchema as Mock).mockResolvedValue(fieldSchema);
(getFieldData as Mock).mockResolvedValue([{ term_uid: "red" }]);
(getFieldType as Mock).mockReturnValue(FieldDataType.TAXONOMY);
(isFieldDisabled as Mock).mockReturnValue({ isDisabled: false });

await handleIndividualFields(eventDetails, elements);

expect(handleAddButtonsForMultiple).toHaveBeenCalled();
});

it("still adds instance buttons for a multiple single-line text", async () => {
const fieldSchema = {
data_type: "text",
Expand Down
1 change: 1 addition & 0 deletions src/visualBuilder/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const ALLOWED_MODAL_EDITABLE_FIELD: FieldDataType[] = [
FieldDataType.ISODATE,
FieldDataType.URL,
FieldDataType.SELECT,
FieldDataType.TAXONOMY,
];

export const ALLOWED_REPLACE_FIELDS: FieldDataType[] = [
Expand Down
3 changes: 2 additions & 1 deletion src/visualBuilder/utils/handleIndividualFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export async function handleIndividualFields(
fieldType
);

// A canvas add button patches an { $fieldValue } item into the Select's plain array
// Select skips add buttons: they would patch a { $fieldValue } item into its plain array.
// Taxonomy keeps them: the editor opens its term picker on add-instance.
if (isFieldMultiple(fieldSchema) && fieldType !== FieldDataType.SELECT) {
if (!isCustomFieldMultipleInstance(fieldSchema, fieldMetadata) && lastEditedField !== editableElement) {
const addButtonLabel =
Expand Down
Loading