@@ -147,6 +147,112 @@ func TestHandlePageTwitterChannelFeedPaginatesPastFirstChunk(t *testing.T) {
147147 }
148148}
149149
150+ func TestHandlePageTwitterChannelFeedDoesNotRepeatThreadAcrossPages (t * testing.T ) {
151+ srv := newTestServer (t )
152+ srv .staticV = func (path string ) string { return "/static/" + path }
153+ if err := srv .db .UpsertChannelProfile (model.ChannelProfile {
154+ ChannelID : "twitter_sample_author" ,
155+ Platform : "twitter" ,
156+ Handle : "sample_author" ,
157+ DisplayName : "Sample Author" ,
158+ }); err != nil {
159+ t .Fatalf ("UpsertChannelProfile: %v" , err )
160+ }
161+
162+ base := time .UnixMilli (1700000000000 )
163+ items := make ([]model.FeedItem , 0 , 42 )
164+ for i := 0 ; i < 39 ; i ++ {
165+ publishedAt := base .Add (time .Duration (300 - i ) * time .Minute )
166+ items = append (items , model.FeedItem {
167+ TweetID : fmt .Sprintf ("sample_filler_%02d" , i + 1 ),
168+ AuthorHandle : "sample_author" ,
169+ AuthorDisplayName : "Sample Author" ,
170+ BodyText : "filler post" ,
171+ PublishedAt : & publishedAt ,
172+ FetchedAt : publishedAt ,
173+ ContentHash : fmt .Sprintf ("sample_filler_hash_%02d" , i + 1 ),
174+ CanonicalTweetID : fmt .Sprintf ("sample_filler_%02d" , i + 1 ),
175+ })
176+ }
177+ rootAt := base .Add (100 * time .Minute )
178+ leafAt := base .Add (101 * time .Minute )
179+ oldAt := base .Add (90 * time .Minute )
180+ items = append (items ,
181+ model.FeedItem {
182+ TweetID : "sample_thread_leaf" ,
183+ AuthorHandle : "sample_author" ,
184+ AuthorDisplayName : "Sample Author" ,
185+ BodyText : "thread leaf body" ,
186+ IsReply : true ,
187+ ReplyToHandle : "sample_author" ,
188+ ReplyToStatus : "sample_thread_root" ,
189+ PublishedAt : & leafAt ,
190+ FetchedAt : leafAt ,
191+ ContentHash : "sample_thread_leaf_hash" ,
192+ CanonicalTweetID : "sample_thread_leaf" ,
193+ },
194+ model.FeedItem {
195+ TweetID : "sample_thread_root" ,
196+ AuthorHandle : "sample_author" ,
197+ AuthorDisplayName : "Sample Author" ,
198+ BodyText : "thread root body" ,
199+ PublishedAt : & rootAt ,
200+ FetchedAt : rootAt ,
201+ ContentHash : "sample_thread_root_hash" ,
202+ CanonicalTweetID : "sample_thread_root" ,
203+ },
204+ model.FeedItem {
205+ TweetID : "sample_old_post" ,
206+ AuthorHandle : "sample_author" ,
207+ AuthorDisplayName : "Sample Author" ,
208+ BodyText : "old post body" ,
209+ PublishedAt : & oldAt ,
210+ FetchedAt : oldAt ,
211+ ContentHash : "sample_old_post_hash" ,
212+ CanonicalTweetID : "sample_old_post" ,
213+ },
214+ )
215+ if _ , err := srv .db .UpsertFeedItems (items ); err != nil {
216+ t .Fatalf ("UpsertFeedItems: %v" , err )
217+ }
218+
219+ req := httptest .NewRequest (http .MethodGet , "/channels/twitter_sample_author" , nil )
220+ req .SetPathValue ("channelID" , "twitter_sample_author" )
221+ rec := httptest .NewRecorder ()
222+ srv .handlePageChannel (rec , req )
223+
224+ if rec .Code != http .StatusOK {
225+ t .Fatalf ("status = %d, want %d" , rec .Code , http .StatusOK )
226+ }
227+ html := rec .Body .String ()
228+ if ! strings .Contains (html , `thread root body` ) || ! strings .Contains (html , `thread leaf body` ) {
229+ t .Fatalf ("initial page should render the thread preview\n %s" , html )
230+ }
231+ if strings .Contains (html , `sample_old_post` ) {
232+ t .Fatalf ("initial page should not include the next representative\n %s" , html )
233+ }
234+ if ! strings .Contains (html , `hx-get="/channels/twitter_sample_author?offset=40"` ) {
235+ t .Fatalf ("initial page missing grouped next-page sentinel\n %s" , html )
236+ }
237+
238+ req = httptest .NewRequest (http .MethodGet , "/channels/twitter_sample_author?offset=40" , nil )
239+ req .Header .Set ("HX-Request" , "true" )
240+ req .SetPathValue ("channelID" , "twitter_sample_author" )
241+ rec = httptest .NewRecorder ()
242+ srv .handlePageChannel (rec , req )
243+
244+ if rec .Code != http .StatusOK {
245+ t .Fatalf ("partial status = %d, want %d" , rec .Code , http .StatusOK )
246+ }
247+ html = rec .Body .String ()
248+ if ! strings .Contains (html , `sample_old_post` ) || ! strings .Contains (html , `old post body` ) {
249+ t .Fatalf ("partial page should continue after the grouped thread\n %s" , html )
250+ }
251+ if strings .Contains (html , `sample_thread_root` ) || strings .Contains (html , `sample_thread_leaf` ) || strings .Contains (html , `thread root body` ) {
252+ t .Fatalf ("partial page repeated a thread already rendered on the first page\n %s" , html )
253+ }
254+ }
255+
150256func TestHandlePageShortsStartsAtOldestMoment (t * testing.T ) {
151257 srv := newTestServer (t )
152258 srv .staticV = func (path string ) string { return "/static/" + path }
0 commit comments