@ -1,14 +1,50 @@
@@ -1,14 +1,50 @@
import { BehaviorSubject , of , Subject } from "rxjs" ;
import { GENERATOR_DISK , UserKeyDefinition } from "@bitwarden/common/platform/state" ;
import { UserId } from "@bitwarden/common/types/guid" ;
import { awaitAsync , FakeSingleUserState , ObservableTracker } from "../../../spec" ;
import { UserBound } from "../dependencies" ;
import { PrivateClassifier } from "../private-classifier" ;
import { StateConstraints } from "../types" ;
import { ClassifiedFormat } from "./classified-format" ;
import { ObjectKey } from "./object-key" ;
import { UserEncryptor } from "./user-encryptor.abstraction" ;
import { UserStateSubject } from "./user-state-subject" ;
const SomeUser = "some user" as UserId ;
type TestType = { foo : string } ;
const SomeKey = new UserKeyDefinition < TestType > ( GENERATOR_DISK , "TestKey" , {
deserializer : ( d ) = > d as TestType ,
clearOn : [ ] ,
} ) ;
const SomeObjectKey = {
target : "object" ,
key : "TestObjectKey" ,
state : GENERATOR_DISK ,
classifier : new PrivateClassifier ( ) ,
format : "classified" ,
options : {
deserializer : ( d ) = > d as TestType ,
clearOn : [ "logout" ] ,
} ,
} satisfies ObjectKey < TestType > ;
const SomeEncryptor : UserEncryptor = {
userId : SomeUser ,
encrypt ( secret ) {
const tmp : any = secret ;
return Promise . resolve ( { foo : ` encrypt( ${ tmp . foo } ) ` } as any ) ;
} ,
decrypt ( secret ) {
const tmp : any = JSON . parse ( secret . encryptedString ) ;
return Promise . resolve ( { foo : ` decrypt( ${ tmp . foo } ) ` } as any ) ;
} ,
} ;
function fooMaxLength ( maxLength : number ) : StateConstraints < TestType > {
return Object . freeze ( {
@ -43,7 +79,11 @@ describe("UserStateSubject", () => {
@@ -43,7 +79,11 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const when $ = new BehaviorSubject ( true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , nextValue , when $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
singleUserId $ ,
nextValue ,
when $ ,
} ) ;
// the interleaved await asyncs are only necessary b/c `nextValue` is called asynchronously
subject . next ( { foo : "next" } ) ;
@ -65,7 +105,11 @@ describe("UserStateSubject", () => {
@@ -65,7 +105,11 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const when $ = new BehaviorSubject ( true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , nextValue , when $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
singleUserId $ ,
nextValue ,
when $ ,
} ) ;
// the interleaved await asyncs are only necessary b/c `nextValue` is called asynchronously
subject . next ( { foo : "next" } ) ;
@ -79,11 +123,35 @@ describe("UserStateSubject", () => {
@@ -79,11 +123,35 @@ describe("UserStateSubject", () => {
expect ( nextValue ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( "ignores repeated singleUserEncryptor$ emissions" , async ( ) = > {
// this test looks for `nextValue` because a subscription isn't necessary for
// the subject to update
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const singleUserEncryptor $ = new BehaviorSubject ( { userId : SomeUser , encryptor : null } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
nextValue ,
singleUserEncryptor $ ,
} ) ;
// the interleaved await asyncs are only necessary b/c `nextValue` is called asynchronously
subject . next ( { foo : "next" } ) ;
await awaitAsync ( ) ;
singleUserEncryptor $ . next ( { userId : SomeUser , encryptor : null } ) ;
await awaitAsync ( ) ;
singleUserEncryptor $ . next ( { userId : SomeUser , encryptor : null } ) ;
singleUserEncryptor $ . next ( { userId : SomeUser , encryptor : null } ) ;
await awaitAsync ( ) ;
expect ( nextValue ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( "waits for constraints$" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new Subject < StateConstraints < TestType > > ( ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
constraints $ . next ( fooMaxLength ( 3 ) ) ;
@ -91,13 +159,28 @@ describe("UserStateSubject", () => {
@@ -91,13 +159,28 @@ describe("UserStateSubject", () => {
expect ( initResult ) . toEqual ( { foo : "ini" } ) ;
} ) ;
it ( "waits for singleUserEncryptor$" , async ( ) = > {
const state = new FakeSingleUserState < ClassifiedFormat < void , Record < string , never > >> (
SomeUser ,
{ id : null , secret : '{"foo":"init"}' , disclosed : { } } ,
) ;
const singleUserEncryptor $ = new Subject < UserBound < " encryptor " , UserEncryptor > > ( ) ;
const subject = new UserStateSubject ( SomeObjectKey , ( ) = > state , { singleUserEncryptor $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
singleUserEncryptor $ . next ( { userId : SomeUser , encryptor : SomeEncryptor } ) ;
const [ initResult ] = await tracker . pauseUntilReceived ( 1 ) ;
expect ( initResult ) . toEqual ( { foo : "decrypt(init)" } ) ;
} ) ;
} ) ;
describe ( "next" , ( ) = > {
it ( "emits the next value" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
const expected : TestType = { foo : "next" } ;
let actual : TestType = null ;
@ -114,7 +197,7 @@ describe("UserStateSubject", () => {
@@ -114,7 +197,7 @@ describe("UserStateSubject", () => {
const initialState = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialState ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let actual : TestType = null ;
subject . subscribe ( ( value ) = > {
@ -132,7 +215,7 @@ describe("UserStateSubject", () => {
@@ -132,7 +215,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const shouldUpdate = jest . fn ( ( ) = > true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , shouldUpdate } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , shouldUpdate } ) ;
const nextVal : TestType = { foo : "next" } ;
subject . next ( nextVal ) ;
@ -147,7 +230,7 @@ describe("UserStateSubject", () => {
@@ -147,7 +230,7 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const shouldUpdate = jest . fn ( ( ) = > true ) ;
const dependencyValue = { bar : "dependency" } ;
const subject = new UserStateSubject ( state , {
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
singleUserId $ ,
shouldUpdate ,
dependencies$ : of ( dependencyValue ) ,
@ -165,7 +248,7 @@ describe("UserStateSubject", () => {
@@ -165,7 +248,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const shouldUpdate = jest . fn ( ( ) = > true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , shouldUpdate } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , shouldUpdate } ) ;
const expected : TestType = { foo : "next" } ;
let actual : TestType = null ;
@ -183,7 +266,7 @@ describe("UserStateSubject", () => {
@@ -183,7 +266,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const shouldUpdate = jest . fn ( ( ) = > false ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , shouldUpdate } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , shouldUpdate } ) ;
subject . next ( { foo : "next" } ) ;
await awaitAsync ( ) ;
@ -200,7 +283,7 @@ describe("UserStateSubject", () => {
@@ -200,7 +283,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , nextValue } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , nextValue } ) ;
const nextVal : TestType = { foo : "next" } ;
subject . next ( nextVal ) ;
@ -215,7 +298,7 @@ describe("UserStateSubject", () => {
@@ -215,7 +298,7 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const dependencyValue = { bar : "dependency" } ;
const subject = new UserStateSubject ( state , {
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
singleUserId $ ,
nextValue ,
dependencies$ : of ( dependencyValue ) ,
@ -236,7 +319,11 @@ describe("UserStateSubject", () => {
@@ -236,7 +319,11 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const when $ = new BehaviorSubject ( true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , nextValue , when $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
singleUserId $ ,
nextValue ,
when $ ,
} ) ;
const nextVal : TestType = { foo : "next" } ;
subject . next ( nextVal ) ;
@ -253,7 +340,11 @@ describe("UserStateSubject", () => {
@@ -253,7 +340,11 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const when $ = new BehaviorSubject ( false ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , nextValue , when $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , {
singleUserId $ ,
nextValue ,
when $ ,
} ) ;
const nextVal : TestType = { foo : "next" } ;
subject . next ( nextVal ) ;
@ -265,42 +356,52 @@ describe("UserStateSubject", () => {
@@ -265,42 +356,52 @@ describe("UserStateSubject", () => {
expect ( nextValue ) . toHaveBeenCalled ( ) ;
} ) ;
it ( "waits to evaluate nextValue until singleUserId$ emits" , async ( ) = > {
// this test looks for `nextValue ` because a subscription isn't necessary for
it ( "waits to evaluate `UserState.update` until singleUserId$ emits" , async ( ) = > {
// this test looks for `nextMock ` because a subscription isn't necessary for
// the subject to update.
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new Subject < UserId > ( ) ;
const nextValue = jest . fn ( ( _ , next ) = > next ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , nextValue } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
// precondition: subject doesn't update after `next`
const nextVal : TestType = { foo : "next" } ;
subject . next ( nextVal ) ;
await awaitAsync ( ) ;
expect ( nextValue ) . not . toHaveBeenCalled ( ) ;
expect ( state . nextMock ) . not . toHaveBeenCalled ( ) ;
singleUserId $ . next ( SomeUser ) ;
await awaitAsync ( ) ;
expect ( nextValue ) . toHaveBeenCalled ( ) ;
expect ( state . nextMock ) . toHaveBeenCalledWith ( { foo : "next" } ) ;
} ) ;
it ( "applies constraints$ on init" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
it ( "waits to evaluate `UserState.update` until singleUserEncryptor$ emits" , async ( ) = > {
const state = new FakeSingleUserState < ClassifiedFormat < void , Record < string , never > >> (
SomeUser ,
{ id : null , secret : '{"foo":"init"}' , disclosed : null } ,
) ;
const singleUserEncryptor $ = new Subject < UserBound < " encryptor " , UserEncryptor > > ( ) ;
const subject = new UserStateSubject ( SomeObjectKey , ( ) = > state , { singleUserEncryptor $ } ) ;
const [ result ] = await tracker . pauseUntilReceived ( 1 ) ;
// precondition: subject doesn't update after `next`
const nextVal : TestType = { foo : "next" } ;
subject . next ( nextVal ) ;
await awaitAsync ( ) ;
expect ( state . nextMock ) . not . toHaveBeenCalled ( ) ;
expect ( result ) . toEqual ( { foo : "in" } ) ;
singleUserEncryptor $ . next ( { userId : SomeUser , encryptor : SomeEncryptor } ) ;
await awaitAsync ( ) ;
const encrypted = { foo : "encrypt(next)" } ;
expect ( state . nextMock ) . toHaveBeenCalledWith ( { id : null , secret : encrypted , disclosed : null } ) ;
} ) ;
it ( "applies dynamic constraints" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( DynamicFooMaxLength ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
const expected : TestType = { foo : "next" } ;
const emission = tracker . expectEmission ( ) ;
@ -311,24 +412,11 @@ describe("UserStateSubject", () => {
@@ -311,24 +412,11 @@ describe("UserStateSubject", () => {
expect ( actual ) . toEqual ( { foo : "" } ) ;
} ) ;
it ( "applies constraints$ on constraints$ emission" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
constraints $ . next ( fooMaxLength ( 1 ) ) ;
const [ , result ] = await tracker . pauseUntilReceived ( 2 ) ;
expect ( result ) . toEqual ( { foo : "i" } ) ;
} ) ;
it ( "applies constraints$ on next" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
subject . next ( { foo : "next" } ) ;
@ -341,7 +429,7 @@ describe("UserStateSubject", () => {
@@ -341,7 +429,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
constraints $ . next ( fooMaxLength ( 3 ) ) ;
@ -355,13 +443,17 @@ describe("UserStateSubject", () => {
@@ -355,13 +443,17 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new Subject < StateConstraints < TestType > > ( ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const results : any [ ] = [ ] ;
subject . subscribe ( ( r ) = > {
results . push ( r ) ;
} ) ;
subject . next ( { foo : "next" } ) ;
constraints $ . next ( fooMaxLength ( 3 ) ) ;
await awaitAsync ( ) ;
// `init` is also waiting and is processed before `next`
const [ , nextResult ] = await tracker . pauseUntilReceived ( 2 ) ;
const [ , nextResult ] = results ;
expect ( nextResult ) . toEqual ( { foo : "nex" } ) ;
} ) ;
@ -370,7 +462,7 @@ describe("UserStateSubject", () => {
@@ -370,7 +462,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 3 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
constraints $ . error ( { some : "error" } ) ;
@ -384,7 +476,7 @@ describe("UserStateSubject", () => {
@@ -384,7 +476,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 3 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
constraints $ . complete ( ) ;
@ -399,7 +491,7 @@ describe("UserStateSubject", () => {
@@ -399,7 +491,7 @@ describe("UserStateSubject", () => {
it ( "emits errors" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
const expected : TestType = { foo : "error" } ;
let actual : TestType = null ;
@ -418,7 +510,7 @@ describe("UserStateSubject", () => {
@@ -418,7 +510,7 @@ describe("UserStateSubject", () => {
const initialState = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialState ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let actual : TestType = null ;
subject . subscribe ( {
@ -437,7 +529,7 @@ describe("UserStateSubject", () => {
@@ -437,7 +529,7 @@ describe("UserStateSubject", () => {
const initialState = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialState ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let shouldNotRun = false ;
subject . subscribe ( {
@ -457,7 +549,7 @@ describe("UserStateSubject", () => {
@@ -457,7 +549,7 @@ describe("UserStateSubject", () => {
it ( "emits completes" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let actual = false ;
subject . subscribe ( {
@ -475,7 +567,7 @@ describe("UserStateSubject", () => {
@@ -475,7 +567,7 @@ describe("UserStateSubject", () => {
const initialState = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialState ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let shouldNotRun = false ;
subject . subscribe ( {
@ -496,7 +588,7 @@ describe("UserStateSubject", () => {
@@ -496,7 +588,7 @@ describe("UserStateSubject", () => {
const initialState = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialState ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let timesRun = 0 ;
subject . subscribe ( {
@ -513,11 +605,36 @@ describe("UserStateSubject", () => {
@@ -513,11 +605,36 @@ describe("UserStateSubject", () => {
} ) ;
describe ( "subscribe" , ( ) = > {
it ( "applies constraints$ on init" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
const [ result ] = await tracker . pauseUntilReceived ( 1 ) ;
expect ( result ) . toEqual ( { foo : "in" } ) ;
} ) ;
it ( "applies constraints$ on constraints$ emission" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject ) ;
constraints $ . next ( fooMaxLength ( 1 ) ) ;
const [ , result ] = await tracker . pauseUntilReceived ( 2 ) ;
expect ( result ) . toEqual ( { foo : "i" } ) ;
} ) ;
it ( "completes when singleUserId$ completes" , async ( ) = > {
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
let actual = false ;
subject . subscribe ( {
@ -531,12 +648,32 @@ describe("UserStateSubject", () => {
@@ -531,12 +648,32 @@ describe("UserStateSubject", () => {
expect ( actual ) . toBeTruthy ( ) ;
} ) ;
it ( "completes when singleUserId$ completes" , async ( ) = > {
const state = new FakeSingleUserState < ClassifiedFormat < void , Record < string , never > >> (
SomeUser ,
{ id : null , secret : '{"foo":"init"}' , disclosed : null } ,
) ;
const singleUserEncryptor $ = new Subject < UserBound < " encryptor " , UserEncryptor > > ( ) ;
const subject = new UserStateSubject ( SomeObjectKey , ( ) = > state , { singleUserEncryptor $ } ) ;
let actual = false ;
subject . subscribe ( {
complete : ( ) = > {
actual = true ;
} ,
} ) ;
singleUserEncryptor $ . complete ( ) ;
await awaitAsync ( ) ;
expect ( actual ) . toBeTruthy ( ) ;
} ) ;
it ( "completes when when$ completes" , async ( ) = > {
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const when $ = new BehaviorSubject ( true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , when $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , when $ } ) ;
let actual = false ;
subject . subscribe ( {
@ -557,7 +694,7 @@ describe("UserStateSubject", () => {
@@ -557,7 +694,7 @@ describe("UserStateSubject", () => {
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
const errorUserId = "error" as UserId ;
let error = false ;
@ -572,11 +709,32 @@ describe("UserStateSubject", () => {
@@ -572,11 +709,32 @@ describe("UserStateSubject", () => {
expect ( error ) . toEqual ( { expectedUserId : SomeUser , actualUserId : errorUserId } ) ;
} ) ;
it ( "errors when singleUserEncryptor$ changes" , async ( ) = > {
const state = new FakeSingleUserState < ClassifiedFormat < void , Record < string , never > >> (
SomeUser ,
{ id : null , secret : '{"foo":"init"}' , disclosed : null } ,
) ;
const singleUserEncryptor $ = new Subject < UserBound < " encryptor " , UserEncryptor > > ( ) ;
const subject = new UserStateSubject ( SomeObjectKey , ( ) = > state , { singleUserEncryptor $ } ) ;
const errorUserId = "error" as UserId ;
let error = false ;
subject . subscribe ( {
error : ( e : unknown ) = > {
error = e as any ;
} ,
} ) ;
singleUserEncryptor $ . next ( { userId : errorUserId , encryptor : SomeEncryptor } ) ;
await awaitAsync ( ) ;
expect ( error ) . toEqual ( { expectedUserId : SomeUser , actualUserId : errorUserId } ) ;
} ) ;
it ( "errors when singleUserId$ errors" , async ( ) = > {
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
const expected = { error : "description" } ;
let actual = false ;
@ -591,12 +749,31 @@ describe("UserStateSubject", () => {
@@ -591,12 +749,31 @@ describe("UserStateSubject", () => {
expect ( actual ) . toEqual ( expected ) ;
} ) ;
it ( "errors when singleUserEncryptor$ errors" , async ( ) = > {
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserEncryptor $ = new Subject < UserBound < " encryptor " , UserEncryptor > > ( ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserEncryptor $ } ) ;
const expected = { error : "description" } ;
let actual = false ;
subject . subscribe ( {
error : ( e : unknown ) = > {
actual = e as any ;
} ,
} ) ;
singleUserEncryptor $ . error ( expected ) ;
await awaitAsync ( ) ;
expect ( actual ) . toEqual ( expected ) ;
} ) ;
it ( "errors when when$ errors" , async ( ) = > {
const initialValue : TestType = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialValue ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const when $ = new BehaviorSubject ( true ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , when $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , when $ } ) ;
const expected = { error : "description" } ;
let actual = false ;
@ -616,7 +793,7 @@ describe("UserStateSubject", () => {
@@ -616,7 +793,7 @@ describe("UserStateSubject", () => {
it ( "returns the userId to which the subject is bound" , ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new Subject < UserId > ( ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
expect ( subject . userId ) . toEqual ( SomeUser ) ;
} ) ;
@ -626,7 +803,7 @@ describe("UserStateSubject", () => {
@@ -626,7 +803,7 @@ describe("UserStateSubject", () => {
it ( "emits the next value with an empty constraint" , async ( ) = > {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
const expected : TestType = { foo : "next" } ;
const emission = tracker . expectEmission ( ) ;
@ -642,7 +819,7 @@ describe("UserStateSubject", () => {
@@ -642,7 +819,7 @@ describe("UserStateSubject", () => {
const initialState = { foo : "init" } ;
const state = new FakeSingleUserState < TestType > ( SomeUser , initialState ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const subject = new UserStateSubject ( state , { singleUserId $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
subject . complete ( ) ;
@ -657,7 +834,7 @@ describe("UserStateSubject", () => {
@@ -657,7 +834,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
const expected = fooMaxLength ( 1 ) ;
const emission = tracker . expectEmission ( ) ;
@ -673,7 +850,7 @@ describe("UserStateSubject", () => {
@@ -673,7 +850,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( DynamicFooMaxLength ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
const expected : TestType = { foo : "next" } ;
const emission = tracker . expectEmission ( ) ;
@ -690,7 +867,7 @@ describe("UserStateSubject", () => {
@@ -690,7 +867,7 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const expected = fooMaxLength ( 2 ) ;
const constraints $ = new BehaviorSubject ( expected ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
const emission = tracker . expectEmission ( ) ;
@ -705,7 +882,7 @@ describe("UserStateSubject", () => {
@@ -705,7 +882,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new BehaviorSubject ( fooMaxLength ( 2 ) ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
const expected = fooMaxLength ( 3 ) ;
constraints $ . next ( expected ) ;
@ -722,7 +899,7 @@ describe("UserStateSubject", () => {
@@ -722,7 +899,7 @@ describe("UserStateSubject", () => {
const state = new FakeSingleUserState < TestType > ( SomeUser , { foo : "init" } ) ;
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const constraints $ = new Subject < StateConstraints < TestType > > ( ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
const expected = fooMaxLength ( 3 ) ;
@ -740,7 +917,7 @@ describe("UserStateSubject", () => {
@@ -740,7 +917,7 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const expected = fooMaxLength ( 3 ) ;
const constraints $ = new BehaviorSubject ( expected ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
constraints $ . error ( { some : "error" } ) ;
@ -756,7 +933,7 @@ describe("UserStateSubject", () => {
@@ -756,7 +933,7 @@ describe("UserStateSubject", () => {
const singleUserId $ = new BehaviorSubject ( SomeUser ) ;
const expected = fooMaxLength ( 3 ) ;
const constraints $ = new BehaviorSubject ( expected ) ;
const subject = new UserStateSubject ( state , { singleUserId $ , constraints $ } ) ;
const subject = new UserStateSubject ( SomeKey , ( ) = > state , { singleUserId $ , constraints $ } ) ;
const tracker = new ObservableTracker ( subject . withConstraints $ ) ;
constraints $ . complete ( ) ;