microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/update-release-process

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Tests/Microsoft.Teams.Cards.Tests/AdaptiveCardsTest.cs

618lines · modecode

1using System.Text.Json;
2
3using Microsoft.Teams.Common;
4
5namespace Microsoft.Teams.Cards.Tests;
6
7public class AdaptiveCardsTest
8{
9 [Fact]
10 public void Should_Serialize_AdaptiveCard_Simple()
11 {
12 // arrange
13 AdaptiveCard card = new AdaptiveCard(new List<CardElement>
14 {
15 new TextBlock("Hello, Adaptive Card!")
16 });
17
18 // act
19 var json = JsonSerializer.Serialize(card);
20
21 // assert
22 using var doc = JsonDocument.Parse(json);
23 var root = doc.RootElement;
24
25 Assert.True(root.TryGetProperty("body", out var bodyElement));
26 Assert.Equal(JsonValueKind.Array, bodyElement.ValueKind);
27 Assert.Equal(1, bodyElement.GetArrayLength());
28
29 var first = bodyElement[0];
30 Assert.Equal("TextBlock", first.GetProperty("type").GetString());
31 Assert.Equal("Hello, Adaptive Card!", first.GetProperty("text").GetString());
32 }
33
34 [Fact]
35 public void Should_Deserialize_AdaptiveCard_Simple()
36 {
37 string json = @"{
38 ""body"": [
39 {
40 ""type"": ""TextBlock"",
41 ""text"": ""Hello, Adaptive Card!""
42 }
43 ]
44 }";
45
46 var card = JsonSerializer.Deserialize<AdaptiveCard>(json);
47
48 Assert.NotNull(card);
49 Assert.Single(card.Body!);
50 Assert.IsType<TextBlock>(card.Body![0]);
51 Assert.Equal("Hello, Adaptive Card!", ((TextBlock)card.Body[0]).Text);
52 }
53
54 [Fact]
55 public void Should_Serialize_BasicCard_WithToggleInput()
56 {
57 // arrange - recreating CreateBasicAdaptiveCard from samples
58 var card = new AdaptiveCard(new List<CardElement>
59 {
60 new TextBlock("Hello world")
61 {
62 Wrap = true,
63 Weight = TextWeight.Bolder
64 },
65 new ToggleInput("Notify me")
66 {
67 Id = "notify"
68 }
69 })
70 {
71 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
72 Actions = new List<Microsoft.Teams.Cards.Action>
73 {
74 new ExecuteAction
75 {
76 Title = "Submit",
77 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "submit_basic" } } }),
78 AssociatedInputs = AssociatedInputs.Auto
79 }
80 }
81 };
82
83 // act
84 var json = JsonSerializer.Serialize(card);
85
86 // assert
87 using var doc = JsonDocument.Parse(json);
88 var root = doc.RootElement;
89
90 Assert.True(root.TryGetProperty("$schema", out var schemaElement));
91 Assert.Equal("http://adaptivecards.io/schemas/adaptive-card.json", schemaElement.GetString());
92
93 Assert.True(root.TryGetProperty("body", out var bodyElement));
94 Assert.Equal(2, bodyElement.GetArrayLength());
95
96 var textBlock = bodyElement[0];
97 Assert.Equal("TextBlock", textBlock.GetProperty("type").GetString());
98 Assert.Equal("Hello world", textBlock.GetProperty("text").GetString());
99 Assert.True(textBlock.GetProperty("weight").GetString() == "Bolder");
100
101 var toggleInput = bodyElement[1];
102 Assert.Equal("Input.Toggle", toggleInput.GetProperty("type").GetString());
103 Assert.Equal("notify", toggleInput.GetProperty("id").GetString());
104
105 Assert.True(root.TryGetProperty("actions", out var actionsElement));
106 Assert.Single(actionsElement.EnumerateArray());
107 }
108
109 [Fact]
110 public void Should_Deserialize_ProfileCard_WithInputs()
111 {
112 string json = @"{
113 ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
114 ""body"": [
115 {
116 ""type"": ""TextBlock"",
117 ""text"": ""User Profile"",
118 ""weight"": ""Bolder"",
119 ""size"": ""Large""
120 },
121 {
122 ""type"": ""Input.Text"",
123 ""id"": ""name"",
124 ""label"": ""Name"",
125 ""value"": ""John Doe""
126 },
127 {
128 ""type"": ""Input.Text"",
129 ""id"": ""email"",
130 ""label"": ""Email"",
131 ""value"": ""john@contoso.com""
132 },
133 {
134 ""type"": ""Input.Toggle"",
135 ""id"": ""subscribe"",
136 ""title"": ""Subscribe to newsletter"",
137 ""value"": ""false""
138 }
139 ],
140 ""actions"": [
141 {
142 ""type"": ""Action.Execute"",
143 ""title"": ""Save"",
144 ""data"": {
145 ""action"": ""save_profile"",
146 ""entity_id"": ""12345""
147 }
148 }
149 ]
150 }";
151
152 var card = JsonSerializer.Deserialize<AdaptiveCard>(json);
153
154 Assert.NotNull(card);
155 // Note: Schema might be serialized as $schema in JSON but not always set on deserialized object
156 Assert.Equal(4, card.Body!.Count);
157
158 var titleBlock = card.Body[0] as TextBlock;
159 Assert.NotNull(titleBlock);
160 Assert.Equal("User Profile", titleBlock.Text);
161 Assert.Equal("Bolder", titleBlock.Weight?.ToString());
162
163 var nameInput = card.Body[1] as TextInput;
164 Assert.NotNull(nameInput);
165 Assert.Equal("name", nameInput.Id);
166 Assert.Equal("Name", nameInput.Label);
167 Assert.Equal("John Doe", nameInput.Value);
168
169 var emailInput = card.Body[2] as TextInput;
170 Assert.NotNull(emailInput);
171 Assert.Equal("email", emailInput.Id);
172 Assert.Equal("Email", emailInput.Label);
173
174 var toggleInput = card.Body[3] as ToggleInput;
175 Assert.NotNull(toggleInput);
176 Assert.Equal("subscribe", toggleInput.Id);
177 Assert.Equal("Subscribe to newsletter", toggleInput.Title);
178
179 Assert.Single(card.Actions!);
180 var executeAction = card!.Actions[0]! as ExecuteAction;
181 Assert.NotNull(executeAction);
182 Assert.Equal("Save", executeAction.Title);
183 }
184
185 [Fact]
186 public void Should_Serialize_TaskFormCard_WithChoiceSet()
187 {
188 // arrange - recreating CreateTaskFormCard from samples
189 var card = new AdaptiveCard(new List<CardElement>
190 {
191 new TextBlock("Create New Task")
192 {
193 Weight = TextWeight.Bolder,
194 Size = TextSize.Large
195 },
196 new TextInput
197 {
198 Id = "title",
199 Label = "Task Title",
200 Placeholder = "Enter task title"
201 },
202 new ChoiceSetInput(new List<Choice>
203 {
204 new() { Title = "High", Value = "high" },
205 new() { Title = "Medium", Value = "medium" },
206 new() { Title = "Low", Value = "low" }
207 })
208 {
209 Id = "priority",
210 Label = "Priority",
211 Value = "medium"
212 },
213 new DateInput
214 {
215 Id = "due_date",
216 Label = "Due Date",
217 Value = "2024-01-15"
218 }
219 })
220 {
221 Schema = "http://adaptivecards.io/schemas/adaptive-card.json"
222 };
223
224 // act
225 var json = JsonSerializer.Serialize(card);
226
227 // assert
228 using var doc = JsonDocument.Parse(json);
229 var root = doc.RootElement;
230
231 Assert.True(root.TryGetProperty("body", out var bodyElement));
232 Assert.Equal(4, bodyElement.GetArrayLength());
233
234 var choiceSetInput = bodyElement[2];
235 Assert.Equal("Input.ChoiceSet", choiceSetInput.GetProperty("type").GetString());
236 Assert.Equal("priority", choiceSetInput.GetProperty("id").GetString());
237 Assert.Equal("medium", choiceSetInput.GetProperty("value").GetString());
238
239 Assert.True(choiceSetInput.TryGetProperty("choices", out var choicesElement));
240 Assert.Equal(3, choicesElement.GetArrayLength());
241 Assert.Equal("High", choicesElement[0].GetProperty("title").GetString());
242 Assert.Equal("high", choicesElement[0].GetProperty("value").GetString());
243
244 var dateInput = bodyElement[3];
245 Assert.Equal("Input.Date", dateInput.GetProperty("type").GetString());
246 Assert.Equal("due_date", dateInput.GetProperty("id").GetString());
247 }
248
249 [Fact]
250 public void Should_Deserialize_ComplexCard_FromJson()
251 {
252 // Using the JSON structure from CreateCardFromJson in samples
253 string json = @"{
254 ""type"": ""AdaptiveCard"",
255 ""body"": [
256 {
257 ""type"": ""ColumnSet"",
258 ""columns"": [
259 {
260 ""type"": ""Column"",
261 ""verticalContentAlignment"": ""center"",
262 ""items"": [
263 {
264 ""type"": ""Image"",
265 ""style"": ""Person"",
266 ""url"": ""https://aka.ms/AAp9xo4"",
267 ""size"": ""Small"",
268 ""altText"": ""Portrait of David Claux""
269 }
270 ],
271 ""width"": ""auto""
272 },
273 {
274 ""type"": ""Column"",
275 ""spacing"": ""medium"",
276 ""verticalContentAlignment"": ""center"",
277 ""items"": [
278 {
279 ""type"": ""TextBlock"",
280 ""weight"": ""Bolder"",
281 ""text"": ""David Claux"",
282 ""wrap"": true
283 }
284 ],
285 ""width"": ""auto""
286 }
287 ]
288 },
289 {
290 ""type"": ""TextBlock"",
291 ""text"": ""This card was created from JSON deserialization!"",
292 ""wrap"": true,
293 ""color"": ""good"",
294 ""spacing"": ""medium""
295 }
296 ],
297 ""actions"": [
298 {
299 ""type"": ""Action.Execute"",
300 ""title"": ""Test JSON Action"",
301 ""data"": {
302 ""Value"": {
303 ""action"": ""test_json""
304 }
305 }
306 }
307 ],
308 ""version"": ""1.5"",
309 ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json""
310 }";
311
312 var card = JsonSerializer.Deserialize<AdaptiveCard>(json)!;
313
314 Assert.NotNull(card);
315 Assert.Equal("1.5", card.Version);
316 // Note: Schema property might not be set during deserialization, focus on content verification
317 Assert.Equal(2, card.Body!.Count);
318
319 var columnSet = card.Body[0] as ColumnSet;
320 Assert.NotNull(columnSet);
321 Assert.Equal(2, columnSet.Columns!.Count);
322
323 var firstColumn = columnSet.Columns[0];
324 Assert.Equal("auto", firstColumn!.Width.ToString());
325 Assert.Single(firstColumn.Items!);
326
327 var image = firstColumn.Items[0] as Image;
328 Assert.NotNull(image);
329 Assert.Equal("https://aka.ms/AAp9xo4", image.Url);
330 Assert.Equal("Person", image.Style?.ToString());
331
332 var textBlock = card.Body[1] as TextBlock;
333 Assert.NotNull(textBlock);
334 Assert.Equal("This card was created from JSON deserialization!", textBlock.Text);
335 Assert.Equal("good", textBlock.Color?.ToString());
336
337 Assert.Single(card.Actions!);
338 var executeAction = card.Actions[0] as ExecuteAction;
339 Assert.NotNull(executeAction);
340 Assert.Equal("Test JSON Action", executeAction.Title);
341 }
342
343 [Fact]
344 public void Should_Serialize_FeedbackCard_WithMultilineInput()
345 {
346 // arrange - recreating CreateFeedbackCard from samples
347 var card = new AdaptiveCard(new List<CardElement>
348 {
349 new TextBlock("Feedback Form")
350 {
351 Weight = TextWeight.Bolder,
352 Size = TextSize.Large
353 },
354 new TextInput
355 {
356 Id = "feedback",
357 Label = "Your Feedback",
358 Placeholder = "Please share your thoughts...",
359 IsMultiline = true,
360 IsRequired = true
361 }
362 })
363 {
364 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
365 Actions = new List<Microsoft.Teams.Cards.Action>
366 {
367 new ExecuteAction
368 {
369 Title = "Submit Feedback",
370 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "submit_feedback" } } }),
371 AssociatedInputs = AssociatedInputs.Auto
372 }
373 }
374 };
375
376 // act
377 var json = JsonSerializer.Serialize(card);
378
379 // assert
380 using var doc = JsonDocument.Parse(json);
381 var root = doc.RootElement;
382
383 Assert.True(root.TryGetProperty("body", out var bodyElement));
384 Assert.Equal(2, bodyElement.GetArrayLength());
385
386 var textInput = bodyElement[1];
387 Assert.Equal("Input.Text", textInput.GetProperty("type").GetString());
388 Assert.Equal("feedback", textInput.GetProperty("id").GetString());
389 Assert.Equal("Your Feedback", textInput.GetProperty("label").GetString());
390 Assert.True(textInput.GetProperty("isMultiline").GetBoolean());
391 Assert.True(textInput.GetProperty("isRequired").GetBoolean());
392
393 Assert.True(root.TryGetProperty("actions", out var actionsElement));
394 var action = actionsElement[0];
395 Assert.Equal("Action.Execute", action.GetProperty("type").GetString());
396 Assert.Equal("Submit Feedback", action.GetProperty("title").GetString());
397 }
398
399 [Fact]
400 public void Should_Deserialize_ValidationCard_WithNumberInput()
401 {
402 string json = @"{
403 ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
404 ""body"": [
405 {
406 ""type"": ""TextBlock"",
407 ""text"": ""Profile with Validation"",
408 ""weight"": ""Bolder"",
409 ""size"": ""Large""
410 },
411 {
412 ""type"": ""Input.Number"",
413 ""id"": ""age"",
414 ""label"": ""Age"",
415 ""isRequired"": true,
416 ""min"": 0,
417 ""max"": 120
418 },
419 {
420 ""type"": ""Input.Text"",
421 ""id"": ""name"",
422 ""label"": ""Name"",
423 ""isRequired"": true,
424 ""errorMessage"": ""Name is required""
425 }
426 ]
427 }";
428
429 var card = JsonSerializer.Deserialize<AdaptiveCard>(json);
430
431 Assert.NotNull(card);
432 Assert.Equal(3, card.Body!.Count);
433
434 var numberInput = card.Body[1] as NumberInput;
435 Assert.NotNull(numberInput);
436 Assert.Equal("age", numberInput.Id);
437 Assert.Equal("Age", numberInput.Label);
438 Assert.True(numberInput.IsRequired);
439 Assert.Equal(0, numberInput.Min);
440 Assert.Equal(120, numberInput.Max);
441
442 var textInput = card.Body[2] as TextInput;
443 Assert.NotNull(textInput);
444 Assert.Equal("name", textInput.Id);
445 Assert.True(textInput.IsRequired);
446 Assert.Equal("Name is required", textInput.ErrorMessage);
447 }
448
449 [Fact]
450 public void Should_Deserialize()
451 {
452 // Test what minimal JsonSerializerOptions are actually required
453 string json = """
454 {
455 "type": "AdaptiveCard",
456 "body": [
457 {
458 "type": "TextBlock",
459 "text": "Hello World",
460 "weight": "Bolder"
461 }
462 ],
463 "actions": [
464 {
465 "type": "Action.Execute",
466 "title": "Submit",
467 "associatedInputs": "auto"
468 }
469 ]
470 }
471 """;
472
473 // Test 1: No options at all
474 var card1 = JsonSerializer.Deserialize<AdaptiveCard>(json);
475 Assert.NotNull(card1);
476 Assert.Single(card1.Body!);
477
478 // Test 2: Only PropertyNameCaseInsensitive
479 var options2 = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
480 var card2 = JsonSerializer.Deserialize<AdaptiveCard>(json, options2);
481 Assert.NotNull(card2);
482 Assert.Single(card2.Body!);
483
484 // Test 3: With CamelCase policy (what we had in docs)
485 var options3 = new JsonSerializerOptions
486 {
487 PropertyNameCaseInsensitive = true,
488 PropertyNamingPolicy = JsonNamingPolicy.CamelCase
489 };
490 var card3 = JsonSerializer.Deserialize<AdaptiveCard>(json, options3);
491 Assert.NotNull(card3);
492 Assert.Single(card3.Body!);
493
494 // All should work the same
495 var textBlock1 = card1.Body![0] as TextBlock;
496 var textBlock2 = card2.Body![0] as TextBlock;
497 var textBlock3 = card3.Body![0] as TextBlock;
498
499 Assert.Equal("Hello World", textBlock1?.Text);
500 Assert.Equal("Hello World", textBlock2?.Text);
501 Assert.Equal("Hello World", textBlock3?.Text);
502 }
503
504 [Fact]
505 public void Should_Not_Serialize_Null_MsTeams_Property_On_SubmitAction()
506 {
507 // arrange
508 var card = new AdaptiveCard(new List<CardElement>
509 {
510 new TextBlock("Test card with Submit action")
511 })
512 {
513 Actions = new List<Microsoft.Teams.Cards.Action>
514 {
515 new SubmitAction
516 {
517 Title = "Submit",
518 Data = new Union<string, SubmitActionData>("test_data")
519 }
520 }
521 };
522
523 // act
524 var json = JsonSerializer.Serialize(card);
525
526 // assert
527 Assert.DoesNotContain("\"msTeams\":null", json);
528 Assert.DoesNotContain("\"msTeams\": null", json);
529
530 // Verify the action is still properly serialized
531 using var doc = JsonDocument.Parse(json);
532 var root = doc.RootElement;
533
534 Assert.True(root.TryGetProperty("actions", out var actionsElement));
535 var action = actionsElement[0];
536 Assert.Equal("Action.Submit", action.GetProperty("type").GetString());
537 Assert.Equal("Submit", action.GetProperty("title").GetString());
538
539 // Verify msTeams property is completely absent, not just null
540 Assert.False(action.TryGetProperty("msTeams", out _));
541 }
542
543 [Fact]
544 public void Should_Serialize_Actions()
545 {
546 var actionJson = """
547 {
548 "type": "Action.OpenUrl",
549 "url": "https://adaptivecards.microsoft.com",
550 "title": "Learn More"
551 }
552 """;
553 var action = OpenUrlAction.Deserialize(actionJson);
554 Assert.NotNull(action);
555 Assert.IsType<OpenUrlAction>(action);
556 Assert.Equal("Learn More", action.Title);
557 Assert.Equal("https://adaptivecards.microsoft.com", action.Url);
558 }
559
560 [Fact]
561 public void SubmitData_Should_Set_Action_Field()
562 {
563 var data = new SubmitData("save_profile");
564
565 var json = JsonSerializer.Serialize(data);
566 using var doc = JsonDocument.Parse(json);
567 var root = doc.RootElement;
568
569 Assert.True(root.TryGetProperty("action", out var actionElement));
570 Assert.Equal("save_profile", actionElement.GetString());
571 }
572
573 [Fact]
574 public void SubmitData_Should_Include_Extra_Data()
575 {
576 var data = new SubmitData("save_profile", new Dictionary<string, object?> { ["entity_id"] = "12345" });
577
578 var json = JsonSerializer.Serialize(data);
579 using var doc = JsonDocument.Parse(json);
580 var root = doc.RootElement;
581
582 Assert.True(root.TryGetProperty("action", out var actionElement));
583 Assert.Equal("save_profile", actionElement.GetString());
584 Assert.True(root.TryGetProperty("entity_id", out var entityElement));
585 Assert.Equal("12345", entityElement.GetString());
586 }
587
588 [Fact]
589 public void OpenDialogData_Should_Set_Msteams_And_DialogId()
590 {
591 var data = new OpenDialogData("simple_form");
592
593 var json = JsonSerializer.Serialize(data);
594 using var doc = JsonDocument.Parse(json);
595 var root = doc.RootElement;
596
597 Assert.True(root.TryGetProperty("msteams", out var msteamsElement));
598 Assert.Equal("task/fetch", msteamsElement.GetProperty("type").GetString());
599 Assert.True(root.TryGetProperty("dialog_id", out var dialogIdElement));
600 Assert.Equal("simple_form", dialogIdElement.GetString());
601 }
602
603 [Fact]
604 public void OpenDialogData_Should_Include_Extra_Data()
605 {
606 var data = new OpenDialogData("simple_form", new Dictionary<string, object?> { ["custom_key"] = "value" });
607
608 var json = JsonSerializer.Serialize(data);
609 using var doc = JsonDocument.Parse(json);
610 var root = doc.RootElement;
611
612 Assert.True(root.TryGetProperty("msteams", out _));
613 Assert.True(root.TryGetProperty("dialog_id", out var dialogIdElement));
614 Assert.Equal("simple_form", dialogIdElement.GetString());
615 Assert.True(root.TryGetProperty("custom_key", out var customElement));
616 Assert.Equal("value", customElement.GetString());
617 }
618}