33
44using System ;
55using System . Collections . Generic ;
6+ using System . IO ;
67using System . Linq ;
78using System . Threading . Tasks ;
89using Microsoft . AspNetCore . Razor ;
1213using Microsoft . CodeAnalysis . ExternalAccess . Razor ;
1314using Microsoft . CodeAnalysis . Razor . CodeActions . Models ;
1415using Microsoft . CodeAnalysis . Razor . Protocol . CodeActions ;
16+ using Microsoft . CodeAnalysis . Razor . Workspaces ;
17+ using Microsoft . CodeAnalysis . Remote . Razor ;
1518using Microsoft . CodeAnalysis . Text ;
1619using Microsoft . VisualStudio . LanguageServer . Protocol ;
1720using Microsoft . VisualStudio . Razor . Settings ;
@@ -408,6 +411,27 @@ @using System.Text
408411 await VerifyCodeActionAsync ( input , expected , RazorPredefinedCodeFixProviderNames . AddImport ) ;
409412 }
410413
414+ [ Fact ]
415+ public async Task AddUsing_Typo ( )
416+ {
417+ var input = """
418+ @code
419+ {
420+ private [||]Stringbuilder _x = new Stringbuilder();
421+ }
422+ """ ;
423+
424+ var expected = """
425+ @using System.Text
426+ @code
427+ {
428+ private StringBuilder _x = new Stringbuilder();
429+ }
430+ """ ;
431+
432+ await VerifyCodeActionAsync ( input , expected , RazorPredefinedCodeFixProviderNames . AddImport ) ;
433+ }
434+
411435 [ Fact ]
412436 public async Task AddUsing_WithExisting ( )
413437 {
@@ -527,6 +551,114 @@ private Task DoesNotExist(MouseEventArgs e)
527551 await VerifyCodeActionAsync ( input , expected , WorkspacesSR . FormatGenerate_Async_Event_Handler_Title ( "DoesNotExist" ) ) ;
528552 }
529553
554+ [ Fact ]
555+ public async Task CreateComponentFromTag ( )
556+ {
557+ await VerifyCodeActionAsync (
558+ input : """
559+ <div></div>
560+
561+ <He[||]llo></Hello>
562+ """ ,
563+ expected : """
564+ <div></div>
565+
566+ <Hello><Hello>
567+ """ ,
568+ codeActionName : WorkspacesSR . Create_Component_FromTag_Title ,
569+ additionalExpectedFiles : [
570+ ( FileUri ( "Hello.razor" ) , "" ) ] ) ;
571+ }
572+
573+ [ Fact ]
574+ public async Task CreateComponentFromTag_Attribute ( )
575+ {
576+ await VerifyCodeActionAsync (
577+ input : """
578+ <div></div>
579+
580+ <Hello wor[||]ld="true"></Hello>
581+ """ ,
582+ expected : """
583+ <div></div>
584+
585+ <Hello><Hello>
586+ """ ,
587+ codeActionName : WorkspacesSR . Create_Component_FromTag_Title ,
588+ additionalExpectedFiles : [
589+ ( FileUri ( "Hello.razor" ) , "" ) ] ) ;
590+ }
591+
592+ [ Fact ]
593+ public async Task ComponentAccessibility_FixCasing ( )
594+ {
595+ await VerifyCodeActionAsync (
596+ input : """
597+ <div></div>
598+
599+ <Edit[||]form></Editform>
600+ """ ,
601+ expected : """
602+ <div></div>
603+
604+ <EditForm></EditForm>
605+ """ ,
606+ codeActionName : "EditForm" ) ;
607+ }
608+
609+ [ Fact ]
610+ public async Task ComponentAccessibility_FullyQualify ( )
611+ {
612+ await VerifyCodeActionAsync (
613+ input : """
614+ <div></div>
615+
616+ <Section[||]Outlet></SectionOutlet>
617+ """ ,
618+ expected : """
619+ <div></div>
620+
621+ <Microsoft.AspNetCore.Components.Sections.SectionOutlet></Microsoft.AspNetCore.Components.Sections.SectionOutlet>
622+ """ ,
623+ codeActionName : "Microsoft.AspNetCore.Components.Sections.SectionOutlet" ) ;
624+ }
625+
626+ [ Fact ]
627+ public async Task ComponentAccessibility_AddUsing ( )
628+ {
629+ await VerifyCodeActionAsync (
630+ input : """
631+ <div></div>
632+
633+ <Section[||]Outlet></SectionOutlet>
634+ """ ,
635+ expected : """
636+ @using Microsoft.AspNetCore.Components.Sections
637+ <div></div>
638+
639+ <SectionOutlet></SectionOutlet>
640+ """ ,
641+ codeActionName : "@using Microsoft.AspNetCore.Components.Sections" ) ;
642+ }
643+
644+ [ Fact ]
645+ public async Task ComponentAccessibility_AddUsing_FixTypo ( )
646+ {
647+ await VerifyCodeActionAsync (
648+ input : """
649+ <div></div>
650+
651+ <Section[||]outlet></Sectionoutlet>
652+ """ ,
653+ expected : """
654+ @using Microsoft.AspNetCore.Components.Sections
655+ <div></div>
656+
657+ <SectionOutlet></SectionOutlet>
658+ """ ,
659+ codeActionName : "SectionOutlet - @using Microsoft.AspNetCore.Components.Sections" ) ;
660+ }
661+
530662 [ Fact ]
531663 public async Task ExtractToCodeBehind ( )
532664 {
@@ -588,6 +720,9 @@ Hello World
588720
589721 private async Task VerifyCodeActionAsync ( TestCode input , string ? expected , string codeActionName , int childActionIndex = 0 , string ? fileKind = null , ( Uri fileUri , string contents ) [ ] ? additionalExpectedFiles = null )
590722 {
723+ var fileSystem = ( RemoteFileSystem ) OOPExportProvider . GetExportedValue < IFileSystem > ( ) ;
724+ fileSystem . GetTestAccessor ( ) . SetFileSystem ( new TestFileSystem ( additionalExpectedFiles ) ) ;
725+
591726 UpdateClientLSPInitializationOptions ( options =>
592727 {
593728 options . ClientCapabilities . TextDocument = new ( )
@@ -611,7 +746,11 @@ private async Task VerifyCodeActionAsync(TestCode input, string? expected, strin
611746 return ;
612747 }
613748
614- await VerifyCodeActionResolveAsync ( document , codeAction , expected , additionalExpectedFiles ) ;
749+ var workspaceEdit = codeAction . Data is null
750+ ? codeAction . Edit . AssumeNotNull ( )
751+ : await ResolveCodeActionAsync ( document , codeAction ) ;
752+
753+ await VerifyCodeActionResultAsync ( document , workspaceEdit , expected , additionalExpectedFiles ) ;
615754 }
616755
617756 private async Task < CodeAction ? > VerifyCodeActionRequestAsync ( CodeAnalysis . TextDocument document , TestCode input , string codeActionName , int childActionIndex )
@@ -673,36 +812,74 @@ private async Task VerifyCodeActionAsync(TestCode input, string? expected, strin
673812 return codeActionToRun ;
674813 }
675814
676- private async Task VerifyCodeActionResolveAsync ( CodeAnalysis . TextDocument document , CodeAction codeAction , string ? expected , ( Uri fileUri , string contents ) [ ] ? additionalExpectedFiles = null )
815+ private async Task VerifyCodeActionResultAsync ( CodeAnalysis . TextDocument document , WorkspaceEdit workspaceEdit , string ? expected , ( Uri fileUri , string contents ) [ ] ? additionalExpectedFiles = null )
816+ {
817+ var validated = false ;
818+ if ( workspaceEdit . TryGetTextDocumentEdits ( out var documentEdits ) )
819+ {
820+ var documentUri = document . CreateUri ( ) ;
821+ var sourceText = await document . GetTextAsync ( DisposalToken ) . ConfigureAwait ( false ) ;
822+
823+ foreach ( var edit in documentEdits )
824+ {
825+ if ( edit . TextDocument . Uri == documentUri )
826+ {
827+ sourceText = sourceText . WithChanges ( edit . Edits . Select ( sourceText . GetTextChange ) ) ;
828+ }
829+ else
830+ {
831+ var contents = Assert . Single ( additionalExpectedFiles . AssumeNotNull ( ) , f => f . fileUri == edit . TextDocument . Uri ) . contents ;
832+ AssertEx . EqualOrDiff ( contents , Assert . Single ( edit . Edits ) . NewText ) ;
833+ }
834+ }
835+
836+ validated = true ;
837+ AssertEx . EqualOrDiff ( expected , sourceText . ToString ( ) ) ;
838+ }
839+
840+ if ( workspaceEdit . DocumentChanges ? . Value is SumType < TextDocumentEdit , CreateFile , RenameFile , DeleteFile > [ ] sumTypeArray )
841+ {
842+ using var builder = new PooledArrayBuilder < TextDocumentEdit > ( ) ;
843+ foreach ( var sumType in sumTypeArray )
844+ {
845+ if ( sumType . Value is CreateFile createFile )
846+ {
847+ validated = true ;
848+ Assert . Single ( additionalExpectedFiles . AssumeNotNull ( ) , f => f . fileUri == createFile . Uri ) ;
849+ }
850+ }
851+ }
852+
853+ Assert . True ( validated , "Test did not validate anything. Code action response type is presumably not supported." ) ;
854+ }
855+
856+ private async Task < WorkspaceEdit > ResolveCodeActionAsync ( CodeAnalysis . TextDocument document , CodeAction codeAction )
677857 {
678858 var requestInvoker = new TestLSPRequestInvoker ( ) ;
679859 var clientSettingsManager = new ClientSettingsManager ( changeTriggers : [ ] ) ;
680-
681860 var endpoint = new CohostCodeActionsResolveEndpoint ( RemoteServiceInvoker , ClientCapabilitiesService , clientSettingsManager , TestHtmlDocumentSynchronizer . Instance , requestInvoker ) ;
682861
683862 var result = await endpoint . GetTestAccessor ( ) . HandleRequestAsync ( document , codeAction , DisposalToken ) ;
684863
685864 Assert . NotNull ( result ? . Edit ) ;
865+ return result . Edit ;
866+ }
686867
687- var workspaceEdit = result . Edit ;
688- Assert . True ( workspaceEdit . TryGetTextDocumentEdits ( out var documentEdits ) ) ;
689-
690- var documentUri = document . CreateUri ( ) ;
691- var sourceText = await document . GetTextAsync ( DisposalToken ) . ConfigureAwait ( false ) ;
868+ private class TestFileSystem ( ( Uri fileUri , string contents ) [ ] ? files ) : IFileSystem
869+ {
870+ public bool FileExists ( string filePath )
871+ {
872+ return false ;
873+ }
692874
693- foreach ( var edit in documentEdits )
875+ public IEnumerable < string > GetDirectories ( string workspaceDirectory )
694876 {
695- if ( edit . TextDocument . Uri == documentUri )
696- {
697- sourceText = sourceText . WithChanges ( edit . Edits . Select ( sourceText . GetTextChange ) ) ;
698- }
699- else
700- {
701- var contents = Assert . Single ( additionalExpectedFiles . AssumeNotNull ( ) , f => f . fileUri == edit . TextDocument . Uri ) . contents ;
702- AssertEx . EqualOrDiff ( contents , Assert . Single ( edit . Edits ) . NewText ) ;
703- }
877+ throw new NotImplementedException ( ) ;
704878 }
705879
706- AssertEx . EqualOrDiff ( expected , sourceText . ToString ( ) ) ;
880+ public IEnumerable < string > GetFiles ( string workspaceDirectory , string searchPattern , SearchOption searchOption )
881+ {
882+ throw new NotImplementedException ( ) ;
883+ }
707884 }
708885}
0 commit comments