11import test from 'node:test'
22import assert from 'node:assert/strict'
3- import { computeDeathIndex } from './scoring'
3+ import {
4+ computeDeathIndex ,
5+ getDeathLabel ,
6+ determineCauseOfDeath ,
7+ generateLastWords ,
8+ computeAge ,
9+ formatDate ,
10+ } from './scoring'
411import type { RepoData } from './types'
512
613function buildRepo ( overrides : Partial < RepoData > = { } ) : RepoData {
@@ -24,13 +31,192 @@ function buildRepo(overrides: Partial<RepoData> = {}): RepoData {
2431 }
2532}
2633
34+ // ── computeDeathIndex ──────────────────────────────────────────────
35+
2736test ( 'inactive repo with many issues gets high death index' , ( ) => {
2837 const repo = buildRepo ( {
2938 lastCommitDate : '2018-01-01T00:00:00.000Z' ,
3039 openIssuesCount : 120 ,
3140 isArchived : false ,
3241 } )
33-
3442 const score = computeDeathIndex ( repo )
3543 assert . ok ( score >= 5 )
3644} )
45+
46+ test ( 'archived repo gets high score' , ( ) => {
47+ const repo = buildRepo ( { isArchived : true } )
48+ const score = computeDeathIndex ( repo )
49+ assert . ok ( score >= 3 , `expected >= 3, got ${ score } ` )
50+ } )
51+
52+ test ( 'repo with last commit today returns 0' , ( ) => {
53+ const repo = buildRepo ( {
54+ lastCommitDate : new Date ( ) . toISOString ( ) ,
55+ openIssuesCount : 0 ,
56+ stargazersCount : 0 ,
57+ isArchived : false ,
58+ } )
59+ const score = computeDeathIndex ( repo )
60+ assert . equal ( score , 0 )
61+ } )
62+
63+ test ( 'repo with 1000+ open issues and no recent commits scores very high' , ( ) => {
64+ const repo = buildRepo ( {
65+ lastCommitDate : '2015-01-01T00:00:00.000Z' ,
66+ openIssuesCount : 1500 ,
67+ stargazersCount : 500 ,
68+ isArchived : true ,
69+ } )
70+ const score = computeDeathIndex ( repo )
71+ assert . ok ( score >= 9 , `expected >= 9, got ${ score } ` )
72+ } )
73+
74+ test ( 'commit 200 days ago with low issues scores modestly' , ( ) => {
75+ const d = new Date ( )
76+ d . setDate ( d . getDate ( ) - 200 )
77+ const repo = buildRepo ( {
78+ lastCommitDate : d . toISOString ( ) ,
79+ openIssuesCount : 5 ,
80+ stargazersCount : 10 ,
81+ isArchived : false ,
82+ } )
83+ const score = computeDeathIndex ( repo )
84+ assert . equal ( score , 1 )
85+ } )
86+
87+ // ── getDeathLabel ──────────────────────────────────────────────────
88+
89+ test ( 'getDeathLabel returns correct labels at boundaries' , ( ) => {
90+ assert . equal ( getDeathLabel ( 0 ) , 'too soon to tell' )
91+ assert . equal ( getDeathLabel ( 2 ) , 'too soon to tell' )
92+ assert . equal ( getDeathLabel ( 3 ) , 'struggling' )
93+ assert . equal ( getDeathLabel ( 5 ) , 'struggling' )
94+ assert . equal ( getDeathLabel ( 6 ) , 'dying' )
95+ assert . equal ( getDeathLabel ( 8 ) , 'dying' )
96+ assert . equal ( getDeathLabel ( 9 ) , 'dead dead' )
97+ assert . equal ( getDeathLabel ( 10 ) , 'dead dead' )
98+ } )
99+
100+ // ── determineCauseOfDeath ──────────────────────────────────────────
101+
102+ test ( 'known repo (atom/atom) returns exact known cause' , ( ) => {
103+ const repo = buildRepo ( { fullName : 'atom/atom' } )
104+ const cause = determineCauseOfDeath ( repo )
105+ assert . equal ( cause , 'GitHub shipped VS Code, then sunset Atom in public' )
106+ } )
107+
108+ test ( 'fork repo returns "Forked but never understood"' , ( ) => {
109+ const d = new Date ( )
110+ d . setDate ( d . getDate ( ) - 100 )
111+ const repo = buildRepo ( {
112+ isFork : true ,
113+ isArchived : false ,
114+ lastCommitDate : d . toISOString ( ) ,
115+ openIssuesCount : 0 ,
116+ stargazersCount : 0 ,
117+ description : 'some fork' ,
118+ } )
119+ const cause = determineCauseOfDeath ( repo )
120+ assert . equal ( cause , 'Forked but never understood' )
121+ } )
122+
123+ test ( 'no description returns "No README. No hope."' , ( ) => {
124+ const d = new Date ( )
125+ d . setDate ( d . getDate ( ) - 100 )
126+ const repo = buildRepo ( {
127+ description : null ,
128+ isFork : false ,
129+ isArchived : false ,
130+ lastCommitDate : d . toISOString ( ) ,
131+ openIssuesCount : 0 ,
132+ stargazersCount : 0 ,
133+ } )
134+ const cause = determineCauseOfDeath ( repo )
135+ assert . equal ( cause , 'No README. No hope.' )
136+ } )
137+
138+ test ( 'archived repo returns "Officially declared dead by author"' , ( ) => {
139+ const repo = buildRepo ( { isArchived : true } )
140+ const cause = determineCauseOfDeath ( repo )
141+ assert . equal ( cause , 'Officially declared dead by author' )
142+ } )
143+
144+ test ( 'commitmentissues repo returns its own easter egg cause' , ( ) => {
145+ const repo = buildRepo ( { fullName : 'dotsystemsdevs/commitmentissues' } )
146+ const cause = determineCauseOfDeath ( repo )
147+ assert . equal ( cause , 'Monetized before loved.' )
148+ } )
149+
150+ // ── generateLastWords ──────────────────────────────────────────────
151+
152+ test ( 'commit message with "fix typo" returns "pls work now"' , ( ) => {
153+ const repo = buildRepo ( { lastCommitMessage : 'fix typo in header' } )
154+ assert . equal ( generateLastWords ( repo ) , 'pls work now' )
155+ } )
156+
157+ test ( 'commit message with "update readme" returns docs quip' , ( ) => {
158+ const repo = buildRepo ( { lastCommitMessage : 'update readme with badges' } )
159+ assert . equal ( generateLastWords ( repo ) , 'at least the docs are good' )
160+ } )
161+
162+ test ( 'commit message with "wip" returns "i\'ll finish this later"' , ( ) => {
163+ const repo = buildRepo ( {
164+ lastCommitMessage : 'wip: refactor auth module' ,
165+ lastCommitDate : new Date ( ) . toISOString ( ) ,
166+ } )
167+ assert . equal ( generateLastWords ( repo ) , "i'll finish this later" )
168+ } )
169+
170+ test ( 'commit message with "merge" returns merge conflict quip' , ( ) => {
171+ const repo = buildRepo ( {
172+ lastCommitMessage : 'Merge pull request #42' ,
173+ lastCommitDate : new Date ( ) . toISOString ( ) ,
174+ } )
175+ assert . equal ( generateLastWords ( repo ) , 'dying in a merge conflict' )
176+ } )
177+
178+ test ( 'very long commit message is truncated to 80 chars' , ( ) => {
179+ const longMsg = 'a' . repeat ( 120 )
180+ const repo = buildRepo ( {
181+ lastCommitMessage : longMsg ,
182+ lastCommitDate : new Date ( ) . toISOString ( ) ,
183+ } )
184+ const words = generateLastWords ( repo )
185+ assert . ok ( words . length <= 80 , `expected <= 80, got ${ words . length } ` )
186+ assert . ok ( words . endsWith ( '...' ) )
187+ } )
188+
189+ test ( 'commitmentissues repo returns its own easter egg last words' , ( ) => {
190+ const repo = buildRepo ( { fullName : 'dotsystemsdevs/commitmentissues' } )
191+ assert . equal ( generateLastWords ( repo ) , "should've farmed laughs before revenue." )
192+ } )
193+
194+ // ── computeAge ─────────────────────────────────────────────────────
195+
196+ test ( 'same month created and last commit returns "less than a month"' , ( ) => {
197+ assert . equal ( computeAge ( '2024-03-15T00:00:00Z' , '2024-03-28T00:00:00Z' ) , 'less than a month' )
198+ } )
199+
200+ test ( 'exactly 1 year returns "1 year"' , ( ) => {
201+ assert . equal ( computeAge ( '2023-01-01T00:00:00Z' , '2024-01-01T00:00:00Z' ) , '1 year' )
202+ } )
203+
204+ test ( '2 years 3 months returns "2 years, 3 months"' , ( ) => {
205+ assert . equal ( computeAge ( '2020-01-01T00:00:00Z' , '2022-04-01T00:00:00Z' ) , '2 years, 3 months' )
206+ } )
207+
208+ test ( '11 months returns "11 months"' , ( ) => {
209+ assert . equal ( computeAge ( '2023-01-01T00:00:00Z' , '2023-12-01T00:00:00Z' ) , '11 months' )
210+ } )
211+
212+ test ( '1 month returns "1 month" (singular)' , ( ) => {
213+ assert . equal ( computeAge ( '2024-01-01T00:00:00Z' , '2024-02-01T00:00:00Z' ) , '1 month' )
214+ } )
215+
216+ // ── formatDate ─────────────────────────────────────────────────────
217+
218+ test ( 'formatDate returns en-GB formatted date' , ( ) => {
219+ const result = formatDate ( '2024-06-15T12:00:00Z' )
220+ assert . ok ( result . includes ( 'June' ) , `expected "June" in "${ result } "` )
221+ assert . ok ( result . includes ( '2024' ) , `expected "2024" in "${ result } "` )
222+ } )
0 commit comments