From 382bca28a1b4d7afadc606fb09d8f7eae7800ef0 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Tue, 3 Mar 2026 16:21:07 -0800 Subject: [PATCH] =?UTF-8?q?feat(avocet):=20LabelView=20=E2=80=94=20wires?= =?UTF-8?q?=20store,=20API,=20card=20stack,=20keyboard,=20easter=20eggs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements Task 13: LabelView.vue wires together the label store, API fetch, card stack, bucket grid, keyboard shortcuts, haptics, motion preference, and three easter egg badges (on-a-roll, speed round, fifty deep). App.vue updated to mount LabelView and restore hacker-mode theme on load. 3 new LabelView tests; all 48 tests pass, build clean. --- web/src/App.vue | 53 +++--- web/src/views/LabelView.test.ts | 46 +++++ web/src/views/LabelView.vue | 318 ++++++++++++++++++++++++++++++++ 3 files changed, 395 insertions(+), 22 deletions(-) create mode 100644 web/src/views/LabelView.test.ts create mode 100644 web/src/views/LabelView.vue diff --git a/web/src/App.vue b/web/src/App.vue index 58b0f21..8f15aa8 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,30 +1,39 @@ - - - diff --git a/web/src/views/LabelView.test.ts b/web/src/views/LabelView.test.ts new file mode 100644 index 0000000..35a4c35 --- /dev/null +++ b/web/src/views/LabelView.test.ts @@ -0,0 +1,46 @@ +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import LabelView from './LabelView.vue' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +// Mock fetch globally +beforeEach(() => { + setActivePinia(createPinia()) + vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ items: [], total: 0 }), + text: async () => '', + })) +}) + +describe('LabelView', () => { + it('shows loading state initially', () => { + const w = mount(LabelView, { + global: { plugins: [createPinia()] }, + }) + // Should show skeleton while loading + expect(w.find('.skeleton-card').exists()).toBe(true) + }) + + it('shows empty state when queue is empty after load', async () => { + const w = mount(LabelView, { + global: { plugins: [createPinia()] }, + }) + // Let all promises resolve + await new Promise(r => setTimeout(r, 0)) + await w.vm.$nextTick() + expect(w.find('.empty-state').exists()).toBe(true) + }) + + it('renders header with action buttons', async () => { + const w = mount(LabelView, { + global: { plugins: [createPinia()] }, + }) + await new Promise(r => setTimeout(r, 0)) + await w.vm.$nextTick() + expect(w.find('.lv-header').exists()).toBe(true) + expect(w.text()).toContain('Undo') + expect(w.text()).toContain('Skip') + expect(w.text()).toContain('Discard') + }) +}) diff --git a/web/src/views/LabelView.vue b/web/src/views/LabelView.vue new file mode 100644 index 0000000..de15f30 --- /dev/null +++ b/web/src/views/LabelView.vue @@ -0,0 +1,318 @@ + + + + +