Skip to content

Commit

Permalink
refactor: centralise state management with PreviewIframeState (#2182)
Browse files Browse the repository at this point in the history
  • Loading branch information
fwouts committed Nov 3, 2023
1 parent 4fcb592 commit bb779ea
Show file tree
Hide file tree
Showing 25 changed files with 303 additions and 264 deletions.
4 changes: 2 additions & 2 deletions app/client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const iframe = document.getElementById("root") as HTMLIFrameElement;
const rpcApi = createAxiosApi("/api/");
const iframeController = createController({
getIframe: () => iframe,
listener: (event) => {
console.log(event);
onStateUpdate: (state) => {
console.log(state);
},
});

Expand Down
10 changes: 4 additions & 6 deletions chromeless/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import type { PreviewEvent, RenderOptions } from "@previewjs/iframe";
import type { PreviewIframeState, RenderOptions } from "@previewjs/iframe";
import { createController } from "@previewjs/iframe";

declare global {
interface Window {
loadIframePreview(previewableId: string, options: RenderOptions): void;
onIframeEvent?(event: PreviewEvent): void;
onIframeStateUpdate?(state: PreviewIframeState): void;
}
}

const controller = createController({
getIframe: () => document.getElementById("iframe") as HTMLIFrameElement,
listener: (event) => {
if (window.onIframeEvent) {
window.onIframeEvent(event);
}
onStateUpdate: (state) => {
window.onIframeStateUpdate?.(state);
},
});

Expand Down
22 changes: 0 additions & 22 deletions chromeless/src/event-listener.ts

This file was deleted.

12 changes: 6 additions & 6 deletions chromeless/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
} from "@previewjs/properties";
import type playwright from "playwright";
import ts from "typescript";
import { setupPreviewEventListener } from "./event-listener.js";
import { getPreviewIframe } from "./iframe.js";
import { setupPreviewStateListener } from "./state-listener.js";

export async function startPreview({
workspace,
Expand All @@ -31,16 +31,16 @@ export async function startPreview({
// No-op by default.
};

const events = await setupPreviewEventListener(page, (event) => {
if (event.kind === "rendered") {
const getState = await setupPreviewStateListener(page, (state) => {
if (state.rendered) {
onRenderingDone();
} else if (event.kind === "error") {
onRenderingError(new Error(event.message));
} else if (state.errors.length > 0) {
onRenderingError(new Error(state.errors[0]!.message));
}
});

return {
events,
getState,
iframe: {
async waitForIdle() {
await waitUntilNetworkIdle(page);
Expand Down
23 changes: 23 additions & 0 deletions chromeless/src/state-listener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { PreviewIframeState } from "@previewjs/iframe";
import type { Page } from "playwright";

export async function setupPreviewStateListener(
page: Page,
listener: (state: PreviewIframeState) => void
) {
let state: PreviewIframeState = {
loading: false,
rendered: false,
errors: [],
logs: [],
actions: [],
};
await page.exposeFunction(
"onIframeStateUpdate",
(newState: PreviewIframeState) => {
state = newState;
listener(state);
}
);
return () => state;
}
75 changes: 41 additions & 34 deletions framework-plugins/react/tests/action-logs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,23 @@ for (const reactVersion of reactVersions()) {
);
await preview.show("src/Button.tsx:Button");
const button = await preview.iframe.waitForSelector("#button");
preview.events.clear();
await button.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "onClick",
type: "fn",
},
{
kind: "log-message",
level: "log",
message: "onClick invoked",
},
]);
expect(preview.getState()).toMatchObject({
logs: [
{
kind: "log-message",
level: "log",
message: "onClick invoked",
},
],
actions: [
{
kind: "action",
path: "onClick",
type: "fn",
},
],
});
});

test("emits action event for explicit callbacks", async (preview) => {
Expand All @@ -64,20 +67,23 @@ for (const reactVersion of reactVersions()) {
}`
);
const button = await preview.iframe.waitForSelector("#button");
preview.events.clear();
await button.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "onClick",
type: "fn",
},
{
kind: "log-message",
level: "log",
message: "onClick callback invoked!",
},
]);
expect(preview.getState()).toMatchObject({
logs: [
{
kind: "log-message",
level: "log",
message: "onClick callback invoked!",
},
],
actions: [
{
kind: "action",
path: "onClick",
type: "fn",
},
],
});
});

test("shows action logs on link click", async (preview) => {
Expand All @@ -93,15 +99,16 @@ for (const reactVersion of reactVersions()) {
);
await preview.show("src/Link.tsx:Link");
const link = await preview.iframe.waitForSelector("#link");
preview.events.clear();
await link.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "https://www.google.com/",
type: "url",
},
]);
expect(preview.getState()).toMatchObject({
actions: [
{
kind: "action",
path: "https://www.google.com/",
type: "url",
},
],
});
});
});
});
Expand Down
1 change: 0 additions & 1 deletion framework-plugins/react/tests/console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ for (const reactVersion of reactVersions()) {
);
await preview.iframe.waitForSelector("#update-1");
await preview.expectLoggedMessages.toMatch(["Render 1"], "log");
preview.events.clear();
await preview.fileManager.update(
"src/App.tsx",
`function App() {
Expand Down
13 changes: 2 additions & 11 deletions framework-plugins/react/tests/error-handling.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, test } from "@playwright/test";
import { test } from "@playwright/test";
import { previewTest } from "@previewjs/testing";
import path from "path";
import url from "url";
Expand Down Expand Up @@ -32,16 +32,7 @@ test.describe.parallel("react/error handling", () => {
await preview.show("src/App.tsx:App").catch(() => {
/* expected error */
});
expect(await preview.events.get()).toEqual([
{
kind: "bootstrapping",
},
{
kind: "error",
source: "load",
message: "Expected error",
},
]);
await preview.expectErrors.toMatch(["Expected error"]);
});

test("handles syntax errors gracefully", async (preview) => {
Expand Down
75 changes: 41 additions & 34 deletions framework-plugins/solid/tests/action-logs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,23 @@ test.describe.parallel("solid/action logs", () => {
);
await preview.show("src/Button.tsx:Button");
const button = await preview.iframe.waitForSelector("#button");
preview.events.clear();
await button.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "onClick",
type: "fn",
},
{
kind: "log-message",
level: "log",
message: "onClick invoked",
},
]);
expect(preview.getState()).toMatchObject({
logs: [
{
kind: "log-message",
level: "log",
message: "onClick invoked",
},
],
actions: [
{
kind: "action",
path: "onClick",
type: "fn",
},
],
});
});

test("emits action event for explicit callbacks", async (preview) => {
Expand All @@ -63,20 +66,23 @@ test.describe.parallel("solid/action logs", () => {
}`
);
const button = await preview.iframe.waitForSelector("#button");
preview.events.clear();
await button.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "onClick",
type: "fn",
},
{
kind: "log-message",
level: "log",
message: "onClick callback invoked!",
},
]);
expect(preview.getState()).toMatchObject({
logs: [
{
kind: "log-message",
level: "log",
message: "onClick callback invoked!",
},
],
actions: [
{
kind: "action",
path: "onClick",
type: "fn",
},
],
});
});

test("shows action logs on link click", async (preview) => {
Expand All @@ -94,14 +100,15 @@ test.describe.parallel("solid/action logs", () => {
);
await preview.show("src/Link.tsx:Link");
const link = await preview.iframe.waitForSelector("#link");
preview.events.clear();
await link.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "https://www.google.com/",
type: "url",
},
]);
expect(preview.getState()).toMatchObject({
actions: [
{
kind: "action",
path: "https://www.google.com/",
type: "url",
},
],
});
});
});
1 change: 0 additions & 1 deletion framework-plugins/solid/tests/console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ test.describe.parallel("solid/console", () => {
);
await preview.iframe.waitForSelector("#update-1");
await preview.expectLoggedMessages.toMatch(["Render 1"], "log");
preview.events.clear();
await preview.fileManager.update(
"src/App.tsx",
`function App() {
Expand Down
17 changes: 9 additions & 8 deletions framework-plugins/svelte/tests/action-logs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ test.describe.parallel("svelte/action logs", () => {
);
await preview.show("src/App.svelte:App");
const link = await preview.iframe.waitForSelector("#link");
preview.events.clear();
await link.click();
expect(preview.events.get()).toEqual([
{
kind: "action",
path: "https://www.google.com/",
type: "url",
},
]);
expect(preview.getState()).toMatchObject({
actions: [
{
kind: "action",
path: "https://www.google.com/",
type: "url",
},
],
});
});
});
1 change: 0 additions & 1 deletion framework-plugins/svelte/tests/console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ test.describe.parallel("svelte/console", () => {
);
await preview.iframe.waitForSelector(".App-updated-1");
await preview.expectLoggedMessages.toMatch(["Render 1"], "log");
preview.events.clear();
await preview.fileManager.update(
"src/App.svelte",
`<div class="App-updated-2">
Expand Down
5 changes: 1 addition & 4 deletions framework-plugins/svelte/tests/error-handling.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,7 @@ test.describe.parallel("svelte/error handling", () => {
"src/App.svelte",
"src/App-renamed.svelte"
);
await preview.expectErrors.toMatch([
"Failed to reload /src/App.svelte",
"Failed to reload /src/App.svelte",
]);
await preview.expectErrors.toMatch(["Failed to reload /src/App.svelte"]);
await preview.expectLoggedMessages.toMatch([]);
});
});
Loading

0 comments on commit bb779ea

Please sign in to comment.