From ffad2bb207bb14ba7ff4860df32b70ed16a3c842 Mon Sep 17 00:00:00 2001 From: Jacob Wartofsky Date: Tue, 7 Jul 2026 15:35:29 -0400 Subject: [PATCH 1/4] fix: loosen item source mapping requirements for CTAs Allows itemSource to select a parent field without CTAs, even if the cards require a CTA, providing that there are text or rich_text fields available. This allows a user to user a constant value to map to the CTA instead of requiring a CTA field. --- .../utils/itemSource/createItemSource.test.ts | 19 ++++++++++++++++++ .../createSlottedItemSource.test.ts | 20 +++++++++++++++++++ .../itemSource/itemSourceFieldTransforms.ts | 14 ++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts b/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts index 86ae55cc91..81e916fe54 100644 --- a/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts +++ b/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts @@ -75,6 +75,25 @@ describe("createItemSource", () => { }); }); + it("treats text and rich text as compatible CTA source requirements", () => { + const ctaSource = createItemSource({ + label: "Articles", + mappingFields: { + cta: { + type: "entityField", + label: "CTA", + filter: { types: ["type.cta"] }, + }, + }, + }); + + expect(ctaSource.field).toMatchObject({ + filter: { + itemSourceTypes: [["type.cta", "type.string", "type.rich_text_v2"]], + }, + }); + }); + it("uses the first default value as the add-item template and seeds manual items", () => { const customizedSource = createItemSource({ label: "Articles", diff --git a/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts b/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts index b4e32f8cc9..05459b4170 100644 --- a/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts +++ b/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts @@ -72,6 +72,26 @@ describe("createSlottedItemSource", () => { }); }); + it("treats text and rich text as compatible CTA source requirements", () => { + const ctaSource = createSlottedItemSource({ + label: "Featured Items", + itemLabel: "Featured Item", + mappingFields: { + cta: { + type: "entityField", + label: "CTA", + filter: { types: ["type.cta"] }, + }, + }, + }); + + expect(ctaSource.field).toMatchObject({ + filter: { + itemSourceTypes: [["type.cta", "type.string", "type.rich_text_v2"]], + }, + }); + }); + it("resolves linked slot mappings against the current mapped item", () => { const resolved = featuredItemsSource.resolveItems( { diff --git a/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts b/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts index a5f4ba3ad9..99f9a11fa9 100644 --- a/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts +++ b/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts @@ -128,6 +128,11 @@ const MAPPING_CONSTANT_VALUE_TYPES: EntityFieldTypes[] = [ ]; const MAPPING_CONSTANT_VALUE_LIST_TYPES: EntityFieldTypes[] = ["type.string"]; +const ITEM_SOURCE_TYPE_COMPATIBILITY: Partial< + Record +> = { + "type.cta": ["type.string", "type.rich_text_v2"], +}; function shouldEnableMappingConstantValue( field: EntityFieldSelectorField @@ -197,7 +202,14 @@ function getNestedItemSourceTypes( field: YextFieldDefinition ): EntityFieldTypes[][] { if (isEntityFieldDefinition(field)) { - return field.filter.types?.length ? [field.filter.types] : []; + return field.filter.types?.length + ? [ + field.filter.types.flatMap((entityFieldType) => [ + entityFieldType, + ...(ITEM_SOURCE_TYPE_COMPATIBILITY[entityFieldType] ?? []), + ]), + ] + : []; } if (field.type === "object" && "objectFields" in field) { From 725457a4d4ba06672a9a0489e2bfb3f3bd423186 Mon Sep 17 00:00:00 2001 From: Jacob Wartofsky Date: Tue, 7 Jul 2026 15:45:07 -0400 Subject: [PATCH 2/4] add test case verifying that text and rich text can satisfy the CTA requirement --- .../src/editor/yextEntityFieldUtils.test.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts b/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts index 5699749e05..bd9c82decd 100644 --- a/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts +++ b/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { productCardsSource } from "../components/pageSections/ProductSection/ProductCardsWrapper.tsx"; import { getEntityFieldDisplayName, getFieldsForSelector, @@ -140,6 +141,79 @@ describe("getFieldsForSelector", () => { ); }); + it("treats c_productReference as a valid source for product cards requirements, despite missing CTA", () => { + const fields = getFieldsForSelector( + { + fields: [ + { + name: "c_productReference", + definition: { + name: "c_productReference", + typeRegistryId: "type.entity_reference", + isList: true, + type: { + documentType: "DOCUMENT_TYPE_ENTITY", + }, + }, + children: { + fields: [ + { + name: "brand", + definition: { + name: "brand", + typeName: "type.string", + type: {}, + }, + }, + { + name: "name", + definition: { + name: "name", + typeName: "type.string", + type: {}, + }, + }, + { + name: "price", + definition: { + name: "price", + typeName: "type.price", + type: {}, + }, + }, + { + name: "primaryPhoto", + definition: { + name: "primaryPhoto", + typeName: "type.image", + type: {}, + }, + }, + ], + }, + }, + ], + displayNames: { + c_productReference: "Product Reference", + "c_productReference.brand": "Product Reference > Brand", + "c_productReference.name": "Product Reference > Name", + "c_productReference.price": "Product Reference > Price", + "c_productReference.primaryPhoto": + "Product Reference > Primary Photo", + }, + }, + productCardsSource.field.filter + ); + + expect(fields).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "c_productReference", + }), + ]) + ); + }); + it("merges duplicate scoped fields when one has a display name and another has nested children", () => { const fields = getFieldsForSelector( { From 1be42c8bc7f6e7e647f46bf7670f1bf3127302f1 Mon Sep 17 00:00:00 2001 From: Jacob Wartofsky Date: Tue, 7 Jul 2026 15:49:23 -0400 Subject: [PATCH 3/4] fix test --- packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts b/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts index bd9c82decd..18d0e5d7be 100644 --- a/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts +++ b/packages/visual-editor/src/editor/yextEntityFieldUtils.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { productCardsSource } from "../components/pageSections/ProductSection/ProductCardsWrapper.tsx"; +import { type EntityFieldSelectorField } from "../fields/EntityFieldSelectorField.tsx"; import { getEntityFieldDisplayName, getFieldsForSelector, @@ -202,7 +203,7 @@ describe("getFieldsForSelector", () => { "Product Reference > Primary Photo", }, }, - productCardsSource.field.filter + (productCardsSource.field as EntityFieldSelectorField).filter ); expect(fields).toEqual( From 31aa2890be85e63ca221d28eb40974d68f39a7c8 Mon Sep 17 00:00:00 2001 From: Jacob Wartofsky Date: Tue, 7 Jul 2026 15:59:52 -0400 Subject: [PATCH 4/4] rich text no longer satisfies CTA --- .../visual-editor/src/utils/itemSource/createItemSource.test.ts | 2 +- .../src/utils/itemSource/createSlottedItemSource.test.ts | 2 +- .../src/utils/itemSource/itemSourceFieldTransforms.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts b/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts index 81e916fe54..2e2145158f 100644 --- a/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts +++ b/packages/visual-editor/src/utils/itemSource/createItemSource.test.ts @@ -89,7 +89,7 @@ describe("createItemSource", () => { expect(ctaSource.field).toMatchObject({ filter: { - itemSourceTypes: [["type.cta", "type.string", "type.rich_text_v2"]], + itemSourceTypes: [["type.cta", "type.string"]], }, }); }); diff --git a/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts b/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts index 05459b4170..35f63c9782 100644 --- a/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts +++ b/packages/visual-editor/src/utils/itemSource/createSlottedItemSource.test.ts @@ -87,7 +87,7 @@ describe("createSlottedItemSource", () => { expect(ctaSource.field).toMatchObject({ filter: { - itemSourceTypes: [["type.cta", "type.string", "type.rich_text_v2"]], + itemSourceTypes: [["type.cta", "type.string"]], }, }); }); diff --git a/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts b/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts index 99f9a11fa9..99c80871f5 100644 --- a/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts +++ b/packages/visual-editor/src/utils/itemSource/itemSourceFieldTransforms.ts @@ -131,7 +131,7 @@ const MAPPING_CONSTANT_VALUE_LIST_TYPES: EntityFieldTypes[] = ["type.string"]; const ITEM_SOURCE_TYPE_COMPATIBILITY: Partial< Record > = { - "type.cta": ["type.string", "type.rich_text_v2"], + "type.cta": ["type.string"], }; function shouldEnableMappingConstantValue(