diff --git a/workspaces/e2e/tests/branch/branch.html b/workspaces/e2e/tests/branch/branch.html index dfc906e..ddabe89 100644 --- a/workspaces/e2e/tests/branch/branch.html +++ b/workspaces/e2e/tests/branch/branch.html @@ -7,12 +7,14 @@ +
+
diff --git a/workspaces/e2e/tests/branch/branch.spec.ts b/workspaces/e2e/tests/branch/branch.spec.ts index f7143cb..c2904da 100644 --- a/workspaces/e2e/tests/branch/branch.spec.ts +++ b/workspaces/e2e/tests/branch/branch.spec.ts @@ -34,3 +34,22 @@ test("branch can have multiple steps", async ({ page }) => { await page.locator(".flows-continue").click(); await expect(page.locator(".flows-tooltip")).toContainText("Last Step"); }); +test("should reset flow when entering branch without targetBranch", async ({ page }) => { + await page.goto("/branch/branch.html?hideNext=false&logErrors=true"); + await expect(page.locator(".flows-tooltip")).toBeVisible(); + await page.locator(".flows-continue").click(); + await expect(page.locator(".flows-tooltip")).toBeHidden(); + await page.locator(".start-flow").click(); + await expect(page.locator(".flows-tooltip")).toBeVisible(); + await expect(page.locator("[data-type='invalidStepError']")).toHaveCount(1); +}); +test("should reset flow when entering out of bound step", async ({ page }) => { + await page.goto("/branch/branch.html?lastStep=true&logErrors=true"); + await page.locator(".enter-1").click(); + await page.locator(".flows-continue").click(); + await page.locator(".flows-option").click(); + await expect(page.locator(".flows-tooltip")).toBeHidden(); + await page.locator(".start-flow").click(); + await expect(page.locator(".flows-tooltip")).toBeVisible(); + await expect(page.locator("[data-type='invalidStepError']")).toHaveCount(1); +}); diff --git a/workspaces/e2e/tests/branch/branch.ts b/workspaces/e2e/tests/branch/branch.ts index 8c99c62..9462ccc 100644 --- a/workspaces/e2e/tests/branch/branch.ts +++ b/workspaces/e2e/tests/branch/branch.ts @@ -1,13 +1,15 @@ import type { FlowSteps } from "@flows/js"; -import { init } from "@flows/js/core"; +import { init, startFlow } from "@flows/js/core"; const lastStep = new URLSearchParams(window.location.search).get("lastStep") === "true"; +const hideNext = new URLSearchParams(window.location.search).get("hideNext") !== "false"; +const logErrors = new URLSearchParams(window.location.search).get("logErrors") === "true"; const steps: FlowSteps = [ { targetElement: ".target", title: "Hello", - hideNext: true, + hideNext, wait: [ { clickElement: ".enter-1", targetBranch: 0 }, { clickElement: ".enter-2", targetBranch: 1 }, @@ -43,6 +45,7 @@ if (lastStep) steps.push({ targetElement: ".target", title: "Last Step", + footerActions: { right: [{ label: "Continue", targetBranch: undefined }] }, }); void init({ @@ -53,4 +56,19 @@ void init({ steps, }, ], + _debug: async (e) => { + if (logErrors) { + const p = document.createElement("p"); + p.classList.add("log-item"); + p.dataset.type = e.type; + p.dataset.referenceId = e.referenceId; + p.innerText = JSON.stringify(e); + document.querySelector(".log")?.appendChild(p); + } + return { referenceId: "" }; + }, +}); + +document.querySelector(".start-flow")?.addEventListener("click", () => { + startFlow("flow"); }); diff --git a/workspaces/e2e/tests/error/error.html b/workspaces/e2e/tests/tooltip-error/tooltip-error.html similarity index 89% rename from workspaces/e2e/tests/error/error.html rename to workspaces/e2e/tests/tooltip-error/tooltip-error.html index 32e05ad..6656db8 100644 --- a/workspaces/e2e/tests/error/error.html +++ b/workspaces/e2e/tests/tooltip-error/tooltip-error.html @@ -12,6 +12,6 @@
- + diff --git a/workspaces/e2e/tests/error/error.spec.ts b/workspaces/e2e/tests/tooltip-error/tooltip-error.spec.ts similarity index 88% rename from workspaces/e2e/tests/error/error.spec.ts rename to workspaces/e2e/tests/tooltip-error/tooltip-error.spec.ts index 4054840..fdf0a3a 100644 --- a/workspaces/e2e/tests/error/error.spec.ts +++ b/workspaces/e2e/tests/tooltip-error/tooltip-error.spec.ts @@ -1,14 +1,14 @@ import { expect, test } from "@playwright/test"; test("Emits error event", async ({ page }) => { - await page.goto("/error/error.html"); + await page.goto("/tooltip-error/tooltip-error.html"); await page.locator(".start-flow").click(); await expect(page.locator("[data-type='tooltipError']")).toHaveCount(1); await expect(page.locator("[data-type='invalidateTooltipError']")).toHaveCount(0); }); test("should not emit any error event if it gets invalidates quickly", async ({ page }) => { - await page.goto("/error/error.html"); + await page.goto("/tooltip-error/tooltip-error.html"); await page.locator(".start-flow").click(); await expect(page.locator("[data-type='tooltipError']")).toHaveCount(0); await page.locator(".add-target").click(); @@ -21,7 +21,7 @@ test("should not emit any error event if it gets invalidates quickly", async ({ test("should emit error and invalidate it if it gets invalidated after a while", async ({ page, }) => { - await page.goto("/error/error.html"); + await page.goto("/tooltip-error/tooltip-error.html"); await page.locator(".start-flow").click(); await expect(page.locator("[data-type='tooltipError']")).toHaveCount(1); await page.locator(".add-target").click(); diff --git a/workspaces/e2e/tests/error/error.ts b/workspaces/e2e/tests/tooltip-error/tooltip-error.ts similarity index 100% rename from workspaces/e2e/tests/error/error.ts rename to workspaces/e2e/tests/tooltip-error/tooltip-error.ts diff --git a/workspaces/js/css/template.css b/workspaces/js/css/template.css index 010360e..e746beb 100644 --- a/workspaces/js/css/template.css +++ b/workspaces/js/css/template.css @@ -110,8 +110,6 @@ left: 0; width: 100%; height: 100%; - -webkit-mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px' viewBox='0 0 16 16'%3e%3cpath fill='currentColor' d='M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z' /%3e%3c/svg%3e") - 50%; mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px' viewBox='0 0 16 16'%3e%3cpath fill='currentColor' d='M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z' /%3e%3c/svg%3e") 50%; background-color: var(--flows-fg-default); @@ -168,7 +166,6 @@ width: var(--flows-size-medium); height: var(--flows-size-medium); transform: rotate(45deg); - -webkit-transform: rotate(45deg); border-radius: var(--flows-borderRadius-xSmall); } .flows-arrow-bottom { diff --git a/workspaces/js/src/core/flow-state.ts b/workspaces/js/src/core/flow-state.ts index d52f216..eb0097f 100644 --- a/workspaces/js/src/core/flow-state.ts +++ b/workspaces/js/src/core/flow-state.ts @@ -1,6 +1,7 @@ import type { DebugEvent, Flow, FlowStep, FlowStepIndex, TrackingEvent } from "../types"; import { hash } from "../lib/hash"; import { isModalStep, isTooltipStep } from "../lib/step-type"; +import { log } from "../lib/log"; import { render } from "./render"; import type { FlowsContext } from "./flows-context"; @@ -73,6 +74,10 @@ export class FlowState { ...props, }); } + + /** + * Called when entering a step or when the flow is started or recreated from local storage. + */ enterStep(): this { const step = this.currentStep; if (step && isTooltipStep(step) && !this.tooltipErrorPromise) @@ -83,6 +88,19 @@ export class FlowState { }); }, 1000); + const isFork = Array.isArray(step); + if (isFork) { + log.error("Stopping flow: entered invalid step, make sure to use targetBranch"); + void this.debug({ type: "invalidStepError" }); + this.destroy(); + } + const isOutOfBoundStep = !step && this.flow?._incompleteSteps !== true; + if (isOutOfBoundStep) { + log.error("Stopping flow: entered out of bound step"); + void this.debug({ type: "invalidStepError" }); + this.destroy(); + } + return this; } diff --git a/workspaces/js/src/core/init.ts b/workspaces/js/src/core/init.ts index 41d15d5..7e641f6 100644 --- a/workspaces/js/src/core/init.ts +++ b/workspaces/js/src/core/init.ts @@ -176,4 +176,5 @@ const _init = (options: FlowsInitOptions): void => { ]); startFlowsBasedOnLocation(); + FlowsContext.getInstance().onLocationChange?.(getPathname(), FlowsContext.getInstance()); }; diff --git a/workspaces/js/src/core/location-change.ts b/workspaces/js/src/core/location-change.ts index 9ef6595..9c4bc0d 100644 --- a/workspaces/js/src/core/location-change.ts +++ b/workspaces/js/src/core/location-change.ts @@ -36,9 +36,9 @@ export const handleLocationChange = (): void => { }); if (matchingWait) state.nextStep(matchingWait.targetBranch).render(); } - - startFlowsBasedOnLocation(); }); + + startFlowsBasedOnLocation(); }; export const startFlowsBasedOnLocation = (): void => { diff --git a/workspaces/js/src/types/options.ts b/workspaces/js/src/types/options.ts index 86e4525..cd05736 100644 --- a/workspaces/js/src/types/options.ts +++ b/workspaces/js/src/types/options.ts @@ -29,7 +29,7 @@ export interface TrackingEvent { export type Tracking = (event: TrackingEvent) => void; export interface DebugEvent extends Omit { - type: "tooltipError" | "invalidateTooltipError"; + type: "tooltipError" | "invalidateTooltipError" | "invalidStepError"; /** * referenceId of the event that the current event is invalidating. */