mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
Resolve state <-> state-test-utils circular dependency (#16093)
* Resolve state <-> state-test-utils circular dependency * Fix type errors
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* need to update test environment so structuredClone works appropriately
|
||||
* @jest-environment ../shared/test.environment.ts
|
||||
*/
|
||||
import { Observable, of } from "rxjs";
|
||||
|
||||
import { awaitAsync, trackEmissions } from "@bitwarden/core-test-utils";
|
||||
import {
|
||||
DeriveDefinition,
|
||||
KeyDefinition,
|
||||
StateDefinition,
|
||||
UserKeyDefinition,
|
||||
} from "@bitwarden/state";
|
||||
import {
|
||||
FakeActiveUserAccessor,
|
||||
FakeActiveUserStateProvider,
|
||||
FakeDerivedStateProvider,
|
||||
FakeGlobalStateProvider,
|
||||
FakeSingleUserStateProvider,
|
||||
} from "@bitwarden/state-test-utils";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { DefaultStateProvider } from "./default-state.provider";
|
||||
|
||||
describe("DefaultStateProvider", () => {
|
||||
let sut: DefaultStateProvider;
|
||||
let activeUserStateProvider: FakeActiveUserStateProvider;
|
||||
let singleUserStateProvider: FakeSingleUserStateProvider;
|
||||
let globalStateProvider: FakeGlobalStateProvider;
|
||||
let derivedStateProvider: FakeDerivedStateProvider;
|
||||
let activeAccountAccessor: FakeActiveUserAccessor;
|
||||
const userId = "fakeUserId" as UserId;
|
||||
|
||||
beforeEach(() => {
|
||||
activeAccountAccessor = new FakeActiveUserAccessor(userId);
|
||||
activeUserStateProvider = new FakeActiveUserStateProvider(activeAccountAccessor);
|
||||
singleUserStateProvider = new FakeSingleUserStateProvider();
|
||||
globalStateProvider = new FakeGlobalStateProvider();
|
||||
derivedStateProvider = new FakeDerivedStateProvider();
|
||||
sut = new DefaultStateProvider(
|
||||
activeUserStateProvider,
|
||||
singleUserStateProvider,
|
||||
globalStateProvider,
|
||||
derivedStateProvider,
|
||||
);
|
||||
});
|
||||
|
||||
describe("activeUserId$", () => {
|
||||
it("should track the active User id from active user state provider", () => {
|
||||
expect(sut.activeUserId$).toBe(activeUserStateProvider.activeUserId$);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
[
|
||||
"getUserState$",
|
||||
(keyDefinition: UserKeyDefinition<string>, userId?: UserId) =>
|
||||
sut.getUserState$(keyDefinition, userId),
|
||||
],
|
||||
[
|
||||
"getUserStateOrDefault$",
|
||||
(keyDefinition: UserKeyDefinition<string>, userId?: UserId) =>
|
||||
sut.getUserStateOrDefault$(keyDefinition, { userId: userId }),
|
||||
],
|
||||
])(
|
||||
"Shared behavior for %s",
|
||||
(
|
||||
_testName: string,
|
||||
methodUnderTest: (
|
||||
keyDefinition: UserKeyDefinition<string>,
|
||||
userId?: UserId,
|
||||
) => Observable<string>,
|
||||
) => {
|
||||
const keyDefinition = new UserKeyDefinition<string>(
|
||||
new StateDefinition("test", "disk"),
|
||||
"test",
|
||||
{
|
||||
deserializer: (s) => s,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
it("should follow the specified user if userId is provided", async () => {
|
||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||
state.nextState("value");
|
||||
const emissions = trackEmissions(methodUnderTest(keyDefinition, userId));
|
||||
|
||||
state.nextState("value2");
|
||||
state.nextState("value3");
|
||||
|
||||
expect(emissions).toEqual(["value", "value2", "value3"]);
|
||||
});
|
||||
|
||||
it("should follow the current active user if no userId is provided", async () => {
|
||||
activeAccountAccessor.switch(userId);
|
||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||
state.nextState("value");
|
||||
const emissions = trackEmissions(methodUnderTest(keyDefinition));
|
||||
|
||||
state.nextState("value2");
|
||||
state.nextState("value3");
|
||||
|
||||
expect(emissions).toEqual(["value", "value2", "value3"]);
|
||||
});
|
||||
|
||||
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(methodUnderTest(keyDefinition));
|
||||
|
||||
activeAccountAccessor.switch("newUserId" as UserId);
|
||||
const newUserEmissions = trackEmissions(sut.getUserState$(keyDefinition));
|
||||
state.nextState("value2");
|
||||
state.nextState("value3");
|
||||
|
||||
expect(emissions).toEqual(["value", "value2", "value3"]);
|
||||
expect(newUserEmissions).toEqual([null]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("getUserState$", () => {
|
||||
const keyDefinition = new UserKeyDefinition<string>(
|
||||
new StateDefinition("test", "disk"),
|
||||
"test",
|
||||
{
|
||||
deserializer: (s) => s,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
it("should not emit any values until a truthy user id is supplied", async () => {
|
||||
activeAccountAccessor.switch(null);
|
||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||
state.nextState("value");
|
||||
|
||||
const emissions = trackEmissions(sut.getUserState$(keyDefinition));
|
||||
|
||||
await awaitAsync();
|
||||
|
||||
expect(emissions).toHaveLength(0);
|
||||
|
||||
activeAccountAccessor.switch(userId);
|
||||
|
||||
await awaitAsync();
|
||||
|
||||
expect(emissions).toEqual(["value"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserStateOrDefault$", () => {
|
||||
const keyDefinition = new UserKeyDefinition<string>(
|
||||
new StateDefinition("test", "disk"),
|
||||
"test",
|
||||
{
|
||||
deserializer: (s) => s,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
it("should emit default value if no userId supplied and first active user id emission in falsy", async () => {
|
||||
activeAccountAccessor.switch(null);
|
||||
|
||||
const emissions = trackEmissions(
|
||||
sut.getUserStateOrDefault$(keyDefinition, {
|
||||
userId: undefined,
|
||||
defaultValue: "I'm default!",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(emissions).toEqual(["I'm default!"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setUserState", () => {
|
||||
const keyDefinition = new UserKeyDefinition<string>(
|
||||
new StateDefinition("test", "disk"),
|
||||
"test",
|
||||
{
|
||||
deserializer: (s) => s,
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
it("should set the state for the active user if no userId is provided", async () => {
|
||||
const value = "value";
|
||||
await sut.setUserState(keyDefinition, value);
|
||||
const state = activeUserStateProvider.getFake(keyDefinition);
|
||||
expect(state.nextMock).toHaveBeenCalledWith([expect.any(String), value]);
|
||||
});
|
||||
|
||||
it("should not set state for a single user if no userId is provided", async () => {
|
||||
const value = "value";
|
||||
await sut.setUserState(keyDefinition, value);
|
||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||
expect(state.nextMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should set the state for the provided userId", async () => {
|
||||
const value = "value";
|
||||
await sut.setUserState(keyDefinition, value, userId);
|
||||
const state = singleUserStateProvider.getFake(userId, keyDefinition);
|
||||
expect(state.nextMock).toHaveBeenCalledWith(value);
|
||||
});
|
||||
|
||||
it("should not set the active user state if userId is provided", async () => {
|
||||
const value = "value";
|
||||
await sut.setUserState(keyDefinition, value, userId);
|
||||
const state = activeUserStateProvider.getFake(keyDefinition);
|
||||
expect(state.nextMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should bind the activeUserStateProvider", () => {
|
||||
const keyDefinition = new UserKeyDefinition(new StateDefinition("test", "disk"), "test", {
|
||||
deserializer: () => null,
|
||||
clearOn: [],
|
||||
});
|
||||
const existing = activeUserStateProvider.get(keyDefinition);
|
||||
const actual = sut.getActive(keyDefinition);
|
||||
expect(actual).toBe(existing);
|
||||
});
|
||||
|
||||
it("should bind the singleUserStateProvider", () => {
|
||||
const userId = "user" as UserId;
|
||||
const keyDefinition = new UserKeyDefinition(new StateDefinition("test", "disk"), "test", {
|
||||
deserializer: () => null,
|
||||
clearOn: [],
|
||||
});
|
||||
const existing = singleUserStateProvider.get(userId, keyDefinition);
|
||||
const actual = sut.getUser(userId, keyDefinition);
|
||||
expect(actual).toBe(existing);
|
||||
});
|
||||
|
||||
it("should bind the globalStateProvider", () => {
|
||||
const keyDefinition = new KeyDefinition(new StateDefinition("test", "disk"), "test", {
|
||||
deserializer: () => null,
|
||||
});
|
||||
const existing = globalStateProvider.get(keyDefinition);
|
||||
const actual = sut.getGlobal(keyDefinition);
|
||||
expect(actual).toBe(existing);
|
||||
});
|
||||
|
||||
it("should bind the derivedStateProvider", () => {
|
||||
const derivedDefinition = new DeriveDefinition(new StateDefinition("test", "disk"), "test", {
|
||||
derive: () => null,
|
||||
deserializer: () => null,
|
||||
});
|
||||
const parentState$ = of(null);
|
||||
const existing = derivedStateProvider.get(parentState$, derivedDefinition, {});
|
||||
const actual = sut.getDerived(parentState$, derivedDefinition, {});
|
||||
expect(actual).toBe(existing);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user