Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, Input } from '@angular/core';
import { OpenFeatureModule } from './open-feature.module';
import { By } from '@angular/platform-browser';
import { Client, ClientProviderEvents, FlagValue, InMemoryProvider, OpenFeature } from '@openfeature/web-sdk';
import {
Client,
ClientProviderEvents,
FlagValue,
type InMemoryFlagConfiguration,
type InMemoryFlagVariants,
TypedInMemoryProvider,
OpenFeature,
} from '@openfeature/web-sdk';
import { TestingProvider } from '../test/test.utils';
import { v4 } from 'uuid';
import {
Expand Down Expand Up @@ -568,16 +576,21 @@ describe('FeatureFlagDirective', () => {
});
});

async function createTestingModule(config?: {
flagConfiguration?: ConstructorParameters<typeof InMemoryProvider>[0];
async function createTestingModule<
T extends Record<string, InMemoryFlagVariants<string>> = Record<string, InMemoryFlagVariants<string>>,
>(config?: {
flagConfiguration?: InMemoryFlagConfiguration<T>;
providerInitDelay?: number;
}): Promise<{ fixture: ComponentFixture<TestComponent>; provider: TestingProvider; domain: string; client: Client }> {
const domain = v4();
const provider = new TestingProvider(config?.flagConfiguration ?? {}, config?.providerInitDelay ?? 0);
const provider = new TestingProvider(config?.flagConfiguration, config?.providerInitDelay ?? 0);

const fixture = TestBed.configureTestingModule({
imports: [
OpenFeatureModule.forRoot({ provider: new InMemoryProvider(), domainBoundProviders: { [domain]: provider } }),
OpenFeatureModule.forRoot({
provider: new TypedInMemoryProvider(),
domainBoundProviders: { [domain]: provider },
}),
TestComponent,
],
}).createComponent(TestComponent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { TestBed } from '@angular/core/testing';
import { Component, inject } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { firstValueFrom, map } from 'rxjs';
import { JsonValue, OpenFeature, ResolutionDetails } from '@openfeature/web-sdk';
import {
type InMemoryFlagConfiguration,
type InMemoryFlagVariants,
JsonValue,
OpenFeature,
ResolutionDetails,
} from '@openfeature/web-sdk';
import { FeatureFlagService } from './feature-flag.service';
import { AsyncPipe } from '@angular/common';
import { TestingProvider } from '../test/test.utils';
Expand Down Expand Up @@ -74,21 +80,20 @@ describe('FeatureFlagService', () => {
let currentConfigChangeDisabledComponentFixture: ComponentFixture<ConfigChangeDisabledComponent>;
let currentContextChangeDisabledComponentFixture: ComponentFixture<ContextChangeDisabledComponent>;

async function createTestingModule(config?: {
flagConfiguration?: ConstructorParameters<typeof TestingProvider>[0];
providerInitDelay?: number;
}) {
async function createTestingModule<
T extends Record<string, InMemoryFlagVariants<string>> = Record<string, InMemoryFlagVariants<string>>,
>(config?: { flagConfiguration?: InMemoryFlagConfiguration<T>; providerInitDelay?: number }) {
const defaultFlagConfig = {
[FLAG_KEY]: {
variants: { default: true },
defaultVariant: 'default',
defaultVariant: 'default' as const,
disabled: false,
},
};
currentProvider = new TestingProvider(
config?.flagConfiguration ?? defaultFlagConfig,
config?.providerInitDelay ?? 0,
);

currentProvider = config?.flagConfiguration
? new TestingProvider(config.flagConfiguration, config.providerInitDelay ?? 0)
: new TestingProvider(defaultFlagConfig, config?.providerInitDelay ?? 0);
TestBed.resetTestingModule();
TestBed.configureTestingModule({
imports: [OpenFeatureModule.forRoot({ provider: currentProvider })],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { InMemoryProvider } from '@openfeature/web-sdk';
import { type InMemoryFlagConfiguration, type InMemoryFlagVariants, TypedInMemoryProvider } from '@openfeature/web-sdk';

export class TestingProvider extends InMemoryProvider {
export class TestingProvider<
T extends Record<string, InMemoryFlagVariants<string>> = Record<string, InMemoryFlagVariants<string>>,
> extends TypedInMemoryProvider<T> {
constructor(
flagConfiguration: ConstructorParameters<typeof InMemoryProvider>[0],
flagConfiguration: InMemoryFlagConfiguration<T>,
Comment thread
MattIPv4 marked this conversation as resolved.
private delay: number,
) {
super(flagConfiguration);
Expand Down
8 changes: 4 additions & 4 deletions packages/nest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,24 @@ The minimum required version of `@openfeature/server-sdk` currently is `1.7.5`.

### Usage

The example below shows how to use the `OpenFeatureModule` with OpenFeature's `InMemoryProvider`.
The example below shows how to use the `OpenFeatureModule` with OpenFeature's `TypedInMemoryProvider`.

```ts
import { Module } from '@nestjs/common';
import { OpenFeatureModule, InMemoryProvider } from '@openfeature/nestjs-sdk';
import { OpenFeatureModule, TypedInMemoryProvider } from '@openfeature/nestjs-sdk';

@Module({
imports: [
OpenFeatureModule.forRoot({
defaultProvider: new InMemoryProvider({
defaultProvider: new TypedInMemoryProvider({
testBooleanFlag: {
defaultVariant: 'default',
variants: { default: true },
disabled: false,
},
}),
providers: {
differentProvider: new InMemoryProvider(),
differentProvider: new TypedInMemoryProvider(),
},
}),
],
Expand Down
6 changes: 3 additions & 3 deletions packages/nest/test/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { InMemoryProvider } from '@openfeature/server-sdk';
import { TypedInMemoryProvider } from '@openfeature/server-sdk';
import type { EvaluationContext } from '@openfeature/server-sdk';
import type { ExecutionContext } from '@nestjs/common';
import { OpenFeatureModule } from '../src';

export const defaultProvider = new InMemoryProvider({
export const defaultProvider = new TypedInMemoryProvider({
testBooleanFlag: {
defaultVariant: 'default',
variants: { default: true },
Expand Down Expand Up @@ -38,7 +38,7 @@ export const defaultProvider = new InMemoryProvider({
});

export const providers = {
domainScopedClient: new InMemoryProvider({
domainScopedClient: new TypedInMemoryProvider({
testBooleanFlag: {
defaultVariant: 'default',
variants: { default: true },
Expand Down
14 changes: 10 additions & 4 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,16 @@ See the [package.json](./package.json) for the required versions.

The `OpenFeatureProvider` is a [React context provider](https://react.dev/reference/react/createContext#provider) which represents a scope for feature flag evaluations within a React application.
It binds an OpenFeature client to all evaluations within child components, and allows the use of evaluation hooks.
The example below shows how to use the `OpenFeatureProvider` with OpenFeature's `InMemoryProvider`.
The example below shows how to use the `OpenFeatureProvider` with OpenFeature's `TypedInMemoryProvider`.

```tsx
import { EvaluationContext, OpenFeatureProvider, useFlag, OpenFeature, InMemoryProvider } from '@openfeature/react-sdk';
import {
EvaluationContext,
OpenFeatureProvider,
useFlag,
OpenFeature,
TypedInMemoryProvider,
} from '@openfeature/react-sdk';

const flagConfig = {
'new-message': {
Expand All @@ -118,11 +124,11 @@ const flagConfig = {
return 'off';
},
},
};
} as const;

// Instantiate and set our provider (be sure this only happens once)!
// Note: there's no need to await its initialization, the React SDK handles re-rendering and suspense for you!
OpenFeature.setProvider(new InMemoryProvider(flagConfig));
OpenFeature.setProvider(new TypedInMemoryProvider(flagConfig));

// Enclose your content in the configured provider
function App() {
Expand Down
34 changes: 18 additions & 16 deletions packages/react/src/provider/test-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { JsonValue, Provider } from '@openfeature/web-sdk';
import { InMemoryProvider, NOOP_PROVIDER, OpenFeature } from '@openfeature/web-sdk';
import type { InMemoryFlagConfiguration, JsonValue, Provider } from '@openfeature/web-sdk';
import { TypedInMemoryProvider, NOOP_PROVIDER, OpenFeature } from '@openfeature/web-sdk';
import React from 'react';
import type { NormalizedOptions } from '../options';
import { OpenFeatureProvider } from './provider';

type FlagValueMap = { [flagKey: string]: JsonValue };
type FlagConfig = ConstructorParameters<typeof InMemoryProvider>[0];
type TestProviderProps = Omit<React.ComponentProps<typeof OpenFeatureProvider>, 'client'> &
(
| {
Expand Down Expand Up @@ -36,7 +35,7 @@ const TEST_VARIANT = 'test-variant';
const TEST_PROVIDER = 'test-provider';

// internal provider which is basically the in-memory provider with a simpler config and some optional fake delays
class TestProvider extends InMemoryProvider {
class TestProvider extends TypedInMemoryProvider {
// initially make this undefined, we still set it if a delay is specified
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - For maximum compatibility with previous versions, we ignore a possible TS error here,
Expand All @@ -54,18 +53,21 @@ class TestProvider extends InMemoryProvider {
private delay = 0,
) {
// convert the simple flagValueMap into an in-memory config
const flagConfig = Object.entries(flagValueMap).reduce((acc: FlagConfig, flag): FlagConfig => {
return {
...acc,
[flag[0]]: {
variants: {
[TEST_VARIANT]: flag[1],
const flagConfig = Object.entries(flagValueMap).reduce(
(acc: InMemoryFlagConfiguration, flag): InMemoryFlagConfiguration => {
return {
...acc,
[flag[0]]: {
variants: {
[TEST_VARIANT]: flag[1],
},
defaultVariant: TEST_VARIANT,
disabled: false,
},
defaultVariant: TEST_VARIANT,
disabled: false,
},
};
}, {});
};
},
{},
);
super(flagConfig);
// only define and init if there's a non-zero delay specified
this.initialize = this.delay ? this.delayedInitialize.bind(this) : undefined;
Expand All @@ -77,7 +79,7 @@ class TestProvider extends InMemoryProvider {
}

/**
* A React Context provider based on the {@link InMemoryProvider}, specifically built for testing.
* A React Context provider based on the {@link TypedInMemoryProvider}, specifically built for testing.
* Use this for testing components that use flag evaluation hooks.
* @param {TestProviderProps} testProviderOptions options for the OpenFeatureTestProvider
* @returns {OpenFeatureProvider} OpenFeatureTestProvider
Expand Down
10 changes: 5 additions & 5 deletions packages/react/test/declarative.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/rea
import { render, screen } from '@testing-library/react';
import { FeatureFlag } from '../src/declarative/FeatureFlag'; // Assuming Feature.tsx is in the same directory or adjust path
import type { Provider } from '../src';
import { InMemoryProvider, OpenFeature, OpenFeatureProvider, ProviderStatus } from '../src';
import { TypedInMemoryProvider, OpenFeature, OpenFeatureProvider, ProviderStatus } from '../src';
import type { EvaluationDetails } from '@openfeature/core';

describe('Feature Component', () => {
Expand All @@ -17,7 +17,7 @@ describe('Feature Component', () => {
const STRING_FLAG_VARIANT = 'greeting';
const STRING_FLAG_VALUE = 'hi';

const FLAG_CONFIG: ConstructorParameters<typeof InMemoryProvider>[0] = {
const FLAG_CONFIG = {
[BOOL_FLAG_KEY]: {
disabled: false,
variants: {
Expand All @@ -42,10 +42,10 @@ describe('Feature Component', () => {
},
defaultVariant: STRING_FLAG_VARIANT,
},
};
} as const;

const makeProvider = () => {
return new InMemoryProvider(FLAG_CONFIG);
return new TypedInMemoryProvider(FLAG_CONFIG);
};

const makeAsyncProvider = () => {
Expand Down Expand Up @@ -245,7 +245,7 @@ describe('Feature Component', () => {

it('should support function-based fallback for error conditions', () => {
// Create a provider that will cause an error
const errorProvider = new InMemoryProvider({});
const errorProvider = new TypedInMemoryProvider({});
OpenFeature.setProvider('error-test', errorProvider);

const fallbackFunction = jest.fn((details: EvaluationDetails<boolean>) => (
Expand Down
29 changes: 13 additions & 16 deletions packages/react/test/evaluation.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { jest } from '@jest/globals';
import type { ProviderEmittableEvents } from '@openfeature/web-sdk';
import type { InMemoryFlagConfiguration, InMemoryFlagVariants, ProviderEmittableEvents } from '@openfeature/web-sdk';
import { ClientProviderEvents } from '@openfeature/web-sdk';
import type { FlagConfiguration } from '@openfeature/web-sdk/src/provider/in-memory-provider/flag-configuration';
import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/react-testing-library/setup
import { act, render, renderHook, screen, waitFor } from '@testing-library/react';
import * as React from 'react';
import { startTransition, useState } from 'react';
import type { EvaluationContext, EvaluationDetails, EventContext, Hook } from '../src/';
import {
ErrorCode,
InMemoryProvider,
TypedInMemoryProvider,
OpenFeature,
OpenFeatureProvider,
StandardResolutionReasons,
Expand All @@ -28,12 +27,10 @@ import { HookFlagQuery } from '../src/internal/hook-flag-query';
import { TestingProvider } from './test.utils';

// custom provider to have better control over the emitted events
class CustomEventInMemoryProvider extends InMemoryProvider {
putConfigurationWithCustomEvent(
flagConfiguration: FlagConfiguration,
event: ProviderEmittableEvents,
eventContext: EventContext,
) {
class CustomEventInMemoryProvider extends TypedInMemoryProvider {
putConfigurationWithCustomEvent<
T extends Record<string, InMemoryFlagVariants<string>> = Record<string, InMemoryFlagVariants<string>>,
>(flagConfiguration: InMemoryFlagConfiguration<T>, event: ProviderEmittableEvents, eventContext: EventContext) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this['_flagConfiguration'] = { ...flagConfiguration }; // private access hack
this.events.emit(event, eventContext);
Expand All @@ -60,7 +57,7 @@ describe('evaluation', () => {
const REASON_ATTR_VALUE = StandardResolutionReasons.STATIC;
const TYPE_ATTR = 'data-type';
const BUTTON_TEXT = 'button';
const FLAG_CONFIG: ConstructorParameters<typeof InMemoryProvider>[0] = {
const FLAG_CONFIG = {
[BOOL_FLAG_KEY]: {
disabled: false,
variants: {
Expand Down Expand Up @@ -100,14 +97,14 @@ describe('evaluation', () => {
off: false,
on: true,
},
contextEvaluator(ctx) {
contextEvaluator(ctx: EvaluationContext) {
return ctx.change ? 'on' : 'off';
},
},
};
} as const;

const makeProvider = () => {
return new InMemoryProvider(FLAG_CONFIG);
return new TypedInMemoryProvider(FLAG_CONFIG);
};

OpenFeature.setProvider(EVALUATION, makeProvider());
Expand Down Expand Up @@ -575,7 +572,7 @@ describe('evaluation', () => {
return FLAG_VARIANT_A;
},
},
};
} as const;

afterEach(() => {
OpenFeature.clearProviders();
Expand Down Expand Up @@ -1011,7 +1008,7 @@ describe('evaluation', () => {
it('should reflect provider state changes on re-render even without provider events', async () => {
let providerValue = 'initial-value';

class SilentUpdateProvider extends InMemoryProvider {
class SilentUpdateProvider extends TypedInMemoryProvider {
resolveBooleanEvaluation() {
return {
value: true,
Expand Down Expand Up @@ -1077,7 +1074,7 @@ describe('evaluation', () => {
});

it('should update flag value when flag key prop changes without provider events', async () => {
const provider = new InMemoryProvider({
const provider = new TypedInMemoryProvider({
'flag-a': {
disabled: false,
variants: { on: 'value-a' },
Expand Down
4 changes: 2 additions & 2 deletions packages/react/test/provider.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { EvaluationContext } from '@openfeature/web-sdk';
import { InMemoryProvider, OpenFeature, ProviderEvents } from '@openfeature/web-sdk';
import { TypedInMemoryProvider, OpenFeature, ProviderEvents } from '@openfeature/web-sdk';
import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/react-testing-library/setup
import { render, renderHook, screen, waitFor, fireEvent, act } from '@testing-library/react';
import * as React from 'react';
Expand Down Expand Up @@ -314,7 +314,7 @@ describe('OpenFeatureProvider', () => {
const DOMAIN1 = 'Wills Domain';
OpenFeature.setProvider(DOMAIN1, suspendingProvider());
OpenFeature.setProvider(
new InMemoryProvider({
new TypedInMemoryProvider({
globalFlagsHere: {
defaultVariant: 'a',
variants: {
Expand Down
Loading
Loading