From 5a58eb73dcbc63c9399849be47df6d839bc7abf6 Mon Sep 17 00:00:00 2001 From: TallblokeUK Date: Sat, 5 Sep 2026 12:22:41 +0100 Subject: [PATCH 1/3] fix: keep the cloud table's select-all honest when nothing on the page can be downloaded The header checkbox of the community cloud table ticked itself whenever no snippet on the page could be downloaded, because "every one of nothing" is selected, and the rows showed no boxes at all, so the column looked broken. The header box now only ticks when there is something to tick and is disabled otherwise, and every row shows a box: a snippet that is already in the library, or needs Pro, gets a disabled one that says so. --- .../CommunityCloud/CloudSnippetsTable.tsx | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/js/components/ManageMenu/CommunityCloud/CloudSnippetsTable.tsx b/src/js/components/ManageMenu/CommunityCloud/CloudSnippetsTable.tsx index 5539a8197..8d78dec74 100644 --- a/src/js/components/ManageMenu/CommunityCloud/CloudSnippetsTable.tsx +++ b/src/js/components/ManageMenu/CommunityCloud/CloudSnippetsTable.tsx @@ -64,46 +64,77 @@ interface TableHeadingCheckboxProps { setSelectedIds: Dispatch>> } +/** + * Selects every snippet on the page that can be downloaded. With nothing + * downloadable the box is disabled and unticked: "all of nothing" must not + * read as a selection. + */ const TableHeadingCheckbox: React.FC = ({ availableIds, selectedIds, setSelectedIds }) => selectedIds.has(snippetId))} + checked={0 < availableIds.length && availableIds.every(snippetId => selectedIds.has(snippetId))} + disabled={0 === availableIds.length} + title={0 === availableIds.length ? __('Nothing on this page can be downloaded.', 'code-snippets') : undefined} onChange={event => setSelectedIds(previous => new Set(event.target.checked ? [...previous, ...availableIds] : [...previous].filter(snippetId => !availableIds.includes(snippetId))) )} - aria-label={__('Select all snippets', 'code-snippets')} + aria-label={__('Select all downloadable snippets', 'code-snippets')} /> +/** Why a snippet cannot be selected for download, or undefined when it can. */ +const unavailableReason = (snippet: CloudSnippetSchema): string | undefined => { + if (snippet.local_id) { + return __('Already in your library.', 'code-snippets') + } + + return isCloudSnippetDownloadable(snippet) + ? undefined + : __('Requires Code Snippets Pro.', 'code-snippets') +} + interface TableRowCheckboxProps { snippet: CloudSnippetSchema selected: Set setSelected: Dispatch>> } -const TableRowCheckbox: React.FC = ({ snippet, selected, setSelected }) => - - {isCloudSnippetDownloadable(snippet) && ( +/** + * Every row shows a box, so the column reads as one control: a snippet that + * cannot be downloaded gets a disabled box that says why. + */ +const TableRowCheckbox: React.FC = ({ snippet, selected, setSelected }) => { + const reason = unavailableReason(snippet) + + return ( + setSelected(previous => new Set(event.target.checked ? [...previous, snippet.id] : [...previous].filter(snippetId => snippetId !== snippet.id)) )} - />)} - + /> + + ) +} export interface CloudSnippetsTableProps { snippets: CloudSnippetSchema[] From 21920f46eb95738dad350bbf71fb2dfe48082856 Mon Sep 17 00:00:00 2001 From: TallblokeUK Date: Sat, 5 Sep 2026 12:44:13 +0100 Subject: [PATCH 2/3] test: expect disabled, explained boxes for rows that cannot be downloaded, and none selected when nothing can The eligibility spec asserted the old shape of the cloud table: no box on a row that cannot be downloaded, and a header named for all snippets. It now expects every row to carry a box, disabled with its reason where the snippet is already in the library or needs Pro, and covers a page where nothing can be downloaded: the header must be unticked and disabled rather than reading as a selection. --- tests/e2e/cloud-download-eligibility.spec.ts | 35 +++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/tests/e2e/cloud-download-eligibility.spec.ts b/tests/e2e/cloud-download-eligibility.spec.ts index 8d9eecc0d..32b91b0e1 100644 --- a/tests/e2e/cloud-download-eligibility.spec.ts +++ b/tests/e2e/cloud-download-eligibility.spec.ts @@ -87,11 +87,15 @@ test.describe('Cloud bulk download eligibility', () => { const table = page.locator('.cloud-snippets-table') await expect(table.getByRole('checkbox', { name: 'Select Eligible Alpha' })).toBeVisible() - await expect(table.getByRole('checkbox', { name: 'Select Linked Beta' })).toHaveCount(0) - await expect(table.getByRole('checkbox', { name: 'Select Pro Gamma' })).toHaveCount(0) + // Rows that cannot be downloaded keep a box, disabled, that says why. + await expect(table.getByRole('checkbox', { name: 'Linked Beta cannot be selected: Already in your library.' })).toBeDisabled() + await expect(table.getByRole('checkbox', { name: 'Pro Gamma cannot be selected: Requires Code Snippets Pro.' })).toBeDisabled() - await table.getByRole('checkbox', { name: 'Select all snippets' }).check() + const selectAll = table.getByRole('checkbox', { name: 'Select all downloadable snippets' }) + await expect(selectAll).not.toBeChecked() + await selectAll.check() await expect(table.getByRole('checkbox', { name: 'Select Eligible Alpha' })).toBeChecked() + await expect(table.getByRole('checkbox', { name: 'Linked Beta cannot be selected: Already in your library.' })).not.toBeChecked() await applyBulkDownload(page) await expect.poll(() => state.downloads).toEqual([ELIGIBLE.id]) @@ -123,13 +127,34 @@ test.describe('Cloud bulk download eligibility', () => { const table = page.locator('.cloud-snippets-table') await expect(table.getByRole('checkbox', { name: 'Select Pro Gamma' })).toBeVisible() - await expect(table.getByRole('checkbox', { name: 'Select Linked Beta' })).toHaveCount(0) + await expect(table.getByRole('checkbox', { name: 'Linked Beta cannot be selected: Already in your library.' })).toBeDisabled() - await table.getByRole('checkbox', { name: 'Select all snippets' }).check() + await table.getByRole('checkbox', { name: 'Select all downloadable snippets' }).check() await applyBulkDownload(page) await expect.poll(() => [...state.downloads].sort((a, b) => a - b)).toEqual([ELIGIBLE.id, PRO_LOCKED.id]) }) + test('a page with nothing downloadable offers no selection at all', async ({ page }) => { + const state: CloudRoutesState = { + snippets: [ + cloudSnippet({ id: 201, name: 'Owned One', local_id: 11 }), + cloudSnippet({ id: 202, name: 'Owned Two', local_id: 12 }) + ], + downloads: [] + } + await forceLicenseState(page, true) + await routeCloudSnippets(page, state) + await openCommunityCloud(page, 'table') + + const table = page.locator('.cloud-snippets-table') + const selectAll = table.getByRole('checkbox', { name: 'Select all downloadable snippets' }) + // "All of nothing" must not read as a selection: the header box is unticked and disabled. + await expect(selectAll).toBeDisabled() + await expect(selectAll).not.toBeChecked() + await expect(table.getByRole('checkbox', { name: 'Owned One cannot be selected: Already in your library.' })).toBeDisabled() + await expect(table.getByRole('checkbox', { name: 'Owned Two cannot be selected: Already in your library.' })).toBeDisabled() + }) + test('selections hidden by a new search are not downloaded', async ({ page }) => { const state: CloudRoutesState = { snippets: [ELIGIBLE, LINKED, PRO_LOCKED], downloads: [] } await forceLicenseState(page, false) From 8781edac959410d8df454ae1cabb6ee89824d16d Mon Sep 17 00:00:00 2001 From: TallblokeUK Date: Sat, 5 Sep 2026 12:57:43 +0100 Subject: [PATCH 3/3] test: name the cloud table's select-all as it now reads --- tests/e2e/code-snippets-community-featured.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/code-snippets-community-featured.spec.ts b/tests/e2e/code-snippets-community-featured.spec.ts index 1087e7c74..92c2d3bac 100644 --- a/tests/e2e/code-snippets-community-featured.spec.ts +++ b/tests/e2e/code-snippets-community-featured.spec.ts @@ -262,7 +262,7 @@ test.describe('Community Cloud Featured Snippets', () => { const table = page.locator('.cloud-snippets-table') await expect(table).toBeVisible({ timeout: TIMEOUTS.DEFAULT }) - const headerCheckbox = table.locator('thead').getByRole('checkbox', { name: 'Select all snippets' }) + const headerCheckbox = table.locator('thead').getByRole('checkbox', { name: 'Select all downloadable snippets' }) const rowCheckbox = table.locator('tbody').getByRole('checkbox', { name: 'Select Mock Cloud Snippet' }) // The table owns the only select-all control; the toolbar checkbox is