@@ -11,6 +11,8 @@ declare global {
1111__filePickerHarness ?: {
1212filesystem : HarnessFilesystem ;
1313reload : ( ) => void ;
14+ lastSelectedPath : string | null ;
15+ lastDoubleClickedPath : string | null ;
1416} ;
1517}
1618}
@@ -120,6 +122,18 @@ const fileExists = (page: Page, path: string) =>
120122
121123const isDir = ( page : Page , path : string ) => callFilesystem ( page , 'isDir' , path ) ;
122124
125+ const getLastSelectedPath = ( page : Page ) : Promise < string | null > => {
126+ return page . evaluate (
127+ ( ) => window . __filePickerHarness ?. lastSelectedPath ?? null
128+ ) ;
129+ } ;
130+
131+ const getLastDoubleClickedPath = ( page : Page ) : Promise < string | null > => {
132+ return page . evaluate (
133+ ( ) => window . __filePickerHarness ?. lastDoubleClickedPath ?? null
134+ ) ;
135+ } ;
136+
123137test . beforeEach ( async ( { page } ) => {
124138page . on ( 'pageerror' , ( error ) => {
125139console . error ( 'pageerror' , error ) ;
@@ -215,14 +229,17 @@ test('arrow up moves focus to the previous visible node', async ({ page }) => {
215229await expectFocused ( page , 'wordpress' ) ;
216230} ) ;
217231
218- test . skip ( 'type-ahead search focuses the first matching node' , async ( {
219- page,
220- } ) => {
232+ test ( 'type-ahead search focuses the first matching node' , async ( { page } ) => {
221233await collapseNode ( page , 'wordpress' ) ;
222234await expandNode ( page , 'wordpress' ) ;
235+ await expandNode ( page , 'wordpress/workspace' ) ;
223236const root = nodeButton ( page , 'wordpress' ) ;
224237await root . focus ( ) ;
225- await root . press ( 'notes' ) ;
238+ await page . keyboard . press ( 'n' ) ;
239+ await page . keyboard . press ( 'o' ) ;
240+ await page . keyboard . press ( 't' ) ;
241+ await page . keyboard . press ( 'e' ) ;
242+ await page . keyboard . press ( 's' ) ;
226243await expectFocused ( page , 'wordpress/workspace/notes.txt' ) ;
227244} ) ;
228245
@@ -405,3 +422,166 @@ test.skip('invalid rename on a new file removes the placeholder entry', async ({
405422fileExists ( page , '/wordpress/workspace/new-file (1).php' )
406423) . resolves . toBe ( false ) ;
407424} ) ;
425+
426+ test ( 'newly created files appear at top of files list' , async ( { page } ) => {
427+ await expandToPath ( page , 'wordpress/workspace' ) ;
428+ await nodeButton ( page , 'wordpress/workspace' ) . click ( {
429+ button : 'right' ,
430+ } ) ;
431+ await page . getByRole ( 'menuitem' , { name : 'Create file' } ) . click ( ) ;
432+
433+ // Wait for the rename input to appear - new files are named 'untitled.php' by default
434+ const pendingPath = 'wordpress/workspace/untitled.php' ;
435+ const input = renameInput ( page , pendingPath ) ;
436+ await expect ( input ) . toBeVisible ( ) ;
437+
438+ // Verify it's shown in edit mode (rename input visible and focused)
439+ await expect ( input ) . toBeFocused ( ) ;
440+
441+ // The file element should be present (as a form while renaming, not a button)
442+ const fileNode = nodeLocator ( page , pendingPath ) ;
443+ await expect ( fileNode ) . toBeVisible ( ) ;
444+
445+ // Complete the rename to verify the file persists
446+ await input . press ( 'Enter' ) ;
447+
448+ // Now it should be a button after renaming is complete
449+ const untitledButton = nodeButton ( page , pendingPath ) ;
450+ await expect ( untitledButton ) . toBeVisible ( ) ;
451+ } ) ;
452+
453+ test ( 'context menu auto-focuses first item' , async ( { page } ) => {
454+ await nodeButton ( page , 'wordpress' ) . click ( { button : 'right' } ) ;
455+ await expect ( page . getByRole ( 'menu' ) ) . toBeVisible ( ) ;
456+
457+ // The first menu item should be focused
458+ const firstMenuItem = page . getByRole ( 'menuitem' , { name : 'Create file' } ) ;
459+ await expect ( firstMenuItem ) . toBeFocused ( ) ;
460+ } ) ;
461+
462+ test ( 'single click on file triggers onSelect but not onDoubleClickFile' , async ( {
463+ page,
464+ } ) => {
465+ await expandToPath ( page , 'wordpress/workspace' ) ;
466+ const file = nodeButton ( page , 'wordpress/workspace/index.php' ) ;
467+
468+ // Single click the file
469+ await file . click ( ) ;
470+
471+ // Wait a bit to ensure single-click timeout completes
472+ await page . waitForTimeout ( 350 ) ;
473+
474+ // onSelect should have been called
475+ const selected = await getLastSelectedPath ( page ) ;
476+ expect ( selected ) . toBe ( '/wordpress/workspace/index.php' ) ;
477+
478+ // onDoubleClickFile should NOT have been called
479+ const doubleClicked = await getLastDoubleClickedPath ( page ) ;
480+ expect ( doubleClicked ) . toBeNull ( ) ;
481+ } ) ;
482+
483+ test ( 'double click on file triggers onDoubleClickFile' , async ( { page } ) => {
484+ await expandToPath ( page , 'wordpress/workspace' ) ;
485+ const file = nodeButton ( page , 'wordpress/workspace/index.php' ) ;
486+
487+ // Double click the file
488+ await file . dblclick ( ) ;
489+
490+ // Wait for double-click handler
491+ await page . waitForTimeout ( 100 ) ;
492+
493+ // onDoubleClickFile should have been called
494+ const doubleClicked = await getLastDoubleClickedPath ( page ) ;
495+ expect ( doubleClicked ) . toBe ( '/wordpress/workspace/index.php' ) ;
496+ } ) ;
497+
498+ test ( 'pressing Enter on file triggers onDoubleClickFile' , async ( { page } ) => {
499+ await expandToPath ( page , 'wordpress/workspace' ) ;
500+ const file = nodeButton ( page , 'wordpress/workspace/index.php' ) ;
501+
502+ // Focus and press Enter
503+ await file . focus ( ) ;
504+ await file . press ( 'Enter' ) ;
505+
506+ // Wait for Enter handler
507+ await page . waitForTimeout ( 100 ) ;
508+
509+ // onDoubleClickFile should have been called
510+ const doubleClicked = await getLastDoubleClickedPath ( page ) ;
511+ expect ( doubleClicked ) . toBe ( '/wordpress/workspace/index.php' ) ;
512+ } ) ;
513+
514+ test ( 'pressing Enter on folder toggles expansion without triggering doubleClick' , async ( {
515+ page,
516+ } ) => {
517+ await collapseNode ( page , 'wordpress' ) ;
518+ await expandNode ( page , 'wordpress' ) ;
519+ await collapseNode ( page , 'wordpress/workspace' ) ;
520+
521+ const folder = nodeButton ( page , 'wordpress/workspace' ) ;
522+ await folder . focus ( ) ;
523+
524+ // Press Enter to expand
525+ await folder . press ( 'Enter' ) ;
526+ await expect ( folder ) . toHaveAttribute ( 'data-expanded' , 'true' ) ;
527+
528+ // onDoubleClickFile should NOT have been called (it's a folder)
529+ const doubleClicked = await getLastDoubleClickedPath ( page ) ;
530+ expect ( doubleClicked ) . toBeNull ( ) ;
531+ } ) ;
532+
533+ test ( 'rename input is not affected by type-ahead search' , async ( { page } ) => {
534+ // First, create a folder with name "123" that could trigger type-ahead
535+ await expandToPath ( page , 'wordpress/workspace' ) ;
536+ await nodeButton ( page , 'wordpress/workspace' ) . click ( { button : 'right' } ) ;
537+ await page . getByRole ( 'menuitem' , { name : 'Create directory' } ) . click ( ) ;
538+
539+ // Find the rename input dynamically (don't hardcode the path as it may be "New Folder (1)" etc)
540+ // Wait for any visible focused input field in the tree
541+ const folderInput = page . locator ( 'input[class*="renameInput"]' ) . first ( ) ;
542+ await expect ( folderInput ) . toBeVisible ( ) ;
543+ await expect ( folderInput ) . toBeFocused ( ) ;
544+
545+ // Rename the new folder to "123"
546+ await folderInput . fill ( '123' ) ;
547+ await folderInput . press ( 'Enter' ) ;
548+
549+ // Wait for the folder to appear with the new name
550+ await expect ( nodeButton ( page , 'wordpress/workspace/123' ) ) . toBeVisible ( ) ;
551+
552+ // Now try to rename a file and type "1" which matches the folder name
553+ await nodeButton ( page , 'wordpress/workspace/index.php' ) . click ( {
554+ button : 'right' ,
555+ } ) ;
556+ await page . getByRole ( 'menuitem' , { name : 'Rename' } ) . click ( ) ;
557+
558+ const fileInput = renameInput ( page , 'wordpress/workspace/index.php' ) ;
559+ await expect ( fileInput ) . toBeVisible ( ) ;
560+ await expect ( fileInput ) . toBeFocused ( ) ;
561+
562+ // Clear the input and type "1" which would normally trigger type-ahead to folder "123"
563+ await fileInput . fill ( '' ) ;
564+ await page . keyboard . press ( '1' ) ;
565+
566+ // The rename input should still be visible and focused (not closed)
567+ await expect ( fileInput ) . toBeVisible ( ) ;
568+ await expect ( fileInput ) . toBeFocused ( ) ;
569+
570+ // The input should contain "1"
571+ await expect ( fileInput ) . toHaveValue ( '1' ) ;
572+
573+ // The folder "123" should NOT be focused (type-ahead should be disabled during rename)
574+ await expect ( nodeButton ( page , 'wordpress/workspace/123' ) ) . not . toBeFocused ( ) ;
575+
576+ // Complete the rename with a valid filename
577+ await fileInput . fill ( '1test.php' ) ;
578+ await fileInput . press ( 'Enter' ) ;
579+
580+ // Verify the file was renamed successfully
581+ await expect (
582+ nodeButton ( page , 'wordpress/workspace/1test.php' )
583+ ) . toBeVisible ( ) ;
584+ await expect (
585+ nodeLocator ( page , 'wordpress/workspace/index.php' )
586+ ) . toHaveCount ( 0 ) ;
587+ } ) ;
0 commit comments