diff --git a/.eslintignore b/.eslintignore
index fec8b78a8..6f46fe5c8 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,3 +2,4 @@
dist/
node_modules/
storybook-static/
+test/e2e/*app
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1db59e43b..edce963a1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,6 +23,7 @@ jobs:
cache: 'yarn'
- run: yarn install
- run: yarn build-storybook
+ - run: yarn test
# semantic-release skips not configured branches(see: release.config.js) or pull-requests
- run: yarn release
env:
diff --git a/.gitignore b/.gitignore
index e3bda7041..dab6e34fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ coverage/
.idea
yarn-error.log
.size-snapshot.json
+test/e2e/*app
+__diff_output__
\ No newline at end of file
diff --git a/package.json b/package.json
index 0a860b253..520a65e16 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
"eslint:ci": "eslint .",
"prettier": "prettier --check .",
"prettier-fix": "prettier --write .",
- "test": "npm run eslint:ci && npm run prettier",
+ "test": "npm run eslint:ci && (cd test/e2e; ./e2e.sh)",
"typecheck": "tsc --noEmit --emitDeclarationOnly false --strict --jsx react",
"typegen": "tsc --emitDeclarationOnly",
"storybook": "NODE_OPTIONS=\"--openssl-legacy-provider\" storybook dev -p 6006",
@@ -111,7 +111,7 @@
"@typescript-eslint/parser": "^5.4.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
- "copyfiles": "^2.3.0",
+ "copyfiles": "^2.4.1",
"eslint": "^7.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.22.0",
@@ -120,10 +120,12 @@
"eslint-plugin-react-hooks": "^4.1.0",
"eslint-plugin-storybook": "^0.6.12",
"husky": "^6.0.0",
- "jest": "^26.4.1",
+ "jest": "^29.5.0",
+ "jest-image-snapshot": "^6.1.0",
"json": "^11.0.0",
"prettier": "^2.4.1",
"pretty-quick": "^3.1.0",
+ "puppeteer": "^20.7.4",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"rimraf": "^3.0.2",
@@ -131,6 +133,7 @@
"rollup-plugin-glslify": "^1.3.0",
"rollup-plugin-multi-input": "^1.4.1",
"semantic-release": "^21.0.6",
+ "serve": "^14.2.0",
"storybook": "^7.0.12",
"three": "^0.149.0",
"ts-node": "^10.9.1",
diff --git a/test/e2e/App.jsx b/test/e2e/App.jsx
new file mode 100644
index 000000000..dcebc82c6
--- /dev/null
+++ b/test/e2e/App.jsx
@@ -0,0 +1,33 @@
+import { Suspense, useEffect } from 'react'
+import { Canvas } from '@react-three/fiber'
+import { Box, Environment, CameraControls } from '@react-three/drei' // eslint-disable-line import/no-unresolved
+
+function App() {
+ return (
+
+ )
+}
+
+function Scene() {
+ useEffect(() => {
+ document.dispatchEvent(new Event('puppeteer:r3f'))
+ }, [])
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default App
diff --git a/test/e2e/__image_snapshots__/snapshot-test-js-snapshot-should-match-previous-one-1-snap.png b/test/e2e/__image_snapshots__/snapshot-test-js-snapshot-should-match-previous-one-1-snap.png
new file mode 100644
index 000000000..54617209a
Binary files /dev/null and b/test/e2e/__image_snapshots__/snapshot-test-js-snapshot-should-match-previous-one-1-snap.png differ
diff --git a/test/e2e/e2e.sh b/test/e2e/e2e.sh
new file mode 100755
index 000000000..447e77b92
--- /dev/null
+++ b/test/e2e/e2e.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+set -ex
+
+PORT=5188
+
+(cd ../../dist; npm pack)
+
+kill_app() {
+ kill $(lsof -ti:$PORT)
+}
+
+kill_app || echo "ok, no previous running process on port $PORT"
+
+#
+# ██╗ ██╗██╗████████╗███████╗
+# ██║ ██║██║╚══██╔══╝██╔════╝
+# ██║ ██║██║ ██║ █████╗
+# ╚██╗ ██╔╝██║ ██║ ██╔══╝
+# ╚████╔╝ ██║ ██║ ███████╗
+# ╚═══╝ ╚═╝ ╚═╝ ╚══════╝
+#
+
+rm -rf viteapp
+
+# create app
+npm create vite@latest viteapp -- --template react
+
+# drei
+(cd viteapp; npm i; npm i ../../../dist/react-three-drei-0.0.0-semantic-release.tgz)
+
+# App.jsx
+cp App.jsx viteapp/src/App.jsx
+
+# build+start+jest
+(cd viteapp; npm run build; npm run preview -- --port $PORT &)
+npx jest snapshot.test.js || kill_app
+kill_app
+
+rm -rf viteapp
+
+#
+# ██████╗██████╗ █████╗
+# ██╔════╝██╔══██╗██╔══██╗
+# ██║ ██████╔╝███████║
+# ██║ ██╔══██╗██╔══██║
+# ╚██████╗██║ ██║██║ ██║
+# ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝
+#
+
+rm -rf craapp
+
+# create app
+npx create-react-app craapp
+
+# drei
+(cd craapp; npm i ../../../dist/react-three-drei-0.0.0-semantic-release.tgz)
+
+# App.jsx
+cp App.jsx craapp/src/App.js
+
+# build+start+jest
+(cd craapp; npm run build; npx serve -s -p $PORT build &)
+npx jest snapshot.test.js || kill_app
+kill_app
+
+rm -rf craapp
+
+#
+# Teardown
+#
+
+echo "✅ e2e ok"
\ No newline at end of file
diff --git a/test/e2e/snapshot.test.js b/test/e2e/snapshot.test.js
new file mode 100644
index 000000000..a07dc7663
--- /dev/null
+++ b/test/e2e/snapshot.test.js
@@ -0,0 +1,37 @@
+const puppeteer = require('puppeteer')
+
+const { toMatchImageSnapshot } = require('jest-image-snapshot')
+expect.extend({ toMatchImageSnapshot })
+
+async function waitForEvent(page, eventName) {
+ await page.evaluate(
+ (eventName) => new Promise((resolve) => document.addEventListener(eventName, resolve, { once: true })),
+ eventName
+ )
+}
+
+describe('snapshot', () => {
+ let browser
+ let page
+ beforeAll(async () => {
+ browser = await puppeteer.launch({ headless: 'new' })
+ page = await browser.newPage()
+ })
+
+ it('should match previous one', async () => {
+ // ⏳ "r3f" event
+ await page.goto('http://localhost:5188')
+ await waitForEvent(page, 'puppeteer:r3f')
+
+ // 📸