fix(gallery): show feedback filter chips on desktop for galleries without categories (#802)

The desktop feedback-filter chips (All/Likes/Saved/Rated/Commented)
were nested inside the categories row conditional, and the standalone
fallback block is lg:hidden — so a gallery without photo categories
(the default) rendered no feedback filter at all on desktop, despite
the docs and a fully working filter implementation behind it.

Render the row whenever either part has content and gate only the
category scroller on categories existing. The media-count label hides
below lg when no categories exist so the mobile layout stays unchanged
(mobile keeps its own chip block). With-categories galleries render
identically to before.

Regression test pins both chip groups in the DOM with and without
categories (fails on the pre-fix component).
This commit is contained in:
Paul Nothaft
2026-07-15 22:51:22 +02:00
parent aab9e1a937
commit b9283386a5
2 changed files with 112 additions and 31 deletions
@@ -154,10 +154,15 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
{/* Category and Feedback Filters */}
<div className="space-y-3">
{/* Categories Row */}
{categories && categories.length > 0 && (
{/* Categories + desktop feedback row. Rendered whenever EITHER part
has content: the desktop feedback chips must not depend on the
(optional) categories existing, or category-less galleries show
no feedback filter at all on desktop (#802 — the lg:hidden
fallback block below only covers mobile/tablet). */}
{((categories && categories.length > 0) || (feedbackEnabled && !!onFilterChange)) && (
<div className="flex items-start lg:items-center justify-between flex-col lg:flex-row gap-3">
{/* Categories: keep in a horizontal scroll container */}
{categories && categories.length > 0 && (
<div className="w-full overflow-x-auto pb-2 lg:pb-0">
<div className="flex items-center gap-2 min-w-max">
<Button
@@ -187,6 +192,7 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
})}
</div>
</div>
)}
{/* Desktop: compact horizontal feedback filter with headline (icons only) */}
{feedbackEnabled && onFilterChange && (
@@ -244,7 +250,10 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
</div>
)}
<p className="text-xs md:text-sm text-muted-theme flex-shrink-0 ml-auto">
{/* Without categories this row only carries desktop content (the
chips are lg-only; mobile has its own block below), so hide
the count below lg to keep the mobile layout unchanged. */}
<p className={`text-xs md:text-sm text-muted-theme flex-shrink-0 ml-auto ${categories && categories.length > 0 ? '' : 'hidden lg:block'}`}>
{photoCount} {t('common.media', 'media')}
</p>
</div>
@@ -0,0 +1,72 @@
/**
* Regression coverage for #802: the desktop feedback-filter chips
* (All / Likes / Saved / Rated / Commented) were nested inside the
* categories row, so a gallery WITHOUT photo categories (the default)
* rendered no feedback filter at all on desktop — the standalone
* fallback block is lg:hidden (mobile/tablet only). These tests pin
* that both chip groups exist in the DOM regardless of categories.
*/
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { PhotoFilterBar } from '../PhotoFilterBar';
vi.mock('react-i18next', async () => {
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next');
return {
...actual,
useTranslation: () => ({
t: (_key: string, fallback?: unknown) =>
typeof fallback === 'string' ? fallback : _key,
i18n: { language: 'en' }
})
};
});
const baseProps = {
categories: [] as Array<{ id: number; name: string; slug: string }>,
photos: [] as never[],
selectedCategoryId: null,
onCategoryChange: vi.fn(),
searchTerm: '',
onSearchChange: vi.fn(),
sortBy: 'date' as const,
onSortChange: vi.fn(),
photoCount: 0,
};
describe('PhotoFilterBar feedback chips (#802)', () => {
it('renders both chip groups (desktop lg:flex + mobile lg:hidden) with NO categories', () => {
render(
<PhotoFilterBar
{...baseProps}
feedbackEnabled
currentFilter="all"
onFilterChange={vi.fn()}
/>
);
// Two groups: the desktop row variant and the mobile fallback. Before
// the fix, only the mobile one rendered when categories were empty,
// leaving desktop with no feedback filter at all.
expect(screen.getAllByText('Feedback Filter')).toHaveLength(2);
});
it('still renders both chip groups when categories exist', () => {
render(
<PhotoFilterBar
{...baseProps}
categories={[{ id: 1, name: 'Ceremony', slug: 'ceremony' }]}
photos={[{ id: 1, category_id: 1 } as never]}
feedbackEnabled
currentFilter="all"
onFilterChange={vi.fn()}
/>
);
expect(screen.getAllByText('Feedback Filter')).toHaveLength(2);
});
it('renders no chips when feedback is disabled', () => {
render(<PhotoFilterBar {...baseProps} />);
expect(screen.queryByText('Feedback Filter')).toBeNull();
});
});