1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-24280] Remove account service from state (#15828)

* Introduce ActiveUserAccessor

* Use ActiveUserAccessor over AccountService

* Updates tests and testing utils to support ActiveUserAccessor

* Update all injection points

* Fix types test

* Use ternary instead
This commit is contained in:
Justin Baur
2025-07-31 09:09:14 -04:00
committed by GitHub
parent 9c8188875a
commit 4f9b2b618f
14 changed files with 118 additions and 104 deletions

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { mock } from "jest-mock-extended";
import { Observable, map, of, switchMap, take } from "rxjs";
import { BehaviorSubject, map, Observable, of, switchMap, take } from "rxjs";
import {
GlobalState,
@@ -16,11 +16,11 @@ import {
DeriveDefinition,
DerivedStateProvider,
UserKeyDefinition,
ActiveUserAccessor,
} from "../src/platform/state";
import { UserId } from "../src/types/guid";
import { DerivedStateDependencies } from "../src/types/state";
import { FakeAccountService } from "./fake-account-service";
import {
FakeActiveUserState,
FakeDerivedState,
@@ -28,6 +28,35 @@ import {
FakeSingleUserState,
} from "./fake-state";
export interface MinimalAccountService {
activeUserId: UserId | null;
activeAccount$: Observable<{ id: UserId } | null>;
}
export class FakeActiveUserAccessor implements MinimalAccountService, ActiveUserAccessor {
private _subject: BehaviorSubject<UserId | null>;
constructor(startingUser: UserId | null) {
this._subject = new BehaviorSubject(startingUser);
this.activeAccount$ = this._subject
.asObservable()
.pipe(map((id) => (id != null ? { id } : null)));
this.activeUserId$ = this._subject.asObservable();
}
get activeUserId(): UserId {
return this._subject.value;
}
activeUserId$: Observable<UserId>;
activeAccount$: Observable<{ id: UserId }>;
switch(user: UserId | null) {
this._subject.next(user);
}
}
export class FakeGlobalStateProvider implements GlobalStateProvider {
mock = mock<GlobalStateProvider>();
establishedMocks: Map<string, FakeGlobalState<unknown>> = new Map();
@@ -138,18 +167,18 @@ export class FakeSingleUserStateProvider implements SingleUserStateProvider {
}
export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
activeUserId$: Observable<UserId>;
activeUserId$: Observable<UserId | null>;
states: Map<string, FakeActiveUserState<unknown>> = new Map();
constructor(
public accountService: FakeAccountService,
public accountServiceAccessor: MinimalAccountService,
readonly updateSyncCallback?: (
key: UserKeyDefinition<unknown>,
userId: UserId,
newValue: unknown,
) => Promise<void>,
) {
this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id));
this.activeUserId$ = accountServiceAccessor.activeAccount$.pipe(map((a) => a?.id));
}
get<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
@@ -182,9 +211,13 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider {
}
private buildFakeState<T>(userKeyDefinition: UserKeyDefinition<T>, initialValue?: T) {
const state = new FakeActiveUserState<T>(this.accountService, initialValue, async (...args) => {
await this.updateSyncCallback?.(userKeyDefinition, ...args);
});
const state = new FakeActiveUserState<T>(
this.accountServiceAccessor,
initialValue,
async (...args) => {
await this.updateSyncCallback?.(userKeyDefinition, ...args);
},
);
state.keyDefinition = userKeyDefinition;
return state;
}
@@ -256,14 +289,14 @@ export class FakeStateProvider implements StateProvider {
return this.derived.get(parentState$, deriveDefinition, dependencies);
}
constructor(public accountService: FakeAccountService) {}
constructor(private activeAccountAccessor: MinimalAccountService) {}
private distributeSingleUserUpdate(
key: UserKeyDefinition<unknown>,
userId: UserId,
newState: unknown,
) {
if (this.activeUser.accountService.activeUserId === userId) {
if (this.activeUser.accountServiceAccessor.activeUserId === userId) {
const state = this.activeUser.getFake(key, { allowInit: false });
state?.nextState(newState, { syncValue: false });
}
@@ -284,7 +317,7 @@ export class FakeStateProvider implements StateProvider {
this.distributeSingleUserUpdate.bind(this),
);
activeUser: FakeActiveUserStateProvider = new FakeActiveUserStateProvider(
this.accountService,
this.activeAccountAccessor,
this.distributeActiveUserUpdate.bind(this),
);
derived: FakeDerivedStateProvider = new FakeDerivedStateProvider();

View File

@@ -18,7 +18,7 @@ import { CombinedState, activeMarker } from "../src/platform/state/user-state";
import { UserId } from "../src/types/guid";
import { DerivedStateDependencies } from "../src/types/state";
import { FakeAccountService } from "./fake-account-service";
import { MinimalAccountService } from "./fake-state-provider";
const DEFAULT_TEST_OPTIONS: StateUpdateOptions<any, any> = {
shouldUpdate: () => true,
@@ -177,7 +177,7 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
combinedState$: Observable<CombinedState<T>>;
constructor(
private accountService: FakeAccountService,
private activeAccountAccessor: MinimalAccountService,
initialValue?: T,
updateSyncCallback?: (userId: UserId, newValue: T) => Promise<void>,
) {
@@ -194,14 +194,10 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
this.state$ = this.combinedState$.pipe(map(([_userId, state]) => state));
}
get userId() {
return this.accountService.activeUserId;
}
nextState(state: T | null, { syncValue }: { syncValue: boolean } = { syncValue: true }) {
this.stateSubject.next({
syncValue,
combinedState: [this.userId, state],
combinedState: [this.activeAccountAccessor.activeUserId, state],
});
}
@@ -216,12 +212,12 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
? await firstValueFrom(options.combineLatestWith.pipe(timeout(options.msTimeout)))
: null;
if (!options.shouldUpdate(current, combinedDependencies)) {
return [this.userId, current];
return [this.activeAccountAccessor.activeUserId, current];
}
const newState = configureState(current, combinedDependencies);
this.nextState(newState);
this.nextMock([this.userId, newState]);
return [this.userId, newState];
this.nextMock([this.activeAccountAccessor.activeUserId, newState]);
return [this.activeAccountAccessor.activeUserId, newState];
}
/** Tracks update values resolved by `FakeState.update` */