diff --git a/catalog/CHANGELOG.md b/catalog/CHANGELOG.md index 285ea607f3a..6246ada7be3 100644 --- a/catalog/CHANGELOG.md +++ b/catalog/CHANGELOG.md @@ -18,6 +18,7 @@ where verb is one of ## Changes +- [Fixed] Package page: clicking a package-name prefix filters the package list to that prefix again — regressed by the unified-search migration ([#4413](https://github.com/quiltdata/quilt/pull/4413)), which stopped reading the link's legacy `filter` param ([#5035](https://github.com/quiltdata/quilt/pull/5035)) - [Fixed] Test suite: register `afterEach(cleanup)` so `@testing-library/react` components are unmounted between tests. With `globals: false` ([#4660](https://github.com/quiltdata/quilt/pull/4660)) RTL's auto-cleanup never registered, leaving components mounted; their deferred passive effects could flush after the jsdom environment was torn down, intermittently failing the run with an unhandled "`document` global … not defined anymore" error even when every test passed ([#5026](https://github.com/quiltdata/quilt/pull/5026)) - [Fixed] Qurator chat: drop `
`-in-`

` DOM-nesting warning from the connector status helper text shown while connectors are still connecting ([#5003](https://github.com/quiltdata/quilt/pull/5003)) - [Added] Bucket Overview v2: a denser redesign gated behind the catalog `beta` features toggle (Admin Settings) — navigational header stats, inline Qurator, and a config-driven Tabulator tables section linking into Athena Queries ([#4995](https://github.com/quiltdata/quilt/pull/4995)) diff --git a/catalog/app/constants/routes.ts b/catalog/app/constants/routes.ts index d0858b4f978..fb6bcb0da05 100644 --- a/catalog/app/constants/routes.ts +++ b/catalog/app/constants/routes.ts @@ -124,15 +124,15 @@ export const bucketDir = route( export type BucketDirArgs = Parameters interface BucketPackageListOpts { - filter?: string - sort?: string - p?: string + // KeywordWildcard value for the search model's `name` filter (e.g. `foo/` → + // matches the `foo/*` prefix). Must stay in sync with PackagesSearchFilterIO. + name?: string } export const bucketPackageList = route( '/b/:bucket/packages/', - (bucket: string, { filter, sort, p }: BucketPackageListOpts = {}) => - `/b/${bucket}/packages/${mkSearch({ filter, sort, p })}`, + (bucket: string, { name }: BucketPackageListOpts = {}) => + `/b/${bucket}/packages/${mkSearch({ name })}`, ) export type BucketPackageListArgs = Parameters diff --git a/catalog/app/containers/Bucket/PackageTree/PackageLink.spec.tsx b/catalog/app/containers/Bucket/PackageTree/PackageLink.spec.tsx new file mode 100644 index 00000000000..78485f5ccf9 --- /dev/null +++ b/catalog/app/containers/Bucket/PackageTree/PackageLink.spec.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' +import { describe, it, expect, vi } from 'vitest' +import { MemoryRouter } from 'react-router-dom' +import { render } from '@testing-library/react' + +import { bucketPackageList, bucketPackageDetail } from 'constants/routes' +import * as NamedRoutes from 'utils/NamedRoutes' +import { + PackagesSearchFilterIO, + ResultType, + parseSearchParams, +} from 'containers/Search/model' + +import PackageLink from './PackageLink' + +// The Search model's import graph pulls in `constants/config`, which throws +// unless a catalog config is present on `window`. +vi.mock('constants/config', () => ({ default: {} })) + +describe('containers/Bucket/PackageTree/PackageLink', () => { + // Regression guard (#4413): the package list reads filters by predicate key + // and ignores unrecognized params, so the prefix link must emit a param that + // round-trips through `parseSearchParams` into the `name` filter. + it('prefix link round-trips to the package list `name` filter', () => { + const { getByRole } = render( + + + + + , + ) + + const href = getByRole('link', { name: 'team/' }).getAttribute('href') + expect(href).toBeTruthy() + + const state = parseSearchParams(new URL(href!, 'http://localhost').search) + + expect(state.resultType).toBe(ResultType.QuiltPackage) + if (state.resultType !== ResultType.QuiltPackage) throw new Error('unreachable') + + expect(state.filter.predicates.name).toMatchObject({ + wildcard: 'team/', + strict: false, + }) + + // ...and the prefix becomes a `team/*` wildcard at the GraphQL layer. + expect(PackagesSearchFilterIO.toGQL(state.filter)?.name?.wildcard).toBe('team/*') + }) + + // The flip side: the pre-fix `filter=` param must stay inert. + it('drops the unrecognized legacy `filter` param', () => { + const state = parseSearchParams('?filter=team/') + if (state.resultType !== ResultType.QuiltPackage) throw new Error('unreachable') + expect(state.filter.predicates.name).toBeNull() + }) +}) diff --git a/catalog/app/containers/Bucket/PackageTree/PackageLink.tsx b/catalog/app/containers/Bucket/PackageTree/PackageLink.tsx index 472b4babc79..000f45753eb 100644 --- a/catalog/app/containers/Bucket/PackageTree/PackageLink.tsx +++ b/catalog/app/containers/Bucket/PackageTree/PackageLink.tsx @@ -21,7 +21,7 @@ export default function PackageLink({ bucket, name }: PackageLinkProps) { const [prefix, suffix] = name.split('/') return ( - {prefix}/ + {prefix}/ {suffix} )