11import makeServiceWorkerEnv from 'service-worker-mock'
2+ import url from 'url' ;
23
34let sw
45
@@ -11,28 +12,111 @@ describe('bootstrap', () => {
1112 } )
1213 serviceWorkerEnv . workbox = {
1314 loadModule : ( ) => null ,
14- expiration : { ExpirationPlugin : jest . fn ( ) } ,
15+ expiration : {
16+ ExpirationPlugin : class ExpirationPlugin {
17+ constructor ( params ) {
18+ Object . assign ( this , params )
19+ }
20+ } ,
21+ } ,
1522 routing : { registerRoute : jest . fn ( ) } ,
1623 }
1724 Object . assign ( global , serviceWorkerEnv )
1825 jest . resetModules ( )
1926 sw = require ( '../service-worker/bootstrap' )
2027 } )
2128
22- it ( 'should add data to the cache' , async ( ) => {
23- const cacheData = { testData : 'testData1' }
24- self . trigger ( 'message' , {
25- data : {
26- action : 'cache-state' ,
27- path : 'testPath' ,
28- cacheData,
29- apiVersion : 'v1' ,
30- } ,
29+ describe ( 'message listener' , ( ) => {
30+ it ( 'should listen for messages to cache a url path' , async ( ) => {
31+ const apiVersion = 'v1'
32+ const apiCacheName = sw . __get__ ( 'getAPICacheName' ) ( apiVersion )
33+ const path = '/api/p/1'
34+ const abortControllers = sw . __get__ ( 'abortControllers' )
35+ expect ( abortControllers . size ) . toEqual ( 0 )
36+ await self . trigger ( 'message' , {
37+ data : {
38+ action : 'cache-path' ,
39+ path,
40+ apiVersion,
41+ } ,
42+ } )
43+ expect ( self . snapshot ( ) . caches [ apiCacheName ] ) . toBeDefined ( )
44+ } )
45+
46+ it ( 'should listen for messages to cache data' , async ( ) => {
47+ const apiVersion = 'v1'
48+ const apiCacheName = sw . __get__ ( 'getAPICacheName' ) ( apiVersion )
49+ const cacheData = { testData : 'testData1' }
50+ self . trigger ( 'message' , {
51+ data : {
52+ action : 'cache-state' ,
53+ path : 'testPath' ,
54+ cacheData,
55+ apiVersion,
56+ } ,
57+ } )
58+ const cache = await caches . open ( apiCacheName )
59+ const cachedValue = await cache . store . get ( 'testPath' ) . response . json ( )
60+ expect ( cachedValue ) . toEqual ( cacheData )
61+ } )
62+
63+ it ( 'should listen for messages to configure caching options' , async ( ) => {
64+ await self . trigger ( 'message' , {
65+ data : {
66+ action : 'configure-runtime-caching' ,
67+ options : { maxEntries : 100 , maxAgeSeconds : 1 } ,
68+ } ,
69+ } )
70+ const options = sw . __get__ ( 'runtimeCacheOptions' )
71+ expect ( options . plugins [ 0 ] . maxEntries ) . toEqual ( 100 )
72+ expect ( options . plugins [ 0 ] . maxAgeSeconds ) . toEqual ( 1 )
3173 } )
32- const cache = await caches . open ( 'runtime-v1' )
33- const cachedValue = await cache . store . get ( 'testPath' ) . response . json ( )
3474
35- expect ( cachedValue ) . toEqual ( cacheData )
75+ it ( 'should listen for messages to abort prefetches' , async ( ) => {
76+ const abortControllers = sw . __get__ ( 'abortControllers' )
77+ abortControllers . add ( new AbortController ( ) )
78+ expect ( abortControllers . size ) . toEqual ( 1 )
79+ await self . trigger ( 'message' , {
80+ data : {
81+ action : 'abort-prefetches' ,
82+ } ,
83+ } )
84+ expect ( abortControllers . size ) . toEqual ( 0 )
85+ } )
86+
87+ it ( 'should listen for messages to resume prefetches' , async ( ) => {
88+ const cachePath = jest . fn ( )
89+ sw . __set__ ( 'cachePath' , cachePath )
90+ const toResume = sw . __get__ ( 'toResume' )
91+ toResume . add ( [ { path : '' , apiVersion : 'v1' } ] )
92+ await self . trigger ( 'message' , {
93+ data : {
94+ action : 'resume-prefetches' ,
95+ } ,
96+ } )
97+ expect ( cachePath ) . toHaveBeenCalled ( )
98+ } )
99+ } )
100+
101+ describe ( 'precacheLinks' , ( ) => {
102+ it ( 'should detect all `data-rsf-prefetch` links in a response' , async ( ) => {
103+ const precacheLinks = sw . __get__ ( 'precacheLinks' )
104+ const text = ( ) =>
105+ Promise . resolve ( '<a href="/api/p/1">No</a><a href="/api/p/2" data-rsf-prefetch>Yes</a>' )
106+ const abortControllers = sw . __get__ ( 'abortControllers' )
107+ expect ( abortControllers . size ) . toEqual ( 0 )
108+ await precacheLinks ( { text } )
109+ expect ( abortControllers . size ) . toEqual ( 1 )
110+ expect ( abortControllers . values ( ) . next ( ) . value . args [ 0 ] . path ) . toEqual ( '/api/p/2' )
111+ } )
112+
113+ it ( 'should ignore links without `data-rsf-prefetch`' , async ( ) => {
114+ const precacheLinks = sw . __get__ ( 'precacheLinks' )
115+ const text = ( ) => Promise . resolve ( '<a href="/api/p/1">No</a>' )
116+ const abortControllers = sw . __get__ ( 'abortControllers' )
117+ await precacheLinks ( { text } )
118+ expect ( abortControllers . size ) . toEqual ( 0 )
119+ } )
36120 } )
37121
38122 describe ( 'abortPrefetches' , ( ) => {
@@ -76,13 +160,15 @@ describe('bootstrap', () => {
76160 } )
77161 } )
78162
79- describe ( 'fetch' , ( ) => {
80- it ( 'should abort prefetches when fetching more important resources' , ( ) => {
163+ describe ( 'fetch listener ' , ( ) => {
164+ it ( 'should abort prefetches when fetching more important resources' , async ( ) => {
81165 const abortControllers = sw . __get__ ( 'abortControllers' )
82- const toResume = sw . __get__ ( 'toResume' )
83- abortControllers . add ( new AbortController ( ) )
84- self . trigger ( 'fetch' )
166+ const abortController = new AbortController ( )
167+ abortController . args = [ { path : '' , apiVersion : 'v1' } ]
168+ abortControllers . add ( abortController )
169+ self . trigger ( 'fetch' , '' )
85170 expect ( abortControllers . size ) . toEqual ( 0 )
171+ const toResume = sw . __get__ ( 'toResume' )
86172 expect ( toResume . size ) . toEqual ( 1 )
87173 } )
88174
@@ -102,5 +188,116 @@ describe('bootstrap', () => {
102188 } catch ( e ) { }
103189 expect ( toResume . size ) . toEqual ( 0 )
104190 } )
191+
192+ it ( 'should fetch from the cache if the request is cached' , async ( ) => {
193+ const getAPICacheName = sw . __get__ ( 'getAPICacheName' )
194+ const addToCache = sw . __get__ ( 'addToCache' )
195+ const cache = await caches . open ( getAPICacheName ( 'v1' ) )
196+ await addToCache ( cache , '/api/p/1' , 'data' )
197+ global . fetch = jest . fn ( )
198+ await self . trigger ( 'fetch' , { respondWith : ( ) => null , request : '/api/p/1' } )
199+ expect ( global . fetch ) . not . toHaveBeenCalled ( )
200+ await self . trigger ( 'fetch' , { respondWith : ( ) => null , request : '/api/p/2' } )
201+ expect ( global . fetch ) . toHaveBeenCalled ( )
202+ } )
203+ } )
204+
205+ describe ( 'util functions' , ( ) => {
206+ it ( 'should detect if a path is for an API request' , ( ) => {
207+ const isApiRequest = sw . __get__ ( 'isApiRequest' ) ;
208+ expect ( isApiRequest ( '/api/p/1' ) ) . toEqual ( true )
209+ expect ( isApiRequest ( '/p/1' ) ) . toEqual ( false )
210+ } )
211+
212+ it ( 'should detect if a request is using a secure connection' , ( ) => {
213+ const isSecure = sw . __get__ ( 'isSecure' ) ;
214+ const secureUrl = url . parse ( 'https://wwww.example.com' )
215+ const localhostUrl = url . parse ( 'http://localhost:3000' )
216+ const insecureUrl = url . parse ( 'http://wwww.example.com' )
217+ expect ( isSecure ( { url : secureUrl } ) ) . toEqual ( true )
218+ expect ( isSecure ( { url : localhostUrl } ) ) . toEqual ( true )
219+ expect ( isSecure ( { url : insecureUrl } ) ) . toEqual ( false )
220+ } )
221+
222+ it ( 'should detect if a request is for a static asset' , ( ) => {
223+ const isStaticAsset = sw . __get__ ( 'isStaticAsset' ) ;
224+ const staticUrl = url . parse ( 'https://wwww.example.com/_next/static/asset.png' )
225+ const nonStaticUrl = url . parse ( 'https://www.example.com/p/1' )
226+ expect ( isStaticAsset ( { url : staticUrl } ) ) . toEqual ( true )
227+ expect ( isStaticAsset ( { url : nonStaticUrl } ) ) . toEqual ( false )
228+ } )
229+
230+ it ( 'should detect if a request is using amp' , ( ) => {
231+ const isAmp = sw . __get__ ( 'isAmp' ) ;
232+ const firstParamUrl = url . parse ( 'https://wwww.example.com/p/1?amp=1' )
233+ const laterParamUrl = url . parse ( 'https://wwww.example.com/p/1?param1=test&=1' )
234+ const innerParamUrl = url . parse ( 'https://wwww.example.com/p/1?param1=test&=1¶m2=test' )
235+ const nonAmpUrl = url . parse ( 'https://www.example.com/p/1' )
236+ expect ( isAmp ( firstParamUrl ) ) . toEqual ( true )
237+ expect ( isAmp ( laterParamUrl ) ) . toEqual ( true )
238+ expect ( isAmp ( innerParamUrl ) ) . toEqual ( true )
239+ expect ( isAmp ( nonAmpUrl ) ) . toEqual ( false )
240+ } )
241+
242+ it ( 'should detect if a request is for a video' , ( ) => {
243+ const isVideo = sw . __get__ ( 'isVideo' ) ;
244+ const videoUrl = url . parse ( 'https://wwww.example.com/p/vid.mp4' )
245+ const videoWithParamsUrl = url . parse ( 'https://wwww.example.com/p/vid.mp4?autoplay=true' )
246+ const nonVideoUrl = url . parse ( 'https://www.example.com/p/1' )
247+ expect ( isVideo ( { url : videoUrl } ) ) . toEqual ( true )
248+ expect ( isVideo ( { url : videoWithParamsUrl } ) ) . toEqual ( true )
249+ expect ( isVideo ( { url : nonVideoUrl } ) ) . toEqual ( false )
250+ } )
251+ } )
252+
253+ describe ( 'install listener' , ( ) => {
254+ it ( 'should delete existing runtime caches when installing' , async ( ) => {
255+ const cacheName = 'delete-me'
256+ const cache = await caches . open ( cacheName )
257+ await cache . put ( 'test' , 'cached info' )
258+ await self . trigger ( 'install' )
259+ console . log ( self . clients . matchAll )
260+ expect ( self . snapshot ( ) . caches [ cacheName ] ) . toBeUndefined ( )
261+ } )
262+
263+ it ( 'should cache non-amp version of pages when users land on AMP page' , async ( ) => {
264+ const cachePath = jest . fn ( )
265+ sw . __set__ ( 'cachePath' , cachePath )
266+ self . clients . clients . push ( new Client ( 'https://example.com/api/p/1?amp=1' ) )
267+ await self . trigger ( 'install' )
268+ console . log ( self . snapshot ( ) . caches )
269+ expect ( cachePath ) . toHaveBeenCalled ( )
270+ } )
271+ } )
272+
273+ describe ( 'matchRuntimePath' , ( ) => {
274+ it ( 'should return true for routes that are cacheable' , ( ) => {
275+ const matchRuntimePath = sw . __get__ ( 'matchRuntimePath' ) ;
276+ expect ( matchRuntimePath ( { url : url . parse ( 'http://example.com/p/1' ) } ) ) . toEqual ( false )
277+ expect ( matchRuntimePath ( { url : url . parse ( 'https://example.com/_next/static/asset' ) } ) ) . toEqual ( false )
278+ expect ( matchRuntimePath ( { url : url . parse ( 'https://example.com/p/1.mp4' ) } ) ) . toEqual ( false )
279+ expect ( matchRuntimePath ( { url : url . parse ( 'https://example.com/p/1' ) } ) ) . toEqual ( true )
280+ } )
281+ } )
282+
283+ describe ( 'offlineResponse' , ( ) => {
284+ it ( 'should send back a standard response for API calls' , async ( ) => {
285+ const offlineResponse = sw . __get__ ( 'offlineResponse' ) ;
286+ const resp = await offlineResponse ( 'v1' , { url : url . parse ( '/api/p/1' ) } )
287+ expect ( JSON . parse ( resp . body . parts [ 0 ] ) ) . toEqual ( { page : 'Offline' } )
288+ } )
289+
290+ it ( 'should send back the app shell for non-API calls' , async ( ) => {
291+ const appShellPath = sw . __get__ ( 'appShellPath' ) ;
292+ const testCachedData = 'test-cache-data'
293+ const offlineResponse = sw . __get__ ( 'offlineResponse' ) ;
294+ const apiVersion = 'v1'
295+ const apiCacheName = sw . __get__ ( 'getAPICacheName' ) ( apiVersion )
296+ const addToCache = sw . __get__ ( 'addToCache' )
297+ const cache = await caches . open ( apiCacheName )
298+ await addToCache ( cache , appShellPath , testCachedData )
299+ const resp = await offlineResponse ( apiVersion , { url : url . parse ( '/p/1' ) } )
300+ expect ( resp . body . parts [ 0 ] ) . toEqual ( testCachedData )
301+ } )
105302 } )
106303} )
0 commit comments