Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
59b3eb2
feat: run ci for multiple react versions
kylemcd Jun 3, 2025
3b0665f
fix: file path reference for run job
kylemcd Jun 3, 2025
dc2ed21
fix: indent
kylemcd Jun 3, 2025
549f29a
fix: include react version in concurrency check
kylemcd Jun 3, 2025
e6826b4
fix: set resolution strategy
kylemcd Jun 3, 2025
55d6338
fix: change install strategy
kylemcd Jun 3, 2025
6ad118a
fix: change install strategy
kylemcd Jun 3, 2025
a4b3396
fix: change install strategy
kylemcd Jun 3, 2025
1b4e751
fix: add install step to child jobs
kylemcd Jun 3, 2025
8e4d53a
fix: type check command
kylemcd Jun 3, 2025
2713684
fix: add registry to setup node comamnd
kylemcd Jun 3, 2025
869c9c6
fix: restore build cache to type check job
kylemcd Jun 3, 2025
3357ab0
fix: combine jobs into steps to simplify output
kylemcd Jun 3, 2025
0562fbf
fix: remove resolutions from pkg json and add it in CI
kylemcd Jun 3, 2025
cb86ce6
fix: pin react testing version in some cases
kylemcd Jun 3, 2025
9c75ff3
feat: move to integration model
kylemcd Jun 3, 2025
a8bc7fd
fix: reconfigure deps and setup test
kylemcd Jun 3, 2025
295ff74
fix: reconfigure setup + remove concurrency at top level
kylemcd Jun 3, 2025
0364c82
feat: add env vars for integration tests
kylemcd Jun 3, 2025
0d2b55e
fix: change how react versions get set
kylemcd Jun 3, 2025
6b8a78b
fix: syntax error
kylemcd Jun 3, 2025
90448bf
fix: syntax error + test structure
kylemcd Jun 3, 2025
35b2274
fix: syntax error
kylemcd Jun 3, 2025
cc03ab8
fix: add env values + testing library pinning
kylemcd Jun 3, 2025
872c021
fix: linting in integration
kylemcd Jun 3, 2025
477bc3d
fix: remove concurrency flag
kylemcd Jun 3, 2025
b9dca6a
fix: downgrade dialog to fix jsx runtime issue
kylemcd Jun 3, 2025
45327a5
chore: change how dialog dep is managed
kylemcd Jun 3, 2025
4ee5960
fix: change how react version is set
kylemcd Jun 3, 2025
8a10d60
fix: change how react version is set
kylemcd Jun 3, 2025
d6ff8a1
fix: change how integration is setup
kylemcd Jun 3, 2025
6107997
Revert "fix: change how integration is setup"
kylemcd Jun 3, 2025
cf6c5ca
chore: revert back to cleaner way
kylemcd Jun 3, 2025
91539dd
chore: upgrade dialog again
kylemcd Jun 3, 2025
d9bc2d3
fix: change jsx flag in react package
kylemcd Jun 3, 2025
2de20b3
fix: change how versions are setup
kylemcd Jun 3, 2025
aa7e966
fix: pkg json read from
kylemcd Jun 3, 2025
48ce16b
chore: verify working
kylemcd Jun 3, 2025
3855620
fix: overiting of resolutions
kylemcd Jun 3, 2025
a667914
chore: verify working
kylemcd Jun 3, 2025
917b5ac
chore: verify working
kylemcd Jun 3, 2025
74d7fd2
fix: move resolutions to main pkg json to get correct resolution
kylemcd Jun 3, 2025
135aa2b
feat: shell based runner
kylemcd Jun 4, 2025
c194c0d
fix: add install and build step to ci
kylemcd Jun 4, 2025
c93ba6a
fix: yarn install commands for CI
kylemcd Jun 4, 2025
96bd498
chore: cleanup
kylemcd Jun 4, 2025
bd2ee45
fix: change ci name + restore test commands
kylemcd Jun 4, 2025
7dcff62
chore: cleanup + add readme
kylemcd Jun 4, 2025
33c2188
fix: add syntax highlighting to code blocks
kylemcd Jun 4, 2025
6e05496
fix: heading
kylemcd Jun 4, 2025
0e515d1
feat: add matrix for running tests
kylemcd Jun 5, 2025
50c727c
fix: spelling mistakes
kylemcd Jun 5, 2025
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
33 changes: 33 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Integration Tests

