1111 FileBrowserFileInfo ,
1212 ListDirectoryArgs ,
1313 ListDirectoryResponse ,
14+ _normalize_selection_mode ,
1415 file_browser ,
1516)
1617from marimo ._utils .paths import normalize_path
@@ -21,7 +22,7 @@ def test_file_browser_init(tmp_path: Path) -> None:
2122 fb = file_browser (initial_path = tmp_path )
2223 assert isinstance (fb ._initial_path , Path )
2324 assert str (fb ._initial_path ) == str (normalize_path (tmp_path ))
24- assert fb ._selection_mode == "file"
25+ assert fb ._selection_mode == frozenset ({ "file" })
2526 assert fb ._filetypes == set ()
2627 assert fb ._restrict_navigation is False
2728
@@ -35,7 +36,7 @@ def test_file_browser_init(tmp_path: Path) -> None:
3536 )
3637 assert fb ._initial_path == normalize_path (tmp_path )
3738 assert fb ._filetypes == set (custom_filetypes )
38- assert fb ._selection_mode == "directory"
39+ assert fb ._selection_mode == frozenset ({ "directory" })
3940 assert fb ._restrict_navigation is True
4041
4142
@@ -352,8 +353,8 @@ def resolve(self) -> CustomPathWithClient:
352353
353354def test_validation () -> None :
354355 with pytest .raises (ValueError ) as e :
355- file_browser (initial_path = "invalid" , selection_mode = "invalid" )
356- assert "Value must be one of " in str (e .value )
356+ file_browser (initial_path = "invalid" , selection_mode = "invalid" ) # type: ignore[arg-type]
357+ assert "Invalid selection_mode " in str (e .value )
357358
358359
359360def test_limit_arg (tmp_path : Path ) -> None :
@@ -1090,3 +1091,144 @@ def test_file_browser_relative_path_sent_to_frontend_as_absolute(
10901091
10911092 finally :
10921093 os .chdir (original_cwd )
1094+
1095+
1096+ class TestNormalizeSelectionMode :
1097+ def test_string_file (self ) -> None :
1098+ assert _normalize_selection_mode ("file" ) == frozenset ({"file" })
1099+
1100+ def test_string_directory (self ) -> None :
1101+ assert _normalize_selection_mode ("directory" ) == frozenset (
1102+ {"directory" }
1103+ )
1104+
1105+ def test_string_all (self ) -> None :
1106+ assert _normalize_selection_mode ("all" ) == frozenset (
1107+ {"file" , "directory" }
1108+ )
1109+
1110+ def test_list_single_file (self ) -> None :
1111+ assert _normalize_selection_mode (["file" ]) == frozenset ({"file" })
1112+
1113+ def test_list_single_directory (self ) -> None :
1114+ assert _normalize_selection_mode (["directory" ]) == frozenset (
1115+ {"directory" }
1116+ )
1117+
1118+ def test_list_both (self ) -> None :
1119+ assert _normalize_selection_mode (["file" , "directory" ]) == frozenset (
1120+ {"file" , "directory" }
1121+ )
1122+
1123+ def test_list_order_independent (self ) -> None :
1124+ assert _normalize_selection_mode (["directory" , "file" ]) == frozenset (
1125+ {"file" , "directory" }
1126+ )
1127+
1128+ def test_list_dedup (self ) -> None :
1129+ assert _normalize_selection_mode (["file" , "file" ]) == frozenset (
1130+ {"file" }
1131+ )
1132+
1133+ def test_tuple_accepted (self ) -> None :
1134+ assert _normalize_selection_mode (("file" , "directory" )) == frozenset (
1135+ {"file" , "directory" }
1136+ )
1137+
1138+ @pytest .mark .parametrize (
1139+ "value" ,
1140+ [
1141+ "both" ,
1142+ "folder" ,
1143+ "" ,
1144+ "FILE" ,
1145+ [],
1146+ ["both" ],
1147+ ["file" , "nope" ],
1148+ ["file" , 1 ],
1149+ None ,
1150+ 123 ,
1151+ ],
1152+ )
1153+ def test_rejects_invalid (self , value : Any ) -> None :
1154+ with pytest .raises (ValueError ):
1155+ _normalize_selection_mode (value )
1156+
1157+
1158+ class TestSelectionModeAll :
1159+ def test_init_all_string (self , tmp_path : Path ) -> None :
1160+ fb = file_browser (initial_path = tmp_path , selection_mode = "all" )
1161+ assert fb ._selection_mode == frozenset ({"file" , "directory" })
1162+
1163+ def test_init_list_form (self , tmp_path : Path ) -> None :
1164+ fb = file_browser (
1165+ initial_path = tmp_path , selection_mode = ["file" , "directory" ]
1166+ )
1167+ assert fb ._selection_mode == frozenset ({"file" , "directory" })
1168+
1169+ def test_init_list_single (self , tmp_path : Path ) -> None :
1170+ fb = file_browser (initial_path = tmp_path , selection_mode = ["directory" ])
1171+ assert fb ._selection_mode == frozenset ({"directory" })
1172+
1173+ def test_init_rejects_both (self , tmp_path : Path ) -> None :
1174+ with pytest .raises (ValueError ):
1175+ file_browser (initial_path = tmp_path , selection_mode = "both" ) # type: ignore[arg-type]
1176+
1177+ def test_init_rejects_empty_list (self , tmp_path : Path ) -> None :
1178+ with pytest .raises (ValueError ):
1179+ file_browser (initial_path = tmp_path , selection_mode = [])
1180+
1181+ def test_wire_format_all (self , tmp_path : Path ) -> None :
1182+ fb = file_browser (initial_path = tmp_path , selection_mode = "all" )
1183+ assert fb ._component_args ["selection-mode" ] == "all"
1184+
1185+ def test_wire_format_file (self , tmp_path : Path ) -> None :
1186+ fb = file_browser (initial_path = tmp_path , selection_mode = "file" )
1187+ assert fb ._component_args ["selection-mode" ] == "file"
1188+
1189+ def test_wire_format_directory (self , tmp_path : Path ) -> None :
1190+ fb = file_browser (initial_path = tmp_path , selection_mode = "directory" )
1191+ assert fb ._component_args ["selection-mode" ] == "directory"
1192+
1193+ def test_wire_format_list_normalized_to_all (self , tmp_path : Path ) -> None :
1194+ fb = file_browser (
1195+ initial_path = tmp_path ,
1196+ selection_mode = ["directory" , "file" ],
1197+ )
1198+ assert fb ._component_args ["selection-mode" ] == "all"
1199+
1200+ def test_list_directory_all_returns_files_and_dirs (
1201+ self , tmp_path : Path
1202+ ) -> None :
1203+ (tmp_path / "sub" ).mkdir ()
1204+ (tmp_path / "a.txt" ).touch ()
1205+ (tmp_path / "b.parquet" ).touch ()
1206+ fb = file_browser (initial_path = tmp_path , selection_mode = "all" )
1207+ response = fb ._list_directory (ListDirectoryArgs (path = str (tmp_path )))
1208+ names = {f ["name" ] for f in response .files }
1209+ assert names == {"sub" , "a.txt" , "b.parquet" }
1210+
1211+ def test_list_directory_all_respects_filetypes_for_files (
1212+ self , tmp_path : Path
1213+ ) -> None :
1214+ (tmp_path / "sub" ).mkdir ()
1215+ (tmp_path / "a.txt" ).touch ()
1216+ (tmp_path / "b.parquet" ).touch ()
1217+ fb = file_browser (
1218+ initial_path = tmp_path ,
1219+ selection_mode = "all" ,
1220+ filetypes = [".parquet" ],
1221+ )
1222+ response = fb ._list_directory (ListDirectoryArgs (path = str (tmp_path )))
1223+ names = {f ["name" ] for f in response .files }
1224+ assert names == {"sub" , "b.parquet" }
1225+
1226+ def test_list_directory_directory_only_unchanged (
1227+ self , tmp_path : Path
1228+ ) -> None :
1229+ (tmp_path / "sub" ).mkdir ()
1230+ (tmp_path / "a.txt" ).touch ()
1231+ fb = file_browser (initial_path = tmp_path , selection_mode = "directory" )
1232+ response = fb ._list_directory (ListDirectoryArgs (path = str (tmp_path )))
1233+ names = {f ["name" ] for f in response .files }
1234+ assert names == {"sub" }
0 commit comments