diff --git a/libs/common/spec/fake-account-service.ts b/libs/common/spec/fake-account-service.ts index fba91014d75..ed6ba6e031b 100644 --- a/libs/common/spec/fake-account-service.ts +++ b/libs/common/spec/fake-account-service.ts @@ -48,32 +48,24 @@ export class FakeAccountService implements AccountService { } async addAccount(userId: UserId, accountData: AccountInfo): Promise { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.mock.addAccount(userId, accountData); + const current = this.accountsSubject["_buffer"][0] ?? {}; + this.accountsSubject.next({ ...current, [userId]: accountData }); + await this.mock.addAccount(userId, accountData); } async setAccountName(userId: UserId, name: string): Promise { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.mock.setAccountName(userId, name); + await this.mock.setAccountName(userId, name); } async setAccountEmail(userId: UserId, email: string): Promise { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.mock.setAccountEmail(userId, email); + await this.mock.setAccountEmail(userId, email); } async setAccountStatus(userId: UserId, status: AuthenticationStatus): Promise { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.mock.setAccountStatus(userId, status); + await this.mock.setAccountStatus(userId, status); } async switchAccount(userId: UserId): Promise { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.mock.switchAccount(userId); + await this.mock.switchAccount(userId); } } diff --git a/libs/common/src/platform/state/implementations/default-state.provider.spec.ts b/libs/common/src/platform/state/implementations/default-state.provider.spec.ts index f81c4dfce23..e7228192f12 100644 --- a/libs/common/src/platform/state/implementations/default-state.provider.spec.ts +++ b/libs/common/src/platform/state/implementations/default-state.provider.spec.ts @@ -1,5 +1,10 @@ +/** + * need to update test environment so structuredClone works appropriately + * @jest-environment ../shared/test.environment.ts + */ import { of } from "rxjs"; +import { trackEmissions } from "../../../../spec"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; import { FakeActiveUserStateProvider, @@ -7,6 +12,7 @@ import { FakeGlobalStateProvider, FakeSingleUserStateProvider, } from "../../../../spec/fake-state-provider"; +import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { UserId } from "../../../types/guid"; import { DeriveDefinition } from "../derive-definition"; import { KeyDefinition } from "../key-definition"; @@ -44,30 +50,46 @@ describe("DefaultStateProvider", () => { }); describe("getUserState$", () => { + const accountInfo = { email: "email", name: "name", status: AuthenticationStatus.LoggedOut }; const keyDefinition = new KeyDefinition(new StateDefinition("test", "disk"), "test", { deserializer: (s) => s, }); - it("should get the state for the active user if no userId is provided", () => { - const state = sut.getUserState$(keyDefinition); - expect(state).toBe(activeUserStateProvider.get(keyDefinition).state$); + it("should follow the specified user if userId is provided", async () => { + const state = singleUserStateProvider.getFake(userId, keyDefinition); + state.nextState("value"); + const emissions = trackEmissions(sut.getUserState$(keyDefinition, userId)); + + state.nextState("value2"); + state.nextState("value3"); + + expect(emissions).toEqual(["value", "value2", "value3"]); }); - it("should not return state for a single user if no userId is provided", () => { - const state = sut.getUserState$(keyDefinition); - expect(state).not.toBe(singleUserStateProvider.get(userId, keyDefinition).state$); + it("should follow the current active user if no userId is provided", async () => { + accountService.activeAccountSubject.next({ id: userId, ...accountInfo }); + const state = singleUserStateProvider.getFake(userId, keyDefinition); + state.nextState("value"); + const emissions = trackEmissions(sut.getUserState$(keyDefinition)); + + state.nextState("value2"); + state.nextState("value3"); + + expect(emissions).toEqual(["value", "value2", "value3"]); }); - it("should get the state for the provided userId", () => { - const userId = "user" as UserId; - const state = sut.getUserState$(keyDefinition, userId); - expect(state).toBe(singleUserStateProvider.get(userId, keyDefinition).state$); - }); + it("should continue to follow the state of the user that was active when called, even if active user changes", async () => { + const state = singleUserStateProvider.getFake(userId, keyDefinition); + state.nextState("value"); + const emissions = trackEmissions(sut.getUserState$(keyDefinition)); - it("should not get the active user state if userId is provided", () => { - const userId = "user" as UserId; - const state = sut.getUserState$(keyDefinition, userId); - expect(state).not.toBe(activeUserStateProvider.get(keyDefinition).state$); + accountService.activeAccountSubject.next({ id: "newUserId" as UserId, ...accountInfo }); + const newUserEmissions = trackEmissions(sut.getUserState$(keyDefinition)); + state.nextState("value2"); + state.nextState("value3"); + + expect(emissions).toEqual(["value", "value2", "value3"]); + expect(newUserEmissions).toEqual([null]); }); }); diff --git a/libs/common/src/platform/state/implementations/default-state.provider.ts b/libs/common/src/platform/state/implementations/default-state.provider.ts index 8af3ba05394..4add19e2ba8 100644 --- a/libs/common/src/platform/state/implementations/default-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-state.provider.ts @@ -1,4 +1,4 @@ -import { Observable } from "rxjs"; +import { Observable, switchMap, take } from "rxjs"; import { UserId } from "../../../types/guid"; import { DerivedStateDependencies } from "../../../types/state"; @@ -25,7 +25,10 @@ export class DefaultStateProvider implements StateProvider { if (userId) { return this.getUser(userId, keyDefinition).state$; } else { - return this.getActive(keyDefinition).state$; + return this.activeUserId$.pipe( + take(1), + switchMap((userId) => this.getUser(userId, keyDefinition).state$), + ); } } diff --git a/libs/common/src/platform/state/state.provider.ts b/libs/common/src/platform/state/state.provider.ts index 8e37120a203..51e41dd9e9c 100644 --- a/libs/common/src/platform/state/state.provider.ts +++ b/libs/common/src/platform/state/state.provider.ts @@ -22,6 +22,9 @@ export abstract class StateProvider { /** * Gets a state observable for a given key and userId. * + * @remarks If userId is falsy the observable returned will point to the currently active user _and not update if the active user changes_. + * This is different to how `getActive` works and more similar to `getUser` for whatever user happens to be active at the time of the call. + * * @param keyDefinition - The key definition for the state you want to get. * @param userId - The userId for which you want the state for. If not provided, the state for the currently active user will be returned. */