diff --git a/news/changelog-1.7.md b/news/changelog-1.7.md index ade95005c31..6cfcb43f94d 100644 --- a/news/changelog-1.7.md +++ b/news/changelog-1.7.md @@ -6,6 +6,7 @@ All changes included in 1.7: - ([#11532](https://github.com/quarto-dev/quarto-cli/issues/11532)): Fix regression for [#660](https://github.com/quarto-dev/quarto-cli/issues/660), which causes files to have incorrect permissions when Quarto is installed in a location not writable by the current user. - ([#11549](https://github.com/quarto-dev/quarto-cli/issues/11549)): Fix regression in rendering `dashboard` tabsets into cards without titles. - ([#11580](https://github.com/quarto-dev/quarto-cli/issues/11580)): Fix regression with documents containing `categories` fields that are not strings. +- ([#11752](https://github.com/quarto-dev/quarto-cli/issues/11752)): Fix regression with non-alphanumeric characters in `categories` preventing correct filtering of listing. ## YAML validation diff --git a/src/resources/projects/website/listing/quarto-listing.js b/src/resources/projects/website/listing/quarto-listing.js index 54d0e1e7f2f..e9a07b2ea39 100644 --- a/src/resources/projects/website/listing/quarto-listing.js +++ b/src/resources/projects/website/listing/quarto-listing.js @@ -2,7 +2,8 @@ const kProgressiveAttr = "data-src"; let categoriesLoaded = false; window.quartoListingCategory = (category) => { - category = atob(category); + // category is URI encoded in EJS template for UTF-8 support + category = decodeURIComponent(atob(category)); if (categoriesLoaded) { activateCategory(category); setCategoryHash(category); diff --git a/tests/docs/playwright/blog/simple-blog/_quarto.yml b/tests/docs/playwright/blog/simple-blog/_quarto.yml index 6822a2fc6cf..26ec96f1970 100644 --- a/tests/docs/playwright/blog/simple-blog/_quarto.yml +++ b/tests/docs/playwright/blog/simple-blog/_quarto.yml @@ -3,5 +3,15 @@ project: website: title: "Testing listing search" + navbar: + left: + - href: index.qmd + text: Home + - href: default.qmd + text: Default + - href: grid.qmd + text: Grid + - href: table.qmd + text: Table format: html diff --git a/tests/docs/playwright/blog/simple-blog/default.qmd b/tests/docs/playwright/blog/simple-blog/default.qmd new file mode 100644 index 00000000000..ea65d783052 --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/default.qmd @@ -0,0 +1,13 @@ +--- +title: "Testing listing default" +listing: + contents: posts2 + sort: "date desc" + type: default + categories: true + filter-ui: true +page-layout: full +title-block-banner: true +--- + + diff --git a/tests/docs/playwright/blog/simple-blog/grid.qmd b/tests/docs/playwright/blog/simple-blog/grid.qmd new file mode 100644 index 00000000000..12a0ab88959 --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/grid.qmd @@ -0,0 +1,13 @@ +--- +title: "Testing listing grid" +listing: + contents: posts3 + sort: "date desc" + type: grid + categories: true + filter-ui: true +page-layout: full +title-block-banner: true +--- + + diff --git a/tests/docs/playwright/blog/simple-blog/index.qmd b/tests/docs/playwright/blog/simple-blog/index.qmd index ecfd0a9de21..bf8310bba0d 100644 --- a/tests/docs/playwright/blog/simple-blog/index.qmd +++ b/tests/docs/playwright/blog/simple-blog/index.qmd @@ -1,13 +1,6 @@ --- -title: "Testing listing search" -listing: - contents: posts - sort: "date desc" - type: table - categories: true - filter-ui: true -page-layout: full -title-block-banner: true +title: "Testing listings" --- +This website is for testing listings diff --git a/tests/docs/playwright/blog/simple-blog/posts/post-with-code/image.jpg b/tests/docs/playwright/blog/simple-blog/posts/post-with-code/image.jpg index 3ec04c8c4e1..5d5ad1f4d2e 100644 Binary files a/tests/docs/playwright/blog/simple-blog/posts/post-with-code/image.jpg and b/tests/docs/playwright/blog/simple-blog/posts/post-with-code/image.jpg differ diff --git a/tests/docs/playwright/blog/simple-blog/posts/welcome/thumbnail.jpg b/tests/docs/playwright/blog/simple-blog/posts/welcome/thumbnail.jpg index 8e3107c9e0a..7789429f394 100644 Binary files a/tests/docs/playwright/blog/simple-blog/posts/welcome/thumbnail.jpg and b/tests/docs/playwright/blog/simple-blog/posts/welcome/thumbnail.jpg differ diff --git a/tests/docs/playwright/blog/simple-blog/posts2/_metadata.yml b/tests/docs/playwright/blog/simple-blog/posts2/_metadata.yml new file mode 100644 index 00000000000..c069d8db30d --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/posts2/_metadata.yml @@ -0,0 +1,4 @@ +# options specified here will apply to all posts in this folder + +# Enable banner style title blocks +title-block-banner: true diff --git a/tests/docs/playwright/blog/simple-blog/posts2/post-with-code/image.jpg b/tests/docs/playwright/blog/simple-blog/posts2/post-with-code/image.jpg new file mode 100644 index 00000000000..5d5ad1f4d2e Binary files /dev/null and b/tests/docs/playwright/blog/simple-blog/posts2/post-with-code/image.jpg differ diff --git a/tests/docs/playwright/blog/simple-blog/posts2/post-with-code/index.qmd b/tests/docs/playwright/blog/simple-blog/posts2/post-with-code/index.qmd new file mode 100644 index 00000000000..5a9b290c4ca --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/posts2/post-with-code/index.qmd @@ -0,0 +1,9 @@ +--- +title: "Post With Code" +author: "Harlow Malloc" +date: "2024-09-06" +categories: [news, code, analysis, apos'trophe] +image: "image.jpg" +--- + +This is a post with executable code. diff --git a/tests/docs/playwright/blog/simple-blog/posts2/welcome/index.qmd b/tests/docs/playwright/blog/simple-blog/posts2/welcome/index.qmd new file mode 100644 index 00000000000..d39a92a0269 --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/posts2/welcome/index.qmd @@ -0,0 +1,14 @@ +--- +title: "Welcome To My Blog" +author: "Tristan O'Malley" +date: "2024-09-03" +categories: [news, 'euros (€)', 免疫] +--- + +This is the first post in a Quarto blog. Welcome! + +![](thumbnail.jpg) + +## About image listing {#img-lst} + +Since this post doesn't specify an explicit `image`, the first image in the post will be used in the listing page of posts. diff --git a/tests/docs/playwright/blog/simple-blog/posts2/welcome/thumbnail.jpg b/tests/docs/playwright/blog/simple-blog/posts2/welcome/thumbnail.jpg new file mode 100644 index 00000000000..7789429f394 Binary files /dev/null and b/tests/docs/playwright/blog/simple-blog/posts2/welcome/thumbnail.jpg differ diff --git a/tests/docs/playwright/blog/simple-blog/posts3/_metadata.yml b/tests/docs/playwright/blog/simple-blog/posts3/_metadata.yml new file mode 100644 index 00000000000..c069d8db30d --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/posts3/_metadata.yml @@ -0,0 +1,4 @@ +# options specified here will apply to all posts in this folder + +# Enable banner style title blocks +title-block-banner: true diff --git a/tests/docs/playwright/blog/simple-blog/posts3/post-with-code/image.jpg b/tests/docs/playwright/blog/simple-blog/posts3/post-with-code/image.jpg new file mode 100644 index 00000000000..5d5ad1f4d2e Binary files /dev/null and b/tests/docs/playwright/blog/simple-blog/posts3/post-with-code/image.jpg differ diff --git a/tests/docs/playwright/blog/simple-blog/posts3/post-with-code/index.qmd b/tests/docs/playwright/blog/simple-blog/posts3/post-with-code/index.qmd new file mode 100644 index 00000000000..5a9b290c4ca --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/posts3/post-with-code/index.qmd @@ -0,0 +1,9 @@ +--- +title: "Post With Code" +author: "Harlow Malloc" +date: "2024-09-06" +categories: [news, code, analysis, apos'trophe] +image: "image.jpg" +--- + +This is a post with executable code. diff --git a/tests/docs/playwright/blog/simple-blog/posts3/welcome/index.qmd b/tests/docs/playwright/blog/simple-blog/posts3/welcome/index.qmd new file mode 100644 index 00000000000..d39a92a0269 --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/posts3/welcome/index.qmd @@ -0,0 +1,14 @@ +--- +title: "Welcome To My Blog" +author: "Tristan O'Malley" +date: "2024-09-03" +categories: [news, 'euros (€)', 免疫] +--- + +This is the first post in a Quarto blog. Welcome! + +![](thumbnail.jpg) + +## About image listing {#img-lst} + +Since this post doesn't specify an explicit `image`, the first image in the post will be used in the listing page of posts. diff --git a/tests/docs/playwright/blog/simple-blog/posts3/welcome/thumbnail.jpg b/tests/docs/playwright/blog/simple-blog/posts3/welcome/thumbnail.jpg new file mode 100644 index 00000000000..7789429f394 Binary files /dev/null and b/tests/docs/playwright/blog/simple-blog/posts3/welcome/thumbnail.jpg differ diff --git a/tests/docs/playwright/blog/simple-blog/table.qmd b/tests/docs/playwright/blog/simple-blog/table.qmd new file mode 100644 index 00000000000..e182f56d5c2 --- /dev/null +++ b/tests/docs/playwright/blog/simple-blog/table.qmd @@ -0,0 +1,13 @@ +--- +title: "Testing listing table" +listing: + contents: posts + sort: "date desc" + type: table + categories: true + filter-ui: true +page-layout: full +title-block-banner: true +--- + + diff --git a/tests/integration/playwright/playwright.config.ts b/tests/integration/playwright/playwright.config.ts index d392c298f75..8b244a4f306 100644 --- a/tests/integration/playwright/playwright.config.ts +++ b/tests/integration/playwright/playwright.config.ts @@ -95,7 +95,7 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ /* We use python for this but we could also try using another tool */ webServer: { - command: 'python -m http.server 8080', + command: 'uv run python -m http.server 8080', url: 'http://127.0.0.1:8080', reuseExistingServer: !isCI, cwd: '../../docs/playwright', diff --git a/tests/integration/playwright/tests/blog-simple-blog.spec.ts b/tests/integration/playwright/tests/blog-simple-blog.spec.ts index 3494ecd44c2..01981c6e0b8 100644 --- a/tests/integration/playwright/tests/blog-simple-blog.spec.ts +++ b/tests/integration/playwright/tests/blog-simple-blog.spec.ts @@ -1,54 +1,69 @@ -import { expect, test } from "@playwright/test"; +import { expect, Page, test } from "@playwright/test"; import { getUrl } from "../src/utils"; -test('List.js is correctly patch to allow searching with lowercase and uppercase', - async ({ page }) => { - await page.goto('./blog/simple-blog/_site/'); - await page.getByPlaceholder('Filter').click(); - await page.getByPlaceholder('Filter').fill('Code'); - await page.getByPlaceholder('Filter').press('Enter'); - await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); - await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeHidden(); - await page.getByPlaceholder('Filter').click(); - await page.getByPlaceholder('Filter').fill(''); - await page.getByPlaceholder('Filter').press('Enter'); - await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); - await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeVisible(); - await page.getByPlaceholder('Filter').click(); - await page.getByPlaceholder('Filter').fill('CODE'); - await page.getByPlaceholder('Filter').press('Enter'); - await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); - await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeHidden(); -}); +const testPages = { + 'posts': 'table.html', + 'posts2': 'default.html', + 'posts3': 'grid.html' +}; -test('Categories link are clickable', async ({ page }) => { - await page.goto('./blog/simple-blog/_site/posts/welcome/'); - await page.locator('div').filter({ hasText: /^news$/ }).click(); - await expect(page).toHaveURL(/_site\/index\.html#category=news$/); - await expect(page.locator(`div.category[data-category="${btoa('news')}"]`)).toHaveClass(/active/); - await page.goto('./blog/simple-blog/_site/posts/welcome/#img-lst'); - await page.locator('div').filter({ hasText: /^news$/ }).click(); - await expect(page).toHaveURL(/_site\/index\.html#category=news$/); - await expect(page.locator(`div.category[data-category="${btoa('news')}"]`)).toHaveClass(/active/); -}); +Object.entries(testPages).forEach(([postDir, pageName]) => { + test(`List.js is correctly patched to allow searching with lowercase and uppercase on ${pageName}`, + async ({ page }) => { + await page.goto(`./blog/simple-blog/_site/${pageName}`); + await page.getByPlaceholder('Filter').click(); + await page.getByPlaceholder('Filter').fill('Code'); + await page.getByPlaceholder('Filter').press('Enter'); + await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeHidden(); + await page.getByPlaceholder('Filter').click(); + await page.getByPlaceholder('Filter').fill(''); + await page.getByPlaceholder('Filter').press('Enter'); + await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeVisible(); + await page.getByPlaceholder('Filter').click(); + await page.getByPlaceholder('Filter').fill('CODE'); + await page.getByPlaceholder('Filter').press('Enter'); + await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeHidden(); + }); -test('Categories links are clickable', async ({ page }) => { - const checkCategoryLink = async (category: string) => { - await page.getByRole('link', { name: category }).click(); - await expect(page).toHaveURL(getUrl(`blog/simple-blog/_site/index.html#category=${encodeURIComponent(category)}`)); + const checkCategoryLink = async (page: Page, category: string, pageName: string) => { + await page.getByText(category, { exact: true }).click(); + await expect(page).toHaveURL(getUrl(`blog/simple-blog/_site/${pageName}#category=${encodeURIComponent(category)}`)); await expect(page.locator(`div.category[data-category="${btoa(encodeURIComponent(category))}"]`)).toHaveClass(/active/); }; - // Checking link is working - await page.goto('./blog/simple-blog/_site/posts/welcome/'); - await checkCategoryLink('news'); - // Including for special characters - await page.getByRole('link', { name: 'Welcome To My Blog' }).click(); - await checkCategoryLink('euros (€)'); - await page.getByRole('link', { name: 'Welcome To My Blog' }).click(); - await checkCategoryLink('免疫'); - await page.goto('./blog/simple-blog/_site/posts/post-with-code/'); - await checkCategoryLink("apos'trophe"); - // special check for when a page is not loaded from non root path - await page.goto('./blog/simple-blog/_site/posts/welcome/#img-lst'); - await checkCategoryLink('news'); + + test(`All Categories links are clickable ${postDir} pages`, + async ({ page }) => { + // Checking link is working + await page.goto(`./blog/simple-blog/_site/${postDir}/welcome/`); + await checkCategoryLink(page, 'news', pageName); + // Including for special characters + await page.getByRole('link', { name: 'Welcome To My Blog' }).click(); + await checkCategoryLink(page, 'euros (€)', pageName); + await page.getByRole('link', { name: 'Welcome To My Blog' }).click(); + await checkCategoryLink(page, '免疫', pageName); + await page.goto(`./blog/simple-blog/_site/${postDir}/post-with-code/`); + await checkCategoryLink(page, "apos'trophe", pageName); + // special check for when a page is not loaded from non root path + await page.goto(`./blog/simple-blog/_site/${postDir}/welcome/#img-lst`); + await checkCategoryLink(page, 'news', pageName); + }); + + if (pageName !== 'table.html') { + test(`Categories link on listing page works for ${pageName}`, async ({ page }) => { + await page.goto(`./blog/simple-blog/_site/${pageName}`); + await checkCategoryLink(page, 'apos\'trophe', pageName); + await expect(page.getByRole('link', { name: 'Post With Code' })).toBeVisible(); + await page.goto(`./blog/simple-blog/_site/${pageName}`); + await checkCategoryLink(page, 'euros (€)', pageName); + await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeVisible(); + await page.goto(`./blog/simple-blog/_site/${pageName}`); + await checkCategoryLink(page, '免疫', pageName); + await expect(page.getByRole('link', { name: 'Welcome To My Blog' })).toBeVisible(); + }); + } }); + +