1+ import { isAllowedValue } from "configcat-common/lib/RolloutEvaluator" ;
12import { ClientCacheState , HookEvents , IConfig , IConfigCatClient , IConfigCatClientSnapshot , IEvaluationDetails , RefreshResult , SettingKeyValue , SettingTypeOf , SettingValue , User } from "../../src" ;
3+ import { DefaultEventEmitter } from "configcat-common/lib/DefaultEventEmitter" ;
4+ import { IEventEmitter } from "configcat-common" ;
5+ import * as ConfigJson from "configcat-common/lib/ConfigJson" ;
6+ import { Config } from "configcat-common/lib/ProjectConfig" ;
27
38export class ConfigCatClientMockBase implements IConfigCatClient {
49 constructor (
@@ -91,4 +96,103 @@ export class ConfigCatClientSnapshotMockBase implements IConfigCatClientSnapshot
9196 getValueDetails < T extends SettingValue > ( key : string , defaultValue : T , user ?: User | undefined ) : IEvaluationDetails < SettingTypeOf < T > > {
9297 throw new Error ( "Method not implemented." ) ;
9398 }
99+ }
100+
101+ export class SimpleValueConfigCatClientMock extends ConfigCatClientMockBase {
102+ private readonly eventEmitter = new DefaultEventEmitter ( ) ;
103+ private readonly readyPromise : Promise < ClientCacheState > ;
104+ private signalReady : ( value : NonNullable < SettingValue > ) => void ;
105+ private cacheState = ClientCacheState . NoFlagData ;
106+ private currentValue : NonNullable < SettingValue > ;
107+ private currentConfig : Config | null = null ;
108+
109+ constructor ( private key : string ) {
110+ super ( ) ;
111+
112+ this . readyPromise = new Promise < NonNullable < SettingValue > > ( resolve => this . signalReady = resolve )
113+ . then ( initialValue => {
114+ this . currentValue = initialValue ;
115+ this . currentConfig = createConfigFromValue ( this . key , initialValue ) ;
116+ this . cacheState = ClientCacheState . HasUpToDateFlagData ;
117+ ( this . eventEmitter as IEventEmitter < HookEvents > ) . emit ( "clientReady" , this . cacheState ) ;
118+ return this . cacheState ;
119+ } ) ;
120+ }
121+
122+ /** Call this method to simulate the `clientReady` event and to provide an initial feature flag value. */
123+ async setReady ( initialValue : NonNullable < SettingValue > ) : Promise < void > {
124+ this . signalReady ( initialValue ) ;
125+
126+ // Allow async continuations to run.
127+ await new Promise < void > ( resolve => setTimeout ( ( ) => resolve ( ) , 0 ) ) ;
128+ }
129+
130+ /** Call this method to simulate the `configChanged` event and to update the current feature flag value. */
131+ changeValue ( newValue : NonNullable < SettingValue > ) : void {
132+ if ( this . currentValue === newValue ) {
133+ return ;
134+ }
135+
136+ this . currentValue = newValue ;
137+ this . currentConfig = createConfigFromValue ( this . key , newValue ) ;
138+ ( this . eventEmitter as IEventEmitter < HookEvents > ) . emit ( "configChanged" , this . currentConfig ) ;
139+ }
140+
141+ async getValueAsync < T extends SettingValue > ( key : string , defaultValue : T , user ?: User | undefined ) : Promise < SettingTypeOf < T > > {
142+ await this . readyPromise ;
143+
144+ if ( key === this . key ) {
145+ return ( isValidSettingValue ( this . currentValue , defaultValue ) ? this . currentValue : defaultValue ) as SettingTypeOf < T > ;
146+ }
147+
148+ return super . getValueAsync ( key , defaultValue , user ) ;
149+ }
150+
151+ snapshot ( ) {
152+ const client = this ;
153+
154+ return new ( class extends ConfigCatClientSnapshotMockBase {
155+ getValue < T extends SettingValue > ( key : string , defaultValue : T , user ?: User | undefined ) : SettingTypeOf < T > {
156+ if ( key === client . key ) {
157+ return ( isValidSettingValue ( client . currentValue , defaultValue ) ? client . currentValue : defaultValue ) as SettingTypeOf < T > ;
158+ }
159+
160+ return super . getValue ( key , defaultValue , user ) ;
161+ }
162+ } ) ( this . cacheState , this . currentConfig ) ;
163+ }
164+
165+ waitForReady ( ) : Promise < ClientCacheState > {
166+ return this . readyPromise ;
167+ }
168+
169+ on < TEventName extends keyof HookEvents > ( eventName : TEventName , listener : ( ...args : HookEvents [ TEventName ] ) => void ) : this {
170+ this . eventEmitter . on ( eventName , listener as ( ...args : any [ ] ) => void ) ;
171+ return this ;
172+ }
173+
174+ off < TEventName extends keyof HookEvents > ( eventName : TEventName , listener : ( ...args : HookEvents [ TEventName ] ) => void ) : this {
175+ this . eventEmitter . off ( eventName , listener as ( ...args : any [ ] ) => void ) ;
176+ return this ;
177+ }
178+ } ;
179+
180+ function createConfigFromValue ( key : string , value : NonNullable < SettingValue > ) : Config {
181+ const [ settingType , settingValue ] : [ ConfigJson . SettingType , ConfigJson . SettingValue ] =
182+ typeof value === "boolean" ? [ ConfigJson . SettingType . Boolean , { b : value } ] :
183+ typeof value === "string" ? [ ConfigJson . SettingType . String , { s : value } ] :
184+ Number . isInteger ( this . changeValue ) ? [ ConfigJson . SettingType . Int , { i : value } ] :
185+ [ ConfigJson . SettingType . Double , { d : value } ] ;
186+
187+ const configJson : Omit < ConfigJson . Config , "p" > = {
188+ f : { [ key ] : { t : settingType , v : settingValue } as ConfigJson . SettingUnion }
189+ } ;
190+
191+ return new Config ( configJson ) ;
192+ }
193+
194+ function isValidSettingValue ( settingValue : NonNullable < SettingValue > , defaultValue : SettingValue ) {
195+ return defaultValue == null
196+ ? isAllowedValue ( settingValue )
197+ : typeof settingValue === typeof defaultValue ;
94198}
0 commit comments