Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live Preview Button Opens the Corresponding Static/Entity Page. #379

Merged
merged 10 commits into from
Sep 25, 2023
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
2 changes: 1 addition & 1 deletion e2e-tests/tests/infra/StudioPlaywrightPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default class StudioPlaywrightPage {
name: "Remove Element",
});

this.livePreviewButton = page.getByRole("link", {
this.livePreviewButton = page.getByRole("button", {
name: "Live Preview",
});

Expand Down
14 changes: 0 additions & 14 deletions e2e-tests/tests/open-live-preview.spec.ts

This file was deleted.

1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/studio-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"react-select": "^5.7.4",
"react-toastify": "^9.1.1",
"react-tooltip": "^5.18.0",
"true-myth": "^6.2.0",
"tailwind-merge": "^1.8.1",
"tailwindcss": "^3.3.3",
"zundo": "2.0.0-beta.12",
Expand Down
79 changes: 69 additions & 10 deletions packages/studio-ui/src/components/OpenLivePreviewButton.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,78 @@
import { PageState, PagesJsState } from "@yext/studio-plugin";
import useStudioStore from "../store/useStudioStore";
import classNames from "classnames";
import { Result } from "true-myth";
import { useCallback } from "react";

// The URL of the Landing Page of the PagesJS Dev Server. Port 5173 is hardcoded for now as it will be the PagesJS Dev port in most cases.
export const PAGES_JS_LANDING_PAGE = "http://localhost:5173";
tmeyer2115 marked this conversation as resolved.
Show resolved Hide resolved

/**
* OpenLivePreviewButton is a button that when clicked, opens the
* pages development server index page in a new tab.
* Port 5173 hardcoded for now as it will be the pages dev port in most cases
* The button rendered by this Component opens a PagesJS-powered Preview of the current Page in a new tab.
* If the Page is an Entity Template, the Preview will use the currently selected Entity Data.
*/
export default function OpenLivePreviewButton(): JSX.Element | null {
export default function OpenLivePreviewButton(): JSX.Element {
const [activePageName, activePageState, activeEntityData] = useStudioStore(
(store) => [
store.pages.activePageName as string,
store.pages.getActivePageState(),
store.pages.getActiveEntityData(),
]
);

const livePreviewUrlResult = getLivePreviewUrl(
activePageState,
activePageName,
activeEntityData
);

const onClick = useCallback(() => {
livePreviewUrlResult.map((url) => window.open(url, "_blank"));
}, [livePreviewUrlResult]);

const buttonClasses = classNames(
"rounded-md px-2 py-1 flex items-center gap-x-2 text-white",
{
"bg-gray-400": livePreviewUrlResult.isErr,
"bg-blue-600 shadow-md hover:bg-blue-700 hover:shadow-lg":
livePreviewUrlResult.isOk,
}
);

return (
<div className="relative inline-block">
<a
className="rounded-md text-white bg-blue-600 px-2 py-1 flex items-center gap-x-2 shadow-md hover:bg-blue-700 hover:shadow-lg"
href="http://localhost:5173/"
target="_blank"
rel="noreferrer"
<button
alextaing marked this conversation as resolved.
Show resolved Hide resolved
className={buttonClasses}
disabled={livePreviewUrlResult.isErr}
onClick={onClick}
>
Live Preview
</a>
</button>
</div>
);
}

function getLivePreviewUrl(
activePageState: PageState | undefined,
pageName: string,
entityData?: Record<string, unknown>
): Result<string, Error> {
const isActivePagesJSPage = !!activePageState?.pagesJS;

if (isActivePagesJSPage) {
const pagesJSState = activePageState.pagesJS as PagesJsState;
const isEntityPage = !!pagesJSState.streamScope;
if (isEntityPage) {
return entityData?.id
? Result.ok(`${PAGES_JS_LANDING_PAGE}/${pageName}/${entityData.id}`)
: Result.err(new Error("Cannot create Preview URL for Entity Page"));
}

const getPathVal = pagesJSState.getPathValue;
return !!getPathVal
? Result.ok(`${PAGES_JS_LANDING_PAGE}/${getPathVal.value}`)
: Result.err(new Error("Cannot create Preview URL for Static Page"));
} else {
return Result.err(new Error("There is no active PagesJS Template"));
}
}
159 changes: 150 additions & 9 deletions packages/studio-ui/tests/components/OpenLivePreviewButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,155 @@
import { render, screen } from "@testing-library/react";
import OpenLivePreviewButton from "../../src/components/OpenLivePreviewButton";
import OpenLivePreviewButton, {
PAGES_JS_LANDING_PAGE,
} from "../../src/components/OpenLivePreviewButton";
import mockActivePage from "../__utils__/mockActivePage";
import { PageState, PropValueKind } from "@yext/studio-plugin";
import mockStore from "../__utils__/mockStore";

it("renders the live preview button", () => {
render(<OpenLivePreviewButton />);
expect(screen.getByRole("link")).toBeDefined();
expect(screen.getByRole("link").textContent).toBe("Live Preview");
const ensureButtonAppearance = (
button: HTMLElement,
shouldBeEnabled: boolean
) => {
expect(button).toBeDefined();
expect(button.textContent).toBe("Live Preview");
shouldBeEnabled
? expect(button).toBeEnabled()
: expect(button).toBeDisabled();
};

describe("button is disabled properly", () => {
it("disables the button when there is no active page", () => {
mockStore({
pages: {
getActivePageState: () => undefined,
getActiveEntityData: () => undefined,
},
});

render(<OpenLivePreviewButton />);
const button = screen.getByRole("button");
ensureButtonAppearance(button, false);
});

it("disables the button when active page is not a PagesJS Template", () => {
const pageState: PageState = {
componentTree: [],
cssImports: [],
filepath: "some/file/path.tsx",
};
mockActivePage(pageState);

render(<OpenLivePreviewButton />);
const button = screen.getByRole("button");
ensureButtonAppearance(button, false);
});

it("disables the button for Static page without a valid GetPathVal", () => {
const pageState: PageState = {
componentTree: [],
cssImports: [],
filepath: "some/file/path.tsx",
pagesJS: {
getPathValue: undefined,
},
};
mockActivePage(pageState);

render(<OpenLivePreviewButton />);
const button = screen.getByRole("button");
ensureButtonAppearance(button, false);
});

it("when an Entity's localData doesn't contain an id, the button is disabled", () => {
const pageState: PageState = {
componentTree: [],
cssImports: [],
filepath: "some/file/path.tsx",
pagesJS: {
getPathValue: undefined,
streamScope: {
entityIds: ["abc"],
},
},
};

mockStore({
pages: {
activePageName: "testpage",
pages: {
testpage: pageState,
},
getActiveEntityData: () => {
return {};
},
},
});

render(<OpenLivePreviewButton />);
const button = screen.getByRole("button");
ensureButtonAppearance(button, false);
});
});

it("opens the pages development server when clicked", () => {
render(<OpenLivePreviewButton />);
expect(screen.getByRole("link")).toBeDefined();
expect(screen.getByRole("link").textContent).toBe("Live Preview");
describe("button links to correct preview url", () => {
window.open = jest.fn();

it("preview url for Static Page is correct", () => {
const pageState: PageState = {
componentTree: [],
cssImports: [],
filepath: "some/file/path.tsx",
pagesJS: {
getPathValue: {
kind: PropValueKind.Literal,
value: "static-page",
},
},
};
mockActivePage(pageState);

render(<OpenLivePreviewButton />);
const button = screen.getByRole("button");
ensureButtonAppearance(button, true);
button.click();
expect(window.open).toBeCalledWith(
`${PAGES_JS_LANDING_PAGE}/static-page`,
"_blank"
);
});

it("preview url for Entity Page is correct", () => {
const pageState: PageState = {
componentTree: [],
cssImports: [],
filepath: "some/file/path.tsx",
pagesJS: {
getPathValue: undefined,
streamScope: {
entityIds: ["abc"],
},
},
};

mockStore({
pages: {
activePageName: "testpage",
pages: {
testpage: pageState,
},
getActiveEntityData: () => {
return { id: "123" };
},
},
});

render(<OpenLivePreviewButton />);
const button = screen.getByRole("button");
ensureButtonAppearance(button, true);
button.click();
expect(window.open).toBeCalledWith(
`${PAGES_JS_LANDING_PAGE}/testpage/123`,
"_blank"
);
});
});