Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/website/e2e/events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ test.describe('Events page — desktop @smoke', () => {
for (const [path, locale] of LOCALES) {
await page.goto(path)
const section = upcomingSection(page, locale)
// Every configured event ages out eventually, so the row assertions
// scale to zero — the list itself has to render either way.
await expect(section.getByRole('list')).toBeAttached()
const rows = section.locator('li')
await expect(rows).toHaveCount(upcomingEvents.length)

Expand Down
14 changes: 8 additions & 6 deletions apps/website/src/data/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,15 +399,17 @@ const events: readonly ComfyEvent[] = [
}
]

// The site is statically built, so classification is fixed at build time: an
// event moves between the upcoming and past sections on the next deploy.
const BUILD_NOW = new Date()
// Sampled once per module load: at build time for the pre-rendered HTML, and
// again in the browser when the events islands hydrate. An event therefore
// leaves the upcoming section on the first page load after it ends, rather than
// on the next deploy; a page left open keeps the list it hydrated with.
const NOW = new Date()

export const upcomingEvents = deriveUpcomingEvents(events, BUILD_NOW)
export const upcomingEvents = deriveUpcomingEvents(events, NOW)

export const pastEvents = derivePastEvents(events, BUILD_NOW)
export const pastEvents = derivePastEvents(events, NOW)

export const featuredEvents = deriveFeaturedEvents(events, BUILD_NOW)
export const featuredEvents = deriveFeaturedEvents(events, NOW)

export const watchablePastEvents: readonly ComfyEvent[] = pastEvents.filter(
(event) => eventVideoId(event)
Expand Down
94 changes: 94 additions & 0 deletions apps/website/src/templates/events/UpcomingEventsSection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// @vitest-environment happy-dom
import { render, screen } from '@testing-library/vue'
import { describe, expect, it, vi } from 'vitest'

import type * as EventsData from '../../data/events'

import UpcomingEventsSection from './UpcomingEventsSection.vue'

// `upcomingEvents` is derived from the wall clock, so it empties out once the
// newest configured event has ended and the e2e coverage of these rows goes
// quiet with it. Stubbing the list keeps the row markup, localization, and
// link targets covered whatever the date and whatever happens to be scheduled.
const { streamedEvent, externalEvent } = vi.hoisted(() => ({
streamedEvent: {
id: 'streamed-event',
category: 'livestream',
title: { en: 'Streamed Event', 'zh-CN': '直播活动' },
description: { en: 'Watch it live.', 'zh-CN': '在线观看。' },
location: { en: 'Online', 'zh-CN': '线上' },
dateLabel: {
en: 'September 1, 2026 · 10AM PT',
'zh-CN': '2026年9月1日 · 上午10点(PT)'
},
startDateTime: '2026-09-01T10:00:00-07:00',
liveVideoId: 'live123'
} satisfies EventsData.ComfyEvent,
externalEvent: {
id: 'external-event',
category: 'community',
title: { en: 'External Event', 'zh-CN': '外部活动' },
description: { en: 'Hosted elsewhere.', 'zh-CN': '由他方举办。' },
location: { en: 'San Francisco', 'zh-CN': '旧金山' },
dateLabel: { en: 'September 8, 2026', 'zh-CN': '2026年9月8日' },
startDateTime: '2026-09-08T10:00:00-07:00',
link: {
href: { en: 'https://lu.ma/comfy-sf', 'zh-CN': 'https://lu.ma/comfy-sf' },
newTab: true
}
} satisfies EventsData.ComfyEvent
}))

vi.mock('../../data/events', async (importOriginal) => ({
...(await importOriginal<typeof EventsData>()),
upcomingEvents: [streamedEvent, externalEvent]
}))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

describe('UpcomingEventsSection', () => {
it('renders a row per upcoming event with its title, blurb, location and date', () => {
render(UpcomingEventsSection)

const rows = screen.getAllByRole('listitem')
expect(rows).toHaveLength(2)

for (const [index, event] of [streamedEvent, externalEvent].entries()) {
const row = rows[index]
expect(row.textContent).toContain(event.title.en)
expect(row.textContent).toContain(event.description.en)
expect(row.textContent).toContain(event.location.en)
expect(row.textContent).toContain(event.dateLabel.en)
}
})

it('links streamed events to their own page and the rest straight out', () => {
render(UpcomingEventsSection)

const streamedLink = screen.getByRole('link', {
name: `${streamedEvent.title.en} — Livestream`
})
expect(streamedLink.getAttribute('href')).toBe('/events/streamed-event')
expect(streamedLink.getAttribute('target')).toBeNull()

const externalLink = screen.getByRole('link', {
name: `${externalEvent.title.en} — Livestream`
})
expect(externalLink.getAttribute('href')).toBe(externalEvent.link.href.en)
expect(externalLink.getAttribute('target')).toBe('_blank')
expect(externalLink.getAttribute('rel')).toBe('noopener noreferrer')
})

it('localizes rows and event-page links for the zh-CN page', () => {
render(UpcomingEventsSection, { props: { locale: 'zh-CN' } })

expect(screen.getByText(streamedEvent.title['zh-CN'])).toBeTruthy()
expect(screen.getByText(streamedEvent.dateLabel['zh-CN'])).toBeTruthy()
expect(screen.queryByText(streamedEvent.title.en)).toBeNull()

const streamedLink = screen.getByRole('link', {
name: `${streamedEvent.title['zh-CN']} — 直播`
})
expect(streamedLink.getAttribute('href')).toBe(
'/zh-CN/events/streamed-event'
)
})
})
Loading