diff --git a/packages/visual-editor/src/components/contentBlocks/image/EmptyImageState.tsx b/packages/visual-editor/src/components/contentBlocks/image/EmptyImageState.tsx index b06ebab83..8299a8977 100644 --- a/packages/visual-editor/src/components/contentBlocks/image/EmptyImageState.tsx +++ b/packages/visual-editor/src/components/contentBlocks/image/EmptyImageState.tsx @@ -11,7 +11,10 @@ import { useReceiveMessage, } from "../../../internal/hooks/useMessage.ts"; import { type ImagePayload } from "../../../fields/ImageField.tsx"; -import { isFakeStarterLocalDev } from "../../../utils/isFakeStarterLocalDev.ts"; +import { + isFakeStarterLocalDev, + isLocalEditorRoute, +} from "../../../utils/isFakeStarterLocalDev.ts"; let pendingEmptyImageSession: | { messageId: string; apply: (payload: ImagePayload) => void } @@ -64,7 +67,10 @@ export const EmptyImageState: React.FC = ({ const handleImageSelection = React.useCallback(() => { if (!hasParentData && constantValueEnabled && isEditing) { - if (isFakeStarterLocalDev()) { + if ( + isFakeStarterLocalDev() || + (typeof window !== "undefined" && isLocalEditorRoute(window.location)) + ) { const userInput = prompt("Enter Image URL:"); if (!userInput) { return; diff --git a/packages/visual-editor/src/fields/ImageField.test.tsx b/packages/visual-editor/src/fields/ImageField.test.tsx index c2b62d266..d2634f07b 100644 --- a/packages/visual-editor/src/fields/ImageField.test.tsx +++ b/packages/visual-editor/src/fields/ImageField.test.tsx @@ -5,11 +5,12 @@ import { TemplatePropsContext } from "../hooks/useDocument.tsx"; import { YextAutoField } from "./YextAutoField.tsx"; import { type ImageField } from "./ImageField.tsx"; -const { sendToParentMock, translatableStringFieldMock } = vi.hoisted(() => ({ +const { sendToParentMock } = vi.hoisted(() => ({ sendToParentMock: vi.fn(), - translatableStringFieldMock: vi.fn(), })); +const initialUrl = window.location.href; + vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); @@ -29,35 +30,20 @@ vi.mock("../internal/hooks/useMessage.ts", () => ({ }), })); -vi.mock("../internal/hooks/useMessageReceivers.ts", () => ({ - useTemplateMetadata: () => ({ - locatorDisplayFields: { - c_title: { - field_type_id: "type.string", - field_name: "Title", - }, - c_photo: { - field_type_id: "type.image", - field_name: "Photo", - }, - }, - }), -})); - -vi.mock("./TranslatableStringField.tsx", async (importOriginal) => { +vi.mock("../internal/hooks/useMessageReceivers.ts", async (importOriginal) => { const actual = - await importOriginal(); + await importOriginal< + typeof import("../internal/hooks/useMessageReceivers.ts") + >(); return { ...actual, - TranslatableStringField: translatableStringFieldMock, + useTemplateMetadata: () => ({ + locatorDisplayFields: {}, + }), }; }); -vi.mock("../utils/isFakeStarterLocalDev.ts", () => ({ - isFakeStarterLocalDev: () => true, -})); - const renderImageField = ( field: ImageField = { type: "image", @@ -67,11 +53,6 @@ const renderImageField = ( ) => { const onChange = vi.fn(); - translatableStringFieldMock.mockImplementation((label: string) => ({ - type: "text", - label, - })); - render( { afterEach(() => { vi.restoreAllMocks(); - translatableStringFieldMock.mockReset(); sendToParentMock.mockReset(); + window.history.replaceState({}, "", initialUrl); }); - it("renders through YextAutoField as a registered field type", () => { + it("prompts for an image URL in fake starter local dev", () => { + window.history.replaceState({}, "", "/dev-location/example"); + const promptSpy = vi .spyOn(window, "prompt") .mockReturnValue("https://example.com/image.jpg"); @@ -115,43 +98,25 @@ describe("ImageField", () => { }); }); - it("passes locator alt text options into the alt text field", () => { - const getAltTextOptions = vi.fn((templateMetadata) => [ - { - label: templateMetadata.locatorDisplayFields?.c_title?.field_name ?? "", - value: "c_title", - }, - ]); + it("prompts for an image URL in local-editor", () => { + window.history.replaceState({}, "", "/local-editor"); - renderImageField( - { - type: "image", - label: "Image", - getAltTextOptions, - }, - { - en: { - alternateText: "", - url: "https://example.com/image.jpg", - height: 1, - width: 1, - }, - hasLocalizedValue: "true", - } - ); - - expect(getAltTextOptions).toHaveBeenCalledWith({ - locatorDisplayFields: { - c_title: { - field_type_id: "type.string", - field_name: "Title", - }, - c_photo: { - field_type_id: "type.image", - field_name: "Photo", - }, + const promptSpy = vi + .spyOn(window, "prompt") + .mockReturnValue("https://example.com/local-editor-image.jpg"); + const { onChange } = renderImageField(); + + fireEvent.click(screen.getByRole("button", { name: "Choose Image" })); + + expect(promptSpy).toHaveBeenCalledWith("Enter Image URL:"); + expect(onChange).toHaveBeenCalledWith({ + en: { + alternateText: "", + url: "https://example.com/local-editor-image.jpg", + height: 1, + width: 1, }, + hasLocalizedValue: "true", }); - expect(screen.getByText("Alt Text (en)")).toBeDefined(); }); }); diff --git a/packages/visual-editor/src/fields/ImageField.tsx b/packages/visual-editor/src/fields/ImageField.tsx index 2712a5650..1e06ff7d0 100644 --- a/packages/visual-editor/src/fields/ImageField.tsx +++ b/packages/visual-editor/src/fields/ImageField.tsx @@ -25,7 +25,10 @@ import { TemplateMetadata, } from "../internal/types/templateMetadata.ts"; import { useTemplateMetadata } from "../internal/hooks/useMessageReceivers.ts"; -import { isFakeStarterLocalDev } from "../utils/isFakeStarterLocalDev.ts"; +import { + isFakeStarterLocalDev, + isLocalEditorRoute, +} from "../utils/isFakeStarterLocalDev.ts"; import { YextAutoField } from "./YextAutoField.tsx"; import { type EmbeddedStringOption } from "../editor/EmbeddedFieldStringInput.tsx"; @@ -110,7 +113,10 @@ export const ImageFieldOverride = ({ e.preventDefault(); /** Handles local development testing outside of Storm */ - if (isFakeStarterLocalDev()) { + if ( + isFakeStarterLocalDev() || + (typeof window !== "undefined" && isLocalEditorRoute(window.location)) + ) { const userInput = prompt("Enter Image URL:"); if (!userInput) { return; diff --git a/packages/visual-editor/src/utils/isFakeStarterLocalDev.test.ts b/packages/visual-editor/src/utils/isFakeStarterLocalDev.test.ts index f4513c986..f0de2161c 100644 --- a/packages/visual-editor/src/utils/isFakeStarterLocalDev.test.ts +++ b/packages/visual-editor/src/utils/isFakeStarterLocalDev.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { isFakeStarterLocalDev, isFakeStarterLocalDevRoute, + isLocalEditorRoute, } from "./isFakeStarterLocalDev.ts"; const initialUrl = window.location.href; @@ -26,6 +27,18 @@ describe("isFakeStarterLocalDevRoute", () => { }); }); +describe("isLocalEditorRoute", () => { + it("matches the local editor route", () => { + expect(isLocalEditorRoute("http://localhost:5173/local-editor")).toBe(true); + expect(isLocalEditorRoute("/local-editor")).toBe(true); + }); + + it("does not match other routes", () => { + expect(isLocalEditorRoute("http://localhost:5173/edit")).toBe(false); + expect(isLocalEditorRoute("/dev-location/example")).toBe(false); + }); +}); + describe("isFakeStarterLocalDev", () => { it("checks the current window location", () => { window.history.replaceState({}, "", "/dev-locator/locator-slug"); diff --git a/packages/visual-editor/src/utils/isFakeStarterLocalDev.ts b/packages/visual-editor/src/utils/isFakeStarterLocalDev.ts index 7c24b0ad5..9ef289b6f 100644 --- a/packages/visual-editor/src/utils/isFakeStarterLocalDev.ts +++ b/packages/visual-editor/src/utils/isFakeStarterLocalDev.ts @@ -16,6 +16,10 @@ export const isFakeStarterLocalDevRoute = (locationLike: LocationLike) => { return getPathname(locationLike).startsWith("/dev-"); }; +export const isLocalEditorRoute = (locationLike: LocationLike) => { + return getPathname(locationLike) === "/local-editor"; +}; + export const isFakeStarterLocalDev = () => { if (typeof window === "undefined") { return false;