on:
pull_request:
workflow_dispatch:

jobs:
run-integration-tests:
strategy:
matrix:
react-version: [18, 19]
runs-on: ubuntu-latest
name: React ${{ matrix.react-version }}
env:
INTEGRATION_KNOCK_PUBLIC_KEY: ${{ secrets.INTEGRATION_KNOCK_PUBLIC_KEY }}
INTEGRATION_KNOCK_USER_ID: ${{ secrets.INTEGRATION_KNOCK_USER_ID }}
INTEGRATION_KNOCK_FEED_ID: ${{ secrets.INTEGRATION_KNOCK_FEED_ID }}
steps:
- name: Checkout Latest
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: "package.json"
cache: "yarn"
registry-url: "https://registry.npmjs.org"
scope: "@knocklabs"
- name: Install Dependencies
run: yarn install
- name: Build Packages
run: yarn build:packages
- name: Run Integration Tests
run: yarn test:integration:react-${{ matrix.react-version }}
3 changes: 3 additions & 0 deletions integration/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INTEGRATION_KNOCK_PUBLIC_KEY=
INTEGRATION_KNOCK_FEED_ID=
INTEGRATION_KNOCK_USER_ID=
17 changes: 17 additions & 0 deletions integration/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: [
"@knocklabs/eslint-config/library.js",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/strict",
],
parserOptions: {
projects: ["tsconfig.json", "tsconfig.node.json"],
},
settings: {
"jsx-a11y": {
polymorphicPropName: "as",
},
},
};
96 changes: 96 additions & 0 deletions integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Knock Javascript SDK Integration Testing

Because the Knock SDK can be utilized in different versions of React, it's vital that we have visibility into this surface area. This package aims to give maintainers an easy way to verify that their package
works across the versions of react that we support.

## Getting started

First you will need to setup your environment variables so that the test runner can properly run any of the functions and components that we are testing. You can find an example of what this file should like in `.env.sample`. For Knock employees, you can find the file contents in the 1Password vault titled "JS SDK Integration Testing Env File".

## Running the integration tests locally

There are 3 different ways that you can run the test suite depending on the result you're looking for.

Run for all versions of react that we support:

```bash
yarn test:integration
```

Run for individual version of react that we support:

```bash
yarn test:integration:react-18
yarn test:integration:react-19
```

Run for a specific version of react not already defined in `package.json`

```bash
./integration/run-integration.sh x.x.x
```

It's recommended that you build your packages using `yarn build:packages` before running this command, to ensure that the test suite references the correctly built packages when running the test suite.

> [!CAUTION]
> When running the integration tests, the runner will perform a `yarn install` so that the correct version of react is present. In doing so, it will add the `resolutions` key to the root `package.json` temporarily while the test suite runs. Please make sure that the `resolutions` key is **NEVER** committed to version control. See more details about how this process works below.

## Adding new tests

To add new tests to our integration test suite, navigate to `./integration/tests`. We try to categorize tests into top level grouping so that they're easier to find later. For this test suite, at this point in time, we're only _really_ looking to see if the current package can work in multiple versions of react. So, all that you need to do is make sure the component or function is called in the test and ran.

Here's an example of adding a component to our test suite.

```tsx
import { NotificationFeed } from "@knocklabs/react";
import { render } from "@testing-library/react";
import { describe, it } from "vitest";

describe("NotificationFeed", () => {
it("should render", () => {
render(<NotificationFeed />);
});
});
```

