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
24 changes: 24 additions & 0 deletions .github/workflows/client-testing-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: tooling/client-testing-plugin

on:
push:
branches: [main, 'feat/**']
paths-ignore:
- '**.md' #Do not need to run CI for markdown changes.
pull_request:
branches: [main, 'feat/**']
paths-ignore:
- '**.md'

jobs:
build-test-client-testing-plugin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: ./actions/setup-yarn
- id: shared
name: Shared CI Steps
uses: ./actions/ci
with:
workspace_name: '@launchdarkly/client-testing-plugin'
workspace_path: packages/tooling/client-testing-plugin
18 changes: 18 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ on:
- packages/store/node-server-sdk-dynamodb
- packages/telemetry/node-server-sdk-otel
- packages/tooling/jest
- packages/tooling/client-testing-plugin
- packages/sdk/browser
- packages/sdk/server-ai
- packages/ai-providers/server-ai-openai
Expand Down Expand Up @@ -107,6 +108,7 @@ jobs:
package-sdk-electron-released: ${{ steps.release.outputs['packages/sdk/electron--release_created'] }}
package-sdk-react-released: ${{ steps.release.outputs['packages/sdk/react--release_created'] }}
package-sdk-openfeature-node-server-released: ${{ steps.release.outputs['packages/sdk/openfeature-node-server--release_created'] }}
package-tooling-client-testing-plugin-released: ${{ steps.release.outputs['packages/tooling/client-testing-plugin--release_created'] }}
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0
id: release
Expand Down Expand Up @@ -391,6 +393,22 @@ jobs:
workspace_path: packages/tooling/jest
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}

release-tooling-client-testing-plugin:
runs-on: ubuntu-latest
needs: ['release-please', 'release-sdk-client']
permissions:
id-token: write
contents: write
if: ${{ always() && !failure() && !cancelled() && needs.release-please.outputs.package-tooling-client-testing-plugin-released == 'true'}}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- id: release-tooling-client-testing-plugin
name: Full release of packages/tooling/client-testing-plugin
uses: ./actions/full-release
with:
workspace_path: packages/tooling/client-testing-plugin
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}

release-server-ai:
runs-on: ubuntu-latest
needs: ['release-please', 'release-sdk-server']
Expand Down
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"packages/store/node-server-sdk-redis": "4.2.28",
"packages/telemetry/node-server-sdk-otel": "1.3.16",
"packages/tooling/jest": "1.0.20",
"packages/tooling/client-testing-plugin": "0.0.1",
"packages/sdk/shopify-oxygen": "0.1.12",
"packages/sdk/react": "4.1.0",
"packages/sdk/openfeature-node-server": "1.2.2"
Expand Down
135 changes: 95 additions & 40 deletions packages/tooling/client-testing-plugin/README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,128 @@
# LaunchDarkly Client Testing Plugin

[![NPM][client-testing-plugin-npm-badge]][client-testing-plugin-npm-link]
[![Actions Status][client-testing-plugin-ci-badge]][client-testing-plugin-ci]
[![Documentation][client-testing-plugin-ghp-badge]][client-testing-plugin-ghp-link]
[![NPM][client-testing-plugin-dm-badge]][client-testing-plugin-npm-link]
[![NPM][client-testing-plugin-dt-badge]][client-testing-plugin-npm-link]

> [!CAUTION]
> This plugin is in pre-release and not subject to backwards compatibility
> guarantees. The API may change based on feedback.
>
> Pin to a specific minor version and review the [changelog](CHANGELOG.md) before upgrading.

A testing plugin for LaunchDarkly client-side JavaScript SDKs. Use it to inject deterministic flag values into a real SDK client during unit tests, integration tests, and local development.

## Install

```bash
yarn add --dev @launchdarkly/client-testing-plugin @launchdarkly/js-client-sdk
yarn add --dev @launchdarkly/client-testing-plugin
```

You also need the client-side SDK you're testing against. The plugin declares each supported SDK as an optional peer dependency, so only install the one you use:

```bash
# pick one
yarn add --dev @launchdarkly/js-client-sdk
yarn add --dev @launchdarkly/react-sdk
```

## Usage

```ts
import { createClient } from '@launchdarkly/js-client-sdk';
import { TestData } from '@launchdarkly/client-testing-plugin';
The plugin ships a small per-SDK wrapper under a subpath export. Each wrapper returns a real SDK client with `TestData` already registered and the required test settings applied -- no boilerplate.

// Seed with a base set of flag values.
const td = new TestData({
'new-ui': true,
greeting: 'Hello!',
});
### `@launchdarkly/js-client-sdk`

const client = createClient(
'<ldClientSideId>', // placeholder -- fill in only for real environments
```ts
import { createTestClient } from '@launchdarkly/client-testing-plugin/js-client-sdk';

const { client, testData } = createTestClient(
{ kind: 'user', key: 'tester' },
{
plugins: [td],
sendEvents: false,
streaming: false,
},
{ 'new-ui': true, greeting: 'Hello!' },
);

await client.start({ bootstrap: {} });

client.boolVariation('new-ui', false); // true
client.stringVariation('greeting', '(default)'); // 'Hello!'

// Update flags at any time -- the SDK fires change events. Setters chain.
td.setBool('new-ui', false).setString('greeting', 'Welcome');
// Update flags at any time - the SDK fires change events. Setters chain.
testData.setBool('new-ui', false).setString('greeting', 'Welcome');
```

### Required LD client options
In order to successfully set up a LD client to use the testing plugin, you **MUST** set the following options:
### `@launchdarkly/react-sdk`

- **`plugins: [td]`** - registers the testing plugin so it can inject overrides.
- **`sendEvents: false`** - keeps analytics events off in tests.
- **`streaming: false`** - (required for `js-client-sdk` and its derivativs, eg `react-sdk`), having streaming on will cause the `js-client-sdk` to automatically open a streaming connection.
- **`bootstrap: {}` (passed to `start()`)** -- gives the SDK an empty initial flag set so it does not block on a network identify call. The plugin's overrides are applied immediately afterward.
```ts
import { createTestClient } from '@launchdarkly/client-testing-plugin/react-sdk';
import { createLDReactProviderWithClient } from '@launchdarkly/react-sdk';

> Refer to the usage example above.
const { client, testData } = createTestClient(
{ kind: 'user', key: 'tester' },
{ 'show-banner': true, greeting: 'Welcome' },
);

## API
await client.start({ bootstrap: {} });

const LDProvider = createLDReactProviderWithClient(client);
render(<LDProvider><MyComponent /></LDProvider>);

// Drive UI updates by mutating testData in `act(...)`.
testData.setString('greeting', 'Updated');
```

A runnable example lives under [`example/sdks/react-sdk/`](./example/sdks/react-sdk/).

### `TestData`
## Manual setup (advanced)

If you need to wire `TestData` into an SDK the plugin does not yet provide a wrapper for, or you want full control over client options, register `TestData` as a plugin yourself:

```ts
class TestData implements LDPlugin {
constructor(initialFlags?: { [key: string]: LDFlagValue });
import { createClient } from '@launchdarkly/js-client-sdk';
import { TestData } from '@launchdarkly/client-testing-plugin';

const td = new TestData({ 'new-ui': true });

setBool(key: string, value: boolean): this;
setString(key: string, value: string): this;
setNumber(key: string, value: number): this;
setJson(key: string, value: object | unknown[]): this;
const client = createClient(
'<ldClientSideId>',
{ kind: 'user', key: 'tester' },
{
plugins: [td],
sendEvents: false,
streaming: false,
},
);

remove(key: string): this;
clear(): this;
}
await client.start({ bootstrap: {} });
```

- **`new TestData(initialFlags?)`** -- seed the instance with a base map of flag keys to values. The values are applied to the SDK client when it initializes.
- **`setBool` / `setString` / `setNumber` / `setJson`** -- set or update a single flag. If the SDK is already running, the change propagates immediately and listeners receive a `change:<key>` event. Every write applies the override, even when the value is unchanged -- mirroring a real connection that can re-deliver a flag and fire a `change` event without the value differing.
- **`remove(key)`** -- drop the override for a single key. If the SDK is connected, also calls `removeOverride`.
- **`clear()`** -- drop all overrides. Useful in `beforeEach` for shared `TestData` instances.
The required SDK settings when wiring manually:

- **`plugins: [td]`** -- registers the testing plugin so it can inject overrides.
- **`sendEvents: false`** -- keeps analytics events off in tests.
- **`streaming: false`** -- (required for `js-client-sdk` and its derivatives, e.g. `react-sdk`); leaving streaming on causes the SDK to open a streaming connection.
- **`bootstrap: {}`** -- passed to `start()`; gives the SDK an empty initial flag set so initialization does not block on a network identify call. The plugin's overrides are applied immediately afterward.

## About LaunchDarkly

- LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
- Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
- Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
- Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
- Grant access to certain features based on user attributes, like payment plan (eg: users on the 'gold' plan get access to more features than users in the 'silver' plan).
- Disable parts of your application to facilitate maintenance, without taking everything offline.
- LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
- Explore LaunchDarkly
- [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information
- [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides
- [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation
- [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates

[client-testing-plugin-ci-badge]: https://github.com/launchdarkly/js-core/actions/workflows/client-testing-plugin.yml/badge.svg
[client-testing-plugin-ci]: https://github.com/launchdarkly/js-core/actions/workflows/client-testing-plugin.yml
[client-testing-plugin-npm-badge]: https://img.shields.io/npm/v/@launchdarkly/client-testing-plugin.svg?style=flat-square
[client-testing-plugin-npm-link]: https://www.npmjs.com/package/@launchdarkly/client-testing-plugin
[client-testing-plugin-ghp-badge]: https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8
[client-testing-plugin-ghp-link]: https://launchdarkly.github.io/js-core/packages/tooling/client-testing-plugin/docs/
[client-testing-plugin-dm-badge]: https://img.shields.io/npm/dm/@launchdarkly/client-testing-plugin.svg?style=flat-square
[client-testing-plugin-dt-badge]: https://img.shields.io/npm/dt/@launchdarkly/client-testing-plugin.svg?style=flat-square
10 changes: 5 additions & 5 deletions packages/tooling/client-testing-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@
"lint:fix": "yarn run lint --fix"
},
"dependencies": {
"@launchdarkly/js-client-sdk-common": "workspace:^"
"@launchdarkly/js-client-sdk-common": "1.28.0"
},
"peerDependencies": {
"@launchdarkly/js-client-sdk": "workspace:^",
"@launchdarkly/react-sdk": "workspace:^"
"@launchdarkly/js-client-sdk": ">=4.8.0",
"@launchdarkly/react-sdk": ">=4.1.0"
},
"peerDependenciesMeta": {
"@launchdarkly/js-client-sdk": {
Expand All @@ -64,8 +64,8 @@
},
"devDependencies": {
"@eslint/js": "^9.0.0",
"@launchdarkly/js-client-sdk": "workspace:^",
"@launchdarkly/react-sdk": "workspace:^",
"@launchdarkly/js-client-sdk": "4.8.0",
"@launchdarkly/react-sdk": "4.1.0",
"@types/jest": "^29.5.0",
"eslint": "^9.0.0",
"eslint-import-resolver-typescript": "^4.0.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/tooling/client-testing-plugin/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": ["../../../typedoc.base.json"],
"entryPoints": ["src/index.ts", "src/clients/js-client-sdk.ts", "src/clients/react-sdk.ts"],
"out": "docs"
}
24 changes: 23 additions & 1 deletion release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,15 @@
]
},
"packages/shared/common": {},
"packages/shared/sdk-client": {},
"packages/shared/sdk-client": {
"extra-files": [
{
"type": "json",
"path": "/packages/tooling/client-testing-plugin/package.json",
"jsonpath": "$.dependencies['@launchdarkly/js-client-sdk-common']"
}
]
},
"packages/shared/sdk-server": {},
"packages/shared/sdk-server-edge": {},
"packages/shared/akamai-edgeworker-sdk": {},
Expand Down Expand Up @@ -257,6 +265,11 @@
"type": "json",
"path": "example-fdv2/package.json",
"jsonpath": "$.dependencies['@launchdarkly/js-client-sdk']"
},
{
"type": "json",
"path": "/packages/tooling/client-testing-plugin/package.json",
"jsonpath": "$.devDependencies['@launchdarkly/js-client-sdk']"
}
]
},
Expand Down Expand Up @@ -313,6 +326,10 @@
"packages/tooling/jest": {
"release-as": "1.0.0"
},
"packages/tooling/client-testing-plugin": {
"bump-minor-pre-major": true,
"prerelease": true
},
"packages/sdk/combined-browser": {
"bump-minor-pre-major": true
},
Expand All @@ -338,6 +355,11 @@
"type": "json",
"path": "examples/vercel-edge/package.json",
"jsonpath": "$.dependencies['@launchdarkly/react-sdk']"
},
{
"type": "json",
"path": "/packages/tooling/client-testing-plugin/package.json",
"jsonpath": "$.devDependencies['@launchdarkly/react-sdk']"
}
]
},
Expand Down
Loading