Commit 2de5c27
feat: create @launchdarkly/js-contract-test-utils package (#1163)
**Requirements**
- [ ] I have added test coverage for new or changed functionality
- [x] I have followed the repository's [pull request submission
guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests)
- [x] I have validated my changes against all supported platform
versions
**Related issues**
- [SDK-2008](https://launchdarkly.atlassian.net/browse/SDK-2008)
- Reference branch: `devin/SDK-1866-1772645970`
**Describe the solution you've provided**
Creates a new **private** package `@launchdarkly/js-contract-test-utils`
at `packages/tooling/contract-test-utils/` that consolidates duplicated
contract test infrastructure shared across client-side SDKs, and
migrates browser, electron, react-native, and React SDK contract tests
to consume it.
### Package exports
| Import Path | Contents | Resolution |
|---|---|---|
| `.` | Universal types (`CommandParams`, `ConfigParams`), `makeLogger`,
`ClientPool` | Source `.ts` |
| `./client` | Re-exports universal + client-side `TestHook`
(fetch-based) | Source `.ts` |
All types import `LDContext`/`LDEvaluationReason`/`LDLogger` from
`@launchdarkly/js-client-sdk-common` for cross-SDK compatibility.
### Migrations included
For each of **browser**, **electron**, **react-native**, and **React**
contract test entities:
- Deleted local `CommandParams.ts`, `ConfigParams.ts`, `makeLogger.ts`,
`TestHook.ts`
- Updated consuming files (`ClientEntity.ts`, `ClientFactory.ts`,
`TestHarnessWebSocket.ts`, `ClientRoot.tsx` as applicable) to import
from `@launchdarkly/js-contract-test-utils/client`
- Added `"@launchdarkly/js-contract-test-utils": "workspace:^"`
dependency
### Review feedback addressed
- Converted `CommandType`, `ValueType`, `HookStage` from TypeScript
`enum` to `as const` objects with derived union types per repo
guidelines
- Fixed browser entity dependency versions from `"*"` to `"workspace:^"`
- Consolidated duplicate `HookStage` definition (was in both
`CommandParams.ts` and `ConfigParams.ts`, now only in
`CommandParams.ts`)
- Removed `.js` extensions from shared package internal imports for
Next.js Turbopack compatibility (React SDK uses `next build` with
Turbopack, which resolves from `.ts` source and can't find `.js`
targets)
- Enabled `unstable_enablePackageExports` in react-native entity's
`metro.config.js` so Metro can resolve the `./client` subpath export
- Fixed `ClientPool.nextId()` off-by-one: now returns current counter
value before incrementing, so the first ID is `"0"` (matching existing
consumer patterns)
- Simplified `ClientPool.add()` API: now calls `nextId()` internally and
returns the assigned ID (`add(client: T): string` instead of requiring
the caller to pass an ID)
**Human review checklist**
- [ ] **README example is stale**:
`packages/tooling/contract-test-utils/README.md` shows old `ClientPool`
usage (`pool.nextId()` then `pool.add(id, entity)`) — should be updated
to use the new `pool.add(entity)` API that returns the ID
- [ ] **`as const` type compatibility**: Verify `CommandType`,
`ValueType`, and `HookStage` union types derived from `as const` objects
are assignable everywhere the old `enum`/`type` union was used
(especially in switch statements and type narrowing)
- [ ] **Metro `unstable_enablePackageExports`**: Confirm this doesn't
cause unexpected resolution changes for other dependencies in the
react-native entity
**Items for reviewer attention**
1. **No unit tests** — This is test infrastructure itself (test coverage
checkbox unchecked). Confirm whether tests are expected at this stage.
2. **Source-only resolution** — All exports resolve to `.ts` source
files (no compiled `dist/`). Internal imports use extensionless paths
for compatibility with both bundlers (Vite, esbuild, Metro) and Next.js
Turbopack. If a future consumer requires compiled ESM output, `.js`
extensions would need to be added back (per Node.js ESM spec), but
current consumers work correctly with source-only resolution.
3. **Server-side migration not included** — Server-node and Shopify
Oxygen contract tests are not migrated in this PR (deferred to follow-up
per earlier discussion). The `ClientPool` utility is exported but not
yet consumed.
4. **Metro bundler configuration** — React Native's Metro bundler
doesn't support `package.json` `exports` field by default. Enabled
`unstable_enablePackageExports` in the react-native entity's
`metro.config.js` to resolve
`@launchdarkly/js-contract-test-utils/client`. This flag is widely
supported in Metro 0.81+ but remains marked "unstable" - please verify
it doesn't cause unexpected issues with other package resolutions.
**Describe alternatives you've considered**
- Considered using plain string union types (`type CommandType =
'evaluate' | 'evaluateAll' | ...`) but this would break the
`CommandType.EvaluateFlag` access pattern used throughout all consumer
files. The `as const` object pattern preserves both the named access and
the string union type.
- Considered importing from SDK-specific packages (e.g.
`@launchdarkly/electron-client-sdk`) but this would create circular
dependencies. Using `@launchdarkly/js-client-sdk-common` keeps the
shared package independent.
- Considered keeping `.js` extensions in imports (per Node.js ESM spec)
but Turbopack cannot resolve these when consuming TypeScript source.
Extensionless imports work for all current consumers (bundlers +
Turbopack) and are acceptable since the package is private and
source-only.
- Considered adding fallback `main` field to package.json for Metro
compatibility, but enabling `unstable_enablePackageExports` is the
recommended approach and cleaner long-term.
**Additional context**
- All 4 migrated SDK contract tests compile and pass lint/prettier
checks
- Package uses workspace protocol (`workspace:^`) for monorepo
dependencies
- No bundle size changes (contract test code is not included in SDK
bundles)
- All 43/43 CI checks pass
**Updates since last revision**
- Migrated React SDK contract tests (deleted 4 local files, updated
imports in `ClientEntity.ts`, `ClientRoot.tsx`,
`TestHarnessWebSocket.ts`)
- Fixed Turbopack module resolution by removing `.js` extensions from
shared package internal imports
- Fixed Metro bundler resolution for react-native by enabling
`unstable_enablePackageExports` in `metro.config.js`
- Fixed `ClientPool.nextId()` off-by-one bug to match existing consumer
patterns (first ID is now `"0"` instead of `"1"`)
- Simplified `ClientPool.add()` to internally call `nextId()` and return
the assigned ID (API changed from `add(id: string, client: T): void` to
`add(client: T): string`)
- All CI checks now pass (43/43)
---
**Link to Devin Session:**
https://app.devin.ai/sessions/f541db4796ce485a892086b8ea79bf95
**Requested by:** @joker23
[SDK-2008]:
https://launchdarkly.atlassian.net/browse/SDK-2008?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches multiple contract-test runners and introduces package
`exports`/Metro resolution changes; low production impact but moderate
risk of breaking test builds across platforms due to module-resolution
and shared type changes.
>
> **Overview**
> Introduces a new **private** workspace package,
`@launchdarkly/js-contract-test-utils`, to centralize shared
contract-test infrastructure (common command/config types, `makeLogger`,
a fetch-based client-side `TestHook`, and a generic `ClientPool`), using
`@launchdarkly/js-client-sdk-common` for cross-SDK type compatibility
and exposing a `./client` subpath export.
>
> Migrates the browser, Electron, React Native, and React contract-test
entities to consume these shared exports, deleting their duplicated
local type/logger/hook files, updating imports/dependencies accordingly,
and enabling Metro `unstable_enablePackageExports` so React Native can
resolve the `./client` subpath.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2ab4f54. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>1 parent bbdd6c6 commit 2de5c27
37 files changed
Lines changed: 307 additions & 1149 deletions
File tree
- packages
- sdk
- browser/contract-tests/entity
- src
- electron/contract-tests/entity
- src
- react-native/contract-tests/entity
- src
- react/contract-tests
- app
- tooling/contract-test-utils
- src
- client-side
- logging
- server-side
- types
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
Lines changed: 9 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
3 | | - | |
4 | | - | |
5 | | - | |
6 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
7 | 11 | | |
8 | 12 | | |
9 | 13 | | |
| |||
Lines changed: 0 additions & 157 deletions
This file was deleted.
Lines changed: 0 additions & 90 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | | - | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
Lines changed: 0 additions & 22 deletions
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| |||
Lines changed: 9 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
14 | 18 | | |
15 | 19 | | |
16 | 20 | | |
| |||
Lines changed: 2 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | | - | |
3 | | - | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
0 commit comments