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); + }); +});