Skip to content
Open
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
6 changes: 1 addition & 5 deletions zookeeper-website/app/pages/_docs/docs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,7 @@ export function DocsPage({ loaderData }: { loaderData: DocsLoaderData }) {
const layoutOptions = baseOptions();

return (
<DocsLayout
{...layoutOptions}
tree={tree as PageTree.Root}
sidebar={{ banner: <OlderDocsPicker /> }}
>
<DocsLayout {...layoutOptions} tree={tree as PageTree.Root}>
<Content tree={tree as PageTree.Root} />
</DocsLayout>
);
Expand Down
84 changes: 36 additions & 48 deletions zookeeper-website/e2e-tests/older-docs-picker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,58 @@
// limitations under the License.
//

import { test, expect } from "@playwright/test";
import { DOCS_ROOT } from "./constants";
import { test, expect, type Page } from "@playwright/test";
import {
getReleasedDocUrl,
getReleasedDocVersions
} from "../app/lib/released-docs-versions";

const EXPECTED_VERSIONS = getReleasedDocVersions();

test.describe("Older Docs Picker – sidebar", () => {
test.beforeEach(async ({ page }) => {
await page.goto(DOCS_ROOT);
await page.waitForLoadState("networkidle");
});
async function openOlderDocsMenu(page: Page) {
await page
.getByRole("button", { name: /^Documentation$/i })
.first()
.click();

test("trigger button is visible in the docs sidebar", async ({ page }) => {
const trigger = page.getByRole("button", { name: /older docs/i });
await expect(trigger).toBeVisible();
});
const olderDocsTrigger = page.getByRole("menuitem", { name: /^Older docs$/ });
await expect(olderDocsTrigger).toBeVisible();
await olderDocsTrigger.focus();
await page.keyboard.press("ArrowRight");

test("popover is closed by default", async ({ page }) => {
await expect(page.getByRole("combobox")).not.toBeVisible();
});
const list = page.getByRole("listbox");
await expect(list).toBeVisible();
return list;
}

test("clicking the trigger opens a popover with a search input", async ({
page
}) => {
await page.getByRole("button", { name: /older docs/i }).click();
await expect(page.getByRole("combobox")).toBeVisible();
test.describe("Older Docs Picker - landing page navigation", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.waitForLoadState("load");
});

test("popover lists released doc versions", async ({ page }) => {
await page.getByRole("button", { name: /older docs/i }).click();
test("trigger is visible in the Documentation dropdown", async ({ page }) => {
await page
.getByRole("button", { name: /^Documentation$/i })
.first()
.click();

await expect(
page.getByRole("menuitem", { name: /^Older docs$/ })
).toBeVisible();
});

const list = page.getByRole("listbox");
await expect(list).toBeVisible();
test("submenu lists released doc versions", async ({ page }) => {
const list = await openOlderDocsMenu(page);

const options = list.getByRole("option");
await expect(options.first()).toBeVisible();
await expect(options).toHaveCount(EXPECTED_VERSIONS.length);
});

test("versions are displayed in descending order", async ({ page }) => {
await page.getByRole("button", { name: /older docs/i }).click();

const options = page.getByRole("option");
const list = await openOlderDocsMenu(page);
const options = list.getByRole("option");
await expect(options.first()).toBeVisible();

const texts = await options.allTextContents();
Expand All @@ -71,9 +77,9 @@ test.describe("Older Docs Picker – sidebar", () => {
test("each version item links to the correct archive path", async ({
page
}) => {
await page.getByRole("button", { name: /older docs/i }).click();
const list = await openOlderDocsMenu(page);
const options = list.getByRole("option");

const options = page.getByRole("option");
await expect(options).toHaveCount(EXPECTED_VERSIONS.length);
for (let i = 0; i < EXPECTED_VERSIONS.length; i++) {
const href = await options.nth(i).getAttribute("href");
Expand All @@ -84,7 +90,7 @@ test.describe("Older Docs Picker – sidebar", () => {
test("typing in the search box filters the version list", async ({
page
}) => {
await page.getByRole("button", { name: /older docs/i }).click();
await openOlderDocsMenu(page);

const input = page.getByRole("combobox");
await expect(input).toBeVisible();
Expand All @@ -107,7 +113,6 @@ test.describe("Older Docs Picker – sidebar", () => {
expect(totalBefore).toBe(EXPECTED_VERSIONS.length);
expect(totalAfter).toBe(expectedMatches);

// Every remaining option must contain the search term
for (let i = 0; i < totalAfter; i++) {
const text = await filtered.nth(i).textContent();
expect(text).toContain(prefix);
Expand All @@ -117,29 +122,12 @@ test.describe("Older Docs Picker – sidebar", () => {
test("searching for a non-existent version shows 'No versions found'", async ({
page
}) => {
await page.getByRole("button", { name: /older docs/i }).click();
await openOlderDocsMenu(page);

const input = page.getByRole("combobox");
await input.fill("99.99.99");
await page.waitForTimeout(200);

await expect(page.getByText(/no versions found/i)).toBeVisible();
});

test("search is cleared when the popover is reopened", async ({ page }) => {
const trigger = page.getByRole("button", { name: /older docs/i });

await trigger.click();
const input = page.getByRole("combobox");
await input.fill("anything");

// Close the popover by pressing Escape
await page.keyboard.press("Escape");
await expect(page.getByRole("combobox")).not.toBeVisible();

// Reopen
await trigger.click();
const newInput = page.getByRole("combobox");
await expect(newInput).toHaveValue("");
});
});
10 changes: 10 additions & 0 deletions zookeeper-website/scripts/build-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ function runCommand(command: string, args: string[], env: NodeJS.ProcessEnv) {
}
}

export async function pruneLandingRootDocsImages(
buildClientDir = BUILD_CLIENT_DIR
) {
await rm(join(buildClientDir, "docs-images"), {
recursive: true,
force: true
});
}

// Builds the live site as two independent Vite builds that get merged for local
// serving (vite preview / e2e) and for publishing:
// 1. Current docs (base /doc/r<CURRENT>/) -> build/doc/r<CURRENT>/. Built first
Expand All @@ -68,6 +77,7 @@ export async function main() {
...process.env,
[DOCS_BUILD_TARGET_ENV]: "landing"
});
await pruneLandingRootDocsImages();

const mergedDocsDir = join(BUILD_CLIENT_DIR, "doc", `r${CURRENT_VERSION}`);
await rm(mergedDocsDir, { recursive: true, force: true });
Expand Down
63 changes: 63 additions & 0 deletions zookeeper-website/unit-tests/build-site.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { pruneLandingRootDocsImages } from "../scripts/build-site";

let tempDir: string;

async function pathExists(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}

beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "zk-build-site-test-"));
});

afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});

describe("pruneLandingRootDocsImages", () => {
it("removes docs images from the landing root without touching merged docs", async () => {
await mkdir(join(tempDir, "docs-images"), { recursive: true });
await writeFile(join(tempDir, "docs-images", "zkservice.jpg"), "");
await mkdir(join(tempDir, "doc", "r3.10.0", "docs-images"), {
recursive: true
});
await writeFile(
join(tempDir, "doc", "r3.10.0", "docs-images", "zkservice.jpg"),
""
);

await pruneLandingRootDocsImages(tempDir);

await expect(pathExists(join(tempDir, "docs-images"))).resolves.toBe(false);
await expect(
pathExists(join(tempDir, "doc", "r3.10.0", "docs-images"))
).resolves.toBe(true);
});
});