Implement watch-event feature (017) with bookmark in RsvpBar
Add client-side watch/bookmark functionality: users can save events to localStorage without RSVPing via a bookmark button next to the "I'm attending" CTA. Watched events appear in the event list with a "Watching" label. Bookmark is only visible for visitors (not attendees or organizers). Includes spec, plan, research, tasks, unit tests, and E2E tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,9 @@ vi.mock('@/api/client', () => ({
|
||||
const mockSaveRsvp = vi.fn()
|
||||
const mockGetRsvp = vi.fn()
|
||||
const mockGetOrganizerToken = vi.fn()
|
||||
const mockSaveWatch = vi.fn()
|
||||
const mockIsStored = vi.fn()
|
||||
const mockRemoveEvent = vi.fn()
|
||||
|
||||
vi.mock('@/composables/useEventStorage', () => ({
|
||||
useEventStorage: vi.fn(() => ({
|
||||
@@ -22,7 +25,10 @@ vi.mock('@/composables/useEventStorage', () => ({
|
||||
getOrganizerToken: mockGetOrganizerToken,
|
||||
saveRsvp: mockSaveRsvp,
|
||||
getRsvp: mockGetRsvp,
|
||||
removeEvent: vi.fn(),
|
||||
removeRsvp: vi.fn(),
|
||||
saveWatch: mockSaveWatch,
|
||||
isStored: mockIsStored,
|
||||
removeEvent: mockRemoveEvent,
|
||||
})),
|
||||
}))
|
||||
|
||||
@@ -68,6 +74,9 @@ beforeEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
mockGetRsvp.mockReturnValue(undefined)
|
||||
mockGetOrganizerToken.mockReturnValue(undefined)
|
||||
mockIsStored.mockReturnValue(false)
|
||||
mockSaveWatch.mockClear()
|
||||
mockRemoveEvent.mockClear()
|
||||
})
|
||||
|
||||
describe('EventDetailView', () => {
|
||||
@@ -366,4 +375,89 @@ describe('EventDetailView', () => {
|
||||
expect(document.body.textContent).toContain('Could not submit RSVP. Please try again.')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
// Bookmark — T007: bookmark state is passed to RsvpBar via props
|
||||
it('passes bookmarked=false to RsvpBar when event is not in storage', async () => {
|
||||
mockIsStored.mockReturnValue(false)
|
||||
mockLoadedEvent()
|
||||
|
||||
const wrapper = await mountWithToken()
|
||||
await flushPromises()
|
||||
|
||||
const rsvpBar = wrapper.findComponent({ name: 'RsvpBar' })
|
||||
expect(rsvpBar.props('bookmarked')).toBe(false)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('passes bookmarked=true to RsvpBar when event is in storage', async () => {
|
||||
mockIsStored.mockReturnValue(true)
|
||||
mockLoadedEvent()
|
||||
|
||||
const wrapper = await mountWithToken()
|
||||
await flushPromises()
|
||||
|
||||
const rsvpBar = wrapper.findComponent({ name: 'RsvpBar' })
|
||||
expect(rsvpBar.props('bookmarked')).toBe(true)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('bookmark event emitted from RsvpBar calls saveWatch', async () => {
|
||||
mockIsStored.mockReturnValue(false)
|
||||
mockLoadedEvent()
|
||||
|
||||
const wrapper = await mountWithToken()
|
||||
await flushPromises()
|
||||
|
||||
const rsvpBar = wrapper.findComponent({ name: 'RsvpBar' })
|
||||
rsvpBar.vm.$emit('bookmark')
|
||||
await flushPromises()
|
||||
|
||||
expect(mockSaveWatch).toHaveBeenCalledWith('test-token', 'Summer BBQ', '2026-03-15T20:00:00+01:00')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('bookmark event emitted from RsvpBar calls removeEvent when user is watcher', async () => {
|
||||
mockIsStored.mockReturnValue(true)
|
||||
mockLoadedEvent()
|
||||
|
||||
const wrapper = await mountWithToken()
|
||||
await flushPromises()
|
||||
|
||||
const rsvpBar = wrapper.findComponent({ name: 'RsvpBar' })
|
||||
rsvpBar.vm.$emit('bookmark')
|
||||
await flushPromises()
|
||||
|
||||
expect(mockRemoveEvent).toHaveBeenCalledWith('test-token')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('bookmark event ignored when user is attendee', async () => {
|
||||
mockGetRsvp.mockReturnValue({ rsvpToken: 'rsvp-1', rsvpName: 'Max' })
|
||||
mockIsStored.mockReturnValue(true)
|
||||
mockLoadedEvent()
|
||||
|
||||
const wrapper = await mountWithToken()
|
||||
await flushPromises()
|
||||
|
||||
const rsvpBar = wrapper.findComponent({ name: 'RsvpBar' })
|
||||
rsvpBar.vm.$emit('bookmark')
|
||||
await flushPromises()
|
||||
|
||||
expect(mockRemoveEvent).not.toHaveBeenCalled()
|
||||
expect(mockSaveWatch).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('passes bookmarked=true to RsvpBar after removeRsvp (event still in storage)', async () => {
|
||||
mockIsStored.mockReturnValue(true)
|
||||
mockGetRsvp.mockReturnValue(undefined)
|
||||
mockLoadedEvent()
|
||||
|
||||
const wrapper = await mountWithToken()
|
||||
await flushPromises()
|
||||
|
||||
const rsvpBar = wrapper.findComponent({ name: 'RsvpBar' })
|
||||
expect(rsvpBar.props('bookmarked')).toBe(true)
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user