feat(home): full-size frame card; horizontal carousel for multi-frame setups
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
Reverts the 240px preview cap — frames render at their natural device aspect again. Single-frame layout unchanged. For multi-frame setups, replaces the compact stack with a horizontal scroll-snap carousel: one large card per slide, full-bleed to the viewport edges, with dot navigation below that tracks the active slide and supports tap-to-jump. Native CSS scroll-snap drives the swipe gesture; no extra JS gesture library. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -106,22 +106,71 @@ describe('HomeView', () => {
|
||||
})
|
||||
}
|
||||
|
||||
// HV-01: N devices renders N FrameCard stubs
|
||||
it('renders one FrameCard per device when devices are present', async () => {
|
||||
// HV-01: N devices renders a carousel of N large FrameCard stubs + N dots
|
||||
it('renders one FrameCard per device in a carousel when multiple devices present', async () => {
|
||||
const devicesStore = useDevicesStore()
|
||||
devicesStore.devices = [
|
||||
makeDevice({ id: 1, name: 'Frame A' }),
|
||||
makeDevice({ id: 2, name: 'Frame B' }),
|
||||
makeDevice({ id: 3, name: 'Frame C' }),
|
||||
]
|
||||
// Mock fetchDevices so onMounted doesn't overwrite devices
|
||||
vi.spyOn(devicesStore, 'fetchDevices').mockResolvedValue()
|
||||
|
||||
const wrapper = mountView()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const cards = wrapper.findAll('.frame-card-stub')
|
||||
expect(cards).toHaveLength(3)
|
||||
expect(wrapper.find('.home-view__carousel').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.frame-card-stub')).toHaveLength(3)
|
||||
// All cards should be the large variant (no more compact stack)
|
||||
const cards = wrapper.findAllComponents({ name: 'FrameCard' })
|
||||
for (const c of cards) expect(c.props('size')).toBe('large')
|
||||
// One navigation dot per device
|
||||
expect(wrapper.findAll('.home-view__dot')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('marks the first dot active by default and updates active dot on scroll', async () => {
|
||||
const devicesStore = useDevicesStore()
|
||||
devicesStore.devices = [
|
||||
makeDevice({ id: 1, name: 'A' }),
|
||||
makeDevice({ id: 2, name: 'B' }),
|
||||
]
|
||||
vi.spyOn(devicesStore, 'fetchDevices').mockResolvedValue()
|
||||
|
||||
const wrapper = mountView()
|
||||
await wrapper.vm.$nextTick()
|
||||
let dots = wrapper.findAll('.home-view__dot')
|
||||
expect(dots[0].classes()).toContain('home-view__dot--active')
|
||||
expect(dots[1].classes()).not.toContain('home-view__dot--active')
|
||||
|
||||
// Simulate the carousel having scrolled to the second slide
|
||||
const carousel = wrapper.find('.home-view__carousel').element as HTMLElement
|
||||
Object.defineProperty(carousel, 'clientWidth', { configurable: true, value: 360 })
|
||||
Object.defineProperty(carousel, 'scrollLeft', { configurable: true, value: 360 })
|
||||
await wrapper.find('.home-view__carousel').trigger('scroll')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
dots = wrapper.findAll('.home-view__dot')
|
||||
expect(dots[1].classes()).toContain('home-view__dot--active')
|
||||
expect(dots[0].classes()).not.toContain('home-view__dot--active')
|
||||
})
|
||||
|
||||
it('clicking a dot scrolls the carousel to that slide', async () => {
|
||||
const devicesStore = useDevicesStore()
|
||||
devicesStore.devices = [makeDevice({ id: 1 }), makeDevice({ id: 2 }), makeDevice({ id: 3 })]
|
||||
vi.spyOn(devicesStore, 'fetchDevices').mockResolvedValue()
|
||||
|
||||
const wrapper = mountView()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const carousel = wrapper.find('.home-view__carousel').element as HTMLElement
|
||||
Object.defineProperty(carousel, 'clientWidth', { configurable: true, value: 360 })
|
||||
const scrollToSpy = vi.fn()
|
||||
;(carousel as any).scrollTo = scrollToSpy
|
||||
|
||||
await wrapper.findAll('.home-view__dot')[2].trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(scrollToSpy).toHaveBeenCalledWith(expect.objectContaining({ left: 720, behavior: 'smooth' }))
|
||||
})
|
||||
|
||||
// HV-01b: single device still renders one FrameCard (large variant branch)
|
||||
|
||||
Reference in New Issue
Block a user