import { mock } from "jest-mock-extended"; import { Observable, map, of, switchMap, take } from "rxjs"; import { GlobalState, GlobalStateProvider, KeyDefinition, ActiveUserState, SingleUserState, SingleUserStateProvider, StateProvider, ActiveUserStateProvider, DerivedState, DeriveDefinition, DerivedStateProvider, UserKeyDefinition, } from "../src/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Needed to type check similarly to the real state providers import { isUserKeyDefinition } from "../src/platform/state/user-key-definition"; import { UserId } from "../src/types/guid"; import { DerivedStateDependencies } from "../src/types/state"; import { FakeAccountService } from "./fake-account-service"; import { FakeActiveUserState, FakeDerivedState, FakeGlobalState, FakeSingleUserState, } from "./fake-state"; export class FakeGlobalStateProvider implements GlobalStateProvider { mock = mock(); establishedMocks: Map> = new Map(); states: Map> = new Map(); get(keyDefinition: KeyDefinition): GlobalState { this.mock.get(keyDefinition); const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`; let result = this.states.get(cacheKey); if (result == null) { let fake: FakeGlobalState; // Look for established mock if (this.establishedMocks.has(keyDefinition.key)) { fake = this.establishedMocks.get(keyDefinition.key) as FakeGlobalState; } else { fake = new FakeGlobalState(); } fake.keyDefinition = keyDefinition; result = fake; this.states.set(cacheKey, result); result = new FakeGlobalState(); this.states.set(cacheKey, result); } return result as GlobalState; } getFake(keyDefinition: KeyDefinition): FakeGlobalState { return this.get(keyDefinition) as FakeGlobalState; } mockFor(keyDefinitionKey: string, initialValue?: T): FakeGlobalState { if (!this.establishedMocks.has(keyDefinitionKey)) { this.establishedMocks.set(keyDefinitionKey, new FakeGlobalState(initialValue)); } return this.establishedMocks.get(keyDefinitionKey) as FakeGlobalState; } } export class FakeSingleUserStateProvider implements SingleUserStateProvider { mock = mock(); establishedMocks: Map> = new Map(); states: Map> = new Map(); get( userId: UserId, keyDefinition: KeyDefinition | UserKeyDefinition, ): SingleUserState { this.mock.get(userId, keyDefinition); if (keyDefinition instanceof KeyDefinition) { keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition); } const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}_${userId}`; let result = this.states.get(cacheKey); if (result == null) { let fake: FakeSingleUserState; // Look for established mock if (this.establishedMocks.has(keyDefinition.key)) { fake = this.establishedMocks.get(keyDefinition.key) as FakeSingleUserState; } else { fake = new FakeSingleUserState(userId); } fake.keyDefinition = keyDefinition; result = fake; this.states.set(cacheKey, result); } return result as SingleUserState; } getFake( userId: UserId, keyDefinition: KeyDefinition | UserKeyDefinition, ): FakeSingleUserState { return this.get(userId, keyDefinition) as FakeSingleUserState; } mockFor(userId: UserId, keyDefinitionKey: string, initialValue?: T): FakeSingleUserState { if (!this.establishedMocks.has(keyDefinitionKey)) { this.establishedMocks.set(keyDefinitionKey, new FakeSingleUserState(userId, initialValue)); } return this.establishedMocks.get(keyDefinitionKey) as FakeSingleUserState; } } export class FakeActiveUserStateProvider implements ActiveUserStateProvider { activeUserId$: Observable; establishedMocks: Map> = new Map(); states: Map> = new Map(); constructor(public accountService: FakeAccountService) { this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id)); } get(keyDefinition: KeyDefinition | UserKeyDefinition): ActiveUserState { if (keyDefinition instanceof KeyDefinition) { keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition); } const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`; let result = this.states.get(cacheKey); if (result == null) { // Look for established mock if (this.establishedMocks.has(keyDefinition.key)) { result = this.establishedMocks.get(keyDefinition.key); } else { result = new FakeActiveUserState(this.accountService); } result.keyDefinition = keyDefinition; this.states.set(cacheKey, result); } return result as ActiveUserState; } getFake(keyDefinition: KeyDefinition | UserKeyDefinition): FakeActiveUserState { return this.get(keyDefinition) as FakeActiveUserState; } mockFor(keyDefinitionKey: string, initialValue?: T): FakeActiveUserState { if (!this.establishedMocks.has(keyDefinitionKey)) { this.establishedMocks.set( keyDefinitionKey, new FakeActiveUserState(this.accountService, initialValue), ); } return this.establishedMocks.get(keyDefinitionKey) as FakeActiveUserState; } } export class FakeStateProvider implements StateProvider { mock = mock(); getUserState$( keyDefinition: KeyDefinition | UserKeyDefinition, userId?: UserId, ): Observable { if (isUserKeyDefinition(keyDefinition)) { this.mock.getUserState$(keyDefinition, userId); } else { this.mock.getUserState$(keyDefinition, userId); } if (userId) { return this.getUser(userId, keyDefinition).state$; } return this.getActive(keyDefinition).state$; } getUserStateOrDefault$( keyDefinition: KeyDefinition | UserKeyDefinition, config: { userId: UserId | undefined; defaultValue?: T }, ): Observable { const { userId, defaultValue = null } = config; if (isUserKeyDefinition(keyDefinition)) { this.mock.getUserStateOrDefault$(keyDefinition, config); } else { this.mock.getUserStateOrDefault$(keyDefinition, config); } if (userId) { return this.getUser(userId, keyDefinition).state$; } return this.activeUserId$.pipe( take(1), switchMap((userId) => userId != null ? this.getUser(userId, keyDefinition).state$ : of(defaultValue), ), ); } async setUserState( keyDefinition: KeyDefinition | UserKeyDefinition, value: T, userId?: UserId, ): Promise<[UserId, T]> { await this.mock.setUserState(keyDefinition, value, userId); if (userId) { return [userId, await this.getUser(userId, keyDefinition).update(() => value)]; } else { return await this.getActive(keyDefinition).update(() => value); } } getActive(keyDefinition: KeyDefinition | UserKeyDefinition): ActiveUserState { return this.activeUser.get(keyDefinition); } getGlobal(keyDefinition: KeyDefinition): GlobalState { return this.global.get(keyDefinition); } getUser( userId: UserId, keyDefinition: KeyDefinition | UserKeyDefinition, ): SingleUserState { return this.singleUser.get(userId, keyDefinition); } getDerived( parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { return this.derived.get(parentState$, deriveDefinition, dependencies); } constructor(public accountService: FakeAccountService) {} global: FakeGlobalStateProvider = new FakeGlobalStateProvider(); singleUser: FakeSingleUserStateProvider = new FakeSingleUserStateProvider(); activeUser: FakeActiveUserStateProvider = new FakeActiveUserStateProvider(this.accountService); derived: FakeDerivedStateProvider = new FakeDerivedStateProvider(); activeUserId$: Observable = this.activeUser.activeUserId$; } export class FakeDerivedStateProvider implements DerivedStateProvider { states: Map> = new Map(); get( parentState$: Observable, deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { let result = this.states.get(deriveDefinition.buildCacheKey()) as DerivedState; if (result == null) { result = new FakeDerivedState(parentState$, deriveDefinition, dependencies); this.states.set(deriveDefinition.buildCacheKey(), result); } return result; } }