diff --git a/workspaces/e2e/tests/cloud/cloud.html b/workspaces/e2e/tests/cloud/cloud.html new file mode 100644 index 0000000..560c8f0 --- /dev/null +++ b/workspaces/e2e/tests/cloud/cloud.html @@ -0,0 +1,17 @@ + + + + + + + + + +
+ + + + diff --git a/workspaces/e2e/tests/cloud/cloud.spec.ts b/workspaces/e2e/tests/cloud/cloud.spec.ts new file mode 100644 index 0000000..8cb4d74 --- /dev/null +++ b/workspaces/e2e/tests/cloud/cloud.spec.ts @@ -0,0 +1,26 @@ +import { expect, test } from "@playwright/test"; +import { invalidFlow, validFlow } from "./flow-mocks"; + +test("Should validate local flow", async ({ page }) => { + await page.route("**/sdk/flows?projectId=my-proj", (route) => route.fulfill({ json: [] })); + await page.goto("/cloud/cloud.html?validLocalFlow=true"); + await expect(page.locator(".flows-tooltip")).toBeVisible(); + await page.goto("/cloud/cloud.html?invalidLocalFlow=true"); + await expect(page.locator(".flows-tooltip")).toBeHidden(); +}); + +test("Should run valid cloud flow", async ({ page }) => { + await page.route("**/sdk/flows?projectId=my-proj", (route) => + route.fulfill({ json: [validFlow] }), + ); + await page.goto("/cloud/cloud.html"); + await expect(page.locator(".flows-tooltip")).toBeVisible(); +}); + +test("Should run invalid cloud flow", async ({ page }) => { + await page.route("**/sdk/flows?projectId=my-proj", (route) => + route.fulfill({ json: [invalidFlow] }), + ); + await page.goto("/cloud/cloud.html"); + await expect(page.locator(".flows-tooltip")).toBeVisible(); +}); diff --git a/workspaces/e2e/tests/cloud/cloud.ts b/workspaces/e2e/tests/cloud/cloud.ts new file mode 100644 index 0000000..f5ca4bf --- /dev/null +++ b/workspaces/e2e/tests/cloud/cloud.ts @@ -0,0 +1,15 @@ +import { init } from "@flows/js"; +import { invalidFlow, validFlow } from "./flow-mocks"; + +const validLocalFlow = new URLSearchParams(window.location.search).get("validLocalFlow") === "true"; +const invalidLocalFlow = + new URLSearchParams(window.location.search).get("invalidLocalFlow") === "true"; + +const flows = []; +if (validLocalFlow) flows.push(validFlow); +if (invalidLocalFlow) flows.push(invalidFlow); + +init({ + projectId: "my-proj", + flows, +}); diff --git a/workspaces/e2e/tests/cloud/flow-mocks.ts b/workspaces/e2e/tests/cloud/flow-mocks.ts new file mode 100644 index 0000000..7349f6a --- /dev/null +++ b/workspaces/e2e/tests/cloud/flow-mocks.ts @@ -0,0 +1,25 @@ +import { Flow } from "@flows/js"; + +export const validFlow: Flow = { + id: "valid-local-flow", + location: "/", + steps: [ + { + title: "Hello", + targetElement: ".target", + }, + ], +}; + +export const invalidFlow: Flow = { + id: "invalid-local-flow", + location: "/", + steps: [ + { + title: "Hello", + targetElement: ".target", + // @ts-ignore + invalidOption: "invalid", + }, + ], +}; diff --git a/workspaces/js/src/cloud/index.ts b/workspaces/js/src/cloud/index.ts index 4917e38..48bd161 100644 --- a/workspaces/js/src/cloud/index.ts +++ b/workspaces/js/src/cloud/index.ts @@ -50,6 +50,7 @@ export const init = async (options: FlowsCloudOptions): Promise => { return flowsInit({ ...options, + validate: false, flows: [...(options.flows ?? []), ...(flows || [])], tracking: (event) => { options.tracking?.(event); @@ -69,7 +70,7 @@ export const init = async (options: FlowsCloudOptions): Promise => { void api(apiUrl) .getPreviewFlow({ flowId, projectId }) .then((flow) => { - context.addFlowData({ ...flow, draft: true }); + context.addFlowData({ ...flow, draft: true }, { validate: false }); startFlow(flow.id, { startDraft: true }); }) .catch((err) => { @@ -80,7 +81,7 @@ export const init = async (options: FlowsCloudOptions): Promise => { void api(apiUrl) .getFlowDetail({ flowId, projectId: options.projectId }) .then((flow) => { - context.addFlowData(flow); + context.addFlowData(flow, { validate: false }); }) .catch((err) => { log.error("Failed to load flow detail", err); diff --git a/workspaces/js/src/core/flows-context.ts b/workspaces/js/src/core/flows-context.ts index c681f48..92b8ab3 100644 --- a/workspaces/js/src/core/flows-context.ts +++ b/workspaces/js/src/core/flows-context.ts @@ -127,14 +127,17 @@ export class FlowsContext { return this; }; - addFlowData(flow: Flow): this { - const validationResult = validateFlow(flow); - if (validationResult.error) - log.error( - `Error validating flow at: flow.${validationResult.error.path.join(".")} with value:`, - validationResult.error.value, - ); - if (!validationResult.valid) return this; + addFlowData(flow: Flow, { validate = true }: { validate?: boolean } = {}): this { + if (validate) { + const validationResult = validateFlow(flow); + if (validationResult.error) + log.error( + `Error validating flow at: flow.${validationResult.error.path.join(".")} with value:`, + validationResult.error.value, + ); + if (!validationResult.valid) return this; + } + if (!this.flowsById) this.flowsById = {}; this.flowsById[flow.id] = flow; this.startInstancesFromLocalStorage(); diff --git a/workspaces/js/src/core/init.ts b/workspaces/js/src/core/init.ts index de176ad..41d15d5 100644 --- a/workspaces/js/src/core/init.ts +++ b/workspaces/js/src/core/init.ts @@ -22,13 +22,16 @@ export const init = (options: FlowsInitOptions): Promise => }); const _init = (options: FlowsInitOptions): void => { - const validationResult = validateFlowsOptions(options); - if (validationResult.error) - log.error( - `Error validating options at: options.${validationResult.error.path.join(".")} with value:`, - validationResult.error.value, - ); - if (!validationResult.valid) return; + const validate = options.validate ?? true; + if (validate) { + const validationResult = validateFlowsOptions(options); + if (validationResult.error) + log.error( + `Error validating options at: options.${validationResult.error.path.join(".")} with value:`, + validationResult.error.value, + ); + if (!validationResult.valid) return; + } const context = FlowsContext.getInstance(); context.updateFromOptions(options); diff --git a/workspaces/js/src/types/options.ts b/workspaces/js/src/types/options.ts index 89f411e..86e4525 100644 --- a/workspaces/js/src/types/options.ts +++ b/workspaces/js/src/types/options.ts @@ -134,6 +134,11 @@ export type FlowsInitOptions = FlowsOptions & { projectId?: string; onLocationChange?: (pathname: string, context: FlowsContext) => void; onIncompleteFlowStart?: (flowId: string, context: FlowsContext) => void; + /** + * Validate options and flows before initializing. + * @defaultValue true + */ + validate?: boolean; }; export interface StartFlowOptions {