1+ // src/api/OpenProcessing.test.ts
2+
3+ import { getCurationSketches , getSketch , getSketchSize , priorityIds , type OpenProcessingCurationResponse } from '@/src/api/OpenProcessing' ;
4+ import { describe , it , expect , vi , beforeEach } from 'vitest' ;
5+
6+ const mockFetch = vi . fn ( ) ;
7+
8+ vi . stubGlobal ( 'fetch' , mockFetch ) ;
9+
10+ // Test data: first item is mock data, second uses actual priority ID from current curation
11+ const getCurationSketchesData : OpenProcessingCurationResponse = [ {
12+ visualID : 101 ,
13+ title : 'Sketch One' ,
14+ description : 'Description One' ,
15+ instructions : 'Instructions One' ,
16+ mode : 'p5js' ,
17+ createdOn : '2025-01-01' ,
18+ userID : 'User One' ,
19+ submittedOn : '2025-01-01' ,
20+ fullname : 'Fullname One'
21+ } ,
22+ {
23+ visualID : Number ( priorityIds [ 0 ] ) , // Real ID from current curation priority list
24+ title : 'Sketch Two' ,
25+ description : 'Description Two' ,
26+ instructions : 'Instructions Two' ,
27+ mode : 'p5js' ,
28+ createdOn : '2025-01-01' ,
29+ userID : 'User Two' ,
30+ submittedOn : '2025-01-01' ,
31+ fullname : 'Fullname Two'
32+ } ]
33+
34+ describe ( 'OpenProcessing API Caching' , ( ) => {
35+
36+ beforeEach ( ( ) => {
37+ vi . clearAllMocks ( ) ;
38+
39+ getCurationSketches . cache . clear ?.( ) ;
40+ getSketch . cache . clear ?.( ) ;
41+ getSketchSize . cache . clear ?.( ) ;
42+ } ) ;
43+
44+ // Case 1: Verify caching for getCurationSketches
45+ it ( 'should only call the API once even if getCurationSketches is called multiple times' , async ( ) => {
46+
47+ mockFetch . mockResolvedValue ( {
48+ ok : true ,
49+ json : ( ) => Promise . resolve ( getCurationSketchesData ) ,
50+ } ) ;
51+
52+ await getCurationSketches ( ) ;
53+ await getCurationSketches ( ) ;
54+
55+ // Check if fetch was called exactly 2 times (for the two curation IDs).
56+ // If this number becomes 4, it means the caching is broken.
57+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 2 ) ;
58+ } ) ;
59+
60+ // Case 2: Verify getSketch uses cached data from getCurationSketches
61+ it ( 'should use cached data from getCurationSketches for getSketch calls' , async ( ) => {
62+
63+ mockFetch . mockResolvedValueOnce ( { // for curationId
64+ ok : true ,
65+ json : ( ) => Promise . resolve ( [ getCurationSketchesData [ 0 ] ] ) ,
66+ } ) . mockResolvedValueOnce ( { // for newCurationId
67+ ok : true ,
68+ json : ( ) => Promise . resolve ( [ getCurationSketchesData [ 1 ] ] ) ,
69+ } ) ;
70+
71+ // Call the main function to populate the cache.
72+ await getCurationSketches ( ) ;
73+ // At this point, fetch has been called twice.
74+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 2 ) ;
75+
76+ // Now, call getSketch with an ID that should be in the cache.
77+ const sketch = await getSketch ( getCurationSketchesData [ 0 ] . visualID ) ;
78+ expect ( sketch . title ) . toBe ( 'Sketch One' ) ;
79+ const sketch2 = await getSketch ( getCurationSketchesData [ 1 ] . visualID ) ;
80+ expect ( sketch2 . title ) . toBe ( 'Sketch Two' ) ;
81+
82+ // Verify that no additional fetch calls were made.
83+ // The call count should still be 2 because the data came from the cache.
84+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 2 ) ;
85+ } ) ;
86+
87+ // Case 3: Verify getSketch fetches individual sketch when not in cache
88+ it ( 'should fetch individual sketch when not in cache' , async ( ) => {
89+
90+ // for curationId
91+ mockFetch . mockResolvedValueOnce ( {
92+ ok : true ,
93+ json : ( ) => Promise . resolve ( [ ] )
94+ } ) . mockResolvedValueOnce ( { // for newCurationId
95+ ok : true ,
96+ json : ( ) => Promise . resolve ( [ ] )
97+ } ) ;
98+
99+ await getCurationSketches ( ) ; // Create empty cache
100+
101+ // Individual sketch API call
102+ mockFetch . mockResolvedValueOnce ( {
103+ ok : true ,
104+ json : ( ) => Promise . resolve ( { visualID : 999 , title : 'Individual Sketch' } )
105+ } ) ;
106+
107+ const sketch = await getSketch ( 999 ) ;
108+ expect ( sketch . title ) . toBe ( 'Individual Sketch' ) ;
109+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 3 ) ; // 2 for empty curations in getCurationSketches + 1 for individual call in getSketch
110+ } ) ;
111+
112+ // Case 4: Overall regression test for total sketch page generation
113+ it ( 'should not exceed the expected number of API calls during a build simulation' , async ( ) => {
114+
115+ // Mock the responses for getCurationSketches.
116+ mockFetch . mockResolvedValueOnce ( { // for curationId
117+ ok : true ,
118+ json : ( ) => Promise . resolve ( [ getCurationSketchesData [ 0 ] ] ) ,
119+ } ) . mockResolvedValueOnce ( { // for newCurationId
120+ ok : true ,
121+ json : ( ) => Promise . resolve ( [ getCurationSketchesData [ 1 ] ] ) ,
122+ } ) ;
123+
124+ // 2. Mock the response for getSketchSize calls.
125+ mockFetch . mockResolvedValue ( { // for all subsequent calls
126+ ok : true ,
127+ json : ( ) => Promise . resolve ( [ { code : 'createCanvas(400, 400);' } ] ) ,
128+ } ) ;
129+
130+ // --- sketch page build simulation ---
131+ // This simulates what happens during `getStaticPaths`.
132+ const sketches = await getCurationSketches ( ) ; // Makes 2 API calls.
133+
134+ // This simulates what happens as each page is generated.
135+ for ( const sketch of sketches ) {
136+ // Inside the page component, getSketch and getSketchSize would be called.
137+ await getSketch ( sketch . visualID ) ; // Uses cache (0 new calls).
138+ await getSketchSize ( sketch . visualID ) ; // Makes 1 new API call.
139+ }
140+ // --- simulation end ---
141+
142+ // Calculate the total expected calls.
143+ // 2 for getCurationSketches + 1 for each sketch's getSketchSize call.
144+ const expectedCalls = 2 + sketches . length ;
145+ expect ( mockFetch ) . toHaveBeenCalledTimes ( expectedCalls ) ;
146+
147+ } ) ;
148+ } ) ;
0 commit comments