Skip to content

Commit

Permalink
Bug #181 (1067 - Gitlab): login bug (#467)
Browse files Browse the repository at this point in the history
Solved problems with login page: now focused components will not show their error message. BindValueMode updated
  • Loading branch information
joao4all authored Oct 10, 2023
1 parent db5f137 commit ded0302
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 29 deletions.
73 changes: 73 additions & 0 deletions COMET.Web.Common.Tests/Components/LoginTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace COMET.Web.Common.Tests.Components
using COMET.Web.Common.ViewModels.Components;

using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;

using Moq;
Expand Down Expand Up @@ -71,6 +72,71 @@ public void Teardown()
this.context.CleanContext();
}

[Test]
public async Task VerifyErrorsShown()
{
var renderer = this.context.RenderComponent<Login>();
var errorsElement = renderer.Find(".validation-errors");
var numberOfRequiredFieldsInFirstLoginTry = renderer.Instance.FieldsFocusedStatus.Count - 1;

Assert.That(errorsElement.InnerHtml, Is.Empty);

await renderer.Find("button").ClickAsync(new MouseEventArgs());
Assert.That(errorsElement.ChildElementCount, Is.EqualTo(numberOfRequiredFieldsInFirstLoginTry));

// Username input field
await renderer.Find("input").FocusAsync(new FocusEventArgs());

Assert.Multiple(() =>
{
Assert.That(renderer.Instance.FieldsFocusedStatus["UserName"], Is.True);
Assert.That(errorsElement.ChildElementCount, Is.EqualTo(numberOfRequiredFieldsInFirstLoginTry - 1));
});

await renderer.Find("input").BlurAsync(new FocusEventArgs());

Assert.Multiple(() =>
{
Assert.That(renderer.Instance.FieldsFocusedStatus["UserName"], Is.False);
Assert.That(errorsElement.ChildElementCount, Is.EqualTo(numberOfRequiredFieldsInFirstLoginTry));
});
}

[Test]
public void VerifyFocusingAndBluring()
{
var renderer = this.context.RenderComponent<Login>();

Assert.That(renderer.Instance.FieldsFocusedStatus, Is.EqualTo(new Dictionary<string, bool>()
{
{ "SourceAddress", false },
{ "UserName", false },
{ "Password", false }
}));

const string fieldToFocusOn = "UserName";
Assert.That(renderer.Instance.FieldsFocusedStatus[fieldToFocusOn], Is.False);
renderer.Instance.HandleFieldFocus(fieldToFocusOn);

Assert.Multiple(()=>
{
foreach (var fieldStatus in renderer.Instance.FieldsFocusedStatus)
{
Assert.That(fieldStatus.Value, fieldStatus.Key == fieldToFocusOn ? Is.True : Is.False);
}
});

renderer.Instance.HandleFieldBlur(fieldToFocusOn);

Assert.Multiple(() =>
{
foreach (var fieldStatus in renderer.Instance.FieldsFocusedStatus)
{
Assert.That(fieldStatus.Value, Is.False);
}
});
}

