diff --git a/AGENTS.md b/AGENTS.md index 067fa7b..9774053 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,7 +65,7 @@ not `behavior`, `licence` not `license`, `centre` not `center`). `minimumReleaseAgeExclude` require a justification comment in the PR - **Peer dependencies are a contract:** `p5`, `react`, and `react-dom` are peer dependencies. The library code must never import anything beyond these at - runtime — `microdiff` and `react-error-boundary` are the only runtime + runtime — `@p5-wrapper/common` and `react-error-boundary` are the only runtime dependencies ### Formatting and Linting @@ -86,9 +86,8 @@ not `behavior`, `licence` not `license`, `centre` not `center`). - **No comments:** Do not add comments to source files. The code should be self-documenting. The only permitted exceptions are `@ts-expect-error` / `@ts-ignore` suppressions with a `@see` reference (see - `src/utils/createP5CanvasInstance.ts` for the existing pattern) and JSDoc on - exported contracts where a URL reference adds value (see - `src/contracts/P5CanvasInstanceRef.ts`) + `src/components/P5CanvasWithSketch.tsx` for the existing pattern) and JSDoc on + exported contracts where a URL reference adds value - **No comments rule does not apply to:** this file, `README.md`, workflow files, and config files with existing comments @@ -102,10 +101,9 @@ not `behavior`, `licence` not `license`, `centre` not `center`). - **Import style:** Use `import { type Foo }` inline type imports, matching the existing code. Imports of contracts across the alias boundaries follow the sorted import order enforced by Prettier -- **Type assertions:** Avoid `as` casts in library code. The one existing - `@ts-expect-error` in `createP5CanvasInstance.ts` documents a known p5 - upstream type inference issue — do not remove it without verifying against the - referenced p5 PR +- **Type assertions:** Avoid `as` casts in library code. No `@ts-expect-error` + or `@ts-ignore` suppressions currently exist; if one is ever needed, it must + carry a `@see` reference and be verified against upstream issue or PR links - **Version pinned to 6.0.3:** `typescript` is an exact pin (`"typescript": "6.0.3"`, no caret), deliberately held back from v7. typescript-eslint does not currently support TypeScript 7 — its @@ -183,24 +181,29 @@ project changes. Do not begin implementation until the plan is approved. design. Components are function components; utilities are pure functions that take arguments and return values — they never reach for globals or hidden state. Keep it this way -- **Types as the public contract:** The `src/contracts/` directory is the type - contract between the library and its consumers. Every exported type is public - API via `src/main.tsx`. Generic defaults flow through `SketchProps` — +- **Types as the public contract:** Shared p5 contracts (`Sketch`, + `P5CanvasInstance`, `Updater`, `SketchProps`, refs, and the generic + `P5CanvasProps`) come from `@p5-wrapper/common` — this + repository's own contracts are the React bindings on top. The only local + contract file is `src/contracts/P5CanvasProps.ts`, which binds common's + `OutputNode` to `ReactNode`. Generic defaults flow through `SketchProps` — understand the generic chain (`Sketch` → `P5CanvasInstance` → - `Updater` → `P5CanvasProps`) before touching any of them -- **One contract per file, one export per file:** Contracts live in - `src/contracts/`, one file per contract, named after the export. Utilities - live in `src/utils/`, one file per function, named after the function. Follow - this pattern for anything new + `Updater` → `P5CanvasProps`) before touching any of + them +- **One contract per file, one export per file:** React-specific contracts live + in `src/contracts/`, one file per contract, named after the export. React + utilities live in `src/utils/`, one file per function, named after the + function. Anything shared across frameworks belongs in `@p5-wrapper/common`, + not here - **No hidden dependencies:** The component tree is deliberately layered — `P5Canvas` (memoisation) → `P5CanvasGuard` (validation + error boundary + suspense) → `P5CanvasWithSketch` (lifecycle). Responsibilities stay in their layer; utils never import components; contracts never import utils - **Imperative p5, declarative React:** p5 instances are imperative and mutable - by nature. The bridge is contained entirely in `P5CanvasWithSketch` and the - `src/utils/` lifecycle functions (`createP5CanvasInstance`, - `updateP5CanvasInstance`, `removeP5CanvasInstance`). Do not leak imperative p5 - patterns into the React layer above + by nature. The bridge is contained entirely in `P5CanvasWithSketch`, which + calls the lifecycle utilities (`createP5CanvasInstance`, + `updateP5CanvasInstance`, `removeP5CanvasInstance`) from `@p5-wrapper/common`. + Do not leak imperative p5 patterns into the React layer above - **Lazy boundaries:** `P5CanvasGuard` and `react-error-boundary` are lazily imported so consumers who never trigger them never pay the bundle cost. Any new heavy dependency must follow the same `React.lazy` pattern @@ -208,9 +211,10 @@ project changes. Do not begin implementation until the plan is approved. ### Testing - **Test first:** Tests for new behaviour are written before or alongside the - implementation, never as an afterthought. Every utility in `src/utils/` has a - corresponding test file in `tests/utils/`; every component in - `src/components/` has one in `tests/components/`. Keep this 1:1 mapping + implementation, never as an afterthought. Every component in `src/components/` + has one in `tests/components/`. Utility functions and shared + contracts/constants are owned and tested by `@p5-wrapper/common`, not here. + Keep the 1:1 component mapping - **Black-box testing:** Test the rendered output and observable behaviour (`data-testid` hooks: `canvas-container`, `loading`, `error`), not internal implementation details @@ -218,10 +222,8 @@ project changes. Do not begin implementation until the plan is approved. API, and `@testing-library/react`. `p5.disableFriendlyErrors = true` is set in `tests/setup.ts` to stop p5's DOM scanning from causing unhandled rejections — do not remove it. `afterEach` cleanup is also mandatory -- **Structure:** Tests mirror the `src/` directory structure - (`tests/components/`, `tests/constants/`, `tests/utils/`, plus - `tests/exports.test.tsx` guarding the public API surface). New `src/` files - must add the matching test file +- **Structure:** Tests live in `tests/components/`, mirroring `src/components/`. + New `src/` component files must add the matching test file - **Coverage:** CI runs `pnpm test:coverage` and comments coverage deltas on PRs. Do not reduce coverage of existing code - **Skipped tests:** Two loading-UI tests are currently `it.skip`-ped due to @@ -303,7 +305,7 @@ sibling `@p5-wrapper/next` package. ``` P5Canvas (src/components/P5Canvas.tsx) - React.memo + propsAreEqual (microdiff deep comparison) + React.memo + propsAreEqual (from @p5-wrapper/common, microdiff deep comparison) └─ P5CanvasGuard (lazy) sketch validation → fallback UI or: ErrorBoundary (lazy react-error-boundary) + Suspense @@ -317,13 +319,13 @@ P5Canvas (src/components/P5Canvas.tsx) Why the layers exist: -- `P5Canvas` only handles memoisation — a deep `microdiff` comparison so p5 is - not needlessly recreated +- `P5Canvas` only handles memoisation — a deep comparison (common's + `propsAreEqual`, powered by `microdiff`) so p5 is not needlessly recreated - `P5CanvasGuard` handles absence (missing `sketch` → `fallback`), errors (`error` render prop → error boundary), and async loading (`loading` → suspense) - `P5CanvasWithSketch` is the only place that touches the p5 instance lifecycle - via the utils + via common's utilities ### Public API @@ -350,14 +352,10 @@ Everything exported from `src/main.tsx` is public API and semver-protected: │ │ ├── P5Canvas.tsx │ │ ├── P5CanvasGuard.tsx │ │ └── P5CanvasWithSketch.tsx -│ ├── constants/ # Exported constants -│ ├── contracts/ # Public-facing types (one contract per file) -│ └── utils/ # Pure functions (one function per file) -├── tests/ # Vitest tests mirroring src/ structure +│ └── contracts/ # React-specific contracts (common bindings) +│ └── P5CanvasProps.ts # Binds common's OutputNode to ReactNode +├── tests/ # Vitest tests for component behaviour │ ├── components/ -│ ├── constants/ -│ ├── utils/ -│ ├── exports.test.tsx # Guards the public API surface │ └── setup.ts # Test bootstrap (canvas mock, cleanup) ├── config/ │ ├── eslint/eslint.config.ts @@ -392,9 +390,9 @@ Everything exported from `src/main.tsx` is public API and semver-protected: Vite's `[format]` placeholder also changed from `esm` to `es` in Vite 8, so the names must never come from the placeholder again. If you change one side, change both in the same commit -- The library externals are `react`, `react/jsx-runtime`, `react-dom`, `p5` — - keep Rollup externals, TypeScript expectations, and peer dependencies in - agreement +- The library externals are `react`, `react/jsx-runtime`, `react-dom`, `p5`, and + `@p5-wrapper/common` — keep Rollup externals, TypeScript expectations, and + peer/runtime dependencies in agreement ## Commands @@ -440,7 +438,7 @@ Everything exported from `src/main.tsx` is public API and semver-protected: secret and runs only in CD - **Never weaken the build contract:** the `exports` map, `files` field, ESM + CJS dual output, and bundled types are what downstream consumers depend on -- **Never introduce a runtime dependency** beyond `microdiff` and +- **Never introduce a runtime dependency** beyond `@p5-wrapper/common` and `react-error-boundary` without discussion — bundle size is a feature of this library - **Never disable or skip tests, lint rules, or type checks** to make a change @@ -453,6 +451,10 @@ Everything exported from `src/main.tsx` is public API and semver-protected: ## Future Topics +- **@p5-wrapper/common adoption:** The shared contracts, utilities, and + constants now come from `@p5-wrapper/common` (adopted on the + `adopt-common-package` branch, pending merge). The only React-specific + contract that remains is `src/contracts/P5CanvasProps.ts` - **Skipped loading-UI tests:** The two `it.skip` suspense tests in `tests/components/P5Canvas.test.tsx` need a reliable strategy before being re-enabled diff --git a/config/vite/library.ts b/config/vite/library.ts index 76e293f..4d7da68 100644 --- a/config/vite/library.ts +++ b/config/vite/library.ts @@ -23,7 +23,13 @@ export function library(root: string): UserConfig { formats: ["es", "cjs"] }, rollupOptions: { - external: ["react", "react/jsx-runtime", "react-dom", "p5"], + external: [ + "react", + "react/jsx-runtime", + "react-dom", + "p5", + "@p5-wrapper/common" + ], output: { assetFileNames: "assets/[name][extname]", dir: dist, @@ -31,7 +37,8 @@ export function library(root: string): UserConfig { p5: "p5", react: "React", "react/jsx-runtime": "jsxRuntime", - "react-dom": "ReactDom" + "react-dom": "ReactDom", + "@p5-wrapper/common": "P5WrapperCommon" } } } diff --git a/package.json b/package.json index 946dd98..5ad9fca 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "url": "https://github.com/P5-wrapper/react/issues" }, "dependencies": { - "microdiff": "^1.6.0", + "@p5-wrapper/common": "^0.1.0", "react-error-boundary": "^6.1.4" }, "peerDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d01513..61c3949 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,9 @@ importers: .: dependencies: - microdiff: - specifier: ^1.6.0 - version: 1.6.0 + '@p5-wrapper/common': + specifier: ^0.1.0 + version: 0.1.0(p5@2.3.2) p5: specifier: '>= 2.0.0' version: 2.3.2 @@ -459,6 +459,12 @@ packages: '@oxc-project/types@0.147.0': resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} + '@p5-wrapper/common@0.1.0': + resolution: {integrity: sha512-a81qDRfnSOQZ0bRZQ+4u9QK63v2c0pKE/NReurbeIe4yI4oo7NJrqdSa6BOfDkXrt9vbqJ2sYeQ9X/ekcZaVtQ==} + engines: {node: '>=24.20.0'} + peerDependencies: + p5: '>= 2.0.0' + '@rolldown/binding-android-arm-eabi@1.2.6': resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3007,6 +3013,11 @@ snapshots: '@oxc-project/types@0.147.0': {} + '@p5-wrapper/common@0.1.0(p5@2.3.2)': + dependencies: + microdiff: 1.6.0 + p5: 2.3.2 + '@rolldown/binding-android-arm-eabi@1.2.6': optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 20e05e2..a988bc7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,3 +12,4 @@ peerDependencyRules: minimumReleaseAgeExclude: - happy-dom@20.13.2 + - "@p5-wrapper/common@0.1.0" diff --git a/src/components/P5Canvas.tsx b/src/components/P5Canvas.tsx index 7251a7e..7648042 100644 --- a/src/components/P5Canvas.tsx +++ b/src/components/P5Canvas.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { propsAreEqual } from "@utils/propsAreEqual"; +import { propsAreEqual } from "@p5-wrapper/common"; const P5CanvasGuard = React.lazy(() => import("@components/P5CanvasGuard")); diff --git a/src/components/P5CanvasGuard.tsx b/src/components/P5CanvasGuard.tsx index 0e677d8..53fb138 100644 --- a/src/components/P5CanvasGuard.tsx +++ b/src/components/P5CanvasGuard.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import P5CanvasWithSketch from "@components/P5CanvasWithSketch"; import { type P5CanvasProps } from "@contracts/P5CanvasProps"; -import { logErrorBoundaryError } from "@utils/logErrorBoundaryError"; +import { logErrorBoundaryError } from "@p5-wrapper/common"; import { ReactNode } from "react"; import { FallbackProps } from "react-error-boundary"; diff --git a/src/components/P5CanvasWithSketch.tsx b/src/components/P5CanvasWithSketch.tsx index e50805a..6455425 100644 --- a/src/components/P5CanvasWithSketch.tsx +++ b/src/components/P5CanvasWithSketch.tsx @@ -1,12 +1,14 @@ import * as React from "react"; -import { CanvasContainerClassName } from "@constants/CanvasContainerClassName"; -import { type CanvasContainerRef } from "@contracts/CanvasContainerRef"; -import { type P5CanvasInstanceRef } from "@contracts/P5CanvasInstanceRef"; -import { type Sketch } from "@contracts/Sketch"; -import { type SketchProps } from "@contracts/SketchProps"; -import { type Updater } from "@contracts/Updater"; -import { removeP5CanvasInstance } from "@utils/removeP5CanvasInstance"; -import { updateP5CanvasInstance } from "@utils/updateP5CanvasInstance"; +import { + CanvasContainerClassName, + type CanvasContainerRef, + type P5CanvasInstanceRef, + removeP5CanvasInstance, + type Sketch, + type SketchProps, + updateP5CanvasInstance, + type Updater +} from "@p5-wrapper/common"; import { type ReactNode } from "react"; interface P5CanvasWithSketchProps { diff --git a/src/constants/CanvasContainerClassName.ts b/src/constants/CanvasContainerClassName.ts deleted file mode 100644 index ce9a05e..0000000 --- a/src/constants/CanvasContainerClassName.ts +++ /dev/null @@ -1 +0,0 @@ -export const CanvasContainerClassName = "canvas-container"; diff --git a/src/contracts/CanvasContainer.ts b/src/contracts/CanvasContainer.ts deleted file mode 100644 index 6492afa..0000000 --- a/src/contracts/CanvasContainer.ts +++ /dev/null @@ -1 +0,0 @@ -export type CanvasContainer = HTMLDivElement; diff --git a/src/contracts/CanvasContainerRef.ts b/src/contracts/CanvasContainerRef.ts deleted file mode 100644 index 546b5fa..0000000 --- a/src/contracts/CanvasContainerRef.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { type CanvasContainer } from "@contracts/CanvasContainer"; -import { type RefObject } from "react"; - -export type CanvasContainerRef = RefObject; diff --git a/src/contracts/P5CanvasInstance.ts b/src/contracts/P5CanvasInstance.ts deleted file mode 100644 index 6775333..0000000 --- a/src/contracts/P5CanvasInstance.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { p5 } from "@contracts/p5"; -import { type SketchProps } from "@contracts/SketchProps"; - -export type P5CanvasInstance = p5 & { - updateWithProps?: (props: Props) => void; - [key: string]: unknown; -}; diff --git a/src/contracts/P5CanvasInstanceRef.ts b/src/contracts/P5CanvasInstanceRef.ts deleted file mode 100644 index ab8bfac..0000000 --- a/src/contracts/P5CanvasInstanceRef.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type P5CanvasInstance } from "@contracts/P5CanvasInstance"; -import { type SketchProps } from "@contracts/SketchProps"; -import { type RefObject } from "react"; - -/** Ref to the active p5.js sketch instance controlling the canvas */ -export type P5CanvasInstanceRef = - RefObject | null>; diff --git a/src/contracts/P5CanvasInternalProps.ts b/src/contracts/P5CanvasInternalProps.ts deleted file mode 100644 index ffef7dc..0000000 --- a/src/contracts/P5CanvasInternalProps.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type Sketch } from "@contracts/Sketch"; -import { type SketchProps } from "@contracts/SketchProps"; -import { type Updater } from "@contracts/Updater"; -import { type ReactNode } from "react"; - -export interface P5CanvasInternalProps< - Props extends SketchProps = SketchProps -> { - sketch?: Sketch; - updater?: Updater; - fallback?: () => ReactNode; - loading?: () => ReactNode; - error?: (error: unknown) => ReactNode; - children?: ReactNode; -} diff --git a/src/contracts/P5CanvasProps.ts b/src/contracts/P5CanvasProps.ts index 684faef..7359fc1 100644 --- a/src/contracts/P5CanvasProps.ts +++ b/src/contracts/P5CanvasProps.ts @@ -1,5 +1,6 @@ -import { type P5CanvasInternalProps } from "@contracts/P5CanvasInternalProps"; -import { type SketchProps } from "@contracts/SketchProps"; +import { type P5CanvasProps as CommonP5CanvasProps } from "@p5-wrapper/common"; +import { type SketchProps } from "@p5-wrapper/common"; +import { type ReactNode } from "react"; export type P5CanvasProps = - P5CanvasInternalProps & Props; + CommonP5CanvasProps; diff --git a/src/contracts/Sketch.ts b/src/contracts/Sketch.ts deleted file mode 100644 index a4c8e87..0000000 --- a/src/contracts/Sketch.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type P5CanvasInstance } from "@contracts/P5CanvasInstance"; -import { type SketchProps } from "@contracts/SketchProps"; - -export type Sketch = ( - instance: P5CanvasInstance -) => void; diff --git a/src/contracts/SketchProps.ts b/src/contracts/SketchProps.ts deleted file mode 100644 index cba943f..0000000 --- a/src/contracts/SketchProps.ts +++ /dev/null @@ -1 +0,0 @@ -export type SketchProps = Record; diff --git a/src/contracts/Updater.ts b/src/contracts/Updater.ts deleted file mode 100644 index 52f87e5..0000000 --- a/src/contracts/Updater.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type P5CanvasInstance } from "@contracts/P5CanvasInstance"; -import { type SketchProps } from "@contracts/SketchProps"; - -export type Updater = ( - instance: P5CanvasInstance, - props: Props -) => void; diff --git a/src/contracts/p5.ts b/src/contracts/p5.ts deleted file mode 100644 index c5841fd..0000000 --- a/src/contracts/p5.ts +++ /dev/null @@ -1,3 +0,0 @@ -import p5 from "p5"; - -export { p5 }; diff --git a/src/main.tsx b/src/main.tsx index 29c5c26..8ded885 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,9 @@ +export { + CanvasContainerClassName, + type P5CanvasInstance, + type Sketch, + type SketchProps, + type Updater +} from "@p5-wrapper/common"; export { P5Canvas } from "@components/P5Canvas"; -export { CanvasContainerClassName } from "@constants/CanvasContainerClassName"; -export { type P5CanvasInstance } from "@contracts/P5CanvasInstance"; export { type P5CanvasProps } from "@contracts/P5CanvasProps"; -export { type Sketch } from "@contracts/Sketch"; -export { type SketchProps } from "@contracts/SketchProps"; -export { type Updater } from "@contracts/Updater"; diff --git a/src/utils/createP5CanvasInstance.ts b/src/utils/createP5CanvasInstance.ts deleted file mode 100644 index 54dcee4..0000000 --- a/src/utils/createP5CanvasInstance.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { type CanvasContainer } from "@contracts/CanvasContainer"; -import { p5 } from "@contracts/p5"; -import { type P5CanvasInstance } from "@contracts/P5CanvasInstance"; -import { type Sketch } from "@contracts/Sketch"; -import { type SketchProps } from "@contracts/SketchProps"; - -export function createP5CanvasInstance( - sketch: Sketch, - canvasContainer: CanvasContainer -): P5CanvasInstance { - // @see https://github.com/processing/p5.js/pull/7863 - // @ts-expect-error The p5 library changes from the above PR caused some issues with the inferred types. - return new p5(sketch, canvasContainer); -} diff --git a/src/utils/logErrorBoundaryError.ts b/src/utils/logErrorBoundaryError.ts deleted file mode 100644 index d155ba8..0000000 --- a/src/utils/logErrorBoundaryError.ts +++ /dev/null @@ -1,61 +0,0 @@ -const errorIntroduction = - "[P5Canvas] The error boundary was triggered. The error message was:"; - -function tidyErrorLogText(text: string): string { - return text - .trim() - .split("\n") - .map(line => line.trim()) - .join("\n"); -} - -function formatErrorLogText(text: string): string { - return tidyErrorLogText(` - ${errorIntroduction} - - ${text} - `); -} - -function createErrorMessage(error: unknown): string { - if (error instanceof Error) { - return `${error.name}("${error.message}")`; - } - - if (typeof error === "symbol") { - return error.toString(); - } - - if (typeof error === "string") { - return `String("${error}")`; - } - - if (typeof error === "number") { - return `Number(${error})`; - } - - if (typeof error === "bigint") { - return `BigInt(${error})`; - } - - if (error instanceof Array) { - return `Array(${JSON.stringify([...error])})`; - } - - if (error instanceof Set) { - return `Set(${JSON.stringify([...error])})`; - } - - if (Object.getPrototypeOf(error) === Object.prototype) { - return `Object(${JSON.stringify(error)})`; - } - - return `Unknown(${typeof error})`; -} - -export function logErrorBoundaryError(error: unknown) { - const message = createErrorMessage(error); - const formattedMessage = formatErrorLogText(message); - - console.error(formattedMessage); -} diff --git a/src/utils/propsAreEqual.ts b/src/utils/propsAreEqual.ts deleted file mode 100644 index f436dcc..0000000 --- a/src/utils/propsAreEqual.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type P5CanvasProps } from "@contracts/P5CanvasProps"; -import { type SketchProps } from "@contracts/SketchProps"; -import diff from "microdiff"; - -export function propsAreEqual( - previous: P5CanvasProps, - next: P5CanvasProps -) { - const differences = diff(previous, next); - - return differences.length === 0; -} diff --git a/src/utils/removeP5CanvasInstance.ts b/src/utils/removeP5CanvasInstance.ts deleted file mode 100644 index 19d0cc1..0000000 --- a/src/utils/removeP5CanvasInstance.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type P5CanvasInstanceRef } from "@contracts/P5CanvasInstanceRef"; -import { type SketchProps } from "@contracts/SketchProps"; - -export function removeP5CanvasInstance( - p5CanvasInstanceRef: P5CanvasInstanceRef -) { - p5CanvasInstanceRef.current?.remove(); - p5CanvasInstanceRef.current = null; -} diff --git a/src/utils/updateP5CanvasInstance.ts b/src/utils/updateP5CanvasInstance.ts deleted file mode 100644 index 9a63a01..0000000 --- a/src/utils/updateP5CanvasInstance.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { type CanvasContainerRef } from "@contracts/CanvasContainerRef"; -import { type P5CanvasInstanceRef } from "@contracts/P5CanvasInstanceRef"; -import { type Sketch } from "@contracts/Sketch"; -import { type SketchProps } from "@contracts/SketchProps"; -import { createP5CanvasInstance } from "@utils/createP5CanvasInstance"; -import { removeP5CanvasInstance } from "@utils/removeP5CanvasInstance"; - -export function updateP5CanvasInstance( - p5CanvasInstanceRef: P5CanvasInstanceRef, - canvasContainerRef: CanvasContainerRef, - sketch: Sketch -) { - if (canvasContainerRef.current === null) { - return null; - } - - removeP5CanvasInstance(p5CanvasInstanceRef); - - return createP5CanvasInstance(sketch, canvasContainerRef.current); -} diff --git a/tests/components/P5Canvas.test.tsx b/tests/components/P5Canvas.test.tsx index faf6588..7c1860d 100644 --- a/tests/components/P5Canvas.test.tsx +++ b/tests/components/P5Canvas.test.tsx @@ -1,8 +1,10 @@ import { P5Canvas } from "@components/P5Canvas"; -import { CanvasContainerClassName } from "@constants/CanvasContainerClassName"; -import { type P5CanvasInstance } from "@contracts/P5CanvasInstance"; -import { type Sketch } from "@contracts/Sketch"; -import { type Updater } from "@contracts/Updater"; +import { + CanvasContainerClassName, + type P5CanvasInstance, + type Sketch, + type Updater +} from "@p5-wrapper/common"; import { render, RenderResult, waitFor } from "@testing-library/react"; import { renderToStaticMarkup, renderToString } from "react-dom/server"; import { assert, describe, expect, it, vi } from "vitest"; diff --git a/tests/constants/CanvasContainerClassName.test.ts b/tests/constants/CanvasContainerClassName.test.ts deleted file mode 100644 index bbe5c22..0000000 --- a/tests/constants/CanvasContainerClassName.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CanvasContainerClassName } from "@constants/CanvasContainerClassName"; -import { describe, expect, it } from "vitest"; - -describe("CanvasContainerClassName", () => { - it("Is exported as a non-empty string", () => { - expect(CanvasContainerClassName).toBeTypeOf("string"); - expect(CanvasContainerClassName.length).toBeGreaterThan(0); - }); -}); diff --git a/tests/exports.test.tsx b/tests/exports.test.tsx deleted file mode 100644 index 6e3f463..0000000 --- a/tests/exports.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { CanvasContainerClassName, P5Canvas } from "@/main"; -import { createElement, isValidElement } from "react"; -import { assert, describe, expect, it, vi } from "vitest"; - -describe("Exports", () => { - describe("CanvasContainerClassName", () => { - it("Exports the css class name used on the canvas container", () => { - expect(CanvasContainerClassName).not.toBeUndefined(); - expect(CanvasContainerClassName).toBe("canvas-container"); - }); - - it("Exports the css class name used on the canvas container as a non-empty string", () => { - expect(typeof CanvasContainerClassName).toBe("string"); - expect(CanvasContainerClassName.length).toBeGreaterThan(0); - }); - - it("Exports the P5 canvas component", () => { - expect(P5Canvas).not.toBeUndefined(); - }); - }); - - describe("P5Canvas", () => { - it("Exports the P5 canvas component as a React element", () => { - const component = createElement(P5Canvas, { - sketch: vi.fn() - }); - - assert(isValidElement(component)); - }); - }); -}); diff --git a/tests/setup.ts b/tests/setup.ts index 1f3cffe..890a8f7 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,4 +1,4 @@ -import { p5 } from "@contracts/p5"; +import { p5 } from "@p5-wrapper/common"; import "@testing-library/jest-dom/vitest"; import { cleanup } from "@testing-library/react"; import { afterEach } from "vitest"; diff --git a/tests/utils/createP5CanvasInstance.test.ts b/tests/utils/createP5CanvasInstance.test.ts deleted file mode 100644 index 675762b..0000000 --- a/tests/utils/createP5CanvasInstance.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { p5 } from "@contracts/p5"; -import { createP5CanvasInstance } from "@utils/createP5CanvasInstance"; -import { describe, expect, it, vi } from "vitest"; - -describe("createP5CanvasInstance", () => { - it("Should construct a valid implementation of p5 in instance mode", () => { - const sketch = vi.fn(); - const canvasContainer = document.createElement("div"); - const instance = createP5CanvasInstance(sketch, canvasContainer); - - expect(instance).toBeInstanceOf(p5); - }); -}); diff --git a/tests/utils/logErrorBoundaryError.test.ts b/tests/utils/logErrorBoundaryError.test.ts deleted file mode 100644 index 158a3ba..0000000 --- a/tests/utils/logErrorBoundaryError.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { logErrorBoundaryError } from "@utils/logErrorBoundaryError"; -import { - afterEach, - beforeEach, - describe, - expect, - it, - MockInstance, - vi -} from "vitest"; - -describe("logErrorBoundaryError", () => { - let errorLoggerSpy: MockInstance; - - beforeEach(() => { - const errorLogger = vi.fn(); - - errorLoggerSpy = vi.spyOn(console, "error").mockImplementation(errorLogger); - }); - - afterEach(() => { - errorLoggerSpy.mockReset(); - errorLoggerSpy.mockRestore(); - }); - - it("Logs the error correctly when provided an `Error` instance", () => { - const error = new Error("An error message"); - - logErrorBoundaryError(error); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining(`Error("An error message")`) - ); - }); - - it("Logs the error correctly when provided a string", () => { - logErrorBoundaryError("A string message"); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining(`String("A string message")`) - ); - }); - - it("Logs the error correctly when provided a number", () => { - logErrorBoundaryError(123); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining("Number(123)") - ); - }); - - it("Logs the error correctly when provided a bigint", () => { - logErrorBoundaryError(BigInt(123)); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining("BigInt(123)") - ); - }); - - it("Logs the error correctly when provided an `Object` instance", () => { - logErrorBoundaryError({ a: 1, b: 2 }); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining('Object({"a":1,"b":2})') - ); - }); - - it("Logs the error correctly when provided an `Array` instance", () => { - logErrorBoundaryError([1, 2, 3]); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining("Array([1,2,3])") - ); - }); - - it("Logs the error correctly when provided an `Set` instance", () => { - logErrorBoundaryError(new Set([1, 2, 3])); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining("Set([1,2,3])") - ); - }); - - it("Logs the error correctly when provided a symbol", () => { - logErrorBoundaryError(Symbol("test")); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining("Symbol(test)") - ); - }); - - it("Logs the error correctly when provided an unhandled `unknown` instance", () => { - logErrorBoundaryError(() => 123); - - expect(errorLoggerSpy).toHaveBeenCalledOnce(); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining( - "[P5Canvas] The error boundary was triggered. The error message was:" - ) - ); - expect(errorLoggerSpy).toHaveBeenCalledWith( - expect.stringContaining("Unknown(function)") - ); - }); -}); diff --git a/tests/utils/propsAreEqual.test.ts b/tests/utils/propsAreEqual.test.ts deleted file mode 100644 index 84bcf30..0000000 --- a/tests/utils/propsAreEqual.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type P5CanvasProps } from "@contracts/P5CanvasProps"; -import { propsAreEqual } from "@utils/propsAreEqual"; -import { describe, expect, it, vi } from "vitest"; - -describe("propsAreEqual", () => { - it("Returns true when the current and next props are the same", () => { - const sketch = vi.fn(); - const current: P5CanvasProps = { sketch }; - const next: P5CanvasProps = { sketch }; - const equal = propsAreEqual(current, next); - - expect(equal).toBe(true); - }); - - it("Returns false when the current and next props are not the same", () => { - const current: P5CanvasProps = { - sketch: () => { - return; - } - }; - const next: P5CanvasProps = { - sketch: () => { - return; - } - }; - const equal = propsAreEqual(current, next); - - expect(equal).toBe(false); - }); -}); diff --git a/tests/utils/removeP5CanvasInstance.test.ts b/tests/utils/removeP5CanvasInstance.test.ts deleted file mode 100644 index bc10eb9..0000000 --- a/tests/utils/removeP5CanvasInstance.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { SketchProps } from "@/main"; -import { p5 } from "@contracts/p5"; -import { type P5CanvasInstanceRef } from "@contracts/P5CanvasInstanceRef"; -import { removeP5CanvasInstance } from "@utils/removeP5CanvasInstance"; -import { createRef } from "react"; -import { describe, expect, it, vi } from "vitest"; - -describe("removeP5CanvasInstance", () => { - it("Calls the remove method on the P5 canvas instance if it exists", () => { - const instance = new p5(() => { - return; - }); - const removeSpy = vi.spyOn(instance, "remove"); - const p5CanvasInstanceRef: P5CanvasInstanceRef = createRef(); - - // @see https://github.com/processing/p5.js/pull/7863 - // @ts-expect-error The p5 library changes from the above PR caused some issues with the inferred types. - p5CanvasInstanceRef.current = instance; - - removeP5CanvasInstance(p5CanvasInstanceRef); - - expect(removeSpy).toHaveBeenCalledOnce(); - }); - - it("Sets the provided P5 canvas instance ref to null", () => { - const instance = new p5(() => { - return; - }); - const p5CanvasInstanceRef: P5CanvasInstanceRef = createRef(); - - // @see https://github.com/processing/p5.js/pull/7863 - // @ts-expect-error The p5 library changes from the above PR caused some issues with the inferred types. - p5CanvasInstanceRef.current = instance; - - expect(p5CanvasInstanceRef.current).not.toBeNull(); - - removeP5CanvasInstance(p5CanvasInstanceRef); - - expect(p5CanvasInstanceRef.current).toBeNull(); - }); -}); diff --git a/tests/utils/updateP5CanvasInstance.test.ts b/tests/utils/updateP5CanvasInstance.test.ts deleted file mode 100644 index b955061..0000000 --- a/tests/utils/updateP5CanvasInstance.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { SketchProps } from "@/main"; -import { type CanvasContainerRef } from "@contracts/CanvasContainerRef"; -import { p5 } from "@contracts/p5"; -import { type P5CanvasInstanceRef } from "@contracts/P5CanvasInstanceRef"; -import { createP5CanvasInstance } from "@utils/createP5CanvasInstance"; -import { updateP5CanvasInstance } from "@utils/updateP5CanvasInstance"; -import { createRef } from "react"; -import { describe, expect, it, vi } from "vitest"; - -describe("updateP5CanvasInstance", () => { - it("Should update a P5 canvas instance to a new version", () => { - const sketch = vi.fn(); - const canvasContainer = document.createElement("div"); - const canvasContainerRef: CanvasContainerRef = createRef(); - const p5CanvasInstanceRef: P5CanvasInstanceRef = createRef(); - const instance = createP5CanvasInstance(sketch, canvasContainer); - - canvasContainerRef.current = canvasContainer; - p5CanvasInstanceRef.current = instance; - - const updatedP5CanvasInstanceRef = updateP5CanvasInstance( - p5CanvasInstanceRef, - canvasContainerRef, - sketch - ); - - expect(instance).toBeInstanceOf(p5); - expect(updatedP5CanvasInstanceRef).toBeInstanceOf(p5); - expect(instance).not.toEqual(updatedP5CanvasInstanceRef); - }); - - it("Should return undefined if the canvasContainerRef value is null", () => { - const sketch = vi.fn(); - const canvasContainer = document.createElement("div"); - const canvasContainerRef: CanvasContainerRef = createRef(); - const p5CanvasInstanceRef: P5CanvasInstanceRef = createRef(); - const instance = createP5CanvasInstance(sketch, canvasContainer); - - p5CanvasInstanceRef.current = instance; - - const updatedP5CanvasInstanceRef = updateP5CanvasInstance( - p5CanvasInstanceRef, - canvasContainerRef, - sketch - ); - - expect(updatedP5CanvasInstanceRef).toBeNull(); - }); -});