## Explanation of the architecture

In order to reproduce the most realistic integration test, we need to:

1. Build our packages with the react version currently present in the repo.
2. Override that react version when testing to see how our code responds in those scenarios, similar to how `peerDependencies` work.

### The issue

Unfortunately, this isn't super straightforward. In our `yarn` monorepo there is a single version of `react` present. We do this so that there are not multiple versions running at the same time to avoid this error:

```
A React Element from an older version of React was rendered. This is not supported. It can happen if:
- Multiple copies of "react" are used
- A library pre-bundled an old copy of "react" or "react/jsx-runtime"
- A compiler tries to "inline" JSX instead of using the runtime.
```

This means that if we want to test specific versions of react in our integration tests, the entire repo will need to resolve to that version. The initial solve would be to add this specific version as the one that is referenced in `@knocklabs/integration`, this won't work. The resolved version of `react` will end up being the version hoisted in the root `node_modules`. If you try to override that by pointing directly to specific version of `react` in the `node_modules` folder via `vitest` alias (or other solution), you will get the above error because the built version and the resolved version will be running at the same time. We could build the packages utilizing the version we want to test against, but that means in some cases the build would not succeed even though the package would work with a lower version of react.

There is no "easy" way around this.

### The solve

Luckily, `yarn` v4 gives us one singular escape hatch, the `resolutions` key in `package.json`. This config will override **EVERY** version of the specified package throughout the repo, yippee. But the caveat is this value is only configurable in the monorepo's root `package.json` file. So, if we want to test specific `react` versions we'll need to add the `resolutions` key defining those versions. Here's how our script works.

1. Take in the `react` version that the maintainer wants to test. Any version should be easily testable without any extra configuration. So we take this in as a parameter when running the script, example: `./integration.run-integration.sh 18.2.0`.
2. Create a copy of the monorepo's root `package.json` file so that we can restore it back to it's original state after the run. This helps to prevent the maintainer from committing configuration changes to version control every time they need to test a different version of `react`.
3. Set the `resolutions` key to the specified `react` version passed as a parameter and write it to the `package.json` file. This sets the version for `react` and `react-dom`.
4. Run `yarn` so that each instance of `react` points to the specified version.
5. Run the test suite from `@knocklabs/integration` via `yarn test:integration:runner`
6. Restore the `package.json` file back to it's original state, removing the `resolutions` key entirely.
7. Run `yarn` again to restore the dependencies back to their original state.

Full script exists here:

```
./integration/run-integration.sh
```

Running the test suite in this way gives the maintainer the ability to locally run the test suite while also allowing us to run the same script in CI. This setup is the best we've found without introducing a TON of overhead to our repo. The crux of this issue is how dependency hoisting is managed in a monorepo. Yarn has a [great article](https://classic.yarnpkg.com/blog/2018/02/15/nohoist/) describing this pattern in more detail, note that this is referencing `yarn` v1 under the "How to use it?" section, which is not applicable to us since we use `yarn` v4.
11 changes: 11 additions & 0 deletions integration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@knocklabs/integration",
"private": true,
"prettier": "@knocklabs/prettier-config",
"engines": {
"node": "20.9.0"
},
"scripts": {
"test:integration": "vitest run"
}
}
36 changes: 36 additions & 0 deletions integration/run-integration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash

set -e # exit on any error

# Get React version from first argument
REACT_VERSION=$1

if [ -z "$REACT_VERSION" ]; then
echo "Error: You must provide a React version."
echo "Usage: $0 <react-version>"
exit 1
fi

