diff --git a/enterprise/e2e/html/hurl/schemas-as.all.hurl b/enterprise/e2e/html/hurl/schemas-as.all.hurl index 5e6530035..43af1c9a7 100644 --- a/enterprise/e2e/html/hurl/schemas-as.all.hurl +++ b/enterprise/e2e/html/hurl/schemas-as.all.hurl @@ -719,3 +719,73 @@ header "Vary" == "Accept, Accept-Encoding" header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ body contains "Draft3" body contains "https://www.learnjsonschema.com/draft3" + +# The web page offers every conversion the metadata advertises, oldest first +GET {{base}}/test/draft3/string +Accept: text/html +HTTP 200 +Cache-Control: public, max-age=0, must-revalidate +Content-Type: text/html; charset=utf-8 +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: frame-ancestors 'none' +X-Frame-Options: DENY +[Asserts] +header "Vary" == "Accept, Accept-Encoding" +header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ +xpath "string(//button[@data-sourcemeta-ui-dropdown-toggle])" == "Convert" +xpath "string(//button[@data-sourcemeta-ui-dropdown-toggle]/@aria-expanded)" == "false" +xpath "count(//ul[@data-sourcemeta-ui-dropdown-menu]/li/a)" == 5 +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[1]/a)" == "Draft4" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[1]/a/@href)" == "/test/draft3/string.json?as=draft4" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[2]/a)" == "Draft6" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[2]/a/@href)" == "/test/draft3/string.json?as=draft6" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[3]/a)" == "Draft7" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[3]/a/@href)" == "/test/draft3/string.json?as=draft7" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[4]/a)" == "2019-09" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[4]/a/@href)" == "/test/draft3/string.json?as=2019-09" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[5]/a)" == "2020-12" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[5]/a/@href)" == "/test/draft3/string.json?as=2020-12" + +# The dialect the schema already declares is left out, as a schema is +# already written in it +GET {{base}}/test/jsonld/draft7 +Accept: text/html +HTTP 200 +Cache-Control: public, max-age=0, must-revalidate +Content-Type: text/html; charset=utf-8 +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: frame-ancestors 'none' +X-Frame-Options: DENY +[Asserts] +header "Vary" == "Accept, Accept-Encoding" +header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ +xpath "string(//button[@data-sourcemeta-ui-dropdown-toggle])" == "Convert" +xpath "count(//ul[@data-sourcemeta-ui-dropdown-menu]/li/a)" == 2 +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[1]/a)" == "2019-09" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[1]/a/@href)" == "/test/jsonld/draft7.json?as=2019-09" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[2]/a)" == "2020-12" +xpath "string(//ul[@data-sourcemeta-ui-dropdown-menu]/li[2]/a/@href)" == "/test/jsonld/draft7.json?as=2020-12" +xpath "count(//ul[@data-sourcemeta-ui-dropdown-menu]/li/a[text()='Draft7'])" == 0 + +# A schema on the newest dialect has nothing left to be offered, even though +# the API still serves it in that dialect +GET {{base}}/test/object +Accept: text/html +HTTP 200 +Cache-Control: public, max-age=0, must-revalidate +Content-Type: text/html; charset=utf-8 +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: frame-ancestors 'none' +X-Frame-Options: DENY +[Asserts] +header "Vary" == "Accept, Accept-Encoding" +header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ +xpath "count(//button[@data-sourcemeta-ui-dropdown-toggle])" == 0 +xpath "count(//ul[@data-sourcemeta-ui-dropdown-menu])" == 0 +xpath "boolean(//a[@href='/test/object.json?bundle=1'])" == true + +GET {{base}}/test/object.json?as=2020-12 +HTTP 200 +Content-Type: application/schema+json +[Asserts] +header "ETag" exists diff --git a/enterprise/e2e/html/playwright/convert.spec.js b/enterprise/e2e/html/playwright/convert.spec.js new file mode 100644 index 000000000..ee670fa31 --- /dev/null +++ b/enterprise/e2e/html/playwright/convert.spec.js @@ -0,0 +1,200 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Schema Convert Dropdown', () => { + test('starts collapsed and lists every dialect the schema can be asked for', + async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + + // The menu is closed until asked for + await expect(toggle).toHaveAttribute('aria-expanded', 'false'); + await expect(menu).not.toBeVisible(); + + await toggle.click(); + + await expect(toggle).toHaveAttribute('aria-expanded', 'true'); + await expect(menu).toBeVisible(); + + // A Draft 3 schema can be asked for in every newer official dialect + const items = menu.locator('a'); + await expect(items).toHaveText([ + 'Draft4', 'Draft6', 'Draft7', '2019-09', '2020-12' + ]); + await expect(items.nth(0)).toHaveAttribute( + 'href', '/test/draft3/string.json?as=draft4'); + await expect(items.nth(1)).toHaveAttribute( + 'href', '/test/draft3/string.json?as=draft6'); + await expect(items.nth(2)).toHaveAttribute( + 'href', '/test/draft3/string.json?as=draft7'); + await expect(items.nth(3)).toHaveAttribute( + 'href', '/test/draft3/string.json?as=2019-09'); + await expect(items.nth(4)).toHaveAttribute( + 'href', '/test/draft3/string.json?as=2020-12'); + }); + + test('leaves out the dialect the schema already declares', + async ({ page }) => { + await page.goto('/test/jsonld/draft7'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + + const items = menu.locator('a'); + await expect(items).toHaveText([ '2019-09', '2020-12' ]); + await expect(items.nth(0)).toHaveAttribute( + 'href', '/test/jsonld/draft7.json?as=2019-09'); + await expect(items.nth(1)).toHaveAttribute( + 'href', '/test/jsonld/draft7.json?as=2020-12'); + }); + + test('a schema on the newest dialect gets no control at all', + async ({ page }) => { + await page.goto('/test/object'); + + await expect(page.locator('[data-sourcemeta-ui-dropdown-toggle]')) + .toHaveCount(0); + await expect(page.locator('a[href="/test/object.json?bundle=1"]')) + .toBeVisible(); + }); + + test('the menu sits below the button it belongs to', async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + + const toggleBox = await toggle.boundingBox(); + const menuBox = await menu.boundingBox(); + expect(menuBox.y).toBeGreaterThanOrEqual(toggleBox.y + toggleBox.height); + expect(menuBox.x).toBe(toggleBox.x); + }); + + test('clicking outside the dropdown closes it', async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + await expect(menu).toBeVisible(); + + await page.locator('footer').click(); + + await expect(toggle).toHaveAttribute('aria-expanded', 'false'); + await expect(menu).not.toBeVisible(); + await expect(toggle).not.toBeFocused(); + }); + + test('pressing escape from inside the menu hands focus back to the button', + async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + await page.keyboard.press('Tab'); + await expect(menu.locator('a').first()).toBeFocused(); + + await page.keyboard.press('Escape'); + + await expect(menu).not.toBeVisible(); + await expect(toggle).toBeFocused(); + }); + + test('tabbing past the last entry closes it and keeps going', + async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + + // Walk out of the menu through its five entries + for (let index = 0; index < 6; index++) { + await page.keyboard.press('Tab'); + } + + await expect(toggle).toHaveAttribute('aria-expanded', 'false'); + await expect(menu).not.toBeVisible(); + + // Whatever follows the menu is where the walk was headed + await expect(page.locator('[data-sourcemeta-ui-tab-target="usage-cli"]')) + .toBeFocused(); + await expect(toggle).not.toBeFocused(); + }); + + test('moving focus out of an entry lands where it was aimed', + async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + const bundle = page.locator('a[href="/test/draft3/string.json?bundle=1"]'); + await toggle.click(); + await page.keyboard.press('Tab'); + await expect(menu.locator('a').first()).toBeFocused(); + + await bundle.focus(); + + await expect(menu).not.toBeVisible(); + await expect(bundle).toBeFocused(); + await expect(toggle).not.toBeFocused(); + }); + + test('clicking the empty part of the menu leaves it open', + async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + + const box = await menu.boundingBox(); + await page.mouse.click(box.x + box.width - 2, box.y + box.height - 2); + + await expect(toggle).toHaveAttribute('aria-expanded', 'true'); + await expect(menu).toBeVisible(); + }); + + test('an entry navigates to the converted schema', async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + await menu.locator('a', { hasText: '2020-12' }).click(); + + await expect(page).toHaveURL('/test/draft3/string.json?as=2020-12'); + }); + + test('pressing escape closes it', async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + await expect(menu).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(toggle).toHaveAttribute('aria-expanded', 'false'); + await expect(menu).not.toBeVisible(); + }); + + test('clicking the button again closes it', async ({ page }) => { + await page.goto('/test/draft3/string'); + + const toggle = page.locator('[data-sourcemeta-ui-dropdown-toggle]'); + const menu = page.locator('[data-sourcemeta-ui-dropdown-menu]'); + await toggle.click(); + await expect(menu).toBeVisible(); + + await toggle.click(); + + await expect(toggle).toHaveAttribute('aria-expanded', 'false'); + await expect(menu).not.toBeVisible(); + }); +}); diff --git a/enterprise/e2e/html/playwright/playwright.config.js b/enterprise/e2e/html/playwright/playwright.config.js new file mode 100644 index 000000000..566a8c968 --- /dev/null +++ b/enterprise/e2e/html/playwright/playwright.config.js @@ -0,0 +1,16 @@ +import { defineConfig } from '@playwright/test'; + +// See https://playwright.dev/docs/test-configuration +export default defineConfig({ + testDir: '.', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'list', + outputDir: '../../../../build/test-results', + use: { + baseURL: process.env.PLAYWRIGHT_BASE_URL, + trace: 'on-first-retry' + } +}); diff --git a/src/web/CMakeLists.txt b/src/web/CMakeLists.txt index 2538054fb..398080437 100644 --- a/src/web/CMakeLists.txt +++ b/src/web/CMakeLists.txt @@ -22,6 +22,7 @@ sourcemeta_esbuild_bundle( DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/main.js" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copy.js" + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/dropdown.js" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/search.js" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/tabs.js" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/editor.js" diff --git a/src/web/helpers.h b/src/web/helpers.h index ec430f5f8..f4a4159fe 100644 --- a/src/web/helpers.h +++ b/src/web/helpers.h @@ -95,62 +95,72 @@ make_schema_health_progress_bar(sourcemeta::core::HTMLWriter &writer, writer.close(); } -inline auto -make_dialect_badge(sourcemeta::core::HTMLWriter &writer, - const sourcemeta::core::JSON::String &base_dialect_uri) - -> void { - const auto [short_name, is_current] = - [&base_dialect_uri]() -> std::pair { - if (base_dialect_uri == "https://json-schema.org/draft/2020-12/schema" || - base_dialect_uri == - "https://json-schema.org/draft/2020-12/hyper-schema") { - return {"2020-12", true}; - } +inline auto dialect_display_name(const std::string_view name) -> std::string { + std::string result{name}; + if (!result.empty()) { + result.front() = static_cast( + std::toupper(static_cast(result.front()))); + } - if (base_dialect_uri == "https://json-schema.org/draft/2019-09/schema" || - base_dialect_uri == - "https://json-schema.org/draft/2019-09/hyper-schema") { - return {"2019-09", false}; - } + return result; +} - if (base_dialect_uri == "http://json-schema.org/draft-07/schema#" || - base_dialect_uri == "http://json-schema.org/draft-07/hyper-schema#") { - return {"draft7", false}; - } +// The short name a dialect goes by, which is also the name a conversion into +// it is asked for by +inline auto +dialect_short_name(const sourcemeta::core::JSON::String &base_dialect_uri) + -> std::string_view { + if (base_dialect_uri == "https://json-schema.org/draft/2020-12/schema" || + base_dialect_uri == + "https://json-schema.org/draft/2020-12/hyper-schema") { + return "2020-12"; + } - if (base_dialect_uri == "http://json-schema.org/draft-06/schema#" || - base_dialect_uri == "http://json-schema.org/draft-06/hyper-schema#") { - return {"draft6", false}; - } + if (base_dialect_uri == "https://json-schema.org/draft/2019-09/schema" || + base_dialect_uri == + "https://json-schema.org/draft/2019-09/hyper-schema") { + return "2019-09"; + } - if (base_dialect_uri == "http://json-schema.org/draft-04/schema#" || - base_dialect_uri == "http://json-schema.org/draft-04/hyper-schema#") { - return {"draft4", false}; - } + if (base_dialect_uri == "http://json-schema.org/draft-07/schema#" || + base_dialect_uri == "http://json-schema.org/draft-07/hyper-schema#") { + return "draft7"; + } - if (base_dialect_uri == "http://json-schema.org/draft-03/schema#" || - base_dialect_uri == "http://json-schema.org/draft-03/hyper-schema#") { - return {"draft3", false}; - } + if (base_dialect_uri == "http://json-schema.org/draft-06/schema#" || + base_dialect_uri == "http://json-schema.org/draft-06/hyper-schema#") { + return "draft6"; + } - return {"unknown", false}; - }(); + if (base_dialect_uri == "http://json-schema.org/draft-04/schema#" || + base_dialect_uri == "http://json-schema.org/draft-04/hyper-schema#") { + return "draft4"; + } - // Capitalize first character - std::string display_name = short_name; - if (!display_name.empty()) { - display_name[0] = static_cast( - std::toupper(static_cast(display_name[0]))); + if (base_dialect_uri == "http://json-schema.org/draft-03/schema#" || + base_dialect_uri == "http://json-schema.org/draft-03/hyper-schema#") { + return "draft3"; } + return "unknown"; +} + +inline auto +make_dialect_badge(sourcemeta::core::HTMLWriter &writer, + const sourcemeta::core::JSON::String &base_dialect_uri) + -> void { + const auto short_name{dialect_short_name(base_dialect_uri)}; + const auto is_current{short_name == "2020-12"}; + writer.a() - .attribute("href", "https://www.learnjsonschema.com/" + short_name) + .attribute("href", + "https://www.learnjsonschema.com/" + std::string{short_name}) .attribute("target", "_blank"); writer.span().attribute( "class", "align-middle badge " + std::string(is_current ? "text-bg-primary" : "text-bg-danger")); - writer.text(display_name); + writer.text(dialect_display_name(short_name)); writer.close(); writer.close(); } diff --git a/src/web/pages/schema.cc b/src/web/pages/schema.cc index 977dc0b05..e99732227 100644 --- a/src/web/pages/schema.cc +++ b/src/web/pages/schema.cc @@ -8,6 +8,7 @@ #include #include +#include // std::ranges::any_of #include // assert #include // std::chrono #include // std::filesystem @@ -81,6 +82,48 @@ auto GenerateWebSchema::handler( .attribute("role", "button"); body.text("Bundle"); body.close(); + + // A schema is already written in the dialect it declares, so offering + // to convert it into that one reads as a choice where there is none + const auto *const conversions{meta.try_at("conversions")}; + const auto declared{ + html::dialect_short_name(meta.at("baseDialect").to_string())}; + if (conversions != nullptr && + std::ranges::any_of(conversions->as_object(), + [&declared](const auto &conversion) { + return conversion.first != declared; + })) { + body.div() + .attribute("class", "btn-group ms-2") + .attribute("data-sourcemeta-ui-dropdown", ""); + body.button() + .attribute("class", "btn btn-secondary dropdown-toggle") + .attribute("type", "button") + .attribute("aria-expanded", "false") + .attribute("data-sourcemeta-ui-dropdown-toggle", ""); + body.text("Convert"); + body.close(); + body.ul() + .attribute("class", "dropdown-menu") + .attribute("data-sourcemeta-ui-dropdown-menu", ""); + for (const auto &conversion : conversions->as_object()) { + if (conversion.first == declared) { + continue; + } + + body.li(); + body.a() + .attribute("class", "dropdown-item") + .attribute("href", meta.at("path").to_string() + + ".json?as=" + conversion.first); + body.text(html::dialect_display_name(conversion.first)); + body.close(); + body.close(); + } + body.close(); + body.close(); + } + body.close(); // Integration snippets diff --git a/src/web/scripts/dropdown.js b/src/web/scripts/dropdown.js new file mode 100644 index 000000000..26b3f3d23 --- /dev/null +++ b/src/web/scripts/dropdown.js @@ -0,0 +1,48 @@ +document.querySelectorAll("[data-sourcemeta-ui-dropdown]").forEach((dropdown) => { + const toggle = dropdown.querySelector("[data-sourcemeta-ui-dropdown-toggle]"); + const menu = dropdown.querySelector("[data-sourcemeta-ui-dropdown-menu]"); + + function close() { + menu.classList.remove("show"); + toggle.setAttribute("aria-expanded", "false"); + } + + // Whoever was reading the menu would be left standing on something that is + // no longer there, with no way back to the control that opened it + function dismiss() { + const wasInside = dropdown.contains(document.activeElement); + close(); + if (wasInside) { + toggle.focus(); + } + } + + toggle.addEventListener("click", () => { + if (toggle.getAttribute("aria-expanded") === "true") { + close(); + } else { + menu.classList.add("show"); + toggle.setAttribute("aria-expanded", "true"); + } + }); + + document.addEventListener("click", (event) => { + if (!dropdown.contains(event.target)) { + close(); + } + }); + + // Focus landing nowhere is focus that never went anywhere, such as a click + // on a part of the menu that cannot hold it + dropdown.addEventListener("focusout", (event) => { + if (event.relatedTarget && !dropdown.contains(event.relatedTarget)) { + close(); + } + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + dismiss(); + } + }); +}); diff --git a/src/web/scripts/main.js b/src/web/scripts/main.js index 17d78a1d0..1dc8b2536 100644 --- a/src/web/scripts/main.js +++ b/src/web/scripts/main.js @@ -1,5 +1,6 @@ import "./tabs.js"; import "./copy.js"; +import "./dropdown.js"; import "./search.js"; import "./dependencies.js"; diff --git a/src/web/style.scss b/src/web/style.scss index 59b29a19b..e648bf1c9 100644 --- a/src/web/style.scss +++ b/src/web/style.scss @@ -27,7 +27,7 @@ $breadcrumb-font-size: 15px; @import "../../vendor/bootstrap/scss/forms"; @import "../../vendor/bootstrap/scss/buttons"; // @import "../../vendor/bootstrap/scss/transitions"; -// @import "../../vendor/bootstrap/scss/dropdown"; +@import "../../vendor/bootstrap/scss/dropdown"; @import "../../vendor/bootstrap/scss/button-group"; @import "../../vendor/bootstrap/scss/nav"; @import "../../vendor/bootstrap/scss/navbar"; @@ -57,6 +57,14 @@ $bootstrap-icons-font: bootstrap-icons; $bootstrap-icons-font-dir: "."; @import "../../vendor/bootstrap-icons/font/bootstrap-icons.scss"; +// Bootstrap only places `.dropdown-menu` when Popper has marked it, and this +// UI drives its dropdowns without Popper +.dropdown-menu { + top: 100%; + left: 0; + margin-top: $dropdown-spacer; +} + // Bootstrap doesn't seem to provide breakpoint-based modifiers to `.w-*` @include media-breakpoint-up(md) { .w-md-auto { diff --git a/test/e2e/html/hurl/html.all.hurl b/test/e2e/html/hurl/html.all.hurl index 6672032c2..cc8badf73 100644 --- a/test/e2e/html/hurl/html.all.hurl +++ b/test/e2e/html/hurl/html.all.hurl @@ -607,3 +607,20 @@ Cache-Control: no-store [Asserts] header "Vary" not exists jsonpath "$.valid" == true + +# A schema with no conversions on offer grows no conversion control. This +# schema is a meta-schema, which is pinned where its metadata is asserted +GET {{base}}/test/schemas/custom-meta-draft7 +Accept: text/html +HTTP 200 +Cache-Control: public, max-age=0, must-revalidate +Content-Type: text/html; charset=utf-8 +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: frame-ancestors 'none' +X-Frame-Options: DENY +[Asserts] +header "Vary" == "Accept, Accept-Encoding" +header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ +xpath "count(//button[@data-sourcemeta-ui-dropdown-toggle])" == 0 +xpath "count(//ul[@data-sourcemeta-ui-dropdown-menu])" == 0 +xpath "boolean(//a[@href='/test/schemas/custom-meta-draft7.json?bundle=1'])" == true diff --git a/test/e2e/html/hurl/schemas-as.community.hurl b/test/e2e/html/hurl/schemas-as.community.hurl index f52c5864c..693b88926 100644 --- a/test/e2e/html/hurl/schemas-as.community.hurl +++ b/test/e2e/html/hurl/schemas-as.community.hurl @@ -170,3 +170,20 @@ header "Content-Security-Policy" not exists header "X-Frame-Options" not exists header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ jsonpath "$.valid" == true + +# With no conversions on offer at all, the web page grows no conversion +# control, however convertible the schema itself looks +GET {{base}}/test/v2.0/schema +Accept: text/html +HTTP 200 +Cache-Control: public, max-age=0, must-revalidate +Content-Type: text/html; charset=utf-8 +Referrer-Policy: strict-origin-when-cross-origin +Content-Security-Policy: frame-ancestors 'none' +X-Frame-Options: DENY +[Asserts] +header "Vary" == "Accept, Accept-Encoding" +header "Date" matches /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0[1-9]|[12][0-9]|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] GMT$/ +xpath "count(//button[@data-sourcemeta-ui-dropdown-toggle])" == 0 +xpath "count(//ul[@data-sourcemeta-ui-dropdown-menu])" == 0 +xpath "boolean(//a[@href='/test/v2.0/schema.json?bundle=1'])" == true