0497394710
- Showcase reveal cadence 80ms -> 160ms (slower, more deliberate one-at-a-time cascade per operator). Bump showcase.spec timer advances to cover 60x160ms. - Gallery filter bar now uses the EXACT gradiated-obsidian frost + blur as TopNav (was a flat rgba(...,0.55) block), so the two read as one continuous piece of chrome with images visibly scrolling under both; the nav's transparent bottom edge against the bar's opaque top leaves a faint seam that separates them at the very top of scroll. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
3.2 KiB
JavaScript
98 lines
3.2 KiB
JavaScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||
import { setActivePinia, createPinia } from 'pinia'
|
||
|
||
// Mock the decode-gate so the cascade is paced purely by the store's cadence
|
||
// in tests (no real <img> load events in happy-dom). Individual tests can
|
||
// override per-call to exercise the gating itself.
|
||
vi.mock('../src/utils/preloadImage.js', () => ({
|
||
preloadImage: vi.fn(() => Promise.resolve()),
|
||
}))
|
||
import { preloadImage } from '../src/utils/preloadImage.js'
|
||
import { useShowcaseStore } from '../src/stores/showcase.js'
|
||
|
||
let nextId
|
||
|
||
function stubShowcase({ empty = false } = {}) {
|
||
globalThis.fetch = vi.fn(async () => {
|
||
const images = empty
|
||
? []
|
||
: Array.from({ length: 3 }, () => ({
|
||
id: nextId++, sha256: 's', mime: 'image/jpeg',
|
||
width: 1, height: 1, thumbnail_url: '/t',
|
||
}))
|
||
return {
|
||
ok: true,
|
||
status: 200,
|
||
statusText: '200',
|
||
text: async () => JSON.stringify({ images }),
|
||
}
|
||
})
|
||
}
|
||
|
||
describe('showcase store: buffered cascade', () => {
|
||
beforeEach(() => {
|
||
setActivePinia(createPinia())
|
||
nextId = 1
|
||
preloadImage.mockReset()
|
||
preloadImage.mockImplementation(() => Promise.resolve())
|
||
vi.useFakeTimers()
|
||
})
|
||
afterEach(() => {
|
||
vi.useRealTimers()
|
||
vi.restoreAllMocks()
|
||
})
|
||
|
||
it('drains buffered items in fire-order at a fixed cadence, no dupes', async () => {
|
||
stubShowcase()
|
||
const s = useShowcaseStore()
|
||
const p = s.loadInitial()
|
||
// Past INITIAL_COUNT (60) × CADENCE (160ms = 9.6s) plus fetch microtasks.
|
||
await vi.advanceTimersByTimeAsync(12000)
|
||
await p
|
||
expect(s.images.length).toBe(60)
|
||
const ids = s.images.map((i) => i.id)
|
||
expect(new Set(ids).size).toBe(60) // dedup held
|
||
expect(ids).toEqual([...ids].sort((a, b) => a - b)) // shown in fire order
|
||
})
|
||
|
||
it('reveals at most one item per cadence tick (decoupled from fetch)', async () => {
|
||
stubShowcase()
|
||
const s = useShowcaseStore()
|
||
const p = s.loadInitial()
|
||
await vi.advanceTimersByTimeAsync(1000)
|
||
const early = s.images.length
|
||
// Even though fetches resolve instantly, the consumer is rate-limited by
|
||
// the cadence — a 1s window must not dump the whole buffer at once.
|
||
expect(early).toBeGreaterThan(0)
|
||
expect(early).toBeLessThan(60)
|
||
await vi.advanceTimersByTimeAsync(12000)
|
||
await p
|
||
expect(s.images.length).toBe(60)
|
||
})
|
||
|
||
it('does not reveal a tile until its image has decoded', async () => {
|
||
stubShowcase()
|
||
let release
|
||
// The first tile's preload hangs until we release it; all others resolve.
|
||
preloadImage.mockImplementationOnce(() => new Promise((r) => { release = r }))
|
||
const s = useShowcaseStore()
|
||
const p = s.loadInitial()
|
||
await vi.advanceTimersByTimeAsync(1000)
|
||
expect(s.images.length).toBe(0) // gated on the un-decoded first image
|
||
release()
|
||
await vi.advanceTimersByTimeAsync(12000)
|
||
await p
|
||
expect(s.images.length).toBe(60) // cascade proceeds once it decodes
|
||
})
|
||
|
||
it('flags an empty library without starting the cascade', async () => {
|
||
stubShowcase({ empty: true })
|
||
const s = useShowcaseStore()
|
||
const p = s.loadInitial()
|
||
await vi.advanceTimersByTimeAsync(1000)
|
||
await p
|
||
expect(s.images.length).toBe(0)
|
||
expect(s.isEmpty).toBe(true)
|
||
})
|
||
})
|