Dashboard: search and filter studies by tag
Build and Push Docker Image / build-and-push (push) Failing after 20s
Build and Push Docker Image / build-and-push (push) Failing after 20s
- The search box matches tags as well as title and passage; "#tag" searches tags only - Tag chips (with counts) replace the Tag dropdown; several can be on at once and a study must have all of them - Tags on each study card are clickable filters - "No studies match" message with a Clear filters link Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc825f0112
commit
88bc59bac0
4 files changed
+138
-21
No files matched your search
@@ -27,6 +27,45 @@ describe('Home page', () => {
|
||||
localStorage.setItem('bible-study-index', JSON.stringify(index));
|
||||
}
|
||||
|
||||
test('studies can be found by tag: chips, clicking a tag on a card, and #tag search', async () => {
|
||||
const add = (id, title, tags) => {
|
||||
seedProject({ id, title });
|
||||
const index = JSON.parse(localStorage.getItem('bible-study-index'));
|
||||
index.find((e) => e.id === id).tags = tags;
|
||||
localStorage.setItem('bible-study-index', JSON.stringify(index));
|
||||
};
|
||||
add('p1', 'Titus Notes', ['hope', 'leadership']);
|
||||
add('p2', 'New Birth', ['grace', 'hope']);
|
||||
add('p3', 'Grace Alone', []);
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
const titles = () => screen.queryAllByRole('heading', { level: 2 }).map((h) => h.textContent).filter((t) => ['Titus Notes', 'New Birth', 'Grace Alone'].includes(t)).sort();
|
||||
const bar = screen.getByRole('group', { name: /filter by tag/i });
|
||||
expect(titles()).toHaveLength(3);
|
||||
|
||||
await user.click(within(bar).getByRole('button', { name: /^hope/ }));
|
||||
expect(titles()).toEqual(['New Birth', 'Titus Notes']);
|
||||
await user.click(within(bar).getByRole('button', { name: /^grace/ })); // both chips: a study needs both
|
||||
expect(titles()).toEqual(['New Birth']);
|
||||
await user.click(within(bar).getByRole('button', { name: 'Clear' }));
|
||||
expect(titles()).toHaveLength(3);
|
||||
|
||||
await user.click(screen.getAllByRole('button', { name: 'leadership' }).find((b) => b.closest('h2, div.rounded-2xl')?.textContent.includes('Titus Notes'))); // a tag on a card
|
||||
expect(titles()).toEqual(['Titus Notes']);
|
||||
await user.click(within(bar).getByRole('button', { name: 'Clear' }));
|
||||
|
||||
await user.type(screen.getByPlaceholderText(/search projects/i), '#grace'); // tags only: not the study named "Grace Alone"
|
||||
expect(titles()).toEqual(['New Birth']);
|
||||
await user.clear(screen.getByPlaceholderText(/search projects/i));
|
||||
await user.type(screen.getByPlaceholderText(/search projects/i), 'lead'); // plain words match tags too
|
||||
expect(titles()).toEqual(['Titus Notes']);
|
||||
await user.clear(screen.getByPlaceholderText(/search projects/i));
|
||||
await user.type(screen.getByPlaceholderText(/search projects/i), '#nothing');
|
||||
expect(await screen.findByRole('status')).toHaveTextContent(/No studies match/);
|
||||
await user.click(screen.getByRole('button', { name: 'Clear filters' }));
|
||||
expect(titles()).toHaveLength(3);
|
||||
});
|
||||
|
||||
test('studies can be shown as a grid or a list, and the choice is remembered', async () => {
|
||||
seedProject();
|
||||
seedProject({ id: 'p2', title: 'Second Study' });
|
||||
|
||||
Reference in new issue
Block a user