Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"papaparse": "^5.5.3",
"path-browserify": "^1.0.1",
"pdfjs-dist": "^4.10.38",
"qrcode.react": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^10.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
ADE_WELCOME_VIDEO_VERSION,
} from "../../../shared/welcomeVideo";
import { ADE_MOBILE_TESTFLIGHT_URL } from "../../../shared/productLinks";
import { docs } from "../../onboarding/docsLinks";

const DIALOG_NAME = /welcome to ade/i;

describe("WelcomeVideoGate", () => {
const originalAde = window.ade;
Expand All @@ -18,6 +21,8 @@ describe("WelcomeVideoGate", () => {
const openExternal = vi.fn();

beforeEach(() => {
window.history.pushState({}, "", "/");
Object.assign(navigator, { clipboard: undefined });
getWelcomeVideoState.mockReset();
markWelcomeVideoSeen.mockReset();
openExternal.mockReset();
Expand All @@ -43,7 +48,7 @@ describe("WelcomeVideoGate", () => {
window.ade = originalAde;
});

it("opens for an unseen video and marks it completed from Continue", async () => {
it("opens for an unseen video and dismisses from the close button", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
Expand All @@ -53,22 +58,75 @@ describe("WelcomeVideoGate", () => {

render(<WelcomeVideoGate />);

expect(await screen.findByRole("dialog", { name: /welcome to ade/i })).toBeTruthy();
expect(screen.queryByText(/start here/i)).toBeNull();
expect(screen.queryByText(/quick orientation/i)).toBeNull();
expect(await screen.findByRole("dialog", { name: DIALOG_NAME })).toBeTruthy();

fireEvent.click(screen.getByRole("button", { name: /close welcome/i }));

await waitFor(() => {
expect(markWelcomeVideoSeen).toHaveBeenCalledWith("dismissed");
});
expect(screen.queryByRole("dialog", { name: DIALOG_NAME })).toBeNull();
});

it("lazily loads the sandboxed video iframe only after the poster is clicked", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
completedAt: null,
dismissedAt: null,
});

render(<WelcomeVideoGate />);

await screen.findByRole("dialog", { name: DIALOG_NAME });

// No iframe up front; the poster stands in until the user opts to play.
expect(screen.queryByTitle("Welcome to ADE video")).toBeNull();

fireEvent.click(screen.getByRole("button", { name: /play the ade intro video/i }));

const video = screen.getByTitle("Welcome to ADE video");
expect(video).toBeTruthy();
expect(video.getAttribute("sandbox")).toBe(
"allow-scripts allow-same-origin allow-presentation allow-popups",
);
expect(video.getAttribute("allow")).toBe("autoplay; encrypted-media; picture-in-picture");
expect(video.getAttribute("src")).toContain("autoplay=1");
});

fireEvent.click(screen.getByRole("button", { name: /continue/i }));

await waitFor(() => {
expect(markWelcomeVideoSeen).toHaveBeenCalledWith("completed");
it("uses route-stable public asset URLs on nested browser routes", async () => {
window.history.pushState({}, "", "/automations/templates");
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
completedAt: null,
dismissedAt: null,
});
expect(screen.queryByRole("dialog", { name: /welcome to ade/i })).toBeNull();

render(<WelcomeVideoGate />);

await screen.findByRole("dialog", { name: DIALOG_NAME });

expect(document.querySelector('img[aria-hidden="true"]')?.getAttribute("src")).toBe(
"/logo.png",
);
expect(
screen
.getByRole("button", { name: /^desktop:/i })
.querySelector("img")
?.getAttribute("src"),
).toBe("/welcome/desktop.webp");
expect(
screen
.getByRole("button", { name: /^mobile:/i })
.querySelector("img")
?.getAttribute("src"),
).toBe("/welcome/mobile.webp");
expect(
screen
.getByRole("button", { name: /^terminal:/i })
.querySelector("img")
?.getAttribute("src"),
).toBe("/welcome/tui.webp");
});

it("stays hidden after a seen video until the replay event opens it", async () => {
Expand All @@ -84,20 +142,20 @@ describe("WelcomeVideoGate", () => {
await waitFor(() => {
expect(getWelcomeVideoState).toHaveBeenCalledTimes(1);
});
expect(screen.queryByRole("dialog", { name: /welcome to ade/i })).toBeNull();
expect(screen.queryByRole("dialog", { name: DIALOG_NAME })).toBeNull();

window.dispatchEvent(new Event(ADE_WELCOME_VIDEO_REPLAY_EVENT));

expect(await screen.findByRole("dialog", { name: /welcome to ade/i })).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: /close welcome video/i }));
expect(await screen.findByRole("dialog", { name: DIALOG_NAME })).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: /close welcome/i }));

await waitFor(() => {
expect(markWelcomeVideoSeen).toHaveBeenCalledWith("dismissed");
});
expect(screen.queryByRole("dialog", { name: /welcome to ade/i })).toBeNull();
expect(screen.queryByRole("dialog", { name: DIALOG_NAME })).toBeNull();
});

it("opens the mobile install link from the welcome actions", async () => {
it("downloads the mobile app from the QR panel", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
Expand All @@ -107,8 +165,87 @@ describe("WelcomeVideoGate", () => {

render(<WelcomeVideoGate />);

fireEvent.click(await screen.findByRole("button", { name: /install mobile app/i }));
fireEvent.click(await screen.findByRole("button", { name: /download for ios/i }));

expect(openExternal).toHaveBeenCalledWith(ADE_MOBILE_TESTFLIGHT_URL);
});

it("copies the install link through the desktop clipboard bridge when available", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
completedAt: null,
dismissedAt: null,
});
const writeClipboardText = vi.fn().mockResolvedValue(undefined);
const writeText = vi.fn().mockResolvedValue(undefined);
window.ade = ({
app: {
...window.ade.app,
writeClipboardText,
},
} as unknown) as typeof window.ade;
Object.assign(navigator, { clipboard: { writeText } });

render(<WelcomeVideoGate />);

fireEvent.click(await screen.findByRole("button", { name: /copy install link/i }));

await waitFor(() => {
expect(writeClipboardText).toHaveBeenCalledWith(ADE_MOBILE_TESTFLIGHT_URL);
});
expect(writeText).not.toHaveBeenCalled();
expect(await screen.findByRole("button", { name: /link copied/i })).toBeTruthy();
});

it("falls back to the browser clipboard and confirms it in place", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
completedAt: null,
dismissedAt: null,
});
const writeText = vi.fn().mockResolvedValue(undefined);
Object.assign(navigator, { clipboard: { writeText } });

render(<WelcomeVideoGate />);

fireEvent.click(await screen.findByRole("button", { name: /copy install link/i }));

await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(ADE_MOBILE_TESTFLIGHT_URL);
});
expect(await screen.findByRole("button", { name: /link copied/i })).toBeTruthy();
});

it("reports copy failure when the Clipboard API is unavailable", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
completedAt: null,
dismissedAt: null,
});
Object.assign(navigator, { clipboard: undefined });

render(<WelcomeVideoGate />);

fireEvent.click(await screen.findByRole("button", { name: /copy install link/i }));

expect(await screen.findByRole("button", { name: /copy failed/i })).toBeTruthy();
});

it("opens the matching docs section from a surface tile", async () => {
getWelcomeVideoState.mockResolvedValue({
videoId: ADE_WELCOME_VIDEO_ID,
version: ADE_WELCOME_VIDEO_VERSION,
completedAt: null,
dismissedAt: null,
});

render(<WelcomeVideoGate />);

fireEvent.click(await screen.findByRole("button", { name: /^desktop:/i }));

expect(openExternal).toHaveBeenCalledWith(docs.home);
});
});
Loading
Loading