@@ -10,23 +10,14 @@ import { createReaderWatch } from '../src/reader-watch.js'
1010
1111/** @import {Reader} from 'styled-map-package' */
1212
13+ const isWin = process . platform === 'win32'
1314const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) )
1415const FIXTURE1_PATH = path . join ( __dirname , 'fixtures' , 'demotiles-z2.smp' )
1516const FIXTURE2_PATH = path . join ( __dirname , 'fixtures' , 'osm-bright-z6.smp' )
1617
1718/** @param {import('node:test').TestContext } t */
18- function tempDir ( t ) {
19- const dir = fs . mkdtempSync ( path . join ( tmpdir ( ) , 'comapeo-map-server-' ) )
20- t . after ( async ( ) => {
21- console . log ( 'tempDir cleanup' )
22- // Wait on Windows to ensure all file handles are released
23- if ( process . platform === 'win32' ) {
24- await setTimeout ( 1000 )
25- }
26- console . log ( 'attempting to delete' )
27- fs . rmSync ( dir , { recursive : true , force : true } )
28- } )
29- return dir
19+ function tempDir ( ) {
20+ return fs . mkdtempSync ( path . join ( tmpdir ( ) , 'comapeo-map-server-' ) )
3021}
3122
3223test ( 'createReaderWatch - valid smp file' , async ( t ) => {
@@ -41,12 +32,15 @@ test('createReaderWatch - valid smp file', async (t) => {
4132} )
4233
4334test ( 'createReaderWatch - file does not exist initially' , async ( t ) => {
44- const dir = tempDir ( t )
35+ const dir = tempDir ( )
4536 const nonExistentPath = path . join ( dir , 'nonexistent.smp' )
4637 assert ( ! fs . existsSync ( nonExistentPath ) , 'File should not exist yet' )
4738
4839 const getReader = createReaderWatch ( nonExistentPath )
49- t . after ( getReader . close )
40+ t . after ( async ( ) => {
41+ await getReader . close ( )
42+ await rmDir ( dir )
43+ } )
5044 const reader1 = getReader ( )
5145 // Attempting to read from non-existent file should throw
5246 await assert . rejects (
@@ -55,33 +49,34 @@ test('createReaderWatch - file does not exist initially', async (t) => {
5549 'Reading from non-existent file should throw error with ENOENT code and filepath in message' ,
5650 )
5751 fs . copyFileSync ( FIXTURE1_PATH , nonExistentPath )
58- await setTimeout ( 200 ) // Wait for watcher to detect change
52+ await syncDir ( dir ) // Ensure copy is flushed and directory is synced
5953 const reader2 = getReader ( )
6054 // Now the reader should point to the new file
6155 const style = await reader2 . getStyle ( )
6256 assert ( style , 'Style should be readable after file is created' )
6357} )
6458
6559test ( 'createReaderWatch - file is replaced with different file' , async ( t ) => {
66- const dir = tempDir ( t )
60+ const dir = tempDir ( )
6761 const targetPath = path . join ( dir , 'replaced.smp' )
6862
6963 // Start with the fixture file
7064 fs . copyFileSync ( FIXTURE1_PATH , targetPath )
71- // Ensure file is fully written (otherwise the copy triggers the watcher)
72- await setTimeout ( 200 )
65+ await syncDir ( dir )
7366
7467 const getReader = createReaderWatch ( targetPath )
75- t . after ( getReader . close )
68+ t . after ( async ( ) => {
69+ await getReader . close ( )
70+ await rmDir ( dir )
71+ } )
7672
7773 // Read from initial file
7874 const reader1 = getReader ( )
7975 const style1 = await reader1 . getStyle ( )
8076 assert . equal ( style1 . name , 'MapLibre' , 'Expected style name from fixture 1' )
8177
8278 fs . copyFileSync ( FIXTURE2_PATH , targetPath )
83- // Wait for file system watcher to detect the change
84- await setTimeout ( 200 )
79+ await syncDir ( dir )
8580
8681 // The reader should be recreated
8782 const reader2 = getReader ( )
@@ -90,13 +85,16 @@ test('createReaderWatch - file is replaced with different file', async (t) => {
9085} )
9186
9287test ( 'createReaderWatch - file is deleted' , async ( t ) => {
93- const dir = tempDir ( t )
88+ const dir = tempDir ( )
9489 const targetPath = path . join ( dir , 'to-delete.smp' )
9590 fs . copyFileSync ( FIXTURE1_PATH , targetPath )
9691 await setTimeout ( 200 ) // Ensure file is fully written
9792
9893 const getReader = createReaderWatch ( targetPath )
99- t . after ( getReader . close )
94+ t . after ( async ( ) => {
95+ await getReader . close ( )
96+ await rmDir ( dir )
97+ } )
10098
10199 const reader1 = getReader ( )
102100 const style1 = await reader1 . getStyle ( )
@@ -108,7 +106,7 @@ test('createReaderWatch - file is deleted', async (t) => {
108106
109107 // Delete the file
110108 fs . unlinkSync ( targetPath )
111- await setTimeout ( 200 ) // Wait for watcher to detect change
109+ await syncDir ( dir )
112110
113111 const reader2 = getReader ( )
114112 try {
@@ -126,15 +124,18 @@ test('createReaderWatch - file is deleted', async (t) => {
126124} )
127125
128126test ( 'createReaderWatch - invalid file (non-smp file)' , async ( t ) => {
129- const dir = tempDir ( t )
127+ const dir = tempDir ( )
130128 const invalidPath = path . join ( dir , 'invalid.smp' )
131129
132130 // Create an invalid file (just some text)
133131 fs . writeFileSync ( invalidPath , 'This is not a valid SMP file' )
134- await setTimeout ( 200 ) // Ensure file is fully written
132+ await syncDir ( dir )
135133
136134 const getReader = createReaderWatch ( invalidPath )
137- t . after ( getReader . close )
135+ t . after ( async ( ) => {
136+ await getReader . close ( )
137+ await rmDir ( dir )
138+ } )
138139
139140 const reader = getReader ( )
140141 assert ( reader , 'Reader instance should be returned even for invalid file' )
@@ -146,7 +147,7 @@ test('createReaderWatch - invalid file (non-smp file)', async (t) => {
146147 )
147148
148149 fs . copyFileSync ( FIXTURE1_PATH , invalidPath )
149- await setTimeout ( 200 ) // Wait for watcher to detect change
150+ await syncDir ( dir )
150151
151152 const reader2 = getReader ( )
152153 const style = await reader2 . getStyle ( )
@@ -158,12 +159,15 @@ test('createReaderWatch - invalid file (non-smp file)', async (t) => {
158159} )
159160
160161test ( 'createReaderWatch - close() cleans up watcher' , async ( t ) => {
161- const dir = tempDir ( t )
162+ const dir = tempDir ( )
162163 const targetPath = path . join ( dir , 'to-close.smp' )
163164 fs . copyFileSync ( FIXTURE1_PATH , targetPath )
164- await setTimeout ( 200 ) // Ensure file is fully written
165+ await syncDir ( dir )
165166
166167 const getReader = createReaderWatch ( targetPath )
168+ t . after ( async ( ) => {
169+ await rmDir ( dir )
170+ } )
167171
168172 const reader1 = getReader ( )
169173 const style = await reader1 . getStyle ( )
@@ -174,7 +178,7 @@ test('createReaderWatch - close() cleans up watcher', async (t) => {
174178
175179 // Modify the file after closing
176180 fs . copyFileSync ( FIXTURE2_PATH , targetPath )
177- await setTimeout ( 200 ) // Wait to see if any watcher events occur
181+ await syncDir ( dir )
178182
179183 const reader2 = getReader ( )
180184 // The reader should still be the old instance
@@ -194,14 +198,17 @@ test('createReaderWatch - close() cleans up watcher', async (t) => {
194198
195199test ( 'createReaderWatch - AbortSignal integration' , async ( t ) => {
196200 const controller = new AbortController ( )
197- const dir = tempDir ( t )
201+ const dir = tempDir ( )
198202 const targetPath = path . join ( dir , 'with-signal.smp' )
199203 fs . copyFileSync ( FIXTURE1_PATH , targetPath )
200- await setTimeout ( 200 ) // Ensure file is fully written
204+ await syncDir ( dir )
201205 const getReader = createReaderWatch ( targetPath , {
202206 signal : controller . signal ,
203207 } )
204- t . after ( getReader . close )
208+ t . after ( async ( ) => {
209+ await getReader . close ( )
210+ await rmDir ( dir )
211+ } )
205212
206213 const reader = getReader ( )
207214 const style = await reader . getStyle ( )
@@ -211,7 +218,7 @@ test('createReaderWatch - AbortSignal integration', async (t) => {
211218 controller . abort ( )
212219
213220 fs . copyFileSync ( FIXTURE2_PATH , targetPath )
214- await setTimeout ( 200 )
221+ await syncDir ( dir )
215222
216223 // reader should still be same instance because watcher is closed
217224 const reader2 = getReader ( )
@@ -229,18 +236,22 @@ test('createReaderWatch - AbortSignal integration', async (t) => {
229236} )
230237
231238test ( 'createReaderWatch - watcher only recreates reader for target file changes' , async ( t ) => {
232- const dir = tempDir ( t )
239+ const dir = tempDir ( )
233240
234241 const targetPath = path . join ( dir , 'target.smp' )
235242 const otherPath = path . join ( dir , 'other-file.txt' )
236243
237244 // Copy fixture to target
238245 fs . copyFileSync ( FIXTURE1_PATH , targetPath )
239- await setTimeout ( 200 ) // Ensure file is fully written
246+ await syncDir ( dir )
240247
241248 const getReader = createReaderWatch ( targetPath )
242249
243250 const reader1 = getReader ( )
251+ t . after ( async ( ) => {
252+ await getReader . close ( )
253+ await rmDir ( dir )
254+ } )
244255 const style1 = await reader1 . getStyle ( )
245256 assert ( style1 , 'Initial style should be readable' )
246257
@@ -249,7 +260,7 @@ test('createReaderWatch - watcher only recreates reader for target file changes'
249260 // the reader should NOT be recreated. However, on some platforms filename
250261 // may be null, causing recreation. We test the happy path here.
251262 fs . writeFileSync ( otherPath , 'Some other content' )
252- await setTimeout ( 200 ) // Ensure file is fully written
263+ await syncDir ( dir )
253264
254265 // reader should be same instance (not changed because of file watcher)
255266 const reader2 = getReader ( )
@@ -282,3 +293,16 @@ async function concat(stream) {
282293 }
283294 return Buffer . concat ( chunks )
284295}
296+
297+ /** @param {string } dirPath */
298+ async function syncDir ( dirPath ) {
299+ const dh = await fs . promises . open ( dirPath , 'r' )
300+ await dh . sync ( )
301+ await dh . close ( )
302+ await setTimeout ( isWin ? 50 : 10 )
303+ }
304+
305+ /** @param {string } dirPath */
306+ async function rmDir ( dirPath ) {
307+ await fs . promises . rm ( dirPath , { recursive : true , force : true , maxRetries : 5 } )
308+ }
0 commit comments