11// Copilot
22
33using System . Reflection ;
4+ using System . IO . Abstractions ;
45using System . IO . Abstractions . TestingHelpers ;
6+ using Moq ;
57
68namespace UnitTests . Views ;
79
@@ -162,7 +164,7 @@ public void OpenDialog_UsesInnerTableSeparatorsWithoutOuterBorders ()
162164 FieldInfo ? tableViewField = typeof ( FileDialog ) . GetField ( "_tableView" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
163165 Assert . NotNull ( tableViewField ) ;
164166
165- TableView tableView = Assert . IsType < TableView > ( tableViewField ! . GetValue ( od ) ) ;
167+ TableView tableView = Assert . IsType < TableView > ( tableViewField . GetValue ( od ) ) ;
166168
167169 Assert . True ( tableView . Style . ShowVerticalCellLines ) ;
168170 Assert . True ( tableView . Style . ShowVerticalHeaderLines ) ;
@@ -181,7 +183,7 @@ public void FileDialog_PathField_End_MovesInsertionPointToEnd ()
181183 FieldInfo ? tbPathField = typeof ( FileDialog ) . GetField ( "_tbPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
182184 Assert . NotNull ( tbPathField ) ;
183185
184- TextField tbPath = Assert . IsType < TextField > ( tbPathField ! . GetValue ( fd ) ) ;
186+ TextField tbPath = Assert . IsType < TextField > ( tbPathField . GetValue ( fd ) ) ;
185187 tbPath . Text = "/testdir/example.txt" ;
186188
187189 tbPath . NewKeyDownEvent ( Key . Home ) ;
@@ -208,7 +210,7 @@ public void FileDialog_PathField_BadChars_AreSuppressed (char badChar)
208210 FieldInfo ? tbPathField = typeof ( FileDialog ) . GetField ( "_tbPath" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
209211 Assert . NotNull ( tbPathField ) ;
210212
211- TextField tbPath = Assert . IsType < TextField > ( tbPathField ! . GetValue ( fd ) ) ;
213+ TextField tbPath = Assert . IsType < TextField > ( tbPathField . GetValue ( fd ) ) ;
212214 tbPath . Text = "/testdir/" ;
213215 tbPath . MoveEnd ( ) ;
214216
@@ -251,6 +253,52 @@ public void FileDialog_MixedMode_PathSetBeforeEndInit_RespectsAllowedTypes ()
251253 Assert . Equal ( [ "allowed.Designer.cs" , "subdir" ] , visibleEntries ) ;
252254 }
253255
256+ [ Fact ]
257+ public void FileDialog_MixedMode_SkipsUnreadableEntry_AndKeepsReadableEntries ( )
258+ {
259+ IFileSystem fileSystem = CreateFileSystemWithDirectory ( out IDirectoryInfo directory ) ;
260+ IFileInfo goodFile = CreateFile ( "/testdir/good.txt" , "good.txt" ) ;
261+ IDirectoryInfo goodDirectory = CreateDirectory ( "/testdir/good-dir" , "good-dir" , directory ) ;
262+ IFileInfo badFile = CreateUnreadableFile ( "/testdir/bad.txt" , "bad.txt" ) ;
263+
264+ Mock . Get ( directory )
265+ . Setup ( d => d . GetFileSystemInfos ( ) )
266+ . Returns ( [ goodFile , badFile , goodDirectory ] ) ;
267+
268+ using FileDialog fd = new TestableFileDialog ( fileSystem ) ;
269+ fd . OpenMode = OpenMode . Mixed ;
270+
271+ fd . Path = "/testdir" ;
272+
273+ Assert . NotNull ( fd . State ) ;
274+ Assert . Contains ( fd . State ! . Children , c => c . Name == "good.txt" ) ;
275+ Assert . Contains ( fd . State . Children , c => c . Name == "good-dir" ) ;
276+ Assert . Contains ( fd . State . Children , c => c . IsParent && c . Name == ".." ) ;
277+ Assert . DoesNotContain ( fd . State . Children , c => c . Name == "bad.txt" ) ;
278+ }
279+
280+ [ Fact ]
281+ public void FileDialog_DirectoryMode_SkipsUnreadableDirectory_AndKeepsParentNavigation ( )
282+ {
283+ IFileSystem fileSystem = CreateFileSystemWithDirectory ( out IDirectoryInfo directory ) ;
284+ IDirectoryInfo goodDirectory = CreateDirectory ( "/testdir/good-dir" , "good-dir" , directory ) ;
285+ IDirectoryInfo badDirectory = CreateUnreadableDirectory ( "/testdir/bad-dir" , "bad-dir" , directory ) ;
286+
287+ Mock . Get ( directory )
288+ . Setup ( d => d . GetDirectories ( ) )
289+ . Returns ( [ goodDirectory , badDirectory ] ) ;
290+
291+ using FileDialog fd = new TestableFileDialog ( fileSystem ) ;
292+ fd . OpenMode = OpenMode . Directory ;
293+
294+ fd . Path = "/testdir" ;
295+
296+ Assert . NotNull ( fd . State ) ;
297+ Assert . Contains ( fd . State ! . Children , c => c . Name == "good-dir" ) ;
298+ Assert . Contains ( fd . State . Children , c => c . IsParent && c . Name == ".." ) ;
299+ Assert . DoesNotContain ( fd . State . Children , c => c . Name == "bad-dir" ) ;
300+ }
301+
254302 [ Fact ]
255303 public void FileDialog_Accepting_Directory_From_Table_Keeps_Path_On_Opened_Directory ( )
256304 {
@@ -310,10 +358,76 @@ private static int FindRowByName (TableView tableView, string name)
310358 return - 1 ;
311359 }
312360
361+ private static IFileSystem CreateFileSystemWithDirectory ( out IDirectoryInfo directory )
362+ {
363+ Mock < IFileSystem > fileSystem = new ( ) ;
364+ Mock < IDirectoryInfoFactory > directoryInfoFactory = new ( ) ;
365+ Mock < IDirectoryInfo > parent = CreateDirectoryMock ( "/" , string . Empty , null ) ;
366+ Mock < IDirectoryInfo > dir = CreateDirectoryMock ( "/testdir" , "testdir" , parent . Object ) ;
367+
368+ directoryInfoFactory . Setup ( f => f . New ( "/testdir" ) ) . Returns ( dir . Object ) ;
369+ fileSystem . SetupGet ( f => f . DirectoryInfo ) . Returns ( directoryInfoFactory . Object ) ;
370+
371+ directory = dir . Object ;
372+
373+ return fileSystem . Object ;
374+ }
375+
376+ private static IFileInfo CreateFile ( string fullName , string name )
377+ {
378+ Mock < IFileInfo > file = new ( ) ;
379+ file . SetupGet ( f => f . Exists ) . Returns ( true ) ;
380+ file . SetupGet ( f => f . FullName ) . Returns ( fullName ) ;
381+ file . SetupGet ( f => f . Name ) . Returns ( name ) ;
382+ file . SetupGet ( f => f . Extension ) . Returns ( System . IO . Path . GetExtension ( name ) ) ;
383+ file . SetupGet ( f => f . Length ) . Returns ( 12 ) ;
384+ file . SetupGet ( f => f . LastWriteTime ) . Returns ( new DateTime ( 2026 , 1 , 1 ) ) ;
385+
386+ return file . Object ;
387+ }
388+
389+ private static IFileInfo CreateUnreadableFile ( string fullName , string name )
390+ {
391+ Mock < IFileInfo > file = Mock . Get ( CreateFile ( fullName , name ) ) ;
392+ file . SetupGet ( f => f . LastWriteTime ) . Throws ( new IOException ( "Transport endpoint is not connected" ) ) ;
393+
394+ return file . Object ;
395+ }
396+
397+ private static IDirectoryInfo CreateDirectory ( string fullName , string name , IDirectoryInfo ? parent )
398+ {
399+ return CreateDirectoryMock ( fullName , name , parent ) . Object ;
400+ }
401+
402+ private static IDirectoryInfo CreateUnreadableDirectory ( string fullName , string name , IDirectoryInfo ? parent )
403+ {
404+ Mock < IDirectoryInfo > directory = CreateDirectoryMock ( fullName , name , parent ) ;
405+ directory . SetupGet ( d => d . LastWriteTime ) . Throws ( new UnauthorizedAccessException ( ) ) ;
406+
407+ return directory . Object ;
408+ }
409+
410+ private static Mock < IDirectoryInfo > CreateDirectoryMock ( string fullName , string name , IDirectoryInfo ? parent )
411+ {
412+ Mock < IDirectoryInfo > directory = new ( ) ;
413+ directory . SetupGet ( d => d . Exists ) . Returns ( true ) ;
414+ directory . SetupGet ( d => d . FullName ) . Returns ( fullName ) ;
415+ directory . SetupGet ( d => d . Name ) . Returns ( name ) ;
416+ directory . SetupGet ( d => d . Extension ) . Returns ( string . Empty ) ;
417+ directory . SetupGet ( d => d . Parent ) . Returns ( parent ) ;
418+ directory . SetupGet ( d => d . LastWriteTime ) . Returns ( new DateTime ( 2026 , 1 , 1 ) ) ;
419+ directory . Setup ( d => d . GetFileSystemInfos ( ) ) . Returns ( [ ] ) ;
420+ directory . Setup ( d => d . GetDirectories ( ) ) . Returns ( [ ] ) ;
421+
422+ return directory ;
423+ }
424+
313425 /// <summary>Testable subclass that exposes the internal file-system constructor.</summary>
314426 private sealed class TestableFileDialog : FileDialog
315427 {
316428 public TestableFileDialog ( MockFileSystem fs ) : base ( fs ) { }
429+
430+ public TestableFileDialog ( IFileSystem fileSystem ) : base ( fileSystem ) { }
317431 }
318432
319433 /// <summary>Testable subclass for OpenDialog.</summary>
0 commit comments