Skip to content

Commit 09801bd

Browse files
authored
Merge pull request #47 from kaloa2025/feature/uiFixForTests
Enhancement : Added UI enhancements and supporting model for Test Sec
2 parents ddc4840 + 472e4b1 commit 09801bd

File tree

4 files changed

+182
-54
lines changed

4 files changed

+182
-54
lines changed

Ellabit/Pages/Challenge.razor

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,98 @@
6565
</MudTabPanel>
6666
<MudTabPanel Text="Tests">
6767

68-
<MudButton Disabled="runningTests" Variant="Variant.Filled" Color="Color.Primary" @onclick="OnExecuteTests">Execute Tests</MudButton>
69-
@if (runningTests)
70-
{
71-
<div class="spinner"></div>
72-
}
73-
<p>
74-
@((MarkupString)(testResults ?? ""))
75-
</p>
76-
@if (!fail && !runningTests)
77-
{
78-
<MudButton Variant="Variant.Filled" Color="Color.Primary" @onclick="OnNextChallenge">Next Challenge</MudButton>
79-
}
68+
<MudPaper Class="pa-4 mb-4" Elevation="1">
69+
<MudButton Disabled="runningTests"
70+
Variant="Variant.Filled"
71+
Color="Color.Primary"
72+
@onclick="OnExecuteTests">
73+
Execute Tests
74+
</MudButton>
75+
</MudPaper>
76+
77+
78+
<MudPaper Class="pa-4" Elevation="1">
79+
80+
@if (runningTests)
81+
{
82+
<div class="spinner"></div>
83+
<div class="d-flex flex-column align-center pa-6">
84+
Running tests
85+
86+
</div>
87+
}
88+
else if (resultList?.Any() == true)
89+
{
90+
@foreach (var t in resultList)
91+
{
92+
<MudPaper Class="pa-3 mb-4 test-card" Elevation="2">
93+
94+
<!-- HEADER -->
95+
<div class="d-flex justify-space-between align-center mb-1">
96+
<MudText Typo="Typo.h5">Test @t.Number</MudText>
97+
98+
@if (t.Passed)
99+
{
100+
<MudChip Color="Color.Success" Variant="Variant.Filled">
101+
PASS
102+
</MudChip>
103+
}
104+
else
105+
{
106+
<MudChip Color="Color.Error" Variant="Variant.Filled">
107+
FAIL
108+
</MudChip>
109+
}
110+
</div>
111+
<!-- OUTPUT SECTION -->
112+
<div class="mt-3">
113+
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Output</MudText>
114+
115+
<div class="d-flex mt-2">
116+
<div class="mr-4">
117+
<MudText><b>Expected</b></MudText>
118+
<MudPaper Class="pa-2" Elevation="0">
119+
@t.ExpectedValue
120+
</MudPaper>
121+
</div>
122+
123+
<div>
124+
<MudText><b>Actual</b></MudText>
125+
<MudPaper Class="pa-2" Elevation="0">
126+
@t.ActualValue
127+
</MudPaper>
128+
</div>
129+
</div>
130+
</div>
131+
132+
@if (!string.IsNullOrWhiteSpace(t.Message))
133+
{
134+
<MudText Class="mt-3" Color="Color.Error">@t.Message</MudText>
135+
}
136+
137+
</MudPaper>
138+
}
139+
140+
@if (!fail)
141+
{
142+
<div class="mt-4">
143+
<MudButton Variant="Variant.Filled" Color="Color.Success" @onclick="OnNextChallenge">
144+
Next Challenge
145+
</MudButton>
146+
</div>
147+
}
148+
}
149+
else
150+
{
151+
<div class="d-flex flex-column align-center pa-5">
152+
No test results yet. Run the tests to see output.
153+
</div>
154+
}
155+
156+
</MudPaper>
157+
80158
</MudTabPanel>
159+
81160
</MudTabs>
82161

83162

