From 087525cca7f41c691d8697a99610e0cd96271139 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 9 Apr 2026 09:40:26 -0400 Subject: [PATCH 1/2] WIP Signed-off-by: Juan Cruz Viotti --- test/e2e/html/playwright/index.spec.js | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/e2e/html/playwright/index.spec.js diff --git a/test/e2e/html/playwright/index.spec.js b/test/e2e/html/playwright/index.spec.js new file mode 100644 index 00000000..6779685f --- /dev/null +++ b/test/e2e/html/playwright/index.spec.js @@ -0,0 +1,38 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Home page directory listing', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); + + test('renders a "Special directories" heading', async ({ page }) => { + const heading = page.getByRole('heading', { name: 'Special directories' }); + await expect(heading).toBeVisible(); + }); + + test('lists /self under the special directories table', async ({ page }) => { + const specialTable = page.locator( + 'h6:has-text("Special directories") + table'); + const selfLink = specialTable.locator('a[href="/self/"]'); + await expect(selfLink).toBeVisible(); + }); + + test('does not list /self in the regular directories table', + async ({ page }) => { + const regularTable = page.locator('table').first(); + const selfLink = regularTable.locator('a[href="/self/"]'); + await expect(selfLink).toHaveCount(0); + }); + + test('lists /test in the regular directories table, not the special one', + async ({ page }) => { + const regularTable = page.locator('table').first(); + const testLink = regularTable.locator('a[href="/test/"]'); + await expect(testLink).toBeVisible(); + + const specialTable = page.locator( + 'h6:has-text("Special directories") + table'); + const testInSpecial = specialTable.locator('a[href="/test/"]'); + await expect(testInSpecial).toHaveCount(0); + }); +}); From 307e1488f6f10c0ecbdd4d8165d2a0c7c33513e8 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 9 Apr 2026 09:51:25 -0400 Subject: [PATCH 2/2] Fix `/self` not presented as a special directory given a base path Signed-off-by: Juan Cruz Viotti --- src/web/helpers.h | 12 +++++++---- src/web/pages/directory.cc | 8 +++---- src/web/pages/index.cc | 8 +++---- test/e2e/html/hurl/html.hurl | 3 +++ test/e2e/path/hurl/html.hurl | 9 ++++++++ test/e2e/path/playwright/navigation.spec.js | 24 +++++++++++++++++++++ 6 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 test/e2e/path/hurl/html.hurl diff --git a/src/web/helpers.h b/src/web/helpers.h index a750f1f0..050f9e86 100644 --- a/src/web/helpers.h +++ b/src/web/helpers.h @@ -308,7 +308,8 @@ inline auto make_file_manager_table_header(sourcemeta::core::HTMLWriter &writer) } inline auto make_file_manager(sourcemeta::core::HTMLWriter &writer, - const sourcemeta::core::JSON &directory) -> void { + const sourcemeta::core::JSON &directory, + const std::string &base_path) -> void { if (directory.at("entries").empty()) { writer.div().attribute("class", "container-fluid p-4 flex-grow-1"); writer.p( @@ -318,12 +319,15 @@ inline auto make_file_manager(sourcemeta::core::HTMLWriter &writer, return; } + const auto self_path{base_path + "/self"}; + const auto self_path_slash{base_path + "/self/"}; + // First pass: check what we have bool has_regular_entries = false; bool has_special_entries = false; for (const auto &entry : directory.at("entries").as_array()) { const auto path = entry.at("path").to_string(); - if (path == "/self" || path == "/self/") { + if (path == self_path || path == self_path_slash) { has_special_entries = true; } else { has_regular_entries = true; @@ -339,7 +343,7 @@ inline auto make_file_manager(sourcemeta::core::HTMLWriter &writer, writer.tbody(); for (const auto &entry : directory.at("entries").as_array()) { const auto path = entry.at("path").to_string(); - if (path != "/self" && path != "/self/") { + if (path != self_path && path != self_path_slash) { make_file_manager_row(writer, entry); } } @@ -357,7 +361,7 @@ inline auto make_file_manager(sourcemeta::core::HTMLWriter &writer, writer.tbody(); for (const auto &entry : directory.at("entries").as_array()) { const auto path = entry.at("path").to_string(); - if (path == "/self" || path == "/self/") { + if (path == self_path || path == self_path_slash) { make_file_manager_row(writer, entry); } } diff --git a/src/web/pages/directory.cc b/src/web/pages/directory.cc index 5a024fdb..576c1e72 100644 --- a/src/web/pages/directory.cc +++ b/src/web/pages/directory.cc @@ -7,9 +7,8 @@ #include #include -#include // assert -#include // std::chrono -#include // std::filesystem +#include // assert +#include // std::chrono namespace sourcemeta::one { @@ -38,7 +37,8 @@ auto GENERATE_WEB_DIRECTORY::handler( html::make_breadcrumb(w, directory.at("breadcrumb"), configuration.base_path); html::make_directory_header(w, directory); - html::make_file_manager(w, directory); + html::make_file_manager(w, directory, + configuration.base_path); }); const auto timestamp_end{std::chrono::steady_clock::now()}; diff --git a/src/web/pages/index.cc b/src/web/pages/index.cc index 2f80ed4a..f151e2cf 100644 --- a/src/web/pages/index.cc +++ b/src/web/pages/index.cc @@ -7,9 +7,8 @@ #include #include -#include // assert -#include // std::chrono -#include // std::filesystem +#include // assert +#include // std::chrono namespace { @@ -49,7 +48,8 @@ auto GENERATE_WEB_INDEX::handler( html::make_page(writer, configuration, canonical, title, description, [&](sourcemeta::core::HTMLWriter &w) { make_hero(w, configuration); - html::make_file_manager(w, directory); + html::make_file_manager(w, directory, + configuration.base_path); }); const auto timestamp_end{std::chrono::steady_clock::now()}; diff --git a/test/e2e/html/hurl/html.hurl b/test/e2e/html/hurl/html.hurl index 03342d13..95f18ffd 100644 --- a/test/e2e/html/hurl/html.hurl +++ b/test/e2e/html/hurl/html.hurl @@ -10,6 +10,9 @@ xpath "string(/html/head/title)" == "Sourcemeta Schemas" xpath "count(//script[not(contains(@src, '?v='))])" == 0 xpath "count(//link[@rel='stylesheet' and not(contains(@href, '?v='))])" == 0 xpath "boolean(//footer//small[contains(., '{{edition}}')])" == true +xpath "boolean(//h6[contains(text(), 'Special directories')])" == true +xpath "boolean(//h6[contains(text(), 'Special directories')]/following-sibling::table[1]//a[@href='/self/'])" == true +xpath "count(//h6[contains(text(), 'Special directories')]/preceding-sibling::table//a[@href='/self/'])" == 0 header "Access-Control-Allow-Origin" not exists # This is a Safari Accept header diff --git a/test/e2e/path/hurl/html.hurl b/test/e2e/path/hurl/html.hurl new file mode 100644 index 00000000..c247b5b6 --- /dev/null +++ b/test/e2e/path/hurl/html.hurl @@ -0,0 +1,9 @@ +GET {{base}}/v1/catalog +Accept: text/html +HTTP 200 +Content-Type: text/html +[Asserts] +xpath "string(/html/head/title)" == "Sourcemeta Schemas" +xpath "boolean(//h6[contains(text(), 'Special directories')])" == true +xpath "boolean(//h6[contains(text(), 'Special directories')]/following-sibling::table[1]//a[@href='/v1/catalog/self/'])" == true +xpath "count(//h6[contains(text(), 'Special directories')]/preceding-sibling::table//a[@href='/v1/catalog/self/'])" == 0 diff --git a/test/e2e/path/playwright/navigation.spec.js b/test/e2e/path/playwright/navigation.spec.js index e23ecf5c..7ac48077 100644 --- a/test/e2e/path/playwright/navigation.spec.js +++ b/test/e2e/path/playwright/navigation.spec.js @@ -63,6 +63,30 @@ test.describe('Navigation with base path', () => { await expect(page).toHaveURL(new RegExp(`${BASE_PATH}/example/`)); }); + test('renders a "Special directories" heading on the root page', + async ({ page }) => { + await page.goto(BASE_PATH); + const heading = page.getByRole('heading', { name: 'Special directories' }); + await expect(heading).toBeVisible(); + }); + + test('lists /self under the special directories table on the root page', + async ({ page }) => { + await page.goto(BASE_PATH); + const specialTable = page.locator( + 'h6:has-text("Special directories") + table'); + const selfLink = specialTable.locator(`a[href="${BASE_PATH}/self/"]`); + await expect(selfLink).toBeVisible(); + }); + + test('does not list /self in the regular directories table on the root page', + async ({ page }) => { + await page.goto(BASE_PATH); + const regularTable = page.locator('table').first(); + const selfLink = regularTable.locator(`a[href="${BASE_PATH}/self/"]`); + await expect(selfLink).toHaveCount(0); + }); + test('static assets load correctly', async ({ page }) => { const responses = []; page.on('response', (response) => {