# Set the root package.json resolutions to the correct
# react version so that we can run tests against it.
JQ_CMD=".resolutions += {}
| .resolutions[\"react\"] = \"$REACT_VERSION\"
| .resolutions[\"react-dom\"] = \"$REACT_VERSION\""

# Back up original package.json
cp ./package.json ./original.json

# Ensure that we always restore the original package.json AND run yarn install on exit
trap 'echo "Restoring original package.json..."; mv ./original.json ./package.json; echo "Running yarn install to restore lockfile and node_modules..."; yarn install --no-immutable' EXIT

# Apply modifications
jq "$JQ_CMD" ./package.json > tmp.json
mv tmp.json ./package.json

echo "Starting run for react version $REACT_VERSION"

# Run commands
yarn install --no-immutable
yarn test:integration:runner


26 changes: 26 additions & 0 deletions integration/tests/feed/NotificationFeed.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
KnockFeedProvider,
KnockProvider,
NotificationFeed,
} from "@knocklabs/react";
import { render } from "@testing-library/react";
import { describe, it } from "vitest";

const Feed = () => {
return (
<KnockProvider
apiKey={process.env.INTEGRATION_KNOCK_PUBLIC_KEY}
userId={process.env.INTEGRATION_KNOCK_USER_ID}
>
<KnockFeedProvider feedId={process.env.INTEGRATION_KNOCK_FEED_ID}>
<NotificationFeed />
</KnockFeedProvider>
</KnockProvider>
);
};

describe("NotificationFeed", () => {
it("should render", () => {
render(<Feed />);
});
});
10 changes: 10 additions & 0 deletions integration/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "@knocklabs/typescript-config/node.json",
"include": ["vitest.config.ts", "tests"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"types": ["node", "vite"],
"incremental": true,
"jsx": "react-jsx"
}
}
15 changes: 15 additions & 0 deletions integration/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import path from "path";
import { loadEnv } from "vite";
import { defineConfig } from "vitest/config";

export default defineConfig(({ mode }) => {
return {
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./vitest.setup.ts"],
env: loadEnv(mode, "./", ""),
include: ["./tests/**/*.test.tsx"],
},
};
});
7 changes: 7 additions & 0 deletions integration/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";

afterEach(() => {
cleanup();
});
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"test:watch": "vitest run --config=./vitest/config.ts --workspace=./vitest/workspaces.ts --watch",
"test:coverage": "vitest run --config=./vitest/config.ts --workspace=./vitest/workspaces.ts --coverage",
"test:ci": "vitest run --config=./vitest/config.ts --workspace=./vitest/workspaces.ts --silent --coverage --reporter=junit --outputFile=test-report.junit.xml",
"test:integration": "yarn test:integration:react-18 && yarn test:integration:react-19",
"test:integration:react-18": "./integration/run-integration.sh 18.2.0",
"test:integration:react-19": "./integration/run-integration.sh 19.1.0",
"test:integration:runner": "cd ./integration && yarn test:integration",
"type:check": "turbo type:check",
"release": "yarn build:packages && yarn release:publish && yarn changeset tag",
"release:publish": "yarn workspaces foreach -Rpt --no-private --from '@knocklabs/*' npm publish --access public --tolerate-republish",
Expand All @@ -29,7 +33,8 @@
},
"workspaces": [
"examples/*",
"packages/*"
"packages/*",
"integration"
],
"manypkg": {
"defaultBranch": "main",
Expand Down
8 changes: 8 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
"dev": {
"cache": false,
"persistent": true
},
"test:integration:runner": {
"cache": false,
"env": [
"INTEGRATION_KNOCK_PUBLIC_KEY",
"INTEGRATION_KNOCK_USER_ID",
"INTEGRATION_KNOCK_FEED_ID"
]
}
}
}
6 changes: 6 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4143,6 +4143,12 @@ __metadata:
languageName: unknown
linkType: soft

"@knocklabs/integration@workspace:integration":
version: 0.0.0-use.local
resolution: "@knocklabs/integration@workspace:integration"
languageName: unknown
linkType: soft

"@knocklabs/javascript@workspace:.":
version: 0.0.0-use.local
resolution: "@knocklabs/javascript@workspace:."
Expand Down