chore: merge executor worktree (10-02 EmptyState) — kept 10-01 AppIcon.vue (resolvedPaths vs paths[name]; 10-01 is canonical)
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div :class="containerClass">
|
||||
<AppIcon v-if="icon" :name="icon" :class="iconClass" />
|
||||
<p :class="headlineClass">{{ headline }}</p>
|
||||
<p v-if="subtext" :class="subtextClass">{{ subtext }}</p>
|
||||
<slot name="cta" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppIcon from './AppIcon.vue'
|
||||
export default {
|
||||
name: 'EmptyState',
|
||||
components: { AppIcon },
|
||||
props: {
|
||||
icon: { type: String, default: null },
|
||||
headline: { type: String, required: true },
|
||||
subtext: { type: String, default: '' },
|
||||
size: { type: String, default: 'md' },
|
||||
},
|
||||
computed: {
|
||||
containerClass() {
|
||||
return this.size === 'sm'
|
||||
? 'flex items-center gap-2 py-1 text-xs text-gray-400'
|
||||
: 'text-center py-10 px-4'
|
||||
},
|
||||
iconClass() {
|
||||
return this.size === 'sm' ? 'w-3.5 h-3.5 shrink-0' : 'w-8 h-8 mx-auto mb-3 text-gray-300'
|
||||
},
|
||||
headlineClass() {
|
||||
return this.size === 'sm' ? '' : 'text-sm font-medium text-gray-500'
|
||||
},
|
||||
subtextClass() {
|
||||
return this.size === 'sm' ? 'hidden' : 'text-xs text-gray-400 mt-1'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import EmptyState from '../EmptyState.vue'
|
||||
|
||||
describe('EmptyState', () => {
|
||||
it('renders headline text from the headline prop', () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'Nothing here' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
expect(w.text()).toContain('Nothing here')
|
||||
})
|
||||
|
||||
it('renders subtext when provided; omits when empty', () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'Title', subtext: 'hello' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
expect(w.text()).toContain('hello')
|
||||
|
||||
const w2 = mount(EmptyState, {
|
||||
props: { headline: 'Title' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
const paragraphs = w2.findAll('p')
|
||||
expect(paragraphs.length).toBe(1)
|
||||
})
|
||||
|
||||
it('renders AppIcon when icon prop is set; renders no icon when null', () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'Title', icon: 'folder' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
expect(w.findComponent({ name: 'AppIcon' }).exists()).toBe(true)
|
||||
|
||||
const w2 = mount(EmptyState, {
|
||||
props: { headline: 'Title', icon: null },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
expect(w2.findComponent({ name: 'AppIcon' }).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders nothing in the CTA area when #cta slot is empty', () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'Title' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
expect(w.find('button').exists()).toBe(false)
|
||||
expect(w.find('a').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders the #cta slot content when provided', () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'Title' },
|
||||
slots: { cta: '<button data-test="cta-btn">Upload</button>' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
expect(w.find('[data-test="cta-btn"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it("size='sm' applies the sidebar micro layout (flex container with gap-2)", () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'No folders yet', size: 'sm' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
const root = w.element
|
||||
expect(root.classList.contains('flex')).toBe(true)
|
||||
expect(root.classList.contains('gap-2')).toBe(true)
|
||||
})
|
||||
|
||||
it('default size (md) applies the centered layout (text-center py-10)', () => {
|
||||
const w = mount(EmptyState, {
|
||||
props: { headline: 'Nothing here' },
|
||||
global: { stubs: { AppIcon: true } },
|
||||
})
|
||||
const root = w.element
|
||||
expect(root.classList.contains('text-center')).toBe(true)
|
||||
expect(root.classList.contains('py-10')).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user