mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
- shareLink ganha modo (direct|announcement), pool de destinos com "Destino no ar" por linha e dados do evento; destination escalar sai via migration com backfill lossless (legado vira direct com destino no ar) - página de anúncio pública portada classe-a-classe, header campaign-site extraído (CampaignPageHeader) e ativação/troca sem reload por poll da rota /api/share-link/<slug>/live (fail-closed, no-store) - agenda Google (action=TEMPLATE) + .ics por rota sob o slug; compartilhar o link curto (kind event); S19 intacto e 404 uniforme preservado Related #1268
119 lines
4.7 KiB
TypeScript
119 lines
4.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
buildCalendarEventIcs,
|
|
buildGoogleCalendarEventUrl,
|
|
DEFAULT_EVENT_DURATION_MS,
|
|
resolveCalendarEventWindow,
|
|
} from '@/lib/calendarEvent'
|
|
|
|
const STARTS_AT = '2026-10-03T22:00:00.000Z'
|
|
const ENDS_AT = '2026-10-04T00:00:00.000Z'
|
|
|
|
describe('resolveCalendarEventWindow', () => {
|
|
it('returns null without a start, so the calendar options hide', () => {
|
|
expect(resolveCalendarEventWindow(null, ENDS_AT)).toBeNull()
|
|
expect(resolveCalendarEventWindow('', ENDS_AT)).toBeNull()
|
|
expect(resolveCalendarEventWindow('lixo', ENDS_AT)).toBeNull()
|
|
})
|
|
|
|
it('falls back to the 2-hour default without an end', () => {
|
|
const window = resolveCalendarEventWindow(STARTS_AT, null)
|
|
expect(window?.end.getTime()).toBe(new Date(STARTS_AT).getTime() + DEFAULT_EVENT_DURATION_MS)
|
|
})
|
|
|
|
it('falls back when the end is invalid or does not move forward', () => {
|
|
const startMs = new Date(STARTS_AT).getTime()
|
|
expect(resolveCalendarEventWindow(STARTS_AT, 'lixo')?.end.getTime()).toBe(
|
|
startMs + DEFAULT_EVENT_DURATION_MS,
|
|
)
|
|
expect(resolveCalendarEventWindow(STARTS_AT, STARTS_AT)?.end.getTime()).toBe(
|
|
startMs + DEFAULT_EVENT_DURATION_MS,
|
|
)
|
|
expect(resolveCalendarEventWindow(STARTS_AT, '2026-10-03T21:00:00.000Z')?.end.getTime()).toBe(
|
|
startMs + DEFAULT_EVENT_DURATION_MS,
|
|
)
|
|
})
|
|
|
|
it('keeps an explicit end that moves forward', () => {
|
|
expect(resolveCalendarEventWindow(STARTS_AT, ENDS_AT)?.end.toISOString()).toBe(ENDS_AT)
|
|
})
|
|
})
|
|
|
|
describe('buildGoogleCalendarEventUrl', () => {
|
|
it('builds an action=TEMPLATE URL with UTC dates, details and location', () => {
|
|
const url = buildGoogleCalendarEventUrl({
|
|
title: 'Plenária da saúde',
|
|
description: 'Encontro online da campanha.',
|
|
location: 'Online',
|
|
startsAt: STARTS_AT,
|
|
endsAt: ENDS_AT,
|
|
})
|
|
expect(url).not.toBeNull()
|
|
|
|
const parsed = new URL(url as string)
|
|
expect(`${parsed.origin}${parsed.pathname}`).toBe('https://calendar.google.com/calendar/render')
|
|
expect(parsed.searchParams.get('action')).toBe('TEMPLATE')
|
|
expect(parsed.searchParams.get('text')).toBe('Plenária da saúde')
|
|
expect(parsed.searchParams.get('dates')).toBe('20261003T220000Z/20261004T000000Z')
|
|
expect(parsed.searchParams.get('details')).toBe('Encontro online da campanha.')
|
|
expect(parsed.searchParams.get('location')).toBe('Online')
|
|
})
|
|
|
|
it('uses the 2-hour default when the end is missing', () => {
|
|
const url = buildGoogleCalendarEventUrl({ title: 'Plenária', startsAt: STARTS_AT })
|
|
const parsed = new URL(url as string)
|
|
expect(parsed.searchParams.get('dates')).toBe('20261003T220000Z/20261004T000000Z')
|
|
expect(parsed.searchParams.get('details')).toBeNull()
|
|
expect(parsed.searchParams.get('location')).toBeNull()
|
|
})
|
|
|
|
it('returns null without a valid start', () => {
|
|
expect(buildGoogleCalendarEventUrl({ title: 'Plenária' })).toBeNull()
|
|
expect(buildGoogleCalendarEventUrl({ title: 'Plenária', startsAt: 'lixo' })).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('buildCalendarEventIcs', () => {
|
|
it('builds a single VEVENT with CRLF and escaped fields', () => {
|
|
const ics = buildCalendarEventIcs({
|
|
uid: 'plenaria@teqo.jorgesolla.com.br',
|
|
title: 'Plenária, saúde',
|
|
description: 'Linha 1\nLinha 2',
|
|
location: 'Online; sala 1',
|
|
startsAt: STARTS_AT,
|
|
endsAt: ENDS_AT,
|
|
updatedAt: '2026-09-23T12:00:00.000Z',
|
|
})
|
|
expect(ics).not.toBeNull()
|
|
|
|
const body = ics as string
|
|
expect(body.startsWith('BEGIN:VCALENDAR\r\n')).toBe(true)
|
|
expect(body.endsWith('END:VCALENDAR')).toBe(true)
|
|
expect(body).toContain('UID:plenaria@teqo.jorgesolla.com.br\r\n')
|
|
expect(body).toContain('DTSTAMP:20260923T120000Z\r\n')
|
|
expect(body).toContain('DTSTART:20261003T220000Z\r\n')
|
|
expect(body).toContain('DTEND:20261004T000000Z\r\n')
|
|
expect(body).toContain('SUMMARY:Plenária\\, saúde\r\n')
|
|
expect(body).toContain('DESCRIPTION:Linha 1\\nLinha 2\r\n')
|
|
expect(body).toContain('LOCATION:Online\\; sala 1\r\n')
|
|
})
|
|
|
|
it('omits optional lines and falls back to the start for DTSTAMP', () => {
|
|
const body = buildCalendarEventIcs({
|
|
uid: 'x@teqo.jorgesolla.com.br',
|
|
title: 'Plenária',
|
|
startsAt: STARTS_AT,
|
|
updatedAt: 'lixo',
|
|
}) as string
|
|
|
|
expect(body).toContain('DTSTAMP:20261003T220000Z\r\n')
|
|
expect(body).not.toContain('DESCRIPTION:')
|
|
expect(body).not.toContain('LOCATION:')
|
|
})
|
|
|
|
it('returns null without a valid start — never an empty file', () => {
|
|
expect(buildCalendarEventIcs({ uid: 'x@teqo', title: 'Plenária' })).toBeNull()
|
|
expect(buildCalendarEventIcs({ uid: 'x@teqo', title: 'Plenária', startsAt: 'lixo' })).toBeNull()
|
|
})
|
|
})
|