Ellabit/Pages/Challenge.razor.cs

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Ellabit.Challenges;
44
using Ellabit.DynamicCode;
55
using Ellabit.Monaco;
6+
using Ellabit.Pages.Models;
67
using IronBlock.Blocks;
78
using Microsoft.AspNetCore.Components;
89
using Microsoft.CodeAnalysis;
@@ -43,7 +44,7 @@ public partial class Challenge : ComponentBase, IDisposable
4344
bool _isDarkMode = false;
4445
int prevTabIndex = -1;
4546
string? code = string.Empty;
46-
string testResults = "";
47+
List<TestDisplayResult> resultList = new();
4748
bool fail = true;
4849
bool runningTests = false;
4950
protected override void OnInitialized()
@@ -122,13 +123,13 @@ public async void OnBlocklyToCSharp()
122123
SyntaxNode syntax = CSharpSyntaxTree.ParseText(_unloadable.Context.Challenge.Code ?? "").GetRoot();
123124
MethodDeclarationSyntax method = (from node in syntax.DescendantNodes()
124125
.OfType<MethodDeclarationSyntax>()
125-
select node).First();
126+
select node).First();
126127
if (method.Body != null)
127128
{
128129
var methodBody = (SyntaxNode)method.Body;
129130
//Replace
130131
var output = syntax.ReplaceNode(methodBody, blockMethodBlock);
131-
132+
132133
output = output.SyntaxTree.GetRoot().NormalizeWhitespace();
133134
code = output.ToFullString();
134135
}
@@ -146,7 +147,7 @@ public static string Evaluate(string xml)
146147

147148
var blockMethodBody = (from method in blockSyntax.DescendantNodes()
148149
.OfType<LocalFunctionStatementSyntax>()
149-
select method.Body).FirstOrDefault();
150+
select method.Body).FirstOrDefault();
150151

151152
return blockMethodBody?.ToFullString() ?? "";
152153

@@ -159,7 +160,7 @@ public void SetChallenge()
159160
{
160161
ChallengeId = 0;
161162
}
162-
if (Challenges.Count < (ChallengeId ?? 0) )
163+
if (Challenges.Count < (ChallengeId ?? 0))
163164
{
164165
if (NavMan == null)
165166
{
@@ -207,7 +208,7 @@ public async Task TabChanged_ClearEditor()
207208
}
208209
if (_editor != null && _unloadable?.Context?.Challenge != null && prevTabIndex == TabIDCode())
209210
{
210-
code = await _editor.GetValue();
211+
code = await _editor.GetValue();
211212
}
212213
}
213214
public int TabIDBlockly()
@@ -242,28 +243,42 @@ public async Task TabChanged_LoadEditor()
242243
public async void OnExecuteTests()
243244
{
244245
ClearChallengeCache();
245-
testResults = "";
246246
StateHasChanged();
247247
await ExecuteTests();
248248
}
249249
private async Task ExecuteTests()
250250
{
251-
testResults = "";
251+
resultList.Clear();
252252
if (!(_unloadable?.Context?.Challenge is IChallenge))
253253
{
254-
testResults += "\nChallenge, missing IChallenge";
254+
resultList.Add(new TestDisplayResult
255+
{
256+
Number = 0,
257+
Passed = false,
258+
Message = "Challenge missing IChallenge interface"
259+
});
255260
return;
256261
}
257262
if (!(_unloadable?.Context?.Challenge is IChallengeTestCode))
258263
{
259-
testResults += "\nInvalid Challenge, missing IChallengeTestCode";
264+
resultList.Add(new TestDisplayResult
265+
{
266+
Number = 0,
267+
Passed = false,
268+
Message = "Invalid Challenge, missing IChallengeTestCode"
269+
});
260270
return;
261271
}
262272
var testCode = _unloadable?.Context?.Challenge as IChallengeTestCode;
263273

264274
if (testCode.Tests == null)
265275
{
266-
testResults += "\nCode didn't compile";
276+
resultList.Add(new TestDisplayResult
277+
{
278+
Number = 0,
279+
Passed = false,
280+
Message = "Code did not compile"
281+
});
267282
return;
268283
}
269284
var origCode = _unloadable?.Context?.Challenge?.Code;
@@ -278,44 +293,35 @@ private async Task ExecuteTests()
278293
{
279294
try
280295
{
281-
var testResult = await _unloadable.Context.RunTest(test);
282-
if (testResult.pass)
283-
{
284-
testResults += $"<br/>Test {testNum} <h6 style='color: green'>Pass</h6>";
285-
}
286-
else
296+
var result = await _unloadable.Context.RunTest(test);
297+
var (actual, expected) = ExtractValues(result.message ?? "");
298+
299+
resultList.Add(new TestDisplayResult
287300
{
288-
testResults += $"<br/>Test {testNum} <h6 style='color: red'>FAILED</h6> " + testResult.message;
289-
fail = true;
290-
}
301+
Number = testNum,
302+
Passed = result.pass,
303+
ExpectedValue = expected,
304+
ActualValue = actual,
305+
});
306+
307+
if (!result.pass) fail = true;
291308
}
292309
catch (Exception ex)
293310
{
294311
fail = true;
295-
if (ex is IOException)
312+
313+
resultList.Add(new TestDisplayResult
296314
{
297-
testResults += $"<br/><h6 style='color: red'>FAILED</h6> " + ex.Message;
298-
return;
299-
}
300-
testResults += $"<br/>Test {testNum} " + "\n <h6 style='color: red'>FAILED</h6> " + ex.Message;
315+
Number = testNum,
316+
Passed = false,
317+
Message = ex.Message
318+
});
319+
320+
if (ex is IOException) return;
301321
}
302-
testNum++;
303-
}
304322

305-
}
306-
catch (Exception ex)
307-
{
308-
if (DialogService != null)
309-
{
310-
await DialogService.ShowMessageBox(
311-
"Error",
312-
ex.Message,
313-
yesText: "OK");
314-
StateHasChanged();
323+
testNum++;
315324
}
316-
317-
testResults += "\nUnexpected Error " + ex.Message;
318-
return;
319325
}
320326
finally
321327
{
@@ -339,7 +345,7 @@ public void OnNextChallenge()
339345
{
340346
NavMan.NavigateTo($"/Ellabit/{ChallengeId}");
341347
} else
342-
{
348+
{
343349
NavMan.NavigateTo($"/{ChallengeId}");
344350
}
345351
}
@@ -375,5 +381,23 @@ public void Dispose()
375381
{
376382
_objRef?.Dispose();
377383
}
384+
private (string actual, string expected) ExtractValues(string message)
385+
{
386+
if (string.IsNullOrWhiteSpace(message))
387+
return ("", "");
388+
389+
var actualMatch = System.Text.RegularExpressions.Regex.Match(
390+
message, @"returned:\s*([^ ]+)",
391+
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
392+
393+
var expectedMatch = System.Text.RegularExpressions.Regex.Match(
394+
message, @"expected:\s*([^ ]+)",
395+
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
396+
397+
var actual = actualMatch.Success ? actualMatch.Groups[1].Value : "";
398+
var expected = expectedMatch.Success ? expectedMatch.Groups[1].Value : "";
399+
400+
return (actual, expected);
401+
}
378402
}
379403
}

Ellabit/Pages/Challenge.razor.css

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,18 @@ tr:hover {
5252
100% {
5353
transform: rotate(360deg)
5454
}
55-
}
55+
}
56+
57+
.test-card {
58+
border-left: 6px solid #3f51b5;
59+
border-radius: 8px;
60+
}
61+
62+
.test-card .pass {
63+
background: #e8f5e9;
64+
}
65+
66+
.test-card .fail {
67+
background: #ffebee;
68+
}
69+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Ellabit.Pages.Models
2+
{
3+
public class TestDisplayResult
4+
{
5+
public int Number { get; set; }
6+
public bool Passed { get; set; }
7+
public string ExpectedValue { get; set; }
8+
public string ActualValue { get; set; }
9+
public string Message { get; set; }
10+
}
11+
}

0 commit comments

Comments
 (0)