fix(showcase): reveal each tile only once its image is fully decoded
The buffered cascade revealed tiles on an 80ms timer regardless of image load, so the flip-in animation played on a gray placeholder and the thumbnail popped in afterward. Worse, MasonryGrid ALSO applied a per-index animation-delay (index×70ms) that compounded on top of the insert cadence, so the cascade visibly dragged and desynced as it grew. Now the producer preloads each queued thumbnail (decode pipelined ahead) and the consumer awaits that decode before pushing the item — every tile animates in fully loaded, strictly one at a time. Drop the compounding CSS stagger; the store's one-item-at-a-time push is the sole pacer, so each tile animates the instant it mounts. New utils/preloadImage.js (load+decode+timeout gate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
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
|
||||
@@ -25,6 +33,8 @@ describe('showcase store: buffered cascade', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
nextId = 1
|
||||
preloadImage.mockReset()
|
||||
preloadImage.mockImplementation(() => Promise.resolve())
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
afterEach(() => {
|
||||
@@ -60,6 +70,21 @@ describe('showcase store: buffered cascade', () => {
|
||||
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(6000)
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user