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 {