Daily Bluesky bot for AT Mot. Invites players and congratulates yesterday's solvers.
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 5.5 kB View raw
1import { describe, it, expect } from 'vitest'; 2import { composePost, postMarker } from '../src/compose.js'; 3import { ORIGIN, type YesterdayCounts } from '../src/config.js'; 4 5const y = (players: number | null, solvers: number, sampled = false): YesterdayCounts => ({ 6 players, 7 solvers, 8 sampled, 9}); 10 11/** Collect the tag names from a post's #tag facets, in order. */ 12function tagNames(facets: ReturnType<typeof composePost>['facets']): string[] { 13 const names: string[] = []; 14 for (const f of facets) { 15 for (const feat of f.features) { 16 if (feat.$type === 'app.bsky.richtext.facet#tag') names.push(feat.tag); 17 } 18 } 19 return names; 20} 21 22describe('postMarker', () => { 23 it('is language-specific and carries the puzzle number', () => { 24 expect(postMarker('en', 5)).toBe('AT Mot #5'); 25 expect(postMarker('fr', 5)).toBe('AT Mot n°5'); 26 }); 27}); 28 29describe('composePost — EN', () => { 30 it('normal: solvers + non-solvers', () => { 31 const { text, langs, facets } = composePost({ lang: 'en', todayN: 5, yesterday: y(33, 25) }); 32 expect(text).toBe( 33 `🟩 AT Mot #5 is live! Six tries to guess today's five-letter word.\n` + 34 `Congrats to the 25 of you who cracked yesterday's word. And to the 8 who didn't, better luck today!\n` + 35 `Play: ${ORIGIN}\n` + 36 `#WordGame #atproto`, 37 ); 38 expect(langs).toEqual(['en']); 39 // One homepage link facet, plus a #tag facet per hashtag. 40 const links = facets.filter((f) => f.features[0]!.$type === 'app.bsky.richtext.facet#link'); 41 expect(links).toHaveLength(1); 42 expect(links[0]!.features[0]).toEqual({ $type: 'app.bsky.richtext.facet#link', uri: ORIGIN }); 43 expect(tagNames(facets)).toEqual(['WordGame', 'atproto']); 44 }); 45 46 it('hashtags become #tag facets; the puzzle "#N" does not', () => { 47 const { facets } = composePost({ lang: 'en', todayN: 3, yesterday: y(2, 2) }); 48 expect(tagNames(facets)).toEqual(['WordGame', 'atproto']); 49 expect(tagNames(facets)).not.toContain('3'); 50 }); 51 52 it('singular solver, no non-solvers', () => { 53 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(1, 1) }); 54 expect(text).toContain('Congrats to the one player who cracked yesterday\'s word.'); 55 expect(text).not.toContain('who didn\'t'); 56 }); 57 58 it('singular non-solver', () => { 59 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(5, 4) }); 60 expect(text).toContain('And to the one who didn\'t, better luck today!'); 61 }); 62 63 it('zero solvers', () => { 64 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(5, 0) }); 65 expect(text).toContain('Nobody cracked yesterday\'s word. Fresh start today!'); 66 }); 67 68 it('everyone solved (no non-solver clause)', () => { 69 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(10, 10) }); 70 expect(text).toContain('Congrats to the 10 of you who cracked yesterday\'s word.'); 71 expect(text).not.toContain('who didn\'t'); 72 }); 73 74 it('sampled: hedged floor, no exact non-solver count', () => { 75 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(50, 20, true) }); 76 expect(text).toContain('Congrats to the 20+ of you who cracked yesterday\'s word. Can you join them today?'); 77 }); 78 79 it('day 1 (no yesterday): invite only', () => { 80 const { text } = composePost({ lang: 'en', todayN: 1, yesterday: null }); 81 expect(text).toBe( 82 `🟩 AT Mot #1 is live! Six tries to guess today's five-letter word.\n` + 83 `Play: ${ORIGIN}\n` + 84 `#WordGame #atproto`, 85 ); 86 }); 87 88 it('Constellation unreachable (players null): no congrats', () => { 89 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(null, 0) }); 90 expect(text).not.toContain('Congrats'); 91 expect(text).not.toContain('Nobody'); 92 }); 93 94 it('nobody played yesterday (players 0): no congrats', () => { 95 const { text } = composePost({ lang: 'en', todayN: 5, yesterday: y(0, 0) }); 96 expect(text).not.toContain('Congrats'); 97 expect(text).not.toContain('Nobody'); 98 }); 99}); 100 101describe('composePost — FR', () => { 102 it('normal: solvers + non-solvers', () => { 103 const { text, langs, facets } = composePost({ lang: 'fr', todayN: 5, yesterday: y(23, 18) }); 104 expect(text).toBe( 105 `🟩 AT Mot n°5 est en ligne ! Six essais pour deviner le mot de cinq lettres du jour.\n` + 106 `Bravo aux 18 qui ont trouvé le mot d'hier. Et aux 5 qui ont séché, meilleure chance aujourd'hui !\n` + 107 `Jouez : ${ORIGIN}\n` + 108 `#JeuDeMots #atproto`, 109 ); 110 expect(langs).toEqual(['fr']); 111 expect(tagNames(facets)).toEqual(['JeuDeMots', 'atproto']); 112 }); 113 114 it('singular solver uses "au seul joueur" + "a trouvé"', () => { 115 const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(1, 1) }); 116 expect(text).toContain('Bravo au seul joueur qui a trouvé le mot d\'hier.'); 117 }); 118 119 it('singular non-solver', () => { 120 const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(5, 4) }); 121 expect(text).toContain('Et au seul qui a séché, meilleure chance aujourd\'hui !'); 122 }); 123 124 it('zero solvers', () => { 125 const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(5, 0) }); 126 expect(text).toContain('Personne n\'a trouvé le mot d\'hier. Nouveau départ aujourd\'hui !'); 127 }); 128 129 it('sampled hedge', () => { 130 const { text } = composePost({ lang: 'fr', todayN: 5, yesterday: y(50, 20, true) }); 131 expect(text).toContain('Bravo aux 20+ qui ont trouvé le mot d\'hier. Saurez-vous les rejoindre aujourd\'hui ?'); 132 }); 133});