Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ public enum AuthStatus
internal void RaiseProfileFinalized() =>
ProfileFinalized?.Invoke();

private MVCStateMachine<AuthStateBase> fsm;
private AuthenticationScreenAudio audio;
// Null until OnViewInstantiated: the view is created lazily on first Show and may never be instantiated.
private MVCStateMachine<AuthStateBase>? fsm;
private AuthenticationScreenAudio? audio;

public AuthenticationScreenController(
ViewFactoryMethod viewFactory,
Expand Down Expand Up @@ -138,8 +139,8 @@ public override void Dispose()
characterPreviewController?.Dispose();

CancelLoginProcess();
audio.Dispose();
fsm.Dispose();
audio?.Dispose();
fsm?.Dispose();
}

protected override void OnViewInstantiated()
Expand Down Expand Up @@ -203,7 +204,7 @@ protected override void OnBeforeViewShow()
}
else
{
fsm.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.IN, true);
fsm!.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.IN, true);
}
}

Expand All @@ -214,10 +215,10 @@ private async UniTaskVoid TryAutoLoginAndProceedAsync(IWeb3Identity storedIdenti
bool autoLoginSuccess = await web3Authenticator.TryAutoLoginAsync(ct);

if (autoLoginSuccess)
fsm.Enter<ProfileFetchingAuthState, ProfileFetchingPayload>(new (storedIdentity, storedIdentity.Source != IWeb3Identity.Web3IdentitySource.TokenFile, ct));
fsm!.Enter<ProfileFetchingAuthState, ProfileFetchingPayload>(new (storedIdentity, storedIdentity.Source != IWeb3Identity.Web3IdentitySource.TokenFile, ct));
else
{
fsm.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.IN, true);
fsm!.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.IN, true);
}
}
catch (OperationCanceledException)
Expand All @@ -226,7 +227,7 @@ private async UniTaskVoid TryAutoLoginAndProceedAsync(IWeb3Identity storedIdenti
catch (Exception e)
{
ReportHub.LogException(e, new ReportData(ReportCategory.AUTHENTICATION));
fsm.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.IN, true);
fsm!.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.IN, true);
}
}

Expand All @@ -235,18 +236,18 @@ protected override void OnViewShow()
base.OnViewShow();

BlockUnwantedInputs();
audio.OnShow();
audio!.OnShow();
}

protected override void OnViewClose()
{
base.OnViewClose();

fsm.CurrentState?.Exit();
fsm!.CurrentState?.Exit();
CancelLoginProcess();

UnblockUnwantedInputs();
audio.OnHide();
audio!.OnHide();
}

protected override async UniTask WaitForCloseIntentAsync(CancellationToken ct)
Expand Down Expand Up @@ -285,7 +286,7 @@ async UniTaskVoid ChangeAccountAsync(CancellationToken ct)

await web3Authenticator.LogoutAsync(ct);

fsm.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.SLIDE, true);
fsm!.Enter<LoginSelectionAuthState, int>(UIAnimationHashes.SLIDE, true);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using NUnit.Framework;

namespace DCL.AuthenticationScreenFlow.Tests
{
[TestFixture]
public class AuthenticationScreenControllerShould
{
[Test]
public void NotThrowOnDisposeWhenViewWasNeverShown()
{
// Registered-but-never-shown lifecycle: the view factory is never invoked, so
// OnViewInstantiated never runs and the lazily-created members stay null
// (sessions with --skip-auth-screen + a valid cached identity).
// The constructor only stores its dependencies; none are dereferenced before the view exists.
var controller = new AuthenticationScreenController(
() => null!,
null!,
null!,
null!,
null!,
null!,
null!,
null!,
null!,
string.Empty,
null!,
null!,
null!,
null!,
null!,
null!,
null!,
null!);

Assert.DoesNotThrow(controller.Dispose);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ public MVCManager(
public void Dispose()
{
foreach (IController controllersValue in controllers.Values)
controllersValue.Dispose();
{
// One controller's failing Dispose must not abort disposal of the remaining
// controllers, the destruction CTS and the windows stack.
try { controllersValue.Dispose(); }
catch (Exception e) { ReportHub.LogException(e, ReportCategory.MVC); }
}

destructionCancellationTokenSource.SafeCancelAndDispose();
windowsStackManager.Dispose();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using MVC.PopupsController.PopupCloser;
using NSubstitute;
using NUnit.Framework;
using System;
using System.Threading;
using UnityEngine.TestTools;

namespace MVC.Tests
{
[TestFixture]
public class MVCManagerDisposeShould
{
private IWindowsStackManager windowsStackManager = null!;
private MVCManager mvcManager = null!;

[SetUp]
public void SetUp()
{
windowsStackManager = Substitute.For<IWindowsStackManager>();
mvcManager = new MVCManager(windowsStackManager, new CancellationTokenSource(), Substitute.For<IPopupCloserView>());
}

[TearDown]
public void TearDown()
{
LogAssert.ignoreFailingMessages = false;
}

[Test]
public void DisposeRemainingControllersAndStackWhenOneControllerThrows()
{
// The guarded disposal loop reports the throwing controller via ReportHub (error-level log).
LogAssert.ignoreFailingMessages = true;

IController<ITestView, TestInputData> throwingController = Substitute.For<IController<ITestView, TestInputData>>();
throwingController.When(c => c.Dispose()).Do(_ => throw new InvalidOperationException("dispose failure"));
IController<IOtherTestView, TestInputData> otherController = Substitute.For<IController<IOtherTestView, TestInputData>>();

mvcManager.RegisterController(throwingController);
mvcManager.RegisterController(otherController);

Assert.DoesNotThrow(mvcManager.Dispose);

// Order-independent: whichever controller is disposed first, both must be reached
// and the windows stack must be disposed after the loop.
otherController.Received(1).Dispose();
windowsStackManager.Received(1).Dispose();
}
}

public interface IOtherTestView : IView { }
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading