Skip to content

Commit

Permalink
Implement a query string flag override data source (#63)
Browse files Browse the repository at this point in the history
* Implement a flag override data source for query string

* Bump version
  • Loading branch information
adams85 authored Nov 8, 2024
1 parent 9e1ca91 commit 8f8cc52
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 5 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/ux-label-mention.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Mention Teams

on:
pull_request:
types:
- labeled

jobs:
react-to-labels:
uses: configcat/.github/.github/workflows/ux-label-mention.yml@master
with:
id: ${{ github.event.pull_request.number }}
repo: ${{ github.repository }}
secrets:
gh_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.TEXT_REVIEW_SLACK_WEBHOOK_URL }}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "configcat-react",
"version": "4.7.0",
"version": "4.8.0",
"scripts": {
"build": "npm run build:esm && npm run build:cjs",
"build:esm": "tsc -p tsconfig.build.esm.json && gulp esm",
Expand Down
152 changes: 152 additions & 0 deletions src/FlagOverrides.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { cleanup, render, screen } from "@testing-library/react";
import React from "react";
import { useFeatureFlag } from "./ConfigCatHooks";
import ConfigCatProvider from "./ConfigCatProvider";
import type { IQueryStringProvider, IReactAutoPollOptions } from ".";
import { OverrideBehaviour, createFlagOverridesFromQueryParams } from ".";

const sdkKey = "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A";

afterEach(cleanup);

describe("Flag Overrides", () => {
it("Query string override should work - changes not watched", async () => {
const TestComponent = () => {
const { value: featureFlag } = useFeatureFlag("stringDefaultCat", "NOT_CAT");
return (<div>Feature flag value: {featureFlag}</div>);
};

const queryStringProvider = {
currentValue: "?cc-stringDefaultCat=OVERRIDE_CAT&stringDefaultCat=NON_OVERRIDE_CAT"
} satisfies IQueryStringProvider;

const options: IReactAutoPollOptions = {
flagOverrides: createFlagOverridesFromQueryParams(OverrideBehaviour.LocalOverRemote, false, void 0, queryStringProvider)
};

const ui = <ConfigCatProvider sdkKey={sdkKey} options={options}><TestComponent /></ConfigCatProvider>;

await render(ui);
await screen.findByText("Feature flag value: OVERRIDE_CAT", void 0, { timeout: 2000 });

cleanup();
queryStringProvider.currentValue = "?cc-stringDefaultCat=CHANGED_OVERRIDE_CAT";

await render(ui);
await screen.findByText("Feature flag value: OVERRIDE_CAT", void 0, { timeout: 2000 });
});

it("Query string override should work - changes watched", async () => {
const TestComponent = () => {
const { value: featureFlag } = useFeatureFlag("stringDefaultCat", "NOT_CAT");
return (<div>Feature flag value: {featureFlag}</div>);
};

const queryStringProvider = {
currentValue: "?cc-stringDefaultCat=OVERRIDE_CAT"
} satisfies IQueryStringProvider;

const options: IReactAutoPollOptions = {
flagOverrides: createFlagOverridesFromQueryParams(OverrideBehaviour.LocalOverRemote, true, void 0, queryStringProvider)
};

const ui = <ConfigCatProvider sdkKey={sdkKey} options={options}><TestComponent /></ConfigCatProvider>;

await render(ui);
await screen.findByText("Feature flag value: OVERRIDE_CAT", void 0, { timeout: 2000 });

cleanup();
queryStringProvider.currentValue = "?cc-stringDefaultCat=CHANGED_OVERRIDE_CAT";

await render(ui);
await screen.findByText("Feature flag value: CHANGED_OVERRIDE_CAT", void 0, { timeout: 2000 });
});

it("Query string override should work - parsed query string", async () => {
const TestComponent = () => {
const { value: featureFlag } = useFeatureFlag("stringDefaultCat", "NOT_CAT");
return (<div>Feature flag value: {featureFlag}</div>);
};

const queryStringProvider = {
currentValue: { "cc-stringDefaultCat": "OVERRIDE_CAT" as string | ReadonlyArray<string> }
} satisfies IQueryStringProvider;

const options: IReactAutoPollOptions = {
flagOverrides: createFlagOverridesFromQueryParams(OverrideBehaviour.LocalOverRemote, true, void 0, queryStringProvider)
};

const ui = <ConfigCatProvider sdkKey={sdkKey} options={options}><TestComponent /></ConfigCatProvider>;

await render(ui);
await screen.findByText("Feature flag value: OVERRIDE_CAT", void 0, { timeout: 2000 });

cleanup();
queryStringProvider.currentValue = { "cc-stringDefaultCat": ["OVERRIDE_CAT", "CHANGED_OVERRIDE_CAT"] };

await render(ui);
await screen.findByText("Feature flag value: CHANGED_OVERRIDE_CAT", void 0, { timeout: 2000 });
});

it("Query string override should work - respects custom parameter name prefix", async () => {
const TestComponent = () => {
const { value: featureFlag } = useFeatureFlag("stringDefaultCat", "NOT_CAT");
return (<div>Feature flag value: {featureFlag}</div>);
};

const queryStringProvider = {
currentValue: "?stringDefaultCat=OVERRIDE_CAT&cc-stringDefaultCat=NON_OVERRIDE_CAT"
} satisfies IQueryStringProvider;

const options: IReactAutoPollOptions = {
flagOverrides: createFlagOverridesFromQueryParams(OverrideBehaviour.LocalOverRemote, void 0, "", queryStringProvider)
};

const ui = <ConfigCatProvider sdkKey={sdkKey} options={options}><TestComponent /></ConfigCatProvider>;

await render(ui);
await screen.findByText("Feature flag value: OVERRIDE_CAT", void 0, { timeout: 2000 });
});

it("Query string override should work - respects force string value suffix", async () => {
const TestComponent = () => {
const { value: boolFeatureFlag } = useFeatureFlag("boolDefaultFalse", false);
const { value: stringFeatureFlag } = useFeatureFlag("stringDefaultCat", "NOT_CAT");
return (<div>Feature flag values: {boolFeatureFlag ? "true" : "false"} ({typeof boolFeatureFlag}), {stringFeatureFlag} ({typeof stringFeatureFlag})</div>);
};

const queryStringProvider = {
currentValue: "?stringDefaultCat;str=TRUE&boolDefaultFalse=TRUE"
} satisfies IQueryStringProvider;

const options: IReactAutoPollOptions = {
flagOverrides: createFlagOverridesFromQueryParams(OverrideBehaviour.LocalOverRemote, void 0, "", queryStringProvider)
};

const ui = <ConfigCatProvider sdkKey={sdkKey} options={options}><TestComponent /></ConfigCatProvider>;

await render(ui);
await screen.findByText("Feature flag values: true (boolean), TRUE (string)", void 0, { timeout: 2000 });
});

it("Query string override should work - handles query string edge cases", async () => {
const TestComponent = () => {
const { value: featureFlag } = useFeatureFlag("stringDefaultCat", "NOT_CAT");
return (<div>Feature flag value: {featureFlag}</div>);
};

const queryStringProvider = {
currentValue: "?&some&=garbage&&cc-stringDefaultCat=OVERRIDE_CAT&=cc-stringDefaultCat&cc-stringDefaultCat"
} satisfies IQueryStringProvider;

const options: IReactAutoPollOptions = {
flagOverrides: createFlagOverridesFromQueryParams(OverrideBehaviour.LocalOverRemote, void 0, void 0, queryStringProvider)
};

const ui = <ConfigCatProvider sdkKey={sdkKey} options={options}><TestComponent /></ConfigCatProvider>;

await render(ui);
await screen.findByText("Feature flag value:", void 0, { timeout: 2000 });
await expect(() => screen.findByText("Feature flag value: OVERRIDE_CAT", void 0, { timeout: 2000 })).rejects.toThrow();
});
});
Loading

0 comments on commit 8f8cc52

Please sign in to comment.