1+ import { renderHook , waitFor } from '@testing-library/react' ;
12import { beforeEach , describe , expect , it , vi } from 'vitest' ;
23
4+ import { createTestWrapper } from './test-query-client.js' ;
5+
36const mockListDiaries = vi . fn ( ) ;
47const mockListDiaryEntries = vi . fn ( ) ;
58const mockListDiaryTags = vi . fn ( ) ;
@@ -8,18 +11,42 @@ vi.mock('@moltnet/api-client', () => ({
811 listDiaries : ( ...args : unknown [ ] ) => mockListDiaries ( ...args ) ,
912 listDiaryEntries : ( ...args : unknown [ ] ) => mockListDiaryEntries ( ...args ) ,
1013 listDiaryTags : ( ...args : unknown [ ] ) => mockListDiaryTags ( ...args ) ,
11- getDiary : vi . fn ( ) ,
12- getDiaryEntryById : vi . fn ( ) ,
13- verifyDiaryEntryById : vi . fn ( ) ,
14+ // Generated *Options helpers from @moltnet /api-client/query are imported
15+ // into hooks.ts but not exercised by useDiarySummaries — stubs are enough
16+ // to avoid the resolver erroring.
17+ searchDiary : vi . fn ( ) ,
18+ } ) ) ;
19+
20+ vi . mock ( '@moltnet/api-client/query' , ( ) => ( {
21+ getDiaryOptions : vi . fn ( ( ) => ( {
22+ queryKey : [ 'getDiary' ] ,
23+ queryFn : vi . fn ( ) ,
24+ } ) ) ,
25+ getDiaryEntryByIdOptions : vi . fn ( ( ) => ( {
26+ queryKey : [ 'getDiaryEntryById' ] ,
27+ queryFn : vi . fn ( ) ,
28+ } ) ) ,
29+ listDiaryEntriesInfiniteOptions : vi . fn ( ( ) => ( {
30+ queryKey : [ 'listDiaryEntries' ] ,
31+ queryFn : vi . fn ( ) ,
32+ } ) ) ,
33+ listDiaryTagsOptions : vi . fn ( ( ) => ( {
34+ queryKey : [ 'listDiaryTags' ] ,
35+ queryFn : vi . fn ( ) ,
36+ } ) ) ,
37+ verifyDiaryEntryByIdOptions : vi . fn ( ( ) => ( {
38+ queryKey : [ 'verifyDiaryEntryById' ] ,
39+ queryFn : vi . fn ( ) ,
40+ } ) ) ,
1441} ) ) ;
1542
1643vi . mock ( '../src/api.js' , ( ) => ( {
1744 getApiClient : ( ) => ( { } ) ,
1845} ) ) ;
1946
20- import { fetchDiarySummaries } from '../src/diaries/api .js' ;
47+ import { useDiarySummaries } from '../src/diaries/hooks .js' ;
2148
22- describe ( 'fetchDiarySummaries ' , ( ) => {
49+ describe ( 'useDiarySummaries ' , ( ) => {
2350 beforeEach ( ( ) => {
2451 vi . clearAllMocks ( ) ;
2552 mockListDiaryEntries . mockResolvedValue ( {
@@ -33,7 +60,13 @@ describe('fetchDiarySummaries', () => {
3360 it ( 'forwards x-moltnet-team-id header when teamId is provided' , async ( ) => {
3461 mockListDiaries . mockResolvedValue ( { data : { items : [ ] } } ) ;
3562
36- await fetchDiarySummaries ( 'team-alpha' ) ;
63+ const { result } = renderHook ( ( ) => useDiarySummaries ( 'team-alpha' ) , {
64+ wrapper : createTestWrapper ( ) ,
65+ } ) ;
66+
67+ await waitFor ( ( ) => {
68+ expect ( result . current . isSuccess ) . toBe ( true ) ;
69+ } ) ;
3770
3871 expect ( mockListDiaries ) . toHaveBeenCalledWith (
3972 expect . objectContaining ( {
@@ -45,20 +78,94 @@ describe('fetchDiarySummaries', () => {
4578 it ( 'omits the team header when teamId is null' , async ( ) => {
4679 mockListDiaries . mockResolvedValue ( { data : { items : [ ] } } ) ;
4780
48- await fetchDiarySummaries ( null ) ;
81+ const { result } = renderHook ( ( ) => useDiarySummaries ( null ) , {
82+ wrapper : createTestWrapper ( ) ,
83+ } ) ;
84+
85+ await waitFor ( ( ) => {
86+ expect ( result . current . isSuccess ) . toBe ( true ) ;
87+ } ) ;
4988
5089 expect ( mockListDiaries ) . toHaveBeenCalledWith (
5190 expect . objectContaining ( { headers : undefined } ) ,
5291 ) ;
5392 } ) ;
5493
55- it ( 'omits the team header when teamId is undefined' , async ( ) => {
56- mockListDiaries . mockResolvedValue ( { data : { items : [ ] } } ) ;
94+ it ( 'aggregates entryCount, tagCount, and latestEntryAt per diary' , async ( ) => {
95+ mockListDiaries . mockResolvedValue ( {
96+ data : {
97+ items : [
98+ {
99+ id : 'd1' ,
100+ name : 'b-diary' ,
101+ visibility : 'private' ,
102+ teamId : 't1' ,
103+ } ,
104+ {
105+ id : 'd2' ,
106+ name : 'a-diary' ,
107+ visibility : 'private' ,
108+ teamId : 't1' ,
109+ } ,
110+ ] ,
111+ } ,
112+ } ) ;
57113
58- await fetchDiarySummaries ( ) ;
114+ mockListDiaryEntries . mockImplementation (
115+ ( { path } : { path : { diaryId : string } } ) => {
116+ if ( path . diaryId === 'd1' ) {
117+ return Promise . resolve ( {
118+ data : {
119+ items : [ { createdAt : '2026-05-22T10:00:00Z' } ] ,
120+ total : 5 ,
121+ } ,
122+ } ) ;
123+ }
124+ return Promise . resolve ( {
125+ data : { items : [ ] , total : 0 } ,
126+ } ) ;
127+ } ,
128+ ) ;
59129
60- expect ( mockListDiaries ) . toHaveBeenCalledWith (
61- expect . objectContaining ( { headers : undefined } ) ,
130+ mockListDiaryTags . mockImplementation (
131+ ( { path } : { path : { diaryId : string } } ) => {
132+ if ( path . diaryId === 'd1' ) {
133+ return Promise . resolve ( {
134+ data : {
135+ tags : [
136+ { tag : 'auth' , count : 3 } ,
137+ { tag : 'db' , count : 2 } ,
138+ ] ,
139+ total : 2 ,
140+ } ,
141+ } ) ;
142+ }
143+ return Promise . resolve ( { data : { tags : [ ] , total : 0 } } ) ;
144+ } ,
62145 ) ;
146+
147+ const { result } = renderHook ( ( ) => useDiarySummaries ( 't1' ) , {
148+ wrapper : createTestWrapper ( ) ,
149+ } ) ;
150+
151+ await waitFor ( ( ) => {
152+ expect ( result . current . isSuccess ) . toBe ( true ) ;
153+ } ) ;
154+
155+ const summaries = result . current . data ?? [ ] ;
156+ // d1 has the most-recent latestEntryAt → sorts before d2.
157+ expect ( summaries . map ( ( s ) => s . id ) ) . toEqual ( [ 'd1' , 'd2' ] ) ;
158+ expect ( summaries [ 0 ] ) . toMatchObject ( {
159+ id : 'd1' ,
160+ entryCount : 5 ,
161+ tagCount : 2 ,
162+ latestEntryAt : '2026-05-22T10:00:00Z' ,
163+ } ) ;
164+ expect ( summaries [ 1 ] ) . toMatchObject ( {
165+ id : 'd2' ,
166+ entryCount : 0 ,
167+ tagCount : 0 ,
168+ latestEntryAt : null ,
169+ } ) ;
63170 } ) ;
64171} ) ;
0 commit comments