The window now starts at the last dispatch, so pressing send twice does not repeat a digest. Cards prefer entries that have a cover, the newest one of them opens the message as a full width image, and the page says when the last digest went out.
158 lines
5.1 KiB
TypeScript
158 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { buildMessage, groupByProject, windowFor } from '~/lib/weekly-report'
|
|
import type { PostListItem } from '~/data/repositories/posts'
|
|
|
|
function item(overrides: Partial<PostListItem> = {}): PostListItem {
|
|
return {
|
|
id: 'c0ffee00-0000-4000-8000-000000000001',
|
|
slug: 'regel-engine',
|
|
title: 'Regel-Engine: Bedingung trifft Aktion',
|
|
teaser: null,
|
|
type: { id: 't1', key: 'feature', labelDe: 'Feature', labelEn: 'Feature', color: '#2b4a9b' },
|
|
audience: 'internal',
|
|
publishAt: new Date('2026-07-30T09:00:00.000Z'),
|
|
authorName: 'Paul',
|
|
coverMediaId: null,
|
|
number: 142,
|
|
projectSlug: 'trakk',
|
|
projectName: 'Trakk',
|
|
projectCode: 'TRK',
|
|
projectColor: '#2e7d5b',
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
const origin = 'https://logbuch.nyo.de'
|
|
|
|
describe('windowFor', () => {
|
|
it('spannt sieben Tage bis zum Aufruf', () => {
|
|
const { from, to } = windowFor(new Date('2026-08-03T06:00:00.000Z'))
|
|
|
|
expect(to.toISOString()).toBe('2026-08-03T06:00:00.000Z')
|
|
expect(from.toISOString()).toBe('2026-07-27T06:00:00.000Z')
|
|
})
|
|
})
|
|
|
|
describe('groupByProject', () => {
|
|
it('fasst Einträge je Projekt zusammen und behält die Reihenfolge', () => {
|
|
const groups = groupByProject([
|
|
item(),
|
|
item({ id: 'b', slug: 'zwei', projectSlug: 'orbit', projectName: 'Orbit', projectCode: 'ORB' }),
|
|
item({ id: 'c', slug: 'drei', number: 143 }),
|
|
])
|
|
|
|
expect(groups.map(group => group.slug)).toEqual(['trakk', 'orbit'])
|
|
expect(groups[0]!.items).toHaveLength(2)
|
|
})
|
|
})
|
|
|
|
describe('buildMessage', () => {
|
|
const base = {
|
|
from: new Date('2026-07-27T06:00:00.000Z'),
|
|
to: new Date('2026-08-03T06:00:00.000Z'),
|
|
}
|
|
|
|
it('bleibt still, wenn nichts veröffentlicht wurde', () => {
|
|
expect(buildMessage({ ...base, total: 0, projects: [] }, origin)).toBeNull()
|
|
})
|
|
|
|
it('nennt Zeitraum, Anzahl und jedes Projekt', () => {
|
|
const projects = groupByProject([item(), item({ id: 'b', slug: 'zwei', projectSlug: 'orbit', projectName: 'Orbit', projectCode: 'ORB' })])
|
|
const message = buildMessage({ ...base, total: 2, projects }, origin)
|
|
|
|
expect(message?.text).toBe('Logbuch, 27.07.2026 bis 03.08.2026: 2 Einträge')
|
|
|
|
const dump = JSON.stringify(message?.blocks)
|
|
|
|
expect(dump).toContain('27.07.2026 bis 03.08.2026')
|
|
expect(dump).toContain('|Trakk>*')
|
|
expect(dump).toContain('|Orbit>*')
|
|
expect(dump).toContain('TRK-0142')
|
|
expect(dump).toContain('https://logbuch.nyo.de/trakk/regel-engine')
|
|
})
|
|
|
|
it('zählt einen einzelnen Eintrag im Singular', () => {
|
|
const message = buildMessage({ ...base, total: 1, projects: groupByProject([item()]) }, origin)
|
|
|
|
expect(message?.text).toContain('1 Eintrag')
|
|
})
|
|
|
|
it('verzichtet auf Emojis', () => {
|
|
const message = buildMessage({ ...base, total: 1, projects: groupByProject([item()]) }, origin)
|
|
|
|
expect(JSON.stringify(message)).not.toMatch(/\p{Extended_Pictographic}/u)
|
|
})
|
|
})
|
|
|
|
describe('Karten', () => {
|
|
const base = {
|
|
from: new Date('2026-07-27T06:00:00.000Z'),
|
|
to: new Date('2026-08-03T06:00:00.000Z'),
|
|
}
|
|
|
|
it('setzt Anreißer und Bild an den Eintrag', () => {
|
|
const cover = new Map([['c0ffee00-0000-4000-8000-000000000001', {
|
|
postId: 'c0ffee00-0000-4000-8000-000000000001',
|
|
mediaId: 'm1',
|
|
alt: 'Regelmaske mit Bedingung und Aktion',
|
|
caption: null,
|
|
width: 1600,
|
|
height: 900,
|
|
variants: [{ width: 480, format: 'webp', path: 'trakk/m1/480.webp' }],
|
|
}]])
|
|
|
|
const projects = groupByProject([item({ teaser: 'Tickets reagieren jetzt selbst.' })])
|
|
const message = buildMessage({ ...base, total: 1, projects }, origin, cover as never)
|
|
const dump = JSON.stringify(message?.blocks)
|
|
|
|
expect(dump).toContain('Tickets reagieren jetzt selbst.')
|
|
expect(dump).toContain('https://logbuch.nyo.de/api/v1/media/file/trakk/m1/480.webp')
|
|
expect(dump).toContain('Regelmaske mit Bedingung und Aktion')
|
|
})
|
|
|
|
it('kürzt lange Anreißer', () => {
|
|
const long = `${'Wort '.repeat(80)}Ende`
|
|
const projects = groupByProject([item({ teaser: long })])
|
|
const dump = JSON.stringify(buildMessage({ ...base, total: 1, projects }, origin)?.blocks)
|
|
|
|
expect(dump).toContain('...')
|
|
expect(dump).not.toContain('Ende')
|
|
})
|
|
|
|
it('zeigt höchstens drei Karten je Projekt, der Rest kommt als Liste', () => {
|
|
const many = Array.from({ length: 5 }, (_, index) => item({
|
|
id: `c0ffee00-0000-4000-8000-00000000000${index}`,
|
|
slug: `beitrag-${index}`,
|
|
number: 140 + index,
|
|
title: `Beitrag ${index}`,
|
|
}))
|
|
|
|
const message = buildMessage({ ...base, total: 5, projects: groupByProject(many) }, origin)
|
|
const sections = (message?.blocks ?? []).filter(block => (block as { type: string }).type === 'section')
|
|
|
|
expect(sections).toHaveLength(5)
|
|
})
|
|
})
|
|
|
|
describe('windowFor mit letztem Versand', () => {
|
|
it('beginnt beim letzten Versand, wenn der jünger ist', () => {
|
|
const { from } = windowFor(
|
|
new Date('2026-08-03T06:00:00.000Z'),
|
|
7,
|
|
new Date('2026-08-01T09:00:00.000Z'),
|
|
)
|
|
|
|
expect(from.toISOString()).toBe('2026-08-01T09:00:00.000Z')
|
|
})
|
|
|
|
it('bleibt bei sieben Tagen, wenn der letzte Versand älter ist', () => {
|
|
const { from } = windowFor(
|
|
new Date('2026-08-03T06:00:00.000Z'),
|
|
7,
|
|
new Date('2026-06-01T09:00:00.000Z'),
|
|
)
|
|
|
|
expect(from.toISOString()).toBe('2026-07-27T06:00:00.000Z')
|
|
})
|
|
})
|