Skip to content

Commit

Permalink
test: buttons and footerActions
Browse files Browse the repository at this point in the history
  • Loading branch information
VojtechVidra committed Jan 16, 2024
1 parent 7016f65 commit fac5d97
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 41 deletions.
10 changes: 9 additions & 1 deletion public/flows.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,22 @@

.flows-footer {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-top: 12px;
gap: 8px;
}
.flows-footer div {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.flows-footer div:nth-child(2) {
flex: 1;
justify-content: center;
}
.flows-footer div:last-child {
justify-content: flex-end;
}

.flows-button {
background-color: var(--flows-background-subtle);
Expand Down
16 changes: 9 additions & 7 deletions src/log.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint-disable no-console -- We are defining user facing console logs */

const badge = "%cFlows%c";
const badgeCss = "color:#fff;background:#ec6441;padding:2px 4px;border-radius:4px";

export const log = {
error: (message: string, ...args: unknown[]): void => {
// eslint-disable-next-line no-console -- ignore
console.error(
`%cFlows%c ${message}`,
"color:#fff;background:#ec6441;padding:2px 4px;border-radius:4px",
"",
...args,
);
console.error(`${badge} ${message}`, badgeCss, "", ...args);
},
warn: (message: string, ...args: unknown[]): void => {
console.warn(`${badge} ${message}`, badgeCss, "", ...args);
},
};
35 changes: 29 additions & 6 deletions src/public-methods.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
import { FlowState } from "./flow-state";
import { FlowsContext } from "./flows-context";
import { log } from "./log";
import type { EndFlowOptions, FlowStep, IdentifyUserOptions, StartFlowOptions } from "./types";
import { flowUserPropertyGroupMatch } from "./user-properties";

export const startFlow = (flowId: string, { again, startDraft }: StartFlowOptions = {}): void => {
if (!flowId) return;
const warn = (message: string): void => {
log.warn(`Failed to start "${flowId}": ${message}`);
};

if (!flowId) {
warn("Missing flowId");
return;
}
const instances = FlowsContext.getInstance().instances;
if (instances.has(flowId)) return;
if (instances.has(flowId)) {
warn("Flow is already running");
return;
}

const flow = FlowsContext.getInstance().flowsById?.[flowId];
if (!flow) return;
if (!flow) {
warn("Missing Flow definition");
return;
}

if (flow.draft && !startDraft) return;
if (flow.draft && !startDraft) {
warn("Flow is not published");
return;
}

// If the flow is draft, we ignore targeting and frequency
if (!flow.draft) {
const flowFrequency = flow.frequency ?? "once";
const flowSeen = FlowsContext.getInstance().seenFlowIds.includes(flowId);
const frequencyMatch = !flowSeen || flowFrequency === "every-time" || again;
if (!frequencyMatch) return;
if (!frequencyMatch) {
warn("User has already seen the Flow");
return;
}

const userPropertiesMatch = flowUserPropertyGroupMatch(
FlowsContext.getInstance().userProperties,
flow.userProperties,
);
if (!userPropertiesMatch) return;
if (!userPropertiesMatch) {
warn("User is not allowed to see the Flow");
return;
}
}

const state = new FlowState(flowId, FlowsContext.getInstance());
Expand Down
39 changes: 15 additions & 24 deletions src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,34 +67,28 @@ const updateTooltip = ({

const getContinueButton = ({
state,
step,
children,
}: {
step: FlowTooltipStep | FlowModalStep;
children?: string;
state: FlowState;
}): HTMLElement =>
state.hasNextStep ? (
<button className="flows-continue flows-button">{step.nextText || "Continue"}</button>
<button className="flows-continue flows-button">{children || "Continue"}</button>
) : (
<button className="flows-finish flows-button">{step.nextText || "Finish"}</button>
<button className="flows-finish flows-button">{children || "Finish"}</button>
);
const getBackButton = ({
state: _state,
step,
}: {
step: FlowTooltipStep | FlowModalStep;
state: FlowState;
}): HTMLElement => <button className="flows-back flows-button">{step.prevText || "Back"}</button>;
const getBackButton = ({ children }: { children?: string }): HTMLElement => (
<button className="flows-back flows-button">{children || "Back"}</button>
);
const getStepFooterActionButton = ({
props,
state,
step,
}: {
props: FooterActionItem;
state: FlowState;
step: FlowModalStep | FlowTooltipStep;
}): HTMLElement => {
if (props.prev) return getBackButton({ step, state });
if (props.next) return getContinueButton({ step, state });
if (props.prev) return getBackButton({ children: props.text });
if (props.next) return getContinueButton({ children: props.text, state });
const buttonClassName = "flows-option flows-button";
if (props.href)
return (
Expand All @@ -115,25 +109,22 @@ const getStepFooterActionButton = ({
const getStepFooterActions = ({
items,
state,
step,
}: {
items?: FooterActionItem[];
step: FlowModalStep | FlowTooltipStep;
state: FlowState;
}): HTMLElement[] =>
(items ?? []).map((item) => getStepFooterActionButton({ props: item, state, step }));
}): HTMLElement[] => (items ?? []).map((item) => getStepFooterActionButton({ props: item, state }));
const getStepFooter = ({
state,
step,
}: {
step: FlowModalStep | FlowTooltipStep;
state: FlowState;
}): HTMLElement | null => {
const backBtn = state.hasPrevStep && !step.hidePrev && getBackButton({ step, state });
const continueBtn = !step.hideNext && getContinueButton({ state, step });
const leftOptions = getStepFooterActions({ items: step.footerActions?.left, state, step });
const centerOptions = getStepFooterActions({ items: step.footerActions?.center, state, step });
const rightOptions = getStepFooterActions({ items: step.footerActions?.right, state, step });
const backBtn = state.hasPrevStep && !step.hidePrev && getBackButton({ children: step.prevText });
const continueBtn = !step.hideNext && getContinueButton({ state, children: step.nextText });
const leftOptions = getStepFooterActions({ items: step.footerActions?.left, state });
const centerOptions = getStepFooterActions({ items: step.footerActions?.center, state });
const rightOptions = getStepFooterActions({ items: step.footerActions?.right, state });
const someFooterBtn =
backBtn || continueBtn || leftOptions.length || centerOptions.length || rightOptions.length;
if (!someFooterBtn) return null;
Expand Down
1 change: 0 additions & 1 deletion tests/arrow/arrow.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Arrow</title>
<link rel="stylesheet" href="/tests/reset.css" />
<link rel="stylesheet" href="/public/flows.css" />
</head>
Expand Down
12 changes: 11 additions & 1 deletion tests/arrow/arrow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ const placements = [
placements.forEach((placement) => {
test(placement, async ({ page }) => {
await page.goto(`/arrow/arrow.html?placement=${placement}`);

await expect(page).toHaveScreenshot({ scale: "css" });
});
});

test("Shows the arrow", async ({ page }) => {
await page.goto(`/arrow/arrow.html?placement=right`);
await expect(page.locator(".flows-arrow")).toHaveCount(2);
});

test("Hides the arrow", async ({ page }) => {
await page.goto(`/arrow/arrow.html?placement=right&hideArrow=true`);
await expect(page.locator(".flows-arrow")).toHaveCount(0);
await expect(page).toHaveScreenshot({ scale: "css" });
});
1 change: 1 addition & 0 deletions tests/arrow/arrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ void init({
placement: new URLSearchParams(window.location.search).get(
"placement",
) as unknown as "top",
hideArrow: new URLSearchParams(window.location.search).get("hideArrow") === "true",
},
],
},
Expand Down
16 changes: 16 additions & 0 deletions tests/buttons/buttons.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/tests/reset.css" />
<link rel="stylesheet" href="/public/flows.css" />
</head>
<body>
<div style="display: grid; place-items: center; height: 100vh">
<div style="background-color: grey; width: 20px; height: 20px" class="target"></div>
</div>

<script type="module" src="./buttons.ts"></script>
</body>
</html>
109 changes: 109 additions & 0 deletions tests/buttons/buttons.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { expect, test } from "@playwright/test";

test("Close is visible", async ({ page }) => {
await page.goto("/buttons/buttons.html");
await expect(page.locator(".flows-tooltip")).toBeVisible();
await expect(page.locator(".flows-cancel")).toBeVisible();
});
test("Hides close", async ({ page }) => {
await page.goto("/buttons/buttons.html?hideClose=true");
await expect(page.locator(".flows-tooltip")).toBeVisible();
await expect(page.locator(".flows-cancel")).not.toBeVisible();
});

test("Next is visible", async ({ page }) => {
await page.goto("/buttons/buttons.html");
await expect(page.locator(".flows-continue")).toBeVisible();
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-continue")).not.toBeVisible();
await expect(page.locator(".flows-finish")).toBeVisible();
});
test("Hides next", async ({ page }) => {
await page.goto("/buttons/buttons.html?hideNext=true");
await expect(page.locator(".flows-continue")).not.toBeVisible();
});

test("Prev is visible", async ({ page }) => {
await page.goto("/buttons/buttons.html");
await expect(page.locator(".flows-back")).not.toBeVisible();
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-back")).toBeVisible();
});
test("Hides prev", async ({ page }) => {
await page.goto("/buttons/buttons.html?hidePrev=true");
await expect(page.locator(".flows-back")).not.toBeVisible();
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-back")).not.toBeVisible();
});

test("Default next text", async ({ page }) => {
await page.goto("/buttons/buttons.html");
await expect(page.locator(".flows-continue")).toHaveText("Continue");
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-finish")).toHaveText("Finish");
});
test("Custom next text", async ({ page }) => {
await page.goto("/buttons/buttons.html?nextText=Next");
await expect(page.locator(".flows-continue")).toHaveText("Next");
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-finish")).toHaveText("Next");
});

test("Default prev text", async ({ page }) => {
await page.goto("/buttons/buttons.html");
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-back")).toHaveText("Back");
});
test("Custom prev text", async ({ page }) => {
await page.goto("/buttons/buttons.html?prevText=Previous");
await page.locator(".flows-continue").click();
await expect(page.locator(".flows-back")).toHaveText("Previous");
});

test("Custom link", async ({ page }) => {
await page.goto("/buttons/buttons.html?customLink=true&hideNext=true");
await expect(page.locator(".flows-option")).toHaveCount(3);
const firstLink = page.locator(".flows-option").first();
await expect(firstLink).toBeVisible();
await expect(firstLink).toHaveText("Google");
await expect(firstLink).toHaveAttribute("href", "https://google.com");
await expect(firstLink).not.toHaveAttribute("target", "_blank");
});
test("Custom external link", async ({ page }) => {
await page.goto("/buttons/buttons.html?customExternalLink=true&hideNext=true");
await expect(page.locator(".flows-option")).toHaveCount(3);
const firstLink = page.locator(".flows-option").first();
await expect(firstLink).toBeVisible();
await expect(firstLink).toHaveText("Google");
await expect(firstLink).toHaveAttribute("href", "https://google.com");
await expect(firstLink).toHaveAttribute("target", "_blank");
});
test("Custom action", async ({ page }) => {
await page.goto("/buttons/buttons.html?customAction=true&hideNext=true");
await expect(page.locator(".flows-option")).toHaveCount(3);
const firstLink = page.locator(".flows-option").first();
await expect(firstLink).toBeVisible();
await expect(firstLink).toHaveText("Action");
await expect(firstLink).toHaveAttribute("data-action", "0");
await expect(firstLink).not.toHaveAttribute("href");
await expect(firstLink).not.toHaveAttribute("target");
});
test("Custom next", async ({ page }) => {
await page.goto("/buttons/buttons.html?customNext=true&hideNext=true");
await expect(page.locator(".flows-continue")).toHaveCount(3);
const firstLink = page.locator(".flows-continue").first();
await expect(firstLink).toBeVisible();
await expect(firstLink).toHaveText("My Next");
await expect(firstLink).not.toHaveAttribute("href");
await expect(firstLink).not.toHaveAttribute("target");
});
// TODO: discuss if prev button should be visible when there is no previous step
test("Custom prev", async ({ page }) => {
await page.goto("/buttons/buttons.html?customPrev=true&hideNext=true");
await expect(page.locator(".flows-back")).toHaveCount(3);
const firstLink = page.locator(".flows-back").first();
await expect(firstLink).toBeVisible();
await expect(firstLink).toHaveText("My Prev");
await expect(firstLink).not.toHaveAttribute("href");
await expect(firstLink).not.toHaveAttribute("target");
});
Loading

0 comments on commit fac5d97

Please sign in to comment.