9
9
using System . Linq ;
10
10
using System . Threading ;
11
11
using System . Threading . Tasks ;
12
+ using Castle . Core . Logging ;
12
13
using Microsoft . AspNetCore . Razor . Language ;
13
14
using Microsoft . AspNetCore . Razor . Language . Components ;
14
15
using Microsoft . AspNetCore . Razor . LanguageServer . CodeActions . Models ;
@@ -38,6 +39,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
38
39
public class CodeActionEndToEndTest ( ITestOutputHelper testOutput ) : SingleServerDelegatingEndpointTestBase ( testOutput )
39
40
{
40
41
private const string GenerateEventHandlerTitle = "Generate Event Handler 'DoesNotExist'" ;
42
+ private const string ExtractToComponentTitle = "Extract element to new component" ;
41
43
private const string GenerateAsyncEventHandlerTitle = "Generate Async Event Handler 'DoesNotExist'" ;
42
44
private const string GenerateEventHandlerReturnType = "void" ;
43
45
private const string GenerateAsyncEventHandlerReturnType = "global::System.Threading.Tasks.Task" ;
@@ -59,6 +61,17 @@ private GenerateMethodCodeActionResolver[] CreateRazorCodeActionResolvers(
59
61
razorFormattingService )
60
62
] ;
61
63
64
+ // TODO: Make this func
65
+ private ExtractToComponentCodeActionResolver [ ] CreateExtractComponentCodeActionResolvers ( string filePath , RazorCodeDocument codeDocument )
66
+ {
67
+ var emptyDocumentContextFactory = new TestDocumentContextFactory ( ) ;
68
+ return [
69
+ new ExtractToComponentCodeActionResolver (
70
+ new GenerateMethodResolverDocumentContextFactory ( filePath , codeDocument ) ,
71
+ TestLanguageServerFeatureOptions . Instance )
72
+ ] ;
73
+ }
74
+
62
75
#region CSharp CodeAction Tests
63
76
64
77
[ Fact ]
@@ -1005,6 +1018,36 @@ await ValidateCodeActionAsync(input,
1005
1018
diagnostics : [ new Diagnostic ( ) { Code = "CS0103" , Message = "The name 'DoesNotExist' does not exist in the current context" } ] ) ;
1006
1019
}
1007
1020
1021
+ [ Fact ]
1022
+ public async Task Handle_ExtractComponent ( )
1023
+ {
1024
+ var input = """
1025
+ <[||]div id="a">
1026
+ <h1>Div a title</h1>
1027
+ <Book Title="To Kill a Mockingbird" Author="Harper Lee" Year="Long ago" />
1028
+ <p>Div a par</p>
1029
+ </div>
1030
+ <div id="shouldSkip">
1031
+ <Movie Title="Aftersun" Director="Charlotte Wells" Year="2022" />
1032
+ </div>
1033
+ """ ;
1034
+
1035
+ var expectedRazorComponent = """
1036
+ <div id="a">
1037
+ <h1>Div a title</h1>
1038
+ <Book Title="To Kill a Mockingbird" Author="Harper Lee" Year="Long ago" />
1039
+ <p>Div a par</p>
1040
+ </div>
1041
+ """ ;
1042
+
1043
+ await ValidateExtractComponentCodeActionAsync (
1044
+ input ,
1045
+ expectedRazorComponent ,
1046
+ ExtractToComponentTitle ,
1047
+ razorCodeActionProviders : [ new ExtractToComponentCodeActionProvider ( LoggerFactory ) ] ,
1048
+ codeActionResolversCreator : CreateExtractComponentCodeActionResolvers ) ;
1049
+ }
1050
+
1008
1051
#endregion
1009
1052
1010
1053
private async Task ValidateCodeBehindFileAsync (
@@ -1148,6 +1191,66 @@ private async Task ValidateCodeActionAsync(
1148
1191
AssertEx . EqualOrDiff ( expected , actual ) ;
1149
1192
}
1150
1193
1194
+ private async Task ValidateExtractComponentCodeActionAsync (
1195
+ string input ,
1196
+ string ? expected ,
1197
+ string codeAction ,
1198
+ int childActionIndex = 0 ,
1199
+ IRazorCodeActionProvider [ ] ? razorCodeActionProviders = null ,
1200
+ Func < string , RazorCodeDocument , IRazorCodeActionResolver [ ] > ? codeActionResolversCreator = null ,
1201
+ RazorLSPOptionsMonitor ? optionsMonitor = null ,
1202
+ Diagnostic [ ] ? diagnostics = null )
1203
+ {
1204
+ TestFileMarkupParser . GetSpan ( input , out input , out var textSpan ) ;
1205
+
1206
+ var razorFilePath = "C:/path/test.razor" ;
1207
+ var componentFilePath = "C:/path/Component.razor" ;
1208
+ var codeDocument = CreateCodeDocument ( input , filePath : razorFilePath ) ;
1209
+ var sourceText = codeDocument . GetSourceText ( ) ;
1210
+ var uri = new Uri ( razorFilePath ) ;
1211
+ var languageServer = await CreateLanguageServerAsync ( codeDocument , razorFilePath ) ;
1212
+ var documentContext = CreateDocumentContext ( uri , codeDocument ) ;
1213
+ var requestContext = new RazorRequestContext ( documentContext , null ! , "lsp/method" , uri : null ) ;
1214
+
1215
+ var result = await GetCodeActionsAsync (
1216
+ uri ,
1217
+ textSpan ,
1218
+ sourceText ,
1219
+ requestContext ,
1220
+ languageServer ,
1221
+ razorCodeActionProviders ,
1222
+ diagnostics ) ;
1223
+
1224
+ Assert . NotEmpty ( result ) ;
1225
+ var codeActionToRun = GetCodeActionToRun ( codeAction , childActionIndex , result ) ;
1226
+
1227
+ if ( expected is null )
1228
+ {
1229
+ Assert . Null ( codeActionToRun ) ;
1230
+ return ;
1231
+ }
1232
+
1233
+ Assert . NotNull ( codeActionToRun ) ;
1234
+
1235
+ var formattingService = await TestRazorFormattingService . CreateWithFullSupportAsync ( LoggerFactory , codeDocument , documentContext . Snapshot , optionsMonitor ? . CurrentValue ) ;
1236
+ var changes = await GetEditsAsync (
1237
+ codeActionToRun ,
1238
+ requestContext ,
1239
+ languageServer ,
1240
+ codeActionResolversCreator ? . Invoke ( razorFilePath , codeDocument ) ?? [ ] ) ;
1241
+
1242
+ var edits = new List < TextChange > ( ) ;
1243
+
1244
+ // Only get changes made in the new component file
1245
+ foreach ( var change in changes . Where ( e => e . TextDocument . Uri . AbsolutePath == componentFilePath ) )
1246
+ {
1247
+ edits . AddRange ( change . Edits . Select ( e => e . ToTextChange ( sourceText ) ) ) ;
1248
+ }
1249
+
1250
+ var actual = sourceText . WithChanges ( edits ) . ToString ( ) ;
1251
+ AssertEx . EqualOrDiff ( expected , actual ) ;
1252
+ }
1253
+
1151
1254
private static VSInternalCodeAction ? GetCodeActionToRun ( string codeAction , int childActionIndex , SumType < Command , CodeAction > [ ] result )
1152
1255
{
1153
1256
var codeActionToRun = ( VSInternalCodeAction ? ) result . SingleOrDefault ( e => ( ( RazorVSInternalCodeAction ) e . Value ! ) . Name == codeAction || ( ( RazorVSInternalCodeAction ) e . Value ! ) . Title == codeAction ) . Value ;
@@ -1306,4 +1409,78 @@ static IEnumerable<TagHelperDescriptor> BuildTagHelpers()
1306
1409
}
1307
1410
}
1308
1411
}
1412
+
1413
+ private class ExtractToComponentResolverDocumentContextFactory : TestDocumentContextFactory
1414
+ {
1415
+ private readonly List < TagHelperDescriptor > _tagHelperDescriptors ;
1416
+
1417
+ public ExtractToComponentResolverDocumentContextFactory
1418
+ ( string filePath ,
1419
+ RazorCodeDocument codeDocument ,
1420
+ TagHelperDescriptor [ ] ? tagHelpers = null ,
1421
+ int ? version = null )
1422
+ : base ( filePath , codeDocument , version )
1423
+ {
1424
+ _tagHelperDescriptors = CreateTagHelperDescriptors ( ) ;
1425
+ if ( tagHelpers is not null )
1426
+ {
1427
+ _tagHelperDescriptors . AddRange ( tagHelpers ) ;
1428
+ }
1429
+ }
1430
+
1431
+ public override bool TryCreate (
1432
+ Uri documentUri ,
1433
+ VSProjectContext ? projectContext ,
1434
+ bool versioned ,
1435
+ [ NotNullWhen ( true ) ] out DocumentContext ? context )
1436
+ {
1437
+ if ( FilePath is null || CodeDocument is null )
1438
+ {
1439
+ context = null ;
1440
+ return false ;
1441
+ }
1442
+
1443
+ var projectWorkspaceState = ProjectWorkspaceState . Create ( _tagHelperDescriptors . ToImmutableArray ( ) ) ;
1444
+ var testDocumentSnapshot = TestDocumentSnapshot . Create ( FilePath , CodeDocument . GetSourceText ( ) . ToString ( ) , CodeAnalysis . VersionStamp . Default , projectWorkspaceState ) ;
1445
+ testDocumentSnapshot . With ( CodeDocument ) ;
1446
+
1447
+ context = CreateDocumentContext ( new Uri ( FilePath ) , testDocumentSnapshot ) ;
1448
+ return true ;
1449
+ }
1450
+
1451
+ private static List < TagHelperDescriptor > CreateTagHelperDescriptors ( )
1452
+ {
1453
+ return BuildTagHelpers ( ) . ToList ( ) ;
1454
+
1455
+ static IEnumerable < TagHelperDescriptor > BuildTagHelpers ( )
1456
+ {
1457
+ var builder = TagHelperDescriptorBuilder . Create ( "oncontextmenu" , "Microsoft.AspNetCore.Components" ) ;
1458
+ builder . SetMetadata (
1459
+ new KeyValuePair < string , string > ( ComponentMetadata . EventHandler . EventArgsType , "Microsoft.AspNetCore.Components.Web.MouseEventArgs" ) ,
1460
+ new KeyValuePair < string , string > ( ComponentMetadata . SpecialKindKey , ComponentMetadata . EventHandler . TagHelperKind ) ) ;
1461
+ yield return builder . Build ( ) ;
1462
+
1463
+ builder = TagHelperDescriptorBuilder . Create ( "onclick" , "Microsoft.AspNetCore.Components" ) ;
1464
+ builder . SetMetadata (
1465
+ new KeyValuePair < string , string > ( ComponentMetadata . EventHandler . EventArgsType , "Microsoft.AspNetCore.Components.Web.MouseEventArgs" ) ,
1466
+ new KeyValuePair < string , string > ( ComponentMetadata . SpecialKindKey , ComponentMetadata . EventHandler . TagHelperKind ) ) ;
1467
+
1468
+ yield return builder . Build ( ) ;
1469
+
1470
+ builder = TagHelperDescriptorBuilder . Create ( "oncopy" , "Microsoft.AspNetCore.Components" ) ;
1471
+ builder . SetMetadata (
1472
+ new KeyValuePair < string , string > ( ComponentMetadata . EventHandler . EventArgsType , "Microsoft.AspNetCore.Components.Web.ClipboardEventArgs" ) ,
1473
+ new KeyValuePair < string , string > ( ComponentMetadata . SpecialKindKey , ComponentMetadata . EventHandler . TagHelperKind ) ) ;
1474
+
1475
+ yield return builder . Build ( ) ;
1476
+
1477
+ builder = TagHelperDescriptorBuilder . Create ( "ref" , "Microsoft.AspNetCore.Components" ) ;
1478
+ builder . SetMetadata (
1479
+ new KeyValuePair < string , string > ( ComponentMetadata . SpecialKindKey , ComponentMetadata . Ref . TagHelperKind ) ,
1480
+ new KeyValuePair < string , string > ( ComponentMetadata . Common . DirectiveAttribute , bool . TrueString ) ) ;
1481
+
1482
+ yield return builder . Build ( ) ;
1483
+ }
1484
+ }
1485
+ }
1309
1486
}
0 commit comments