feat: results skeleton on the search page

The search loader now awaits only the chip cloud (its auth gate) and streams
the view-specific results, so switching tag / view / sort / text shows a
SearchResultsSkeleton in place of the frozen previous list until the query
resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-07 07:14:43 +02:00
parent f1e66141f4
commit 5b51bcf056
8 changed files with 262 additions and 107 deletions

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import Skeleton from './Skeleton.svelte';
/**
* Placeholder for a tagged-page / chapter / manga results list while a
* search re-queries. Mirrors the TaggedPageRow shape (56px 2/3 cover +
* two text lines) so the list keeps its height as results swap in.
*/
let { count = 6, testid = 'search-results-skeleton' }: { count?: number; testid?: string } =
$props();
</script>
<ul class="list" data-testid={testid} aria-hidden="true">
{#each Array(count) as _}
<li class="row">
<Skeleton width="56px" aspectRatio="2 / 3" radius="sm" />
<div class="meta">
<Skeleton variant="text" width="60%" radius="sm" />
<Skeleton variant="text" width="40%" radius="sm" />
</div>
</li>
{/each}
</ul>
<style>
.list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.row {
display: grid;
grid-template-columns: 56px 1fr;
gap: var(--space-3);
align-items: center;
}
.meta {
display: flex;
flex-direction: column;
gap: var(--space-2);
min-width: 0;
}
</style>

View File

@@ -0,0 +1,22 @@
import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/svelte';
import SearchResultsSkeleton from './SearchResultsSkeleton.svelte';
afterEach(() => cleanup());
describe('SearchResultsSkeleton', () => {
it('renders the requested number of row placeholders', () => {
render(SearchResultsSkeleton, { props: { count: 3, testid: 'sk' } });
expect(screen.getByTestId('sk').querySelectorAll('.row').length).toBe(3);
});
it('defaults to 6 rows', () => {
render(SearchResultsSkeleton, { props: { testid: 'sk' } });
expect(screen.getByTestId('sk').querySelectorAll('.row').length).toBe(6);
});
it('is decorative (aria-hidden)', () => {
render(SearchResultsSkeleton, { props: { testid: 'sk' } });
expect(screen.getByTestId('sk').getAttribute('aria-hidden')).toBe('true');
});
});