1+ import { describe , expect , test , vi , beforeEach , afterEach } from 'vitest'
2+
3+ import { getGeoLocation , mockLocation } from './geo-location.js'
4+
5+ // Mock fetch
6+ global . fetch = vi . fn ( )
7+ const mockFetch = vi . mocked ( fetch )
8+
9+ describe ( 'geolocation' , ( ) => {
10+ let mockState : { get : vi . Mock ; set : vi . Mock }
11+
12+ beforeEach ( ( ) => {
13+ vi . clearAllMocks ( )
14+ mockState = {
15+ get : vi . fn ( ) ,
16+ set : vi . fn ( ) ,
17+ }
18+ } )
19+
20+ afterEach ( ( ) => {
21+ vi . restoreAllMocks ( )
22+ } )
23+
24+ describe ( 'getGeoLocation' , ( ) => {
25+ test ( 'returns mock location when mode is "mock"' , async ( ) => {
26+ const result = await getGeoLocation ( {
27+ mode : 'mock' ,
28+ state : mockState ,
29+ } )
30+
31+ expect ( result ) . toEqual ( mockLocation )
32+ expect ( mockState . get ) . not . toHaveBeenCalled ( )
33+ expect ( mockState . set ) . not . toHaveBeenCalled ( )
34+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
35+ } )
36+
37+ test ( 'returns custom mock location when geoCountry is provided' , async ( ) => {
38+ const result = await getGeoLocation ( {
39+ mode : 'cache' ,
40+ geoCountry : 'FR' ,
41+ state : mockState ,
42+ } )
43+
44+ expect ( result ) . toEqual ( {
45+ city : 'Mock City' ,
46+ country : { code : 'FR' , name : 'Mock Country' } ,
47+ subdivision : { code : 'SD' , name : 'Mock Subdivision' } ,
48+ longitude : 0 ,
49+ latitude : 0 ,
50+ timezone : 'UTC' ,
51+ } )
52+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
53+ } )
54+
55+ test ( 'returns cached data when mode is "cache" and data is fresh' , async ( ) => {
56+ const cachedData = {
57+ city : 'Cached City' ,
58+ country : { code : 'CA' , name : 'Canada' } ,
59+ subdivision : { code : 'ON' , name : 'Ontario' } ,
60+ longitude : - 79.3832 ,
61+ latitude : 43.6532 ,
62+ timezone : 'America/Toronto' ,
63+ }
64+
65+ mockState . get . mockReturnValue ( {
66+ data : cachedData ,
67+ timestamp : Date . now ( ) - 1000 * 60 * 60 , // 1 hour ago
68+ } )
69+
70+ const result = await getGeoLocation ( {
71+ mode : 'cache' ,
72+ state : mockState ,
73+ } )
74+
75+ expect ( result ) . toEqual ( cachedData )
76+ expect ( mockState . get ) . toHaveBeenCalledWith ( 'geolocation' )
77+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
78+ } )
79+
80+ test ( 'fetches new data when mode is "cache" but data is stale' , async ( ) => {
81+ const staleData = {
82+ city : 'Stale City' ,
83+ country : { code : 'CA' , name : 'Canada' } ,
84+ subdivision : { code : 'ON' , name : 'Ontario' } ,
85+ longitude : - 79.3832 ,
86+ latitude : 43.6532 ,
87+ timezone : 'America/Toronto' ,
88+ }
89+
90+ const freshData = {
91+ city : 'Fresh City' ,
92+ country : { code : 'US' , name : 'United States' } ,
93+ subdivision : { code : 'NY' , name : 'New York' } ,
94+ longitude : - 74.006 ,
95+ latitude : 40.7128 ,
96+ timezone : 'America/New_York' ,
97+ }
98+
99+ mockState . get . mockReturnValue ( {
100+ data : staleData ,
101+ timestamp : Date . now ( ) - 1000 * 60 * 60 * 25 , // 25 hours ago (stale)
102+ } )
103+
104+ mockFetch . mockResolvedValue ( {
105+ json : ( ) => Promise . resolve ( { geo : freshData } ) ,
106+ } as Response )
107+
108+ const result = await getGeoLocation ( {
109+ mode : 'cache' ,
110+ state : mockState ,
111+ } )
112+
113+ expect ( result ) . toEqual ( freshData )
114+ expect ( mockState . get ) . toHaveBeenCalledWith ( 'geolocation' )
115+ expect ( mockState . set ) . toHaveBeenCalledWith ( 'geolocation' , {
116+ data : freshData ,
117+ timestamp : expect . any ( Number ) ,
118+ } )
119+ expect ( mockFetch ) . toHaveBeenCalledWith ( 'https://netlifind.netlify.app' , {
120+ method : 'GET' ,
121+ signal : expect . any ( AbortSignal ) ,
122+ } )
123+ } )
124+
125+ test ( 'always fetches new data when mode is "update"' , async ( ) => {
126+ const cachedData = {
127+ city : 'Cached City' ,
128+ country : { code : 'CA' , name : 'Canada' } ,
129+ subdivision : { code : 'ON' , name : 'Ontario' } ,
130+ longitude : - 79.3832 ,
131+ latitude : 43.6532 ,
132+ timezone : 'America/Toronto' ,
133+ }
134+
135+ const freshData = {
136+ city : 'Fresh City' ,
137+ country : { code : 'US' , name : 'United States' } ,
138+ subdivision : { code : 'NY' , name : 'New York' } ,
139+ longitude : - 74.006 ,
140+ latitude : 40.7128 ,
141+ timezone : 'America/New_York' ,
142+ }
143+
144+ mockState . get . mockReturnValue ( {
145+ data : cachedData ,
146+ timestamp : Date . now ( ) - 1000 * 60 * 60 , // 1 hour ago (fresh)
147+ } )
148+
149+ mockFetch . mockResolvedValue ( {
150+ json : ( ) => Promise . resolve ( { geo : freshData } ) ,
151+ } as Response )
152+
153+ const result = await getGeoLocation ( {
154+ mode : 'update' ,
155+ state : mockState ,
156+ } )
157+
158+ expect ( result ) . toEqual ( freshData )
159+ expect ( mockState . set ) . toHaveBeenCalledWith ( 'geolocation' , {
160+ data : freshData ,
161+ timestamp : expect . any ( Number ) ,
162+ } )
163+ expect ( mockFetch ) . toHaveBeenCalledWith ( 'https://netlifind.netlify.app' , {
164+ method : 'GET' ,
165+ signal : expect . any ( AbortSignal ) ,
166+ } )
167+ } )
168+
169+ test ( 'uses cached data when offline is true, even if stale' , async ( ) => {
170+ const cachedData = {
171+ city : 'Cached City' ,
172+ country : { code : 'CA' , name : 'Canada' } ,
173+ subdivision : { code : 'ON' , name : 'Ontario' } ,
174+ longitude : - 79.3832 ,
175+ latitude : 43.6532 ,
176+ timezone : 'America/Toronto' ,
177+ }
178+
179+ mockState . get . mockReturnValue ( {
180+ data : cachedData ,
181+ timestamp : Date . now ( ) - 1000 * 60 * 60 * 25 , // 25 hours ago (stale)
182+ } )
183+
184+ const result = await getGeoLocation ( {
185+ mode : 'cache' ,
186+ offline : true ,
187+ state : mockState ,
188+ } )
189+
190+ expect ( result ) . toEqual ( cachedData )
191+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
192+ } )
193+
194+ test ( 'returns mock location when offline is true and no cached data' , async ( ) => {
195+ mockState . get . mockReturnValue ( undefined )
196+
197+ const result = await getGeoLocation ( {
198+ mode : 'update' ,
199+ offline : true ,
200+ state : mockState ,
201+ } )
202+
203+ expect ( result ) . toEqual ( mockLocation )
204+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
205+ } )
206+
207+ test ( 'returns mock location when API request fails' , async ( ) => {
208+ mockState . get . mockReturnValue ( undefined )
209+ mockFetch . mockRejectedValue ( new Error ( 'Network error' ) )
210+
211+ const result = await getGeoLocation ( {
212+ mode : 'update' ,
213+ state : mockState ,
214+ } )
215+
216+ expect ( result ) . toEqual ( mockLocation )
217+ expect ( mockFetch ) . toHaveBeenCalledWith ( 'https://netlifind.netlify.app' , {
218+ method : 'GET' ,
219+ signal : expect . any ( AbortSignal ) ,
220+ } )
221+ } )
222+
223+ test ( 'uses cached data when country matches geoCountry' , async ( ) => {
224+ const cachedData = {
225+ city : 'Paris' ,
226+ country : { code : 'FR' , name : 'France' } ,
227+ subdivision : { code : 'IDF' , name : 'Île-de-France' } ,
228+ longitude : 2.3522 ,
229+ latitude : 48.8566 ,
230+ timezone : 'Europe/Paris' ,
231+ }
232+
233+ mockState . get . mockReturnValue ( {
234+ data : cachedData ,
235+ timestamp : Date . now ( ) - 1000 * 60 * 60 * 25 , // 25 hours ago (stale)
236+ } )
237+
238+ const result = await getGeoLocation ( {
239+ mode : 'update' ,
240+ geoCountry : 'FR' ,
241+ state : mockState ,
242+ } )
243+
244+ expect ( result ) . toEqual ( cachedData )
245+ expect ( mockFetch ) . not . toHaveBeenCalled ( )
246+ } )
247+ } )
248+ } )
0 commit comments