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"; 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, userKeyDefinition: UserKeyDefinition): SingleUserState { this.mock.get(userId, userKeyDefinition); const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}_${userId}`; let result = this.states.get(cacheKey); if (result == null) { let fake: FakeSingleUserState; // Look for established mock if (this.establishedMocks.has(userKeyDefinition.key)) { fake = this.establishedMocks.get(userKeyDefinition.key) as FakeSingleUserState; } else { fake = new FakeSingleUserState(userId); } fake.keyDefinition = userKeyDefinition; result = fake; this.states.set(cacheKey, result); } return result as SingleUserState; } getFake(userId: UserId, userKeyDefinition: UserKeyDefinition): FakeSingleUserState { return this.get(userId, userKeyDefinition) 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(userKeyDefinition: UserKeyDefinition): ActiveUserState { const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}`; let result = this.states.get(cacheKey); if (result == null) { // Look for established mock if (this.establishedMocks.has(userKeyDefinition.key)) { result = this.establishedMocks.get(userKeyDefinition.key); } else { result = new FakeActiveUserState(this.accountService); } result.keyDefinition = userKeyDefinition; this.states.set(cacheKey, result); } return result as ActiveUserState; } getFake(userKeyDefinition: UserKeyDefinition): FakeActiveUserState { return this.get(userKeyDefinition) 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$(userKeyDefinition: UserKeyDefinition, userId?: UserId): Observable { this.mock.getUserState$(userKeyDefinition, userId); if (userId) { return this.getUser(userId, userKeyDefinition).state$; } return this.getActive(userKeyDefinition).state$; } getUserStateOrDefault$( userKeyDefinition: UserKeyDefinition, config: { userId: UserId | undefined; defaultValue?: T }, ): Observable { const { userId, defaultValue = null } = config; this.mock.getUserStateOrDefault$(userKeyDefinition, config); if (userId) { return this.getUser(userId, userKeyDefinition).state$; } return this.activeUserId$.pipe( take(1), switchMap((userId) => userId != null ? this.getUser(userId, userKeyDefinition).state$ : of(defaultValue), ), ); } async setUserState( userKeyDefinition: UserKeyDefinition, value: T, userId?: UserId, ): Promise<[UserId, T]> { await this.mock.setUserState(userKeyDefinition, value, userId); if (userId) { return [userId, await this.getUser(userId, userKeyDefinition).update(() => value)]; } else { return await this.getActive(userKeyDefinition).update(() => value); } } getActive(userKeyDefinition: UserKeyDefinition): ActiveUserState { return this.activeUser.get(userKeyDefinition); } getGlobal(keyDefinition: KeyDefinition): GlobalState { return this.global.get(keyDefinition); } getUser(userId: UserId, userKeyDefinition: UserKeyDefinition): SingleUserState { return this.singleUser.get(userId, userKeyDefinition); } 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; } }