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
19 changes: 19 additions & 0 deletions .changeset/vite-8-drop-react-16.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@knocklabs/react-core": minor
"@knocklabs/react": minor
---

Drop React 16 support

`@knocklabs/react` and `@knocklabs/react-core` now compile with React's
automatic JSX runtime, so their published output imports `react/jsx-runtime`.
That entry point only exists in React 17 and later, so the `react` and
`react-dom` peer ranges narrow from `^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0`
to `^17.0.0 || ^18.0.0 || ^19.0.0`.

If you are on React 16, stay on the previous release. Everyone on React 17+ can
upgrade with no code changes.

This comes out of the move to Vite 8, which compiles JSX with oxc before Babel
runs, making the previous classic-runtime setup (`jsxRuntime: "classic"` plus
`babel-plugin-react-require`) unworkable.
6 changes: 3 additions & 3 deletions examples/client-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
},
"devDependencies": {
"@testing-library/dom": "^10.4.1",
"@vitejs/plugin-react": "^4.5.1",
"@vitejs/plugin-react": "^6.0.4",
"@vitejs/plugin-react-swc": "^4.3.0",
"jsdom": "^29.1.0",
"prettier": "^3.5.3",
"vite": "^5.4.19",
"vitest": "^3.2.4"
"vite": "^8.1.5",
"vitest": "^4.1.10"
},
"engines": {
"node": "22.17.0"
Expand Down
4 changes: 2 additions & 2 deletions examples/guide-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
"@eslint/js": "^9.9.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.1",
"@vitejs/plugin-react": "^6.0.4",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^16.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.61.0",
"vite": "^5.4.19"
"vite": "^8.1.5"
},
"engines": {
"node": "22.17.0"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
"@knocklabs/prettier-config": "workspace:^",
"@knocklabs/typescript-config": "workspace:^",
"@manypkg/cli": "^0.25.0",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/coverage-v8": "^4.1.10",
"prettier": "^3.5.3",
"turbo": "^2.9.16",
"vitest": "^3.2.4"
"vitest": "^4.1.10"
},
"engines": {
"node": ">=24.13.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
"rimraf": "^6.0.1",
"rollup": "^4.46.2",
"typescript": "^5.8.3",
"vite": "^5.4.19",
"vitest": "^3.2.4"
"vite": "^8.1.5",
"vitest": "^4.1.10"
},
"dependencies": {
"@babel/runtime": "^7.28.6",
Expand Down
35 changes: 20 additions & 15 deletions packages/client/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,27 @@ const skipRetryDelays = (apiClient: ApiClient) => {
.mockResolvedValue(undefined);
};

// `ApiClient` calls `new Socket(...)`, and vitest 4 only lets a mock stand in
// as a constructor when its implementation is a `function` (or class), not an
// arrow function.
const { createSocketMock } = vi.hoisted(() => ({
createSocketMock: () => ({
connect: vi.fn(),
disconnect: vi.fn(),
isConnected: vi.fn().mockReturnValue(false),
onOpen: vi.fn(),
onClose: vi.fn(),
onError: vi.fn(),
channel: vi.fn().mockReturnValue({
join: vi.fn(),
leave: vi.fn(),
push: vi.fn(),
on: vi.fn(),
off: vi.fn(),
}),
}),
createSocketMock: function () {
return {
connect: vi.fn(),
disconnect: vi.fn(),
isConnected: vi.fn().mockReturnValue(false),
onOpen: vi.fn(),
onClose: vi.fn(),
onError: vi.fn(),
channel: vi.fn().mockReturnValue({
join: vi.fn(),
leave: vi.fn(),
push: vi.fn(),
on: vi.fn(),
off: vi.fn(),
}),
};
},
}));

vi.mock("phoenix", () => ({
Expand Down
16 changes: 12 additions & 4 deletions packages/client/test/clients/feed/feed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,9 @@ describe("Feed", () => {

global.BroadcastChannel = vi
.fn()
.mockImplementation(() => mockBroadcastChannel);
.mockImplementation(function () {
return mockBroadcastChannel;
} as never);
vi.stubGlobal("self", global);

const _feed = new Feed(
Expand Down Expand Up @@ -913,7 +915,9 @@ describe("Feed", () => {

global.BroadcastChannel = vi
.fn()
.mockImplementation(() => mockBroadcastChannel);
.mockImplementation(function () {
return mockBroadcastChannel;
} as never);
vi.stubGlobal("self", global);

const feedItem = createUnreadFeedItem();
Expand Down Expand Up @@ -958,7 +962,9 @@ describe("Feed", () => {

global.BroadcastChannel = vi
.fn()
.mockImplementation(() => mockBroadcastChannel);
.mockImplementation(function () {
return mockBroadcastChannel;
} as never);
vi.stubGlobal("self", global);

// Create a feedItem with circular reference to trigger JSON error
Expand Down Expand Up @@ -1006,7 +1012,9 @@ describe("Feed", () => {

global.BroadcastChannel = vi
.fn()
.mockImplementation(() => mockBroadcastChannel);
.mockImplementation(function () {
return mockBroadcastChannel;
} as never);
vi.stubGlobal("self", global);

// Mock fetch response for when broadcast message triggers refetch
Expand Down
4 changes: 3 additions & 1 deletion packages/client/test/clients/feed/socket-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ describe("FeedSocketManager", () => {
unsubscribeFromSocketEvents: vi.fn(),
} as unknown as Feed;

vi.mocked(Store).mockImplementation(() => mockStore);
vi.mocked(Store).mockImplementation(function () {
return mockStore;
} as never);
vi.mocked(mockSocket.channel).mockReturnValue(mockChannel);

socketManager = new FeedSocketManager(mockSocket);
Expand Down
4 changes: 3 additions & 1 deletion packages/client/test/clients/guide/guide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ const mockStore = {
};

vi.mock("@tanstack/store", () => ({
Store: vi.fn(() => mockStore),
Store: vi.fn(function () {
return mockStore;
}),
}));

// Mock phoenix
Expand Down
15 changes: 8 additions & 7 deletions packages/client/test/test-utils/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,11 @@ export const createEventEmitterMock = () => ({
removeAllListeners: vi.fn(),
});

export const mockJwtDecode = () => {
vi.mock("jwt-decode", () => ({
jwtDecode: vi.fn().mockReturnValue({
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
}),
}));
};
// `vi.mock` is hoisted to the top of the module regardless of where it's
// written, so it lives at the top level here rather than inside a helper that
// only looked like it had to be called.
vi.mock("jwt-decode", () => ({
jwtDecode: vi.fn().mockReturnValue({
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
}),
}));
2 changes: 1 addition & 1 deletion packages/client/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default defineConfig(({ mode }) => {
},
rollupOptions: {
output: {
interop: "compat",
strict: true,
entryFileNames: () => {
return `[name].${CJS ? "js" : "mjs"}`;
},
Expand Down
6 changes: 3 additions & 3 deletions packages/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@types/react-native-htmlview": "^0.16.6",
"@typescript-eslint/eslint-plugin": "^8.59.4",
"@typescript-eslint/parser": "^8.59.4",
"@vitejs/plugin-react": "^4.5.1",
"@vitejs/plugin-react": "^6.0.4",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.2",
Expand All @@ -81,9 +81,9 @@
"react-native-gesture-handler": "~2.30.0",
"rimraf": "^6.0.1",
"typescript": "^5.8.3",
"vite": "^5.4.19",
"vite": "^8.1.5",
"vite-plugin-dts": "^4.5.0",
"vite-plugin-no-bundle": "^4.0.0",
"vitest": "^3.2.4"
"vitest": "^4.1.10"
}
}
2 changes: 1 addition & 1 deletion packages/expo/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default defineConfig(({ mode }) => {
"expo-notifications",
],
output: {
interop: "compat",
strict: true,
format: formats[0],
globals: {
react: "React",
Expand Down
11 changes: 5 additions & 6 deletions packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"description": "A set of React components to build notification experiences powered by Knock",
"author": "@knocklabs",
"version": "0.14.0",
"sideEffects": false,
"license": "MIT",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.mjs",
Expand Down Expand Up @@ -45,7 +46,7 @@
"url": "https://github.com/knocklabs/javascript/issues"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"dependencies": {
"@knocklabs/client": "workspace:^",
Expand All @@ -61,20 +62,18 @@
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.59.4",
"@typescript-eslint/parser": "^8.59.4",
"@vitejs/plugin-react": "^4.5.1",
"babel-plugin-react-require": "^4.0.3",
"@vitejs/plugin-react": "^6.0.4",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.2",
"jsdom": "^29.1.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"rimraf": "^6.0.1",
"rollup-plugin-execute": "^1.1.1",
"typescript": "^5.8.3",
"vite": "^5.4.19",
"vite": "^8.1.5",
"vite-plugin-dts": "^4.5.0",
"vite-plugin-no-bundle": "^4.0.0",
"vitest": "^3.2.4"
"vitest": "^4.1.10"
}
}
4 changes: 3 additions & 1 deletion packages/react-core/test/core/KnockProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const { knock, mockApiClient } = createMockKnock("test_api_key");

// Mock the Knock client constructor to return our mock instance
vi.mock("@knocklabs/client", () => ({
default: vi.fn().mockImplementation(() => knock),
default: vi.fn().mockImplementation(function () {
return knock;
}),
}));

// Mock API responses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import KnockClient, { type MsTeamsChannelConnection } from "@knocklabs/client";
import { act, renderHook } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

import { mockMsTeamsContext, mockTranslations } from "../test-utils/mocks";
import { mockMsTeamsContext } from "../test-utils/mocks";

// ----------------------------------------------------------------------------------
// Shared mocks
Expand Down Expand Up @@ -53,7 +53,6 @@ vi.mock("swr", () =>

// Apply shared mocks _before_ loading the hook to ensure context is mocked first
mockMsTeamsContext();
mockTranslations();

let useConnectedMsTeamsChannels: typeof import("../../src/modules/ms-teams/hooks/useConnectedMsTeamsChannels").default;

Expand Down
3 changes: 1 addition & 2 deletions packages/react-core/test/ms-teams/useMsTeamsAuth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { act, renderHook } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";

import type UseMsTeamsAuth from "../../src/modules/ms-teams/hooks/useMsTeamsAuth";
import { mockMsTeamsContext, mockTranslations } from "../test-utils/mocks";
import { mockMsTeamsContext } from "../test-utils/mocks";

const TEST_BRANCH_SLUG = "lorem-ipsum-branch";

Expand All @@ -19,7 +19,6 @@ mockMsTeamsContext({
setConnectionStatus: mockSetConnectionStatus,
setActionLabel: mockSetActionLabel,
});
mockTranslations();

const mockMsTeamsClient = {
revokeAccessToken: vi.fn(),
Expand Down
12 changes: 7 additions & 5 deletions packages/react-core/test/test-utils/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ export const mockMsTeamsContext = (
/**
* Simple translation mock so tests can rely on t(key) => key without importing
* real language bundles.
*
* `vi.mock` is hoisted to the top of the module, so this applies to every test
* file importing these helpers — it used to be wrapped in an exported function
* that callers had to invoke, which misrepresented when it actually ran.
*/
export const mockTranslations = () => {
vi.mock("../../src/modules/i18n", () => ({
useTranslations: () => ({ t: (k: string) => k }),
}));
};
vi.mock("../../src/modules/i18n", () => ({
useTranslations: () => ({ t: (k: string) => k }),
}));

// ---------------------------------------------------------------------------
// Internal: manage a shared overrides object to satisfy Vitest hoisting.
Expand Down
9 changes: 2 additions & 7 deletions packages/react-core/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ export default defineConfig(({ mode }) => {
dts({
outDir: "dist/types",
}),
react({
jsxRuntime: "classic",
babel: {
plugins: ["react-require"],
},
}),
react(),
noBundlePlugin({ root: "./src" }),
codecovVitePlugin({
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
Expand All @@ -43,7 +38,7 @@ export default defineConfig(({ mode }) => {
// External packages that should not be bundled
external: ["react"],
output: {
interop: "compat",
strict: true,
globals: {
react: "React",
},
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"@types/react-native-htmlview": "^0.16.6",
"@typescript-eslint/eslint-plugin": "^8.59.4",
"@typescript-eslint/parser": "^8.59.4",
"@vitejs/plugin-react": "^4.5.1",
"@vitejs/plugin-react": "^6.0.4",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.5.2",
Expand All @@ -70,7 +70,7 @@
"react-native-gesture-handler": "~2.30.0",
"rimraf": "^6.0.1",
"typescript": "^5.8.3",
"vite": "^5.4.19",
"vite": "^8.1.5",
"vite-plugin-dts": "^4.5.0",
"vite-plugin-no-bundle": "^4.0.0"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default defineConfig(({ mode }) => {
// External packages that should not be bundled into your library.
external: ["react", "react-native"],
output: {
interop: "compat",
strict: true,
format: formats[0],
globals: {
react: "React",
Expand Down
Loading
Loading