From fe569911abe161379e2ec08d54fb3973ddff4ef4 Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 03:48:49 -0400 Subject: [PATCH] test: cover GroupLinkStatus' absent-collection fallback in the browser data/community-groups.json is rewritten in one pass by `npm run check:community-group-links`, so an interrupted run can leave the Community page a document carrying no `groups` collection. GroupLinkStatus guards that with `(groupsData?.groups || [])`, and no build reached the fallback: the ordinary coverage build and the variant build both carry the collection, and the variant build needs two drifted groups for its plural arms, so the cleared shape cannot share it. Clear `groups` on the existing data-no-revision build, which reads nothing from community-groups.json today, so the shape costs no additional Docusaurus compile. The collection is set to null rather than [] because an empty array is truthy and would take the left operand. The new spec asserts the real route beside the no-revision one, so a dropped warning is evidence of the fallback rather than of a route that 404d. Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- tests/e2e-coverage-describe-gate.test.mjs | 1 + tests/e2e-data-fixture-validity.test.mjs | 7 ++ .../data-no-revision/community-groups.json | 6 ++ .../group-link-status-absent-groups.spec.js | 92 +++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 tests/e2e/fixtures/data-no-revision/community-groups.json create mode 100644 tests/e2e/group-link-status-absent-groups.spec.js diff --git a/tests/e2e-coverage-describe-gate.test.mjs b/tests/e2e-coverage-describe-gate.test.mjs index 9774f11e..ea06ab18 100644 --- a/tests/e2e-coverage-describe-gate.test.mjs +++ b/tests/e2e-coverage-describe-gate.test.mjs @@ -54,6 +54,7 @@ const COVERAGE_GATED = [ 'data-fixtures.spec.js', 'data-variants.spec.js', 'focus-trap-empty-dialog.spec.js', + 'group-link-status-absent-groups.spec.js', 'group-link-status-variant.spec.js', 'member-directory-freshness-all-absent.spec.js', 'member-directory-freshness-unlinked.spec.js', diff --git a/tests/e2e-data-fixture-validity.test.mjs b/tests/e2e-data-fixture-validity.test.mjs index 4b2ddda5..1f9dec98 100644 --- a/tests/e2e-data-fixture-validity.test.mjs +++ b/tests/e2e-data-fixture-validity.test.mjs @@ -138,6 +138,13 @@ const EXPECTED = new Map([ adds: [], }, ], + [ + 'data-no-revision/community-groups.json', + { + why: "clears the groups collection to reach GroupLinkStatus's `groupsData?.groups || []` fallback; the gate requires a non-empty collection, which is exactly the document an interrupted link-check run leaves behind", + adds: ['community-groups.json: groups must be a non-empty array'], + }, + ], [ 'data-no-revision/members.json', { diff --git a/tests/e2e/fixtures/data-no-revision/community-groups.json b/tests/e2e/fixtures/data-no-revision/community-groups.json new file mode 100644 index 00000000..6dd82d7a --- /dev/null +++ b/tests/e2e/fixtures/data-no-revision/community-groups.json @@ -0,0 +1,6 @@ +{ + "description": "Clears the groups collection so GroupLinkStatus takes the `groupsData?.groups || []` fallback in src/components/GroupLinkStatus/index.js instead of filtering a real array. That arm is the component's guard against a partial data/community-groups.json -- `npm run check:community-group-links` writes the file in one pass, so an interrupted or failed run can leave the document without its collection -- and it is the only arm of the component no build reaches: the ordinary coverage build and tests/e2e/fixtures/data-variant/community-groups.json both carry groups, and a cleared collection cannot go in the variant build, which appends a second drifted group to reach the plural stale.length arms. It is set to null rather than [] because an empty array is truthy and would take the left operand, leaving the fallback unreached. It rides this build because nothing the no-revision build already covers reads community-groups.json, so it costs no additional Docusaurus compile (see the preamble of tests/tools/e2e-data-fixtures.cjs).", + "set": { + "groups": null + } +} diff --git a/tests/e2e/group-link-status-absent-groups.spec.js b/tests/e2e/group-link-status-absent-groups.spec.js new file mode 100644 index 00000000..a9c1fdb5 --- /dev/null +++ b/tests/e2e/group-link-status-absent-groups.spec.js @@ -0,0 +1,92 @@ +// End-to-end coverage for the absent-collection fallback in +// src/components/GroupLinkStatus/index.js: +// +// const stale = (groupsData?.groups || []).filter(...) +// +// The `|| []` operand is the component's guard against a +// data/community-groups.json that carries no collection at all. +// `npm run check:community-group-links` rewrites the document in one pass, so +// a run that is interrupted, or that fails after truncating the file, can +// leave the Community page a document without `groups`. The guard is what +// makes that render the page without the drift warning rather than throw on +// `undefined.filter` and take the whole route down. +// +// Why this is the one arm of the component no existing build reaches. +// tests/e2e/group-link-status-variant.spec.js covers the plural drift arms +// from the variant build, which *appends* a second drifted group, and the +// ordinary coverage build appends one; both therefore carry `groups`, and the +// left operand wins in each. Clearing the collection cannot go in either: +// the variant build needs two drifted groups to render the plural grammar, +// and the ordinary build is where the singular arm and the drift warning are +// measured from. The shape rides tests/e2e/fixtures/data-no-revision/ because +// nothing that build already covers reads community-groups.json, so it costs +// no additional Docusaurus compile (see the preamble of +// tests/tools/e2e-data-fixtures.cjs). +// +// The collection is cleared to null rather than to an empty array because an +// empty array is truthy: `[] || []` takes the left operand and leaves the +// fallback unreached. +// +// The real route is asserted beside the no-revision one. An assertion that a +// page carries no drift warning passes just as well when the route 404s or +// the build lost the page, so the pair is what makes it evidence that the +// fallback supplied the empty collection rather than that nothing rendered. +import { test, expect } from '../tools/e2e-coverage.cjs'; +import { loadSiteData } from '../tools/e2e-data-fixtures.cjs'; + +const COMMUNITY_PATH = '/community'; +const NO_REVISION_BASE = '/e2e-coverage-no-revision'; + +const COVERAGE_ENV = { E2E_COVERAGE: '1' }; +const NO_REVISION_ENV = { + E2E_COVERAGE: '1', + E2E_COVERAGE_BUILD: 'no-revision', +}; + +// Read through the overlays rather than hard-coded, so an edited fixture fails +// here instead of leaving a test that asserts nothing. +const groups = loadSiteData('community-groups.json', COVERAGE_ENV); +const absentGroups = loadSiteData('community-groups.json', NO_REVISION_ENV); + +const isStale = (group) => group.archived || group.reachable === false; +const UPDATED_LINE = /Upstream group status last updated on/; +const DRIFT_WARNING = /archived or unreachable upstream/; + +// The no-revision site is only built by the coverage run; outside it the base +// path does not exist. +const describeCoverage = + process.env.E2E_COVERAGE === '1' ? test.describe : test.describe.skip; + +describeCoverage('a community-groups document carrying no collection', () => { + test('the drift warning is dropped rather than throwing on the missing collection', async ({ + page, + }) => { + // The shapes the two pages are built from. Stated as assertions rather + // than used to skip: an overlay that stopped clearing the collection + // would leave the fallback unexercised, which is the silent retirement + // this pair exists to catch. + expect(Array.isArray(groups.groups)).toBe(true); + expect(groups.groups.filter(isStale).length).toBeGreaterThan(0); + expect(absentGroups.groups).toBe(null); + // The last-updated line is the evidence that the component rendered at + // all on the no-revision page, so it has to survive the cleared + // collection: generatedAt is left alone by this overlay. + expect(absentGroups.generatedAt).toEqual(groups.generatedAt); + + await page.goto(COMMUNITY_PATH); + await expect(page.getByText(DRIFT_WARNING)).toBeVisible(); + + await page.goto(`${NO_REVISION_BASE}${COMMUNITY_PATH}`); + // The route built and served: the page's own heading is prose around the + // component, so it renders whether or not the corpus has anything to say. + await expect( + page.getByRole('heading', { name: 'Community', level: 1 }), + ).toBeVisible(); + // The component itself still rendered -- this is an empty collection, not + // a page that lost the component or a build that lost the route. + await expect(page.getByText(UPDATED_LINE)).toBeVisible(); + // ...and with nothing to report it says nothing, rather than reporting an + // empty list of drifted groups. + await expect(page.getByText(DRIFT_WARNING)).toHaveCount(0); + }); +});