diff --git a/zookeeper-website/app/pages/_docs/docs/index.tsx b/zookeeper-website/app/pages/_docs/docs/index.tsx
index 256418539db..3ebb1bc848b 100644
--- a/zookeeper-website/app/pages/_docs/docs/index.tsx
+++ b/zookeeper-website/app/pages/_docs/docs/index.tsx
@@ -173,11 +173,7 @@ export function DocsPage({ loaderData }: { loaderData: DocsLoaderData }) {
const layoutOptions = baseOptions();
return (
- }}
- >
+
);
diff --git a/zookeeper-website/e2e-tests/older-docs-picker.spec.ts b/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
index 2321fadb28c..256ee72e8c7 100644
--- a/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
+++ b/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
@@ -16,8 +16,7 @@
// 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
@@ -25,33 +24,41 @@ import {
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();
@@ -59,9 +66,8 @@ test.describe("Older Docs Picker – sidebar", () => {
});
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();
@@ -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");
@@ -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();
@@ -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);
@@ -117,7 +122,7 @@ 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");
@@ -125,21 +130,4 @@ test.describe("Older Docs Picker – sidebar", () => {
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("");
- });
});
diff --git a/zookeeper-website/scripts/build-site.ts b/zookeeper-website/scripts/build-site.ts
index f046c3cb322..69ebbf6dc1f 100644
--- a/zookeeper-website/scripts/build-site.ts
+++ b/zookeeper-website/scripts/build-site.ts
@@ -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/) -> build/doc/r/. Built first
@@ -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 });
diff --git a/zookeeper-website/unit-tests/build-site.test.ts b/zookeeper-website/unit-tests/build-site.test.ts
new file mode 100644
index 00000000000..f2e86df9214
--- /dev/null
+++ b/zookeeper-website/unit-tests/build-site.test.ts
@@ -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 {
+ 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);
+ });
+});