[Test]
public async Task VerifyPerformLogin()
{
Expand All @@ -80,6 +146,13 @@ public async Task VerifyPerformLogin()
this.authenticationService.Setup(x => x.Login(It.IsAny<AuthenticationDto>()))
.ReturnsAsync(AuthenticationStateKind.ServerFail);

Assert.That(renderer.Instance.FieldsFocusedStatus, Is.EqualTo(new Dictionary<string, bool>()
{
{ "SourceAddress", false },
{ "UserName", false },
{ "Password", false }
}));

await renderer.InvokeAsync(editForm.Instance.OnValidSubmit.InvokeAsync);

Assert.Multiple(() =>
Expand Down
91 changes: 62 additions & 29 deletions COMET.Web.Common/Components/Login.razor
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,68 @@

<EditForm Context="editFormContext" Model="@(this.ViewModel.AuthenticationDto)" OnValidSubmit="this.ExecuteLogin">
<DataAnnotationsValidator/>
<DxFormLayout CaptionPosition="CaptionPosition.Vertical">
@if (string.IsNullOrEmpty(this.ViewModel.serverConnectionService.ServerConfiguration.ServerAddress))
{
<DxFormLayoutItem Caption="Source Address:" ColSpanLg="12">
<Template>
<DxTextBox Id="sourceaddress" @bind-Text="@(this.ViewModel.AuthenticationDto.SourceAddress)"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
NullText="Enter the url for the server"
Enabled="@string.IsNullOrEmpty(this.RequestedServer)" />
</Template>
</DxFormLayoutItem>
}
<DxFormLayoutItem Caption="UserName:" BeginRow="true" ColSpanLg="12">
<Template>
<DxTextBox Id="username" @bind-Text="@(this.ViewModel.AuthenticationDto.UserName)"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
NullText="Enter your username"/>
</Template>
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Password:" BeginRow="true" ColSpanLg="12">
<Template>
<DxTextBox Id="password" @bind-Text="@(this.ViewModel.AuthenticationDto.Password)"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
NullText="Enter your password"
Password="true"/>
</Template>
</DxFormLayoutItem>
</DxFormLayout>
<ValidationSummary/>
<DxFormLayout CaptionPosition="CaptionPosition.Vertical">
@if (string.IsNullOrEmpty(this.ViewModel.serverConnectionService.ServerConfiguration.ServerAddress))
{
<DxFormLayoutItem Caption="Source Address:" ColSpanLg="12">
<Template>
<DxTextBox Id="sourceaddress" @bind-Text="@(this.ViewModel.AuthenticationDto.SourceAddress)"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
NullText="Enter the url for the server"
BindValueMode="BindValueMode.OnInput"
@onfocus="@(() => this.HandleFieldFocus("SourceAddress"))"
@onblur="@(() => this.HandleFieldBlur("SourceAddress"))"
Enabled="@string.IsNullOrEmpty(this.RequestedServer)" />
</Template>
</DxFormLayoutItem>
}
<DxFormLayoutItem Caption="UserName:" BeginRow="true" ColSpanLg="12">
<Template>
<DxTextBox Id="username" @bind-Text="@(this.ViewModel.AuthenticationDto.UserName)"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
NullText="Enter your username"
BindValueMode="BindValueMode.OnInput"
@onfocus="@(() => this.HandleFieldFocus("UserName"))"
@onblur="@(() => this.HandleFieldBlur("UserName"))" />
</Template>
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Password:" BeginRow="true" ColSpanLg="12">
<Template>
<DxTextBox Id="password" @bind-Text="@(this.ViewModel.AuthenticationDto.Password)"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
NullText="Enter your password"
BindValueMode="BindValueMode.OnInput"
@onfocus="@(() => this.HandleFieldFocus("Password"))"
@onblur="@(() => this.HandleFieldBlur("Password"))"
Password="true"/>
</Template>
</DxFormLayoutItem>
</DxFormLayout>

<ul class="validation-errors">
@foreach (var fieldFocusedStatus in this.FieldsFocusedStatus)
{
if (fieldFocusedStatus.Value)
{
continue;
}

@if (fieldFocusedStatus.Key == "SourceAddress" && !string.IsNullOrEmpty(editFormContext.GetValidationMessages(() => this.ViewModel.AuthenticationDto.SourceAddress).FirstOrDefault()))
{
<li class="validation-message"><ValidationMessage For="() => this.ViewModel.AuthenticationDto.SourceAddress" /></li>
}

@if (fieldFocusedStatus.Key == "UserName" && !string.IsNullOrEmpty(editFormContext.GetValidationMessages(() => this.ViewModel.AuthenticationDto.UserName).FirstOrDefault()))
{
<li class="validation-message"><ValidationMessage For="() => this.ViewModel.AuthenticationDto.UserName" /></li>
}

@if (fieldFocusedStatus.Key == "Password" && !string.IsNullOrEmpty(editFormContext.GetValidationMessages(() => this.ViewModel.AuthenticationDto.Password).FirstOrDefault()))
{
<li class="validation-message"><ValidationMessage For="() => this.ViewModel.AuthenticationDto.Password" /></li>
}
}
</ul>

@if (!string.IsNullOrEmpty(this.ErrorMessage))
{
Expand Down
31 changes: 31 additions & 0 deletions COMET.Web.Common/Components/Login.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace COMET.Web.Common.Components
using COMET.Web.Common.ViewModels.Components;

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

using ReactiveUI;

Expand Down Expand Up @@ -64,6 +65,11 @@ public partial class Login
/// </summary>
public bool LoginEnabled { get; set; } = true;

/// <summary>
/// The dictionary of focus status from the form fields
/// </summary>
public Dictionary<string, bool> FieldsFocusedStatus { get; private set; }

/// <summary>
/// Method invoked when the component is ready to start, having received its
/// initial parameters from its parent in the render tree.
Expand All @@ -72,6 +78,13 @@ protected override void OnInitialized()
{
base.OnInitialized();

this.FieldsFocusedStatus = new Dictionary<string, bool>()
{
{ "SourceAddress", false },
{ "UserName", false },
{ "Password", false }
};

this.Disposables.Add(this.WhenAnyValue(x => x.ViewModel.AuthenticationState)
.Subscribe(_ => this.ComputeDisplayProperties()));
}
Expand Down Expand Up @@ -120,5 +133,23 @@ private async Task ExecuteLogin()
await this.ViewModel.ExecuteLogin();
this.LoginEnabled = true;
}

/// <summary>
/// Handles the focus event of the given fieldName
/// </summary>
/// <param name="fieldName">Form field name, as indexed in <see cref="FieldsFocusedStatus"/></param>
public void HandleFieldFocus(string fieldName)
{
this.FieldsFocusedStatus[fieldName] = true; // Set the field as focused
}

/// <summary>
/// Handles the blur event of the given fieldName
/// </summary>
/// <param name="fieldName">Form field name, as indexed in <see cref="FieldsFocusedStatus"/></param>
public void HandleFieldBlur(string fieldName)
{
this.FieldsFocusedStatus[fieldName] = false; // Set the field as not focused when it loses focus
}
}
}

0 comments on commit ded0302

Please sign in to comment.