diff --git a/LayerComponents/CascadingState.razor b/LayerComponents/CascadingState.razor index 9f6bfbe..9cae022 100644 --- a/LayerComponents/CascadingState.razor +++ b/LayerComponents/CascadingState.razor @@ -27,6 +27,8 @@ public PublicCacheData PublicCacheData { get; private set; } = new(); /// Whether a root is signed in (its JWT is still valid). public bool IsSignedIn { get; private set; } + /// The session has been read once: from then on a null PersonaId means the login has no persona, not "not yet". + public bool IsSessionLoaded { get; private set; } /// The root's login name, shown to its owner only. public string RootUserName { get; private set; } /// The root's personas. @@ -93,6 +95,7 @@ if (!IsSignedIn) { ClearSession(); + IsSessionLoaded = true; StateHasChanged(); return; } @@ -105,10 +108,12 @@ { PersonaId = null; Persona = null; + IsSessionLoaded = true; StateHasChanged(); return; } + IsSessionLoaded = true; await SwitchPersona(personaId, cancellationToken); } diff --git a/Pages/Index.razor b/Pages/Index.razor index 173c7bc..234b8a3 100644 --- a/Pages/Index.razor +++ b/Pages/Index.razor @@ -155,6 +155,7 @@ (double Latitude, double Longitude)? position; string positionError; string loadedPersonaId; + bool waitingForPersona; FeedQuery lastQuery; DateTime? lastPollAt; TimeSpan? lastLoadTook; @@ -183,9 +184,11 @@ return; } + // (while the session is still being read the persona isn't known yet: the feed is loading, not empty) if (CascadingState.PersonaId is null) { - IsLoading = false; + waitingForPersona = !CascadingState.IsSessionLoaded; + IsLoading = waitingForPersona; return; } @@ -219,11 +222,19 @@ return true; } - // The persona changed (NavMenu's switcher): everything on the page belongs to the old one. + // The persona is known (the session was read after the page opened) or changed (NavMenu's switcher): everything on the + // page belongs to it. protected override async Task OnParametersSetAsync() { - if (CascadingState.PersonaId is null || CascadingState.PersonaId == loadedPersonaId || IsLoading && loadedPersonaId is null) + if (waitingForPersona && CascadingState.IsSessionLoaded && CascadingState.PersonaId is null) + { + waitingForPersona = false; + IsLoading = false; return; + } + if (CascadingState.PersonaId is null || CascadingState.PersonaId == loadedPersonaId || IsLoading && loadedPersonaId is null && !waitingForPersona) + return; + waitingForPersona = false; await Reload(); } diff --git a/tests/e2e/Login/LoginTests.cs b/tests/e2e/Login/LoginTests.cs index cb9ea49..f053e2f 100644 --- a/tests/e2e/Login/LoginTests.cs +++ b/tests/e2e/Login/LoginTests.cs @@ -34,11 +34,12 @@ namespace decePubClient.E2E.Login await session.Page.Locator("nav [title='Switch persona']").First.ClickAsync(); await session.Page.Locator("nav").GetByRole(AriaRole.Button).Filter(new() { HasText = sibling.Name ?? sibling.UserName }).First.ClickAsync(); + // the sibling's home, read with another token than the first persona's (whose own home may still be loading when the + // switch is asked for) + string FirstToken() => session.ApiCalls.FirstOrDefault(c => new Uri(c.Url).AbsolutePath.StartsWith("/api/") && c.Authorization is not null).Authorization; Assert.True(await Pasture.Eventually(() => Task.FromResult(session.ApiCalls.Skip(before) - .Any(c => new Uri(c.Url).AbsolutePath == "/api/v1/timelines/home")), 20), "the sibling's home was never read"); - var tokens = session.ApiCalls.Where(c => new Uri(c.Url).AbsolutePath.StartsWith("/api/") && c.Authorization is not null) - .Select(c => c.Authorization).Distinct().ToList(); - Assert.True(tokens.Count >= 2, "both personas' requests went out with one token"); + .Any(c => new Uri(c.Url).AbsolutePath == "/api/v1/timelines/home" && c.Authorization is not null && c.Authorization != FirstToken())), 20), + "the sibling's home was never read with its own token"); await session.AssertGuards(); }