@@ -209,6 +209,198 @@ describe("mods.fs", function()
209209 end
210210 end )
211211
212+ describe (" listdir()" , function ()
213+ it (" returns an empty list for an empty directory" , function ()
214+ local root = make_tmp_dir ()
215+ assert .is_true (fs .listdir (root ):isempty ())
216+ assert .is_true (fs .rm (root , true ))
217+ end )
218+
219+ it (" lists direct children by default" , function ()
220+ local root = make_tmp_dir ()
221+ local subdir = join (root , " sub" )
222+ local hidden_dir = join (root , " .hidden" )
223+ local target = join (root , " data.txt" )
224+ local hidden_target = join (root , " .secret" )
225+ local nested = join (subdir , " nested.txt" )
226+ local hidden_nested = join (hidden_dir , " nested.txt" )
227+
228+ assert .is_true (fs .mkdir (subdir ))
229+ assert .is_true (fs .mkdir (hidden_dir ))
230+ assert .is_true (fs .write_text (target , " abc" ))
231+ assert .is_true (fs .write_text (hidden_target , " zzz" ))
232+ assert .is_true (fs .write_text (nested , " xyz" ))
233+ assert .is_true (fs .write_text (hidden_nested , " qqq" ))
234+
235+ assert .same ({ hidden_dir , hidden_target , target , subdir }, fs .listdir (root ):sort ())
236+
237+ assert .is_true (fs .rm (root , true ))
238+ end )
239+
240+ it (" supports recursive listing" , function ()
241+ local root = make_tmp_dir ()
242+ local subdir = join (root , " sub" )
243+ local hidden_dir = join (root , " .hidden" )
244+ local target = join (root , " data.txt" )
245+ local hidden_target = join (root , " .secret" )
246+ local nested = join (subdir , " nested.txt" )
247+ local hidden_nested = join (hidden_dir , " nested.txt" )
248+
249+ assert .is_true (fs .mkdir (subdir ))
250+ assert .is_true (fs .mkdir (hidden_dir ))
251+ assert .is_true (fs .write_text (target , " abc" ))
252+ assert .is_true (fs .write_text (hidden_target , " zzz" ))
253+ assert .is_true (fs .write_text (nested , " xyz" ))
254+ assert .is_true (fs .write_text (hidden_nested , " qqq" ))
255+
256+ assert .same (
257+ { hidden_dir , hidden_nested , hidden_target , target , subdir , nested },
258+ fs .listdir (root , { recursive = true }):sort ()
259+ )
260+
261+ assert .is_true (fs .rm (root , true ))
262+ end )
263+
264+ it (" supports hidden filtering" , function ()
265+ local root = make_tmp_dir ()
266+ local subdir = join (root , " sub" )
267+ local hidden_dir = join (root , " .hidden" )
268+ local target = join (root , " data.txt" )
269+ local hidden_target = join (root , " .secret" )
270+ local nested = join (subdir , " nested.txt" )
271+ local hidden_nested = join (hidden_dir , " nested.txt" )
272+
273+ assert .is_true (fs .mkdir (subdir ))
274+ assert .is_true (fs .mkdir (hidden_dir ))
275+ assert .is_true (fs .write_text (target , " abc" ))
276+ assert .is_true (fs .write_text (hidden_target , " zzz" ))
277+ assert .is_true (fs .write_text (nested , " xyz" ))
278+ assert .is_true (fs .write_text (hidden_nested , " qqq" ))
279+
280+ assert .same ({ target , subdir }, fs .listdir (root , { hidden = false }):sort ())
281+ assert .same ({ target , subdir , nested }, fs .listdir (root , { hidden = false , recursive = true }):sort ())
282+
283+ assert .is_true (fs .rm (root , true ))
284+ end )
285+
286+ it (" supports file type filtering" , function ()
287+ local root = make_tmp_dir ()
288+ local subdir = join (root , " sub" )
289+ local hidden_dir = join (root , " .hidden" )
290+ local target = join (root , " data.txt" )
291+ local hidden_target = join (root , " .secret" )
292+ local nested = join (subdir , " nested.txt" )
293+ local hidden_nested = join (hidden_dir , " nested.txt" )
294+
295+ assert .is_true (fs .mkdir (subdir ))
296+ assert .is_true (fs .mkdir (hidden_dir ))
297+ assert .is_true (fs .write_text (target , " abc" ))
298+ assert .is_true (fs .write_text (hidden_target , " zzz" ))
299+ assert .is_true (fs .write_text (nested , " xyz" ))
300+ assert .is_true (fs .write_text (hidden_nested , " qqq" ))
301+
302+ assert .same ({ hidden_target , target }, fs .listdir (root , { type = " file" }):sort ())
303+
304+ assert .is_true (fs .rm (root , true ))
305+ end )
306+
307+ it (" supports directory type filtering" , function ()
308+ local root = make_tmp_dir ()
309+ local subdir = join (root , " sub" )
310+ local hidden_dir = join (root , " .hidden" )
311+ local target = join (root , " data.txt" )
312+ local hidden_target = join (root , " .secret" )
313+ local nested = join (subdir , " nested.txt" )
314+ local hidden_nested = join (hidden_dir , " nested.txt" )
315+
316+ assert .is_true (fs .mkdir (subdir ))
317+ assert .is_true (fs .mkdir (hidden_dir ))
318+ assert .is_true (fs .write_text (target , " abc" ))
319+ assert .is_true (fs .write_text (hidden_target , " zzz" ))
320+ assert .is_true (fs .write_text (nested , " xyz" ))
321+ assert .is_true (fs .write_text (hidden_nested , " qqq" ))
322+
323+ assert .same ({ hidden_dir , subdir }, fs .listdir (root , { type = " directory" }):sort ())
324+
325+ assert .is_true (fs .rm (root , true ))
326+ end )
327+
328+ it (" fails for a non-directory path" , function ()
329+ local root = make_tmp_dir ()
330+ local target = join (root , " data.txt" )
331+
332+ assert .is_true (fs .write_text (target , " abc" ))
333+
334+ local items , errmsg = fs .listdir (target )
335+ assert .is_nil (items )
336+ assert .is_string (errmsg )
337+
338+ assert .is_true (fs .rm (root , true ))
339+ end )
340+
341+ it (" fails for a missing path" , function ()
342+ local root = make_tmp_dir ()
343+ local missing = join (root , " missing" )
344+
345+ local items , errmsg = fs .listdir (missing )
346+ assert .is_nil (items )
347+ assert .is_string (errmsg )
348+
349+ assert .is_true (fs .rm (root , true ))
350+ end )
351+
352+ if is_unix then
353+ it (" follows symlinked directories when requested" , function ()
354+ local root = make_tmp_dir ()
355+ local target_dir = join (root , " target" )
356+ local nested = join (target_dir , " nested.txt" )
357+ local link_dir = join (root , " linked" )
358+
359+ assert .is_true (fs .mkdir (target_dir , true ))
360+ assert .is_true (fs .write_text (nested , " abc" ))
361+
362+ local ok = lfs .link (target_dir , link_dir , true )
363+ if not ok then
364+ assert .is_true (fs .rm (root , true ))
365+ return
366+ end
367+
368+ assert .same ({ target_dir }, fs .listdir (root , { type = " directory" }):sort ())
369+ assert .same ({ link_dir }, fs .listdir (root , { type = " link" }):sort ())
370+ assert .same (
371+ { link_dir , join (link_dir , " nested.txt" ), target_dir , nested },
372+ fs .listdir (root , {
373+ recursive = true ,
374+ follow_links = true ,
375+ }):sort ()
376+ )
377+
378+ assert .is_true (fs .rm (root , true ))
379+ end )
380+
381+ it (" includes broken symlinks and does not traverse them" , function ()
382+ local root = make_tmp_dir ()
383+ local target = join (root , " missing" )
384+ local link = join (root , " broken" )
385+
386+ assert .is_true (lfs .link (target , link , true ))
387+
388+ assert .same ({ link }, fs .listdir (root ))
389+ assert .same ({ link }, fs .listdir (root , { recursive = true }))
390+ assert .same ({ link }, fs .listdir (root , { type = " link" }))
391+
392+ assert .is_true (fs .rm (root , true ))
393+ end )
394+ end
395+
396+ it (" labels option validation errors with the function name" , function ()
397+ assert .has_error (function ()
398+ --- @diagnostic disable-next-line : assign-type-mismatch
399+ _ = fs .listdir (cwd , { recursive = " yes" })
400+ end , " listdir.opts.recursive: boolean expected, got string" )
401+ end )
402+ end )
403+
212404 describe (" mkdir()" , function ()
213405 it (" creates a directory without parent mode" , function ()
214406 local root = make_tmp_dir ()
@@ -669,6 +861,7 @@ describe("mods.fs", function()
669861 assert .has_error (function () fs .getmtime () end , " bad argument #1 to 'getmtime' (string expected, got no value)" )
670862 assert .has_error (function () fs .getsize () end , " bad argument #1 to 'getsize' (string expected, got no value)" )
671863 assert .has_error (function () fs .lexists ({}) end , " bad argument #1 to 'lexists' (string expected, got table)" )
864+ assert .has_error (function () fs .listdir (false ) end , " bad argument #1 to 'listdir' (string expected, got boolean)" )
672865 assert .has_error (function () fs .mkdir () end , " bad argument #1 to 'mkdir' (string expected, got no value)" )
673866 assert .has_error (function () fs .read_bytes ({}) end , " bad argument #1 to 'read_bytes' (string expected, got table)" )
674867 assert .has_error (function () fs .read_text ({}) end , " bad argument #1 to 'read_text' (string expected, got table)" )
@@ -679,10 +872,23 @@ describe("mods.fs", function()
679872
680873 -- Argument #2 validation.
681874 assert .has_error (function () fs .cp (" a" ) end , " bad argument #2 to 'cp' (string expected, got no value)" )
875+ assert .has_error (function () fs .listdir (" src" , false ) end , " bad argument #2 to 'listdir' (table expected, got boolean)" )
682876 assert .has_error (function () fs .mkdir (" tmp" , 1 ) end , " bad argument #2 to 'mkdir' (boolean expected, got number)" )
683877 assert .has_error (function () fs .rm (" tmp" , 1 ) end , " bad argument #2 to 'rm' (boolean expected, got number)" )
684878 assert .has_error (function () fs .samefile (readme_file , 123 ) end , " bad argument #2 to 'samefile' (string expected, got number)" )
685879 assert .has_error (function () fs .write_bytes (readme_file , {}) end , " bad argument #2 to 'write_bytes' (string expected, got table)" )
686880 assert .has_error (function () fs .write_text (readme_file ) end , " bad argument #2 to 'write_text' (string expected, got no value)" )
881+
882+ -- Option validation.
883+
884+ local hidden = { hidden = 1 }
885+ local rec = { recursive = 1 }
886+ local follow = { follow_links = 1 }
887+ local tp = { type = 1 }
888+
889+ assert .has_error (function () fs .listdir (" src" , follow ) end , " listdir.opts.follow_links: boolean expected, got number" )
890+ assert .has_error (function () fs .listdir (" src" , hidden ) end , " listdir.opts.hidden: boolean expected, got number" )
891+ assert .has_error (function () fs .listdir (" src" , rec ) end , " listdir.opts.recursive: boolean expected, got number" )
892+ assert .has_error (function () fs .listdir (" src" , tp ) end , " listdir.opts.type: string expected, got number" )
687893 end )
688894end )
0 commit comments