Skip to content

Commit 50c86ed

Browse files
Merge pull request #34 from codedbychavez/add-additional-test-cases
Add additional test cases
2 parents dc4b562 + eac0e76 commit 50c86ed

File tree

2 files changed

+338
-40
lines changed

2 files changed

+338
-40
lines changed

tests/mocks/ConfigCatClientMocks.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import { isAllowedValue } from "configcat-common/lib/RolloutEvaluator";
12
import { 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

38
export 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

Comments
 (0)