Rename compatibility-tests to compat-tests

This commit is contained in:
Aria Minaei 2023-07-28 19:49:52 +02:00 committed by Aria
parent f2a25aa48e
commit bc7fcf8e0a
48 changed files with 12 additions and 14 deletions

5
compat-tests/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# these lock files include checksums of the @theatre/*@compat packages, which
# would change after every edit to their source, so we better not keep them
# in the repo
/fixtures/*/package/package-lock.json
/fixtures/*/package/yarn.lock

21
compat-tests/README.md Normal file
View file

@ -0,0 +1,21 @@
# Compatibility tests
This setup helps us test whether Theatre.js is compatible with popular tools in the JS ecosystem, such as Vite, Next.js, webpack, react, vue, etc.
## The directory structure
- `./fixtures` (contains the fixtures - read on for more details)
- `parcel2-react18/`: The name of the fixture. This name means we're testing a minimal setup of Theatre.js alongside `parcel2` and `react18`.
- `package/`: This is the npm package that contains a minimal setup of `theatre+parcel2+react18`.
- `production.compat-test.ts`: This is a jest test for creating a production build of this setup.
- `*.compat-test.ts`: Any `.compat-test.ts` file will be picked up by jest, so you can use more files to test different aspects of the fixture.
## How to run the tests
1. First, we run `yarn run install-fixtures`, which tries to install Theatre.js on a fixture as if `@theatre/core|studio|r3f` were installed through npm. This script runs a [local npm registry](https://github.com/verdaccio/verdaccio) and publishes a production build of all the Theatre.js packages to it. Then, it iterates through `./fixtures/*/package` and runs `$ npm install` on them, using that local npm registry.
**If this step fails**, that usually means one of `@theatre/*` packages has a `dependency/peerDependency` that cannot be satisfied by `npm/yarn`. So this is always the first thing to fix.
1. Then, we run `$ yarn test:compat:run`, which will run jest on all of `*.compat-test.ts` files, each of which tests an aspect of a test setup.
2. Most of our fixtures don't actually have `.compat-test.ts` files, so we'll have to run them manually and see if Theatre still works in them, jut like a manual QA pass.
> **Gotchas**
> Some bundlers like webpack are not configured to work well with yarn workspaces by default. For example, the webpack config of create-react-app, tries to look up the node_modules chain to find missing dependencies, which is not a behavior that we want in build-tests setups. So if a setup doesn't work, try running it outside the monorepo to see if being in the monorepo is what's causing it to fail.

View file

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -0,0 +1 @@
Testing `@theatre/core` and `@theatre/studio` with `npm`, `create-react-app`, and `react@18`

View file

@ -0,0 +1,34 @@
{
"name": "@compat/cra-react18",
"version": "0.1.0",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"dependencies": {
"@react-three/drei": ">7.2.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@theatre/core": "0.0.1-COMPAT.1",
"@theatre/r3f": "0.0.1-COMPAT.1",
"@theatre/studio": "0.0.1-COMPAT.1",
"react-scripts": "^5.0.1",
"three": ">0.132.0",
"web-vitals": "^1.0.1"
},
"eslintConfig": {
"extends": ["react-app", "react-app/jest"]
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -0,0 +1,92 @@
import {getProject} from '@theatre/core'
import ReactDOM from 'react-dom/client'
import React from 'react'
import {Canvas} from '@react-three/fiber'
import studio from '@theatre/studio'
import {editable as e, SheetProvider} from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
studio.extend(extension)
studio.initialize({usePersistentStorage: false})
}
// credit: https://codesandbox.io/s/camera-pan-nsb7f
function Plane({color, theatreKey, ...props}) {
return (
<e.mesh {...props} theatreKey={theatreKey}>
<boxBufferGeometry />
<meshStandardMaterial color={color} />
</e.mesh>
)
}
function App() {
return (
<Canvas
gl={{preserveDrawingBuffer: true}}
linear
frameloop="demand"
dpr={[1.5, 2]}
style={{position: 'absolute', top: 0, left: 0}}
>
<SheetProvider sheet={getProject('Playground - R3F').sheet('R3F-Canvas')}>
{/* @ts-ignore */}
<e.orthographicCamera makeDefault theatreKey="Camera" />
<ambientLight intensity={0.4} />
<e.pointLight
position={[-10, -10, 5]}
intensity={2}
color="#ff20f0"
theatreKey="Light 1"
/>
<e.pointLight
position={[0, 0.5, -1]}
distance={1}
intensity={2}
color="#e4be00"
theatreKey="Light 2"
/>
<group position={[0, -0.9, -3]}>
<Plane
color="hotpink"
rotation-x={-Math.PI / 2}
position-z={2}
scale={[4, 20, 0.2]}
theatreKey="plane1"
/>
<Plane
color="#e4be00"
rotation-x={-Math.PI / 2}
position-y={1}
scale={[4.2, 0.2, 4]}
theatreKey="plane2"
/>
<Plane
color="#736fbd"
rotation-x={-Math.PI / 2}
position={[-1.7, 1, 3.5]}
scale={[0.5, 4, 4]}
theatreKey="plane3"
/>
<Plane
color="white"
rotation-x={-Math.PI / 2}
position={[0, 4.5, 3]}
scale={[2, 0.03, 4]}
theatreKey="plane4"
/>
</group>
</SheetProvider>
</Canvas>
)
}
const project = getProject('Project')
const sheet = project.sheet('Sheet')
const obj = sheet.object('Obj', {str: 'some string', num: 0})
const container = document.getElementById('root')
const root = ReactDOM.createRoot(container)
root.render(<App obj={obj}>hi</App>)

View file

@ -0,0 +1,31 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

View file

@ -0,0 +1 @@
This is a starter template for [Learn Next.js](https://nextjs.org/learn).

View file

@ -0,0 +1,17 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@theatre/core": "0.0.1-COMPAT.1",
"@theatre/r3f": "0.0.1-COMPAT.1",
"@theatre/studio": "0.0.1-COMPAT.1",
"theatric": "0.0.1-COMPAT.1",
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

View file

@ -0,0 +1,92 @@
import {getProject} from '@theatre/core'
import React from 'react'
import {Canvas} from '@react-three/fiber'
import studio from '@theatre/studio'
import {editable as e, SheetProvider} from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
import playgroundState from './playgroundState.json'
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
studio.extend(extension)
studio.initialize({usePersistentStorage: false})
}
const sheet = getProject('Playground - R3F', {state: playgroundState}).sheet(
'R3F-Canvas',
)
// credit: https://codesandbox.io/s/camera-pan-nsb7f
function Plane({color, theatreKey, ...props}) {
return (
<e.mesh {...props} theatreKey={theatreKey}>
<boxBufferGeometry />
<meshStandardMaterial color={color} />
</e.mesh>
)
}
function App() {
return (
<Canvas
gl={{preserveDrawingBuffer: true}}
linear
frameloop="demand"
dpr={[1.5, 2]}
style={{position: 'absolute', top: 0, left: 0}}
>
<SheetProvider sheet={sheet}>
{/* @ts-ignore */}
<e.orthographicCamera makeDefault theatreKey="Camera" />
<ambientLight intensity={0.4} />
<e.pointLight
position={[-10, -10, 5]}
intensity={2}
color="#ff20f0"
theatreKey="Light 1"
/>
<e.pointLight
position={[0, 0.5, -1]}
distance={1}
intensity={2}
color="#e4be00"
theatreKey="Light 2"
/>
<group position={[0, -0.9, -3]}>
<Plane
color="hotpink"
rotation-x={-Math.PI / 2}
position-z={2}
scale={[4, 20, 0.2]}
theatreKey="plane1"
/>
<Plane
color="#e4be00"
rotation-x={-Math.PI / 2}
position-y={1}
scale={[4.2, 0.2, 4]}
theatreKey="plane2"
/>
<Plane
color="#736fbd"
rotation-x={-Math.PI / 2}
position={[-1.7, 1, 3.5]}
scale={[0.5, 4, 4]}
theatreKey="plane3"
/>
<Plane
color="white"
rotation-x={-Math.PI / 2}
position={[0, 4.5, 3]}
scale={[2, 0.03, 4]}
theatreKey="plane4"
/>
</group>
</SheetProvider>
</Canvas>
)
}
export default function Home() {
return <App></App>
}

View file

@ -0,0 +1,24 @@
{
"sheetsById": {
"R3F-Canvas": {
"staticOverrides": {
"byObject": {
"plane1": {
"position": {
"x": -0.06000000000000002
}
},
"plane2": {
"position": {
"x": 0,
"y": -1.1043953439330743,
"z": 6.322692591942688
}
}
}
}
}
},
"definitionVersion": "0.4.0",
"revisionHistory": ["lSnZ_QVusR3qNnVN"]
}

View file

@ -0,0 +1,77 @@
import {button, initialize, useControls} from 'theatric'
import {render} from 'react-dom'
import React, {useState} from 'react'
// initialize()
function SomeComponent({id}) {
const {foo, $get, $set} = useControls(
{
foo: 0,
bar: 0,
bez: button(() => {
$set((p) => p.foo, 2)
$set((p) => p.bar, 3)
console.log($get((p) => p.foo))
}),
},
{folder: id},
)
return (
<div>
{id}: {foo}
</div>
)
}
export default function App() {
const {bar, $set, $get} = useControls({
bar: {foo: 'bar'},
baz: button(() => console.log($get((p) => p.bar))),
})
const {another, panel, yo} = useControls(
{
another: '',
panel: '',
yo: 0,
},
{panel: 'My panel'},
)
const {} = useControls({})
const [showComponent, setShowComponent] = useState(false)
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div>{JSON.stringify(bar)}</div>
<SomeComponent id="first" />
<SomeComponent id="second" />
<button
onClick={() => {
setShowComponent(!showComponent)
}}
>
Show another component
</button>
<button
onClick={() => {
$set((p) => p.bar.foo, $get((p) => p.bar.foo) + 1)
}}
>
Increment stuff
</button>
{showComponent && <SomeComponent id="hidden" />}
{yo}
</div>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,69 @@
// @cspotcode/zx is zx in CommonJS
import {$, cd, path} from '@cspotcode/zx'
import {chromium, devices} from 'playwright'
$.verbose = true
const PATH_TO_PACKAGE = path.join(__dirname, `./package`)
describe(`next / production`, () => {
test(`\`$ next build\` should succeed and have a predictable output`, async () => {
cd(PATH_TO_PACKAGE)
const {exitCode, stdout} = await $`npm run build`
// at this point, the build should have succeeded
expect(exitCode).toEqual(0)
// now let's check the output to make sure it's what we expect
// all of stdout until the line that contains "Route (pages)". That's because what comes after that
// line is a list of all the pages that were built, and we don't want to snapshot that because it changes every time.
const stdoutUntilRoutePages = stdout.split(`Route (pages)`)[0]
// This test will fail if `next build` outputs anything unexpected.
// I'm commenting this out because the output of `next build` is not predictable
// TOOD: figure out a different way to test this
// expect(stdoutUntilRoutePages).toMatchSnapshot()
})
// this test is not ready yet, so we'll skip it
describe.skip(`$ next start`, () => {
let browser, page
beforeAll(async () => {
browser = await chromium.launch()
})
afterAll(async () => {
await browser.close()
})
beforeEach(async () => {
page = await browser.newPage()
})
afterEach(async () => {
await page.close()
})
// just a random port I'm hoping is free everywhere.
const port = 30978
test('`$ next start` serves the app, and the app works', async () => {
// run the production server but don't wait for it to finish
cd(PATH_TO_PACKAGE)
const p = $`npm run start -- --port ${port}`
// await p
try {
page.on('console', (msg) => console.log('PAGE LOG:', msg.text()))
await page.goto(`http://localhost:${port}`)
// wait three seconds
await page.waitForTimeout(3000)
} finally {
p.kill()
}
try {
await p
} catch (e) {
if (e.signal !== 'SIGKILL' && e.signal !== 'SIGTERM') {
throw e
}
}
})
})
})

View file

@ -0,0 +1,3 @@
/.cache
/dist
/package-lock.json

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Example - DOM</title>
</head>
<body>
<div id="root"></div>
<script src="./src/index.js"></script>
</body>
</html>

View file

@ -0,0 +1,17 @@
{
"name": "@compat/parcel1-react17",
"scripts": {
"dev": "parcel serve ./index.html"
},
"dependencies": {
"@react-three/drei": "^7.3.1",
"@react-three/fiber": "^7.0.6",
"@theatre/core": "0.0.1-COMPAT.1",
"@theatre/r3f": "0.0.1-COMPAT.1",
"@theatre/studio": "0.0.1-COMPAT.1",
"parcel-bundler": "^1.12.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"three": "^0.137.0"
}
}

View file

@ -0,0 +1,91 @@
import {getProject} from '@theatre/core'
import ReactDOM from 'react-dom'
import React from 'react'
import {Canvas} from '@react-three/fiber'
import studio from '@theatre/studio'
import {editable as e, SheetProvider} from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
studio.extend(extension)
studio.initialize({usePersistentStorage: false})
}
// credit: https://codesandbox.io/s/camera-pan-nsb7f
function Plane({color, theatreKey, ...props}) {
return (
<e.mesh {...props} theatreKey={theatreKey}>
<boxBufferGeometry />
<meshStandardMaterial color={color} />
</e.mesh>
)
}
function App() {
return (
<Canvas
gl={{preserveDrawingBuffer: true}}
linear
frameloop="demand"
dpr={[1.5, 2]}
style={{position: 'absolute', top: 0, left: 0}}
>
<SheetProvider sheet={getProject('Playground - R3F').sheet('R3F-Canvas')}>
{/* @ts-ignore */}
<e.orthographicCamera makeDefault theatreKey="Camera" />
<ambientLight intensity={0.4} />
<e.pointLight
position={[-10, -10, 5]}
intensity={2}
color="#ff20f0"
theatreKey="Light 1"
/>
<e.pointLight
position={[0, 0.5, -1]}
distance={1}
intensity={2}
color="#e4be00"
theatreKey="Light 2"
/>
<group position={[0, -0.9, -3]}>
<Plane
color="hotpink"
rotation-x={-Math.PI / 2}
position-z={2}
scale={[4, 20, 0.2]}
theatreKey="plane1"
/>
<Plane
color="#e4be00"
rotation-x={-Math.PI / 2}
position-y={1}
scale={[4.2, 0.2, 4]}
theatreKey="plane2"
/>
<Plane
color="#736fbd"
rotation-x={-Math.PI / 2}
position={[-1.7, 1, 3.5]}
scale={[0.5, 4, 4]}
theatreKey="plane3"
/>
<Plane
color="white"
rotation-x={-Math.PI / 2}
position={[0, 4.5, 3]}
scale={[2, 0.03, 4]}
theatreKey="plane4"
/>
</group>
</SheetProvider>
</Canvas>
)
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
)

View file

@ -0,0 +1,3 @@
/.cache
/dist
/package-lock.json

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Example - DOM</title>
</head>
<body>
<div id="root"></div>
<script src="./src/index.js"></script>
</body>
</html>

View file

@ -0,0 +1,14 @@
{
"name": "@compat/parcel1-react18",
"scripts": {
"dev": "parcel serve ./index.html"
},
"dependencies": {
"@theatre/core": "0.0.1-COMPAT.1",
"@theatre/r3f": "0.0.1-COMPAT.1",
"@theatre/studio": "0.0.1-COMPAT.1",
"parcel-bundler": "^1.12.5",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}
}

View file

@ -0,0 +1,92 @@
import {getProject} from '@theatre/core'
import ReactDOM from 'react-dom/client'
import React from 'react'
import {Canvas} from '@react-three/fiber'
import studio from '@theatre/studio'
import {editable as e, SheetProvider} from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
studio.extend(extension)
studio.initialize({usePersistentStorage: false})
}
// credit: https://codesandbox.io/s/camera-pan-nsb7f
function Plane({color, theatreKey, ...props}) {
return (
<e.mesh {...props} theatreKey={theatreKey}>
<boxBufferGeometry />
<meshStandardMaterial color={color} />
</e.mesh>
)
}
function App() {
return (
<Canvas
gl={{preserveDrawingBuffer: true}}
linear
frameloop="demand"
dpr={[1.5, 2]}
style={{position: 'absolute', top: 0, left: 0}}
>
<SheetProvider sheet={getProject('Playground - R3F').sheet('R3F-Canvas')}>
{/* @ts-ignore */}
<e.orthographicCamera makeDefault theatreKey="Camera" />
<ambientLight intensity={0.4} />
<e.pointLight
position={[-10, -10, 5]}
intensity={2}
color="#ff20f0"
theatreKey="Light 1"
/>
<e.pointLight
position={[0, 0.5, -1]}
distance={1}
intensity={2}
color="#e4be00"
theatreKey="Light 2"
/>
<group position={[0, -0.9, -3]}>
<Plane
color="hotpink"
rotation-x={-Math.PI / 2}
position-z={2}
scale={[4, 20, 0.2]}
theatreKey="plane1"
/>
<Plane
color="#e4be00"
rotation-x={-Math.PI / 2}
position-y={1}
scale={[4.2, 0.2, 4]}
theatreKey="plane2"
/>
<Plane
color="#736fbd"
rotation-x={-Math.PI / 2}
position={[-1.7, 1, 3.5]}
scale={[0.5, 4, 4]}
theatreKey="plane3"
/>
<Plane
color="white"
rotation-x={-Math.PI / 2}
position={[0, 4.5, 3]}
scale={[2, 0.03, 4]}
theatreKey="plane4"
/>
</group>
</SheetProvider>
</Canvas>
)
}
const project = getProject('Project')
const sheet = project.sheet('Sheet')
const obj = sheet.object('Obj', {str: 'some string', num: 0})
const container = document.getElementById('root')
const root = ReactDOM.createRoot(container)
root.render(<App obj={obj}>hi</App>)

View file

@ -0,0 +1,4 @@
/.parcel-cache
/.cache
/dist
/package-lock.json

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Example - DOM</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/index.js"></script>
</body>
</html>

View file

@ -0,0 +1,14 @@
{
"name": "@compat/parcel2-react18",
"scripts": {
"dev": "parcel serve ./index.html"
},
"dependencies": {
"@theatre/core": "0.0.1-COMPAT.1",
"@theatre/r3f": "0.0.1-COMPAT.1",
"@theatre/studio": "0.0.1-COMPAT.1",
"parcel": "^2.5.0",
"react": "^18.1.0",
"react-dom": "^18.1.0"
}
}

View file

@ -0,0 +1,92 @@
import {getProject} from '@theatre/core'
import ReactDOM from 'react-dom/client'
import React from 'react'
import {Canvas} from '@react-three/fiber'
import studio from '@theatre/studio'
import {editable as e, SheetProvider} from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
studio.extend(extension)
studio.initialize({usePersistentStorage: false})
}
// credit: https://codesandbox.io/s/camera-pan-nsb7f
function Plane({color, theatreKey, ...props}) {
return (
<e.mesh {...props} theatreKey={theatreKey}>
<boxBufferGeometry />
<meshStandardMaterial color={color} />
</e.mesh>
)
}
function App() {
return (
<Canvas
gl={{preserveDrawingBuffer: true}}
linear
frameloop="demand"
dpr={[1.5, 2]}
style={{position: 'absolute', top: 0, left: 0}}
>
<SheetProvider sheet={getProject('Playground - R3F').sheet('R3F-Canvas')}>
{/* @ts-ignore */}
<e.orthographicCamera makeDefault theatreKey="Camera" />
<ambientLight intensity={0.4} />
<e.pointLight
position={[-10, -10, 5]}
intensity={2}
color="#ff20f0"
theatreKey="Light 1"
/>
<e.pointLight
position={[0, 0.5, -1]}
distance={1}
intensity={2}
color="#e4be00"
theatreKey="Light 2"
/>
<group position={[0, -0.9, -3]}>
<Plane
color="hotpink"
rotation-x={-Math.PI / 2}
position-z={2}
scale={[4, 20, 0.2]}
theatreKey="plane1"
/>
<Plane
color="#e4be00"
rotation-x={-Math.PI / 2}
position-y={1}
scale={[4.2, 0.2, 4]}
theatreKey="plane2"
/>
<Plane
color="#736fbd"
rotation-x={-Math.PI / 2}
position={[-1.7, 1, 3.5]}
scale={[0.5, 4, 4]}
theatreKey="plane3"
/>
<Plane
color="white"
rotation-x={-Math.PI / 2}
position={[0, 4.5, 3]}
scale={[2, 0.03, 4]}
theatreKey="plane4"
/>
</group>
</SheetProvider>
</Canvas>
)
}
const project = getProject('Project')
const sheet = project.sheet('Sheet')
const obj = sheet.object('Obj', {str: 'some string', num: 0})
const container = document.getElementById('root')
const root = ReactDOM.createRoot(container)
root.render(<App obj={obj}>hi</App>)

View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,27 @@
{
"name": "@compat/vite-react18",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^9.11.3",
"@react-three/fiber": "^8.0.19",
"@theatre/core": "0.0.1-COMPAT.1",
"@theatre/r3f": "0.0.1-COMPAT.1",
"@theatre/studio": "0.0.1-COMPAT.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"three": "^0.141.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^1.3.0",
"typescript": "^4.6.3",
"vite": "^2.9.9"
}
}

View file

@ -0,0 +1,92 @@
import {getProject} from '@theatre/core'
import ReactDOM from 'react-dom/client'
import React from 'react'
import {Canvas} from '@react-three/fiber'
import studio from '@theatre/studio'
import {editable as e, SheetProvider} from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
studio.extend(extension)
studio.initialize({usePersistentStorage: false})
}
// credit: https://codesandbox.io/s/camera-pan-nsb7f
function Plane({color, theatreKey, ...props}: any) {
return (
<e.mesh {...props} theatreKey={theatreKey}>
<boxBufferGeometry />
<meshStandardMaterial color={color} />
</e.mesh>
)
}
function App() {
return (
<Canvas
gl={{preserveDrawingBuffer: true}}
linear
frameloop="demand"
dpr={[1.5, 2]}
style={{position: 'absolute', top: 0, left: 0}}
>
<SheetProvider sheet={getProject('Playground - R3F').sheet('R3F-Canvas')}>
{/* @ts-ignore */}
<e.orthographicCamera makeDefault theatreKey="Camera" />
<ambientLight intensity={0.4} />
<e.pointLight
position={[-10, -10, 5]}
intensity={2}
color="#ff20f0"
theatreKey="Light 1"
/>
<e.pointLight
position={[0, 0.5, -1]}
distance={1}
intensity={2}
color="#e4be00"
theatreKey="Light 2"
/>
<group position={[0, -0.9, -3]}>
<Plane
color="hotpink"
rotation-x={-Math.PI / 2}
position-z={2}
scale={[4, 20, 0.2]}
theatreKey="plane1"
/>
<Plane
color="#e4be00"
rotation-x={-Math.PI / 2}
position-y={1}
scale={[4.2, 0.2, 4]}
theatreKey="plane2"
/>
<Plane
color="#736fbd"
rotation-x={-Math.PI / 2}
position={[-1.7, 1, 3.5]}
scale={[0.5, 4, 4]}
theatreKey="plane3"
/>
<Plane
color="white"
rotation-x={-Math.PI / 2}
position={[0, 4.5, 3]}
scale={[2, 0.03, 4]}
theatreKey="plane4"
/>
</group>
</SheetProvider>
</Canvas>
)
}
const project = getProject('Project')
const sheet = project.sheet('Sheet')
const obj = sheet.object('Obj', {str: 'some string', num: 0})
const container = document.getElementById('root')
const root = ReactDOM.createRoot(container)
root.render(<App obj={obj}>hi</App>)

View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

View file

@ -0,0 +1,7 @@
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

20
compat-tests/package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "@theatre/compat-tests",
"private": true,
"type": "module",
"scripts": {
"install-fixtures": "zx ./scripts/install-fixtures.mjs",
"clean": "zx ./scripts/clean.mjs"
},
"dependencies": {
"@cspotcode/zx": "^6.1.2",
"node-cleanup": "^2.1.2",
"playwright": "^1.29.1",
"prettier": "^2.6.2",
"verdaccio": "^5.10.2",
"verdaccio-auth-memory": "^10.2.0",
"verdaccio-memory": "^10.2.0",
"zx": "^7.1.1"
},
"version": "0.0.1-COMPAT.1"
}

View file

@ -0,0 +1,39 @@
/**
* Build the test setups
*/
import path from 'path'
import {cd, fs, $} from 'zx'
import {getCompatibilityTestSetups} from './utils.mjs'
const absPathOfCompatibilityTestSetups = getCompatibilityTestSetups()
const setupsWithErros = []
// Try building the setups
;(async function () {
for (const setupDir of absPathOfCompatibilityTestSetups) {
try {
cd(setupDir)
const pathToSetup = path.join(setupDir, setupDir)
fs.removeSync(path.join(pathToSetup, 'node_modules'))
fs.removeSync(path.join(pathToSetup, 'package-lock.json'))
fs.removeSync(path.join(pathToSetup, 'yarn.lock'))
await $`npm install`
await $`npm run build`
} catch (err) {
console.error(err)
setupsWithErros.push(setupDir)
}
}
// Stop if there were any errors during the build process,
// and print all of them to the console.
if (setupsWithErros.length !== 0) {
throw new Error(
`The following setups had problems when their dependencies were being installed:\n${(
setupsWithErros.join('\n'),
)}`,
)
}
})()

View file

@ -0,0 +1,5 @@
import {installTests} from './scripts.mjs'
;(async function runCI() {
await installTests()
process.exit(0)
})()

View file

@ -0,0 +1,3 @@
import {clean} from './scripts.mjs'
await clean()

View file

@ -0,0 +1,3 @@
import {installFixtures} from './scripts.mjs'
installFixtures()

View file

@ -0,0 +1,367 @@
/**
* Utility functions for the compatibility tests
*/
import prettier from 'prettier'
import path from 'path'
import {globby, argv, YAML, $, fs, cd, os, within} from 'zx'
import onCleanup from 'node-cleanup'
import * as verdaccioPackage from 'verdaccio'
import {chromium, devices} from 'playwright'
/**
* @param {string} pkg
* @returns boolean
*/
const isTheatreDependency = (pkg) =>
pkg.startsWith('@theatre/') || pkg === 'theatric'
const verbose = !!argv['verbose']
if (!verbose) {
$.verbose = false
console.log(
'Running in quiet mode. Add --verbose to see the output of all commands.',
)
}
// 'verdaccio' is not an es module so we have to do this:
// @ts-ignore
const startVerdaccioServer = verdaccioPackage.default.default
const config = {
VERDACCIO_PORT: 4823,
VERDACCIO_HOST: `localhost`,
get VERDACCIO_URL() {
return `http://${config.VERDACCIO_HOST}:${config.VERDACCIO_PORT}/`
},
PATH_TO_COMPAT_TESTS_ROOT: path.join(__dirname, '..'),
MONOREPO_ROOT: path.join(__dirname, '../..'),
}
/**
* Set environment variables so that yarn and npm use verdaccio as the registry.
* These are only set for the current process.
*/
process.env.YARN_NPM_PUBLISH_REGISTRY = config.VERDACCIO_URL
process.env.YARN_UNSAFE_HTTP_WHITELIST = config.VERDACCIO_HOST
process.env.YARN_NPM_AUTH_IDENT = 'test:test'
process.env.NPM_CONFIG_REGISTRY = config.VERDACCIO_URL
const tempVersion =
'0.0.1-COMPAT.' +
// a random integer between 1 and 50000
(Math.floor(Math.random() * 50000) + 1).toString()
/**
* This script starts verdaccio and publishes all the packages in the monorepo to it, then
* it runs `npm install` on all the test packages, and finally it closes verdaccio.
*/
export async function installFixtures() {
onCleanup((exitCode, signal) => {
onCleanup.uninstall()
restoreTestPackageJsons()
process.kill(process.pid, signal)
return false
})
console.log('Using temporary version: ' + tempVersion)
console.log('Patching package.json files in ./test-*')
const restoreTestPackageJsons = await patchTestPackageJsons()
console.log('Starting verdaccio')
const verdaccioServer = await startVerdaccio(config.VERDACCIO_PORT)
console.log(`Verdaccio is running on ${config.VERDACCIO_URL}`)
console.log('Releasing @theatre/* packages to verdaccio')
await releaseToVerdaccio()
console.log('Running `$ npm install` on test packages')
await runNpmInstallOnTestPackages()
console.log('All tests installed successfully')
await verdaccioServer.close()
restoreTestPackageJsons()
console.log('Done')
}
async function runNpmInstallOnTestPackages() {
const packagePaths = await getCompatibilityTestSetups()
for (const pathToPackageDir of packagePaths) {
cd(pathToPackageDir)
try {
console.log('Running npm install on ' + pathToPackageDir + '...')
await $`npm install --registry ${config.VERDACCIO_URL} --loglevel ${
verbose ? 'warn' : 'error'
} --fund false`
} catch (error) {
console.error(`Failed to install dependencies for ${pathToPackageDir}
Try running \`npm install\` in that directory manually via:
cd ${pathToPackageDir}
npm install --registry ${config.VERDACCIO_URL}
Original error: ${error}`)
}
}
}
/**
* Takes an absolute path to a package.json file and replaces all of its
* dependencies on `@theatre/*` packatges to `version`.
*
* @param {string} pathToPackageJson absolute path to the package.json file
* @param {string} version The version to set all `@theatre/*` dependencies to
*/
async function patchTheatreDependencies(pathToPackageJson, version) {
const originalFileContent = fs.readFileSync(pathToPackageJson, {
encoding: 'utf-8',
})
// get the package.json file's content
const packageJson = JSON.parse(originalFileContent)
// find all dependencies on '@theatre/*' packages and replace them with the local version
for (const dependencyType of [
'dependencies',
'devDependencies',
'peerDependencies',
]) {
const dependencies = packageJson[dependencyType]
if (dependencies) {
for (const dependencyName of Object.keys(dependencies)) {
if (isTheatreDependency(dependencyName)) {
dependencies[dependencyName] = version
}
}
}
}
// run the json through prettier
const jsonStringPrettified = prettier.format(
JSON.stringify(packageJson, null, 2),
{
parser: 'json',
filepath: pathToPackageJson,
},
)
// write the modified package.json file
fs.writeFileSync(pathToPackageJson, jsonStringPrettified, {encoding: 'utf-8'})
}
async function patchTestPackageJsons() {
const packagePaths = (await getCompatibilityTestSetups()).map(
(pathToPackageDir) => path.join(pathToPackageDir, 'package.json'),
)
// replace all dependencies on @theatre/* packages with the local version
for (const pathToPackageJson of packagePaths) {
patchTheatreDependencies(pathToPackageJson, tempVersion)
}
return () => {
// replace all dependencies on @theatre/* packages with the 0.0.1-COMPAT.1
for (const pathToPackageJson of packagePaths) {
patchTheatreDependencies(pathToPackageJson, '0.0.1-COMPAT.1')
}
}
}
/**
* Starts the verdaccio server and returns a promise that resolves when the serve is up and ready
*
* Credit: https://github.com/storybookjs/storybook/blob/92b23c080d03433765cbc7a60553d036a612a501/scripts/run-registry.ts
*/
const startVerdaccio = (port) => {
let resolved = false
return Promise.race([
new Promise((resolve) => {
const config = {
...YAML.parse(
fs.readFileSync(path.join(__dirname, '../verdaccio.yml'), 'utf8'),
),
}
if (verbose) {
config.logs.level = 'warn'
}
const onReady = (webServer) => {
webServer.listen(port, () => {
resolved = true
resolve(webServer)
})
}
startVerdaccioServer(
config,
6000,
undefined,
'1.0.0',
'verdaccio',
onReady,
)
}),
new Promise((_, rej) => {
setTimeout(() => {
if (!resolved) {
resolved = true
rej(new Error(`TIMEOUT - verdaccio didn't start within 10s`))
}
}, 10000)
}),
])
}
const packagesToPublish = [
'@theatre/core',
'@theatre/studio',
'@theatre/dataverse',
'@theatre/react',
'@theatre/browser-bundles',
'@theatre/r3f',
'theatric',
]
/**
* Assigns a new version to each of @theatre/* packages. If there a package depends on another package in this monorepo,
* this function makes sure the dependency version is fixed at "version"
*
* @param {{name: string, location: string}[]} workspacesListObjects - An Array of objects containing information about the workspaces
* @param {string} version - Version of the latest commit (or any other string)
* @returns {Promise<() => void>} - An async function that restores the package.json files to their original version
*/
async function writeVersionsToPackageJSONs(workspacesListObjects, version) {
/**
* An array of functions each of which restores a certain package.json to its original state
* @type {Array<() => void>}
*/
const restores = []
for (const workspaceData of workspacesListObjects) {
const pathToPackage = path.resolve(
config.MONOREPO_ROOT,
workspaceData.location,
'./package.json',
)
const originalFileContent = fs.readFileSync(pathToPackage, {
encoding: 'utf-8',
})
const originalJson = JSON.parse(originalFileContent)
restores.push(() => {
fs.writeFileSync(pathToPackage, originalFileContent, {encoding: 'utf-8'})
})
let {dependencies, peerDependencies, devDependencies} = originalJson
// Normally we don't have to override the package versions in dependencies because yarn would already convert
// all the "workspace:*" versions to a fixed version before publishing. However, packages like @theatre/studio
// have a peerDependency on @theatre/core set to "*" (meaning they would work with any version of @theatre/core).
// This is not the desired behavior in pre-release versions, so here, we'll fix those "*" versions to the set version.
for (const deps of [dependencies, peerDependencies, devDependencies]) {
if (!deps) continue
for (const wpObject of workspacesListObjects) {
if (deps[wpObject.name]) {
deps[wpObject.name] = version
}
}
}
const newJson = {
...originalJson,
version,
dependencies,
peerDependencies,
devDependencies,
}
fs.writeFileSync(pathToPackage, JSON.stringify(newJson, undefined, 2), {
encoding: 'utf-8',
})
}
return () =>
restores.forEach((fn) => {
fn()
})
}
/**
* Builds all the @theatre/* packages with version number 0.0.1-COMPAT.1 and publishes
* them all to the verdaccio registry
*/
async function releaseToVerdaccio() {
cd(config.MONOREPO_ROOT)
// @ts-ignore ignore
process.env.THEATRE_IS_PUBLISHING = true
const workspacesListString = await $`yarn workspaces list --json`
const workspacesListObjects = workspacesListString.stdout
.split(os.EOL)
// strip out empty lines
.filter(Boolean)
.map((x) => JSON.parse(x))
const restorePackages = await writeVersionsToPackageJSONs(
workspacesListObjects,
tempVersion,
)
// Restore the package.json files to their original state when the process is killed
process.on('SIGINT', async function cleanup(a) {
restorePackages()
})
try {
await $`yarn clean`
await $`yarn build`
await Promise.all(
packagesToPublish.map(async (workspaceName) => {
const npmTag = 'compat'
await $`yarn workspace ${workspaceName} npm publish --access public --tag ${npmTag}`
}),
)
} finally {
restorePackages()
}
}
/**
* Get all the setups from `./compat-tests/`
*
* @returns {Promise<Array<string>>} An array containing the absolute paths to the compatibility test setups
*/
export async function getCompatibilityTestSetups() {
const fixturePackageJsonFiles = await globby(
'./fixtures/*/package/package.json',
{
cwd: config.PATH_TO_COMPAT_TESTS_ROOT,
gitignore: false,
onlyFiles: true,
},
)
return fixturePackageJsonFiles.map((entry) => {
return path.join(config.PATH_TO_COMPAT_TESTS_ROOT, entry, '../')
})
}
/**
* Deletes ../test-*\/(node_modules|package-lock.json|yarn.lock)
*/
export async function clean() {
const toDelete = await globby(
'./fixtures/*/package/(node_modules|yarn.lock|package-lock.json)',
{
cwd: config.PATH_TO_COMPAT_TESTS_ROOT,
// node_modules et al are gitignored, but we still want to clean them
gitignore: false,
// include directories too
onlyFiles: false,
},
)
return await Promise.all(
toDelete.map((fileOrDir) => {
console.log('deleting', fileOrDir)
return fs.remove(path.join(config.PATH_TO_COMPAT_TESTS_ROOT, fileOrDir))
}),
)
}

View file

@ -0,0 +1,31 @@
store:
memory:
limit: 1000
auth:
auth-memory:
users:
test:
name: test
password: test
uplinks:
npmjs:
url: https://registry.npmjs.org/
cache: false
packages:
'@theatre/*':
access: $all
publish: $all
'theatric':
access: $all
publish: $all
'@*/*':
access: $all
publish: $all
proxy: npmjs
'**':
access: $all
proxy: npmjs
logs:
type: stdout
format: pretty
level: error