Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions catalog/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ complete sentence without it.

## Changes

- [Fixed] Search sidebar: the facet "Sort by" control announces what it is — its label used to land on a hidden input, leaving assistive tech to read the control as its current ordering and nothing more ([#5261](https://github.com/quiltdata/quilt/pull/5261))
- [Fixed] Queries: the query selector announces its label to assistive tech, and no longer claims "Custom" is loaded while its helper text reports the query failed to load ([#5260](https://github.com/quiltdata/quilt/pull/5260))
- [Changed] The `data-products` demo fixture data no longer ships in the bundles a browser downloads on the volumes landing; it loads only when the preview is on ([#5259](https://github.com/quiltdata/quilt/pull/5259))
- [Fixed] Quilt+ URI parsing retains the informational `catalog` field, reads a raw `+` in an unencoded path as a `+` rather than a space, and shares its compatibility corpus with quilt3 ([#5255](https://github.com/quiltdata/quilt/pull/5255))
Expand Down
47 changes: 47 additions & 0 deletions catalog/app/containers/Search/Layout/PackageFilters.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react'
import { render, cleanup, fireEvent, screen } from '@testing-library/react'
import { describe, it, expect, vi, afterEach } from 'vitest'

import * as SearchUIModel from '../model'

import { AvailablePackagesMetaFilters } from './PackageFilters'

vi.mock('constants/config', () => ({ default: {} }))

function renderFilters() {
return render(
<AvailablePackagesMetaFilters
filtering={SearchUIModel.FacetsFilteringState.Disabled()}
facets={{
available: [],
visible: SearchUIModel.EMPTY_FACET_TREE,
hidden: SearchUIModel.EMPTY_FACET_TREE,
}}
ordering={{
value: SearchUIModel.DEFAULT_FACET_ORDERING,
set: vi.fn(),
offered: true,
}}
fetching={false}
/>,
)
}

describe('containers/Search/Layout/PackageFilters', () => {
afterEach(cleanup)

describe('the accessible name', () => {
// The exact name, not a substring: the caption alone matches /Sort by/ too,
// so a loosened assertion passes on wiring that drops the ordering.
it('carries the label and the selected ordering', () => {
renderFilters()
expect(screen.getByRole('button', { name: 'Sort by: Name A → Z' })).toBeTruthy()
})

it('names the popup listbox too', () => {
renderFilters()
fireEvent.mouseDown(screen.getByRole('button'))
expect(screen.getByRole('listbox', { name: 'Sort by:' })).toBeTruthy()
})
})
})
16 changes: 13 additions & 3 deletions catalog/app/containers/Search/Layout/PackageFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as FiltersUI from 'components/Filters'
import Skeleton from 'components/Skeleton'
import * as JSONPointer from 'utils/JSONPointer'
import * as NamedRoutes from 'utils/NamedRoutes'
import useId from 'utils/useId'

import FilterWidget from '../FilterWidget'
import { PACKAGE_FILTER_LABELS } from '../i18n'
Expand Down Expand Up @@ -189,14 +190,18 @@ interface AvailablePackagesMetaFiltersProps {
fetching: boolean
}

function AvailablePackagesMetaFilters({
// Exported for testing: the ordering control's accessible name lives in how
// props land on MUI's Select internals, which only a render can assert.
export function AvailablePackagesMetaFilters({
className,
filtering,
facets,
ordering,
fetching,
}: AvailablePackagesMetaFiltersProps) {
const classes = useAvailablePackagesMetaFiltersStyles()
const orderLabelId = useId()
const orderButtonId = useId()

const [expanded, setExpanded] = React.useState(false)
const toggleExpanded = React.useCallback(() => setExpanded((x) => !x), [])
Expand Down Expand Up @@ -237,7 +242,7 @@ function AvailablePackagesMetaFilters({
on the client-filter path. */}
{ordering.offered && (
<div className={classes.order}>
<span className={classes.orderLabel} id="meta-order-label">
<span className={classes.orderLabel} id={orderLabelId}>
Sort by:
</span>
<FiltersUI.Select<string>
Expand All @@ -246,7 +251,12 @@ function AvailablePackagesMetaFilters({
disabled={fetching}
extents={FACET_ORDERING_VALUES}
getOptionLabel={(value) => FACET_ORDERING_LABELS[value]}
inputProps={{ 'aria-labelledby': 'meta-order-label' }}
// `labelId` reaches the focusable display div and names the popup
// listbox; `id` must come with it, since MUI joins the two into
// `aria-labelledby="labelId buttonId"` and `labelId` alone would
// override the div's contents, dropping the ordering from the name.
labelId={orderLabelId}
id={orderButtonId}
onChange={(value) =>
ordering.set(SearchUIModel.parseFacetOrdering(value, ordering.value))
}
Expand Down
Loading