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
18 changes: 10 additions & 8 deletions apps/web/src/app/[locale]/(public)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function HeroWindow({ workspace, compiled, terminal }: { workspace: string; comp
<span className="window-dot bg-[var(--traffic-yellow)]" />
<span className="window-dot bg-[var(--traffic-green)]" />
<span className="ml-3 text-[13px] font-medium text-[var(--text2)]">main.pseudo</span>
<span className="ml-auto text-xs font-medium text-[var(--green)]">{compiled}</span>
<span className="ml-auto text-xs font-medium text-[var(--text3)]">{compiled}</span>
</div>
<div className="grid md:grid-cols-[170px_1fr]">
<aside className="hidden border-r border-[var(--separator)] bg-[var(--sidebar)] px-2 py-3 text-[13px] md:block">
Expand Down Expand Up @@ -174,19 +174,21 @@ export default async function LandingPage({ params }: LocaleParams) {
</p>
<div className="mt-8 flex flex-wrap gap-3">
<Link href={href("/app")} className="site-btn site-btn-accent">
{t.ctaStart} <Play size={16} />
{t.ctaStart} <ArrowRight size={16} aria-hidden />
</Link>
<Link href={href("/practice")} className="site-btn site-btn-ghost">
{t.ctaPractice} <Shuffle size={16} />
</Link>
<Link href={href("/docs")} className="site-btn site-btn-ghost">
{t.ctaDocs} <ArrowRight size={16} />
</Link>
</div>
<p className="mt-6 text-sm text-[var(--ink-3)]">{t.noAccount}</p>
<Link href={href("/docs/pseudocode-compiler")} className="mt-3 inline-flex items-center gap-2 text-sm font-semibold text-[var(--accent)]">
{t.compilerGuide} <ArrowRight size={14} />
</Link>
<div className="mt-3 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm font-semibold text-[var(--accent)]">
<Link href={href("/docs")} className="inline-flex items-center gap-2 hover:underline">
{t.ctaDocs} <ArrowRight size={14} aria-hidden />
</Link>
<Link href={href("/docs/pseudocode-compiler")} className="inline-flex items-center gap-2 hover:underline">
{t.compilerGuide} <ArrowRight size={14} aria-hidden />
</Link>
</div>
</div>
<HeroWindow workspace={t.windowWorkspace} compiled={t.windowCompiled} terminal={t.windowTerminal} />
</section>
Expand Down
48 changes: 28 additions & 20 deletions apps/web/src/app/[locale]/app/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ vi.mock("@/lib/auth-components", () => ({
vi.mock("@/lib/storage", () => ({
loadWorkspace: loadWorkspaceMock,
saveWorkspace: saveWorkspaceMock,
saveDeviceCheckpoint: vi.fn(async () => {}),
takeAccountSyncPending: vi.fn(() => false),
takeRecoveredCopies: vi.fn(() => false),
isAccountUnreachable: vi.fn(() => false),
}));

/** Makes loadWorkspace resolve to `workspace`. */
Expand Down Expand Up @@ -386,16 +390,16 @@ describe("HomePage workspace flow", () => {
render(<HomePage />);
await screen.findByRole("textbox", { name: "Mock editor" });

fireEvent.click(getExplorerHeaderButton("Create File"));
const createDialog = await screen.findByRole("dialog", { name: "Create New File" });
fireEvent.click(getExplorerHeaderButton("New file"));
const createDialog = await screen.findByRole("dialog", { name: "New file" });

const nameInput = within(createDialog).getByLabelText("File name");
expect(nameInput).toHaveValue("Untitled");
expect(within(createDialog).getByText(".pseudo")).toBeInTheDocument();

fireEvent.change(nameInput, { target: { value: "Renamed Doc.pseudo" } });
expect(nameInput).toHaveValue("Renamed Doc");
fireEvent.click(within(createDialog).getByRole("button", { name: "Create File" }));
fireEvent.click(within(createDialog).getByRole("button", { name: "Create" }));
await waitFor(() => {
expect(getExplorerButton("Renamed Doc.pseudo")).toBeInTheDocument();
});
Expand All @@ -410,14 +414,18 @@ describe("HomePage workspace flow", () => {
mockLoadedWorkspace(createEmptyWorkspace("2026-03-15T00:00:00.000Z"));
render(<HomePage />);

expect(await screen.findByText("Welcome to PseudoEditor")).toBeInTheDocument();
expect(await screen.findByText("Start writing pseudocode")).toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Create New File" }));
const dialog = await screen.findByRole("dialog", { name: "Create New File" });
fireEvent.click(
within(screen.getByText("Start writing pseudocode").closest("section") as HTMLElement).getByRole("button", {
name: "New file",
}),
);
const dialog = await screen.findByRole("dialog", { name: "New file" });
fireEvent.change(within(dialog).getByLabelText("File name"), {
target: { value: "main.pseudo" },
});
fireEvent.click(within(dialog).getByRole("button", { name: "Create File" }));
fireEvent.click(within(dialog).getByRole("button", { name: "Create" }));

await waitFor(() => {
expect(screen.getByRole("textbox", { name: "Mock editor" })).toHaveValue("");
Expand Down Expand Up @@ -528,13 +536,13 @@ describe("HomePage workspace flow", () => {
mockLoadedWorkspace(createWorkspaceFixture());
const { unmount } = render(<HomePage />);
const editor = await screen.findByRole("textbox", { name: "Mock editor" });
expect(editor).toHaveAttribute("data-color-scheme", "classic");
expect(editor).toHaveAttribute("data-color-scheme", "pseudoeditor");

fireEvent.click(screen.getByRole("button", { name: "Open settings" }));
const dialog = await screen.findByRole("dialog", { name: "Settings" });
const picker = within(dialog).getByRole("radiogroup", { name: "Syntax colours" });
expect(within(picker).getAllByRole("radio")).toHaveLength(6);
expect(within(picker).getByRole("radio", { name: "Classic" })).toBeChecked();
expect(within(picker).getAllByRole("radio")).toHaveLength(7);
expect(within(picker).getByRole("radio", { name: "PseudoEditor" })).toBeChecked();

fireEvent.click(within(picker).getByRole("radio", { name: "Ocean" }));

Expand All @@ -556,7 +564,7 @@ describe("HomePage workspace flow", () => {
await screen.findByRole("textbox", { name: "Mock editor" });

fireEvent.contextMenu(getExplorerButton("Helper.pseudo"));
fireEvent.click(await screen.findByRole("menuitem", { name: "Move Up" }));
fireEvent.click(await screen.findByRole("menuitem", { name: "Move up" }));

await waitFor(() => {
const savedState = lastLocallySavedWorkspace();
Expand Down Expand Up @@ -614,7 +622,7 @@ describe("HomePage workspace flow", () => {

fireEvent.contextMenu(getExplorerButton("Helper.pseudo"));
fireEvent.click(await screen.findByRole("menuitem", { name: "Delete" }));
expect(screen.getByText('Delete "Helper.pseudo"?')).toBeInTheDocument();
expect(screen.getByRole("dialog", { name: 'Delete "Helper.pseudo"?' })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Delete" }));

await waitFor(() => {
Expand All @@ -635,15 +643,15 @@ describe("HomePage workspace flow", () => {
fireEvent.click(helperRow);
fireEvent.keyDown(helperRow, { key: "Delete" });

const dialog = await screen.findByRole("dialog", { name: "Confirm Delete" });
const dialog = await screen.findByRole("dialog", { name: 'Delete "Helper.pseudo"?' });
expect(within(dialog).getByRole("button", { name: "Cancel" })).toHaveFocus();

fireEvent.keyDown(dialog, { key: "Escape" });

await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
expect(helperRow).toHaveFocus();
expect(screen.getByRole("tree", { name: "Explorer" })).toHaveFocus();
expect(getExplorerRow("Helper.pseudo")).toBeInTheDocument();
});

Expand All @@ -656,16 +664,16 @@ describe("HomePage workspace flow", () => {
fireEvent.contextMenu(helperRow);
fireEvent.click(await screen.findByRole("menuitem", { name: "Rename" }));

const dialog = await screen.findByRole("dialog", { name: "Rename Item" });
const nameInput = within(dialog).getByRole("textbox", { name: "Item name" });
const dialog = await screen.findByRole("dialog", { name: "Rename" });
const nameInput = within(dialog).getByRole("textbox", { name: "Name" });
expect(nameInput).toHaveFocus();

fireEvent.keyDown(nameInput, { key: "Escape" });

await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
expect(helperRow).toHaveFocus();
expect(screen.getByRole("tree", { name: "Explorer" })).toHaveFocus();
});

it("uses the phone layout on narrow windows regardless of user agent and hides the flowchart switch", async () => {
Expand All @@ -677,7 +685,7 @@ describe("HomePage workspace flow", () => {
render(<HomePage />);
await screen.findByRole("textbox", { name: "Mock editor" });

fireEvent.click(screen.getByRole("button", { name: "SETTINGS" }));
fireEvent.click(within(screen.getByRole("navigation", { name: "Workspace sections" })).getByRole("button", { name: "Settings" }));
expect(screen.getByText("Theme")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Switch to flowchart view" })).not.toBeInTheDocument();
} finally {
Expand Down Expand Up @@ -946,7 +954,7 @@ describe("HomePage workspace flow", () => {
loadWorkspaceMock.mockResolvedValue(createEmptyWorkspace("2026-03-15T00:00:00.000Z"));
render(<HomePage />);

expect(await screen.findByText("Welcome to PseudoEditor")).toBeInTheDocument();
expect(await screen.findByText("Start writing pseudocode")).toBeInTheDocument();

const event = new Event("beforeunload", { cancelable: true });
Object.defineProperty(event, "returnValue", {
Expand All @@ -967,7 +975,7 @@ describe("HomePage workspace flow", () => {
await screen.findByRole("textbox", { name: "Mock editor" });
expect(loadWorkspaceMock).toHaveBeenCalledWith(expect.any(String), { mode: "memory" });
const signInButton = screen.getByRole("button", { name: "Log In" });
expect(signInButton).toHaveClass("bg-[var(--accent)]", "text-[var(--on-accent)]");
expect(signInButton).toHaveClass("ui-btn", "ui-btn-primary");
expect(screen.queryByText(/Need an account/i)).not.toBeInTheDocument();

fireEvent.click(screen.getByRole("button", { name: "Save workspace" }));
Expand Down
Loading
Loading