diff --git a/__tests__/editorUrl.test.ts b/__tests__/editorUrl.test.ts new file mode 100644 index 00000000..97d0b27e --- /dev/null +++ b/__tests__/editorUrl.test.ts @@ -0,0 +1,13 @@ +import { getInitialJSONFromSearch } from "@/containers/editor/editor/url"; + +describe("editor URL params", () => { + test("reads encoded json param", () => { + const json = '{"hello":"world","count":1}'; + + expect(getInitialJSONFromSearch(`?json=${encodeURIComponent(json)}`)).toBe(json); + }); + + test("returns undefined when json param is absent", () => { + expect(getInitialJSONFromSearch("?foo=bar")).toBeUndefined(); + }); +}); diff --git a/e2e/tests/editor.spec.ts b/e2e/tests/editor.spec.ts index 2c5ad0e6..19c3acd8 100644 --- a/e2e/tests/editor.spec.ts +++ b/e2e/tests/editor.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "@playwright/test"; -import { clearEditor, getEditor, selectAllInEditor, writeToClipboard } from "../helpers/utils"; +import { clearEditor, getEditor, getEditorText, selectAllInEditor, writeToClipboard } from "../helpers/utils"; test.describe("edit in the editor", () => { test.beforeEach(async ({ page }) => { @@ -37,4 +37,15 @@ test.describe("edit in the editor", () => { await page.keyboard.type("{"); await expect(page.getByRole("treeitem").getByText("hello")).toBeVisible(); }); + + test("loads json from URL params", async ({ page }) => { + const json = JSON.stringify({ hello: "world", count: 1 }); + + await page.goto(`/editor?json=${encodeURIComponent(json)}`); + await getEditor(page); + + await expect(page.getByRole("treeitem").getByText("hello")).toBeVisible(); + await expect(page.getByRole("treeitem").getByText("world")).toBeVisible(); + await expect.poll(() => getEditorText(page)).toContain('"count": 1'); + }); }); diff --git a/src/containers/editor/editor/Editor.tsx b/src/containers/editor/editor/Editor.tsx index c0598d07..078c738d 100644 --- a/src/containers/editor/editor/Editor.tsx +++ b/src/containers/editor/editor/Editor.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, type ComponentPropsWithoutRef } from "react"; +import { useEffect, useRef, type ComponentPropsWithoutRef } from "react"; import Loading from "@/components/Loading"; import { vsURL } from "@/lib/editor/cdn"; import { EditorWrapper, type Kind } from "@/lib/editor/editor"; @@ -10,6 +10,7 @@ import { loader, Editor as MonacoEditor } from "@monaco-editor/react"; import { useTranslations } from "next-intl"; import { useShallow } from "zustand/shallow"; import { example } from "./data"; +import { getInitialJSONFromSearch } from "./url"; loader.config({ paths: { vs: vsURL } }); @@ -105,10 +106,21 @@ export function useEditTree(kind: Kind) { function useDisplayExample(kind: Kind) { const editor = useEditor("main"); const incrEditorInitCount = useStatusStore((state) => state.incrEditorInitCount); + const didSetInitialText = useRef(false); useEffect(() => { - if (kind === "main" && editor && incrEditorInitCount() <= 1) { - editor.parseAndSet(example); + if (kind !== "main" || !editor || didSetInitialText.current) { + return; } + + const initialJSON = getInitialJSONFromSearch(window.location.search); + const initialText = initialJSON ?? (incrEditorInitCount() <= 1 ? example : undefined); + + if (initialText === undefined) { + return; + } + + didSetInitialText.current = true; + editor.parseAndSet(initialText); }, [editor]); } diff --git a/src/containers/editor/editor/url.ts b/src/containers/editor/editor/url.ts new file mode 100644 index 00000000..f10f6ba9 --- /dev/null +++ b/src/containers/editor/editor/url.ts @@ -0,0 +1,6 @@ +export const initialJSONParamName = "json"; + +export function getInitialJSONFromSearch(search: string) { + const searchParams = new URLSearchParams(search); + return searchParams.has(initialJSONParamName) ? searchParams.get(initialJSONParamName)! : undefined; +}