Web app: new entities (Image, RenderedAsset, SharedImage, Token, DeviceImageHistory), enums, repositories, controllers, message handlers, migrations, tests, frontend upload/library/sticker UI, Vue components. Firmware: EPD background screen binaries + gen scripts, setup_bg header. Infra: ddev config, test bundle, gitignore coverage dir. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import FrameCard from '@/components/FrameCard.vue'
|
||||
|
||||
// Mock vue-konva to avoid canvas issues if transitively imported
|
||||
vi.mock('vue-konva', () => ({}))
|
||||
|
||||
const defaultProps = {
|
||||
deviceId: 1,
|
||||
name: 'Living Room',
|
||||
size: 'large' as const,
|
||||
status: 'ok' as const,
|
||||
orientation: 'landscape' as const,
|
||||
}
|
||||
|
||||
describe('FrameCard', () => {
|
||||
it('renders device name', () => {
|
||||
const wrapper = mount(FrameCard, { props: defaultProps })
|
||||
expect(wrapper.text()).toContain('Living Room')
|
||||
})
|
||||
|
||||
it('does not show status badge when status is ok', () => {
|
||||
const wrapper = mount(FrameCard, { props: defaultProps })
|
||||
expect(wrapper.find('.frame-card__status-badge').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows "Offline" badge when status is offline', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, status: 'offline' },
|
||||
})
|
||||
const badge = wrapper.find('.frame-card__status-badge')
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.text()).toContain('Offline')
|
||||
})
|
||||
|
||||
it('shows "Sync issue" badge when status is sync-fail', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, status: 'sync-fail' },
|
||||
})
|
||||
const badge = wrapper.find('.frame-card__status-badge')
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.text()).toContain('Sync issue')
|
||||
})
|
||||
|
||||
it('applies offline modifier class when status is offline', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, status: 'offline' },
|
||||
})
|
||||
expect(wrapper.classes()).toContain('frame-card--offline')
|
||||
})
|
||||
|
||||
it('applies sync-fail modifier class when status is sync-fail', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, status: 'sync-fail' },
|
||||
})
|
||||
expect(wrapper.classes()).toContain('frame-card--sync-fail')
|
||||
})
|
||||
|
||||
it('shows settings button in large size', () => {
|
||||
const wrapper = mount(FrameCard, { props: { ...defaultProps, size: 'large' } })
|
||||
expect(wrapper.find('.frame-card__settings-btn').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('does not show settings button in compact size', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, size: 'compact' },
|
||||
})
|
||||
expect(wrapper.find('.frame-card__settings-btn').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows img element when thumbnailUrl is provided', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, thumbnailUrl: '/thumb/test.jpg' },
|
||||
})
|
||||
const img = wrapper.find('img.frame-card__img')
|
||||
expect(img.exists()).toBe(true)
|
||||
expect(img.attributes('src')).toBe('/thumb/test.jpg')
|
||||
})
|
||||
|
||||
it('shows empty preview placeholder when no thumbnailUrl', () => {
|
||||
const wrapper = mount(FrameCard, { props: defaultProps })
|
||||
expect(wrapper.find('.frame-card__empty-preview').exists()).toBe(true)
|
||||
expect(wrapper.find('img.frame-card__img').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows photo count in compact size', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, size: 'compact', photoCount: 3 },
|
||||
})
|
||||
expect(wrapper.text()).toContain('3 photos')
|
||||
})
|
||||
|
||||
it('uses singular "photo" when photoCount is 1', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, size: 'compact', photoCount: 1 },
|
||||
})
|
||||
expect(wrapper.text()).toContain('1 photo')
|
||||
expect(wrapper.text()).not.toContain('1 photos')
|
||||
})
|
||||
|
||||
it('emits add-photo with deviceId when add button clicked', async () => {
|
||||
const wrapper = mount(FrameCard, { props: defaultProps })
|
||||
await wrapper.find('.frame-card__add-btn').trigger('click')
|
||||
expect(wrapper.emitted('add-photo')).toBeTruthy()
|
||||
expect(wrapper.emitted('add-photo')![0]).toEqual([1])
|
||||
})
|
||||
|
||||
it('emits edit with deviceId when settings button clicked (large)', async () => {
|
||||
const wrapper = mount(FrameCard, { props: { ...defaultProps, size: 'large' } })
|
||||
await wrapper.find('.frame-card__settings-btn').trigger('click')
|
||||
expect(wrapper.emitted('edit')).toBeTruthy()
|
||||
expect(wrapper.emitted('edit')![0]).toEqual([1])
|
||||
})
|
||||
|
||||
it('sets landscape aspect ratio style in large mode', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, size: 'large', orientation: 'landscape' },
|
||||
})
|
||||
const preview = wrapper.find('.frame-card__preview')
|
||||
// Vue serializes { aspectRatio: '5/3' } as 'aspect-ratio: 5 / 3;'
|
||||
expect(preview.attributes('style')).toMatch(/aspect-ratio:\s*5\s*\/\s*3/)
|
||||
})
|
||||
|
||||
it('sets portrait aspect ratio style in large mode', () => {
|
||||
const wrapper = mount(FrameCard, {
|
||||
props: { ...defaultProps, size: 'large', orientation: 'portrait' },
|
||||
})
|
||||
const preview = wrapper.find('.frame-card__preview')
|
||||
expect(preview.attributes('style')).toMatch(/aspect-ratio:\s*3\s*\/\s*5/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user