diff --git a/compat-tests/fixtures/cra-react18/package/.gitignore b/compat-tests/fixtures/cra/package/.gitignore similarity index 100% rename from compat-tests/fixtures/cra-react18/package/.gitignore rename to compat-tests/fixtures/cra/package/.gitignore diff --git a/compat-tests/fixtures/cra-react18/package/README.md b/compat-tests/fixtures/cra/package/README.md similarity index 100% rename from compat-tests/fixtures/cra-react18/package/README.md rename to compat-tests/fixtures/cra/package/README.md diff --git a/compat-tests/fixtures/cra-react18/package/package.json b/compat-tests/fixtures/cra/package/package.json similarity index 100% rename from compat-tests/fixtures/cra-react18/package/package.json rename to compat-tests/fixtures/cra/package/package.json diff --git a/compat-tests/fixtures/cra-react18/package/public/index.html b/compat-tests/fixtures/cra/package/public/index.html similarity index 100% rename from compat-tests/fixtures/cra-react18/package/public/index.html rename to compat-tests/fixtures/cra/package/public/index.html diff --git a/compat-tests/fixtures/cra-react18/package/src/index.js b/compat-tests/fixtures/cra/package/src/index.js similarity index 100% rename from compat-tests/fixtures/cra-react18/package/src/index.js rename to compat-tests/fixtures/cra/package/src/index.js diff --git a/compat-tests/fixtures/next-latest/next-latest.compat-test.ts b/compat-tests/fixtures/next-latest/next-latest.compat-test.ts new file mode 100644 index 0000000..82b1081 --- /dev/null +++ b/compat-tests/fixtures/next-latest/next-latest.compat-test.ts @@ -0,0 +1,69 @@ +// @cspotcode/zx is zx in CommonJS +import {$, cd, path, ProcessPromise} from '@cspotcode/zx' +import {testServerAndPage} from '../../utils/testUtils' + +$.verbose = false + +const PATH_TO_PACKAGE = path.join(__dirname, `./package`) + +describe(`next`, () => { + describe(`build`, () => { + test(`command runs without error`, async () => { + cd(PATH_TO_PACKAGE) + const {exitCode} = await $`npm run build` + // at this point, the build should have succeeded + expect(exitCode).toEqual(0) + }) + + describe(`next start`, () => { + testServerAndPage({ + startServerOnPort: (port: number) => { + cd(PATH_TO_PACKAGE) + + return $`npm run start -- --port ${port}` + }, + waitTilServerIsReady: async ( + process: ProcessPromise, + port: number, + ) => { + for await (const chunk of process.stdout) { + const chunkString = chunk.toString() + + if (chunkString.includes(`started server`)) { + // next's server is running now + break + } + } + + return {url: `http://localhost:${port}`} + }, + }) + }) + }) + + // this test is not ready yet, so we'll skip it + describe(`$ next dev`, () => { + testServerAndPage({ + startServerOnPort: (port: number) => { + cd(PATH_TO_PACKAGE) + + return $`npm run dev -- --port ${port}` + }, + waitTilServerIsReady: async ( + process: ProcessPromise, + port: number, + ) => { + for await (const chunk of process.stdout) { + const chunkString = chunk.toString() + + if (chunkString.includes(`compiled client and server successfully`)) { + // next's server is running now + break + } + } + + return {url: `http://localhost:${port}`} + }, + }) + }) +}) diff --git a/compat-tests/fixtures/next/package/.gitignore b/compat-tests/fixtures/next-latest/package/.gitignore similarity index 100% rename from compat-tests/fixtures/next/package/.gitignore rename to compat-tests/fixtures/next-latest/package/.gitignore diff --git a/compat-tests/fixtures/next/package/README.md b/compat-tests/fixtures/next-latest/package/README.md similarity index 100% rename from compat-tests/fixtures/next/package/README.md rename to compat-tests/fixtures/next-latest/package/README.md diff --git a/compat-tests/fixtures/next-latest/package/next-env.d.ts b/compat-tests/fixtures/next-latest/package/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/compat-tests/fixtures/next-latest/package/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/compat-tests/fixtures/next/package/package.json b/compat-tests/fixtures/next-latest/package/package.json similarity index 100% rename from compat-tests/fixtures/next/package/package.json rename to compat-tests/fixtures/next-latest/package/package.json diff --git a/compat-tests/fixtures/next-latest/package/pages/index.js b/compat-tests/fixtures/next-latest/package/pages/index.js new file mode 100644 index 0000000..9b6bd9c --- /dev/null +++ b/compat-tests/fixtures/next-latest/package/pages/index.js @@ -0,0 +1,13 @@ +import React from 'react' +import studio from '@theatre/studio' +import extension from '@theatre/r3f/dist/extension' +import App from '../src/App/App' + +if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { + studio.extend(extension) + studio.initialize({usePersistentStorage: false}) +} + +export default function Home() { + return +} diff --git a/compat-tests/fixtures/next/package/public/favicon.ico b/compat-tests/fixtures/next-latest/package/public/favicon.ico similarity index 100% rename from compat-tests/fixtures/next/package/public/favicon.ico rename to compat-tests/fixtures/next-latest/package/public/favicon.ico diff --git a/compat-tests/fixtures/vite-react18/package/src/App.tsx b/compat-tests/fixtures/next-latest/package/src/App/App.tsx similarity index 71% rename from compat-tests/fixtures/vite-react18/package/src/App.tsx rename to compat-tests/fixtures/next-latest/package/src/App/App.tsx index d67a211..9c4d0fc 100644 --- a/compat-tests/fixtures/vite-react18/package/src/App.tsx +++ b/compat-tests/fixtures/next-latest/package/src/App/App.tsx @@ -1,5 +1,5 @@ import {getProject} from '@theatre/core' -import React from 'react' +import React, {useEffect} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' @@ -16,6 +16,20 @@ function Plane({color, theatreKey, ...props}: any) { } export default function App() { + const [light2, setLight2] = React.useState(null) + + useEffect(() => { + if (!light2) return + // see the note on below to understand why we're doing this + const intensityInStateJson = 3 + const currentIntensity = light2.intensity + if (currentIntensity !== intensityInStateJson) { + console.error(`Test failed: light2.intensity is ${currentIntensity}`) + } else { + console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) + } + }, [light2]) + return ( + {/* @ts-ignore */} diff --git a/compat-tests/fixtures/next-latest/package/src/App/state.json b/compat-tests/fixtures/next-latest/package/src/App/state.json new file mode 100644 index 0000000..24328bb --- /dev/null +++ b/compat-tests/fixtures/next-latest/package/src/App/state.json @@ -0,0 +1,19 @@ +{ + "sheetsById": { + "R3F-Canvas": { + "staticOverrides": { + "byObject": { + "Light 2": { + "intensity": 3 + } + } + } + } + }, + "definitionVersion": "0.4.0", + "revisionHistory": [ + "jVNB3VWU34BIQK7M", + "-NXkC2GceSVBoVqa", + "Bw7ng1kdcWmMO5DN" + ] +} diff --git a/compat-tests/fixtures/next-latest/package/tsconfig.json b/compat-tests/fixtures/next-latest/package/tsconfig.json new file mode 100644 index 0000000..6db37c0 --- /dev/null +++ b/compat-tests/fixtures/next-latest/package/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/compat-tests/fixtures/next/package/pages/playgroundState.json b/compat-tests/fixtures/next/package/pages/playgroundState.json deleted file mode 100644 index f68a557..0000000 --- a/compat-tests/fixtures/next/package/pages/playgroundState.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "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"] -} diff --git a/compat-tests/fixtures/next/package/pages/theatric.js b/compat-tests/fixtures/next/package/pages/theatric.js deleted file mode 100644 index 6408c7d..0000000 --- a/compat-tests/fixtures/next/package/pages/theatric.js +++ /dev/null @@ -1,77 +0,0 @@ -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 ( -
- {id}: {foo} -
- ) -} - -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 ( -
-
{JSON.stringify(bar)}
- - - - - {showComponent && } - {yo} -
- ) -} diff --git a/compat-tests/fixtures/next/production.compat-test.ts b/compat-tests/fixtures/next/production.compat-test.ts deleted file mode 100644 index a757034..0000000 --- a/compat-tests/fixtures/next/production.compat-test.ts +++ /dev/null @@ -1,69 +0,0 @@ -// @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 - } - } - }) - }) -}) diff --git a/compat-tests/fixtures/parcel1-react17/package/.gitignore b/compat-tests/fixtures/parcel1/package/.gitignore similarity index 100% rename from compat-tests/fixtures/parcel1-react17/package/.gitignore rename to compat-tests/fixtures/parcel1/package/.gitignore diff --git a/compat-tests/fixtures/parcel1-react17/package/index.html b/compat-tests/fixtures/parcel1/package/index.html similarity index 100% rename from compat-tests/fixtures/parcel1-react17/package/index.html rename to compat-tests/fixtures/parcel1/package/index.html diff --git a/compat-tests/fixtures/parcel1-react18/package/package.json b/compat-tests/fixtures/parcel1/package/package.json similarity index 88% rename from compat-tests/fixtures/parcel1-react18/package/package.json rename to compat-tests/fixtures/parcel1/package/package.json index 65fc3a3..9e442de 100644 --- a/compat-tests/fixtures/parcel1-react18/package/package.json +++ b/compat-tests/fixtures/parcel1/package/package.json @@ -1,5 +1,4 @@ { - "name": "@compat/parcel1-react18", "scripts": { "dev": "parcel serve ./index.html" }, diff --git a/compat-tests/fixtures/parcel1-react18/package/src/index.js b/compat-tests/fixtures/parcel1/package/src/index.js similarity index 100% rename from compat-tests/fixtures/parcel1-react18/package/src/index.js rename to compat-tests/fixtures/parcel1/package/src/index.js diff --git a/compat-tests/fixtures/parcel2-react18/package/src/index.js b/compat-tests/fixtures/parcel2-react18/package/src/index.js deleted file mode 100644 index 5703d55..0000000 --- a/compat-tests/fixtures/parcel2-react18/package/src/index.js +++ /dev/null @@ -1,92 +0,0 @@ -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 ( - - - - - ) -} - -function App() { - return ( - - - {/* @ts-ignore */} - - - - - - - - - - - - - ) -} - -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(hi) diff --git a/compat-tests/fixtures/parcel1-react18/package/.gitignore b/compat-tests/fixtures/react17/package/.gitignore similarity index 100% rename from compat-tests/fixtures/parcel1-react18/package/.gitignore rename to compat-tests/fixtures/react17/package/.gitignore diff --git a/compat-tests/fixtures/parcel1-react18/package/index.html b/compat-tests/fixtures/react17/package/index.html similarity index 100% rename from compat-tests/fixtures/parcel1-react18/package/index.html rename to compat-tests/fixtures/react17/package/index.html diff --git a/compat-tests/fixtures/parcel1-react17/package/package.json b/compat-tests/fixtures/react17/package/package.json similarity index 91% rename from compat-tests/fixtures/parcel1-react17/package/package.json rename to compat-tests/fixtures/react17/package/package.json index d67d805..5691eef 100644 --- a/compat-tests/fixtures/parcel1-react17/package/package.json +++ b/compat-tests/fixtures/react17/package/package.json @@ -1,5 +1,4 @@ { - "name": "@compat/parcel1-react17", "scripts": { "dev": "parcel serve ./index.html" }, diff --git a/compat-tests/fixtures/parcel1-react17/package/src/index.js b/compat-tests/fixtures/react17/package/src/index.js similarity index 100% rename from compat-tests/fixtures/parcel1-react17/package/src/index.js rename to compat-tests/fixtures/react17/package/src/index.js diff --git a/compat-tests/fixtures/parcel2-react18/package/.gitignore b/compat-tests/fixtures/react18/package/.gitignore similarity index 100% rename from compat-tests/fixtures/parcel2-react18/package/.gitignore rename to compat-tests/fixtures/react18/package/.gitignore diff --git a/compat-tests/fixtures/parcel2-react18/package/index.html b/compat-tests/fixtures/react18/package/index.html similarity index 100% rename from compat-tests/fixtures/parcel2-react18/package/index.html rename to compat-tests/fixtures/react18/package/index.html diff --git a/compat-tests/fixtures/parcel2-react18/package/package.json b/compat-tests/fixtures/react18/package/package.json similarity index 57% rename from compat-tests/fixtures/parcel2-react18/package/package.json rename to compat-tests/fixtures/react18/package/package.json index 27550c1..cd90c6b 100644 --- a/compat-tests/fixtures/parcel2-react18/package/package.json +++ b/compat-tests/fixtures/react18/package/package.json @@ -1,7 +1,8 @@ { - "name": "@compat/parcel2-react18", "scripts": { - "dev": "parcel serve ./index.html" + "dev": "parcel serve ./index.html", + "build": "parcel build ./index.html", + "start": "serve dist" }, "dependencies": { "@theatre/core": "0.0.1-COMPAT.1", @@ -9,6 +10,7 @@ "@theatre/studio": "0.0.1-COMPAT.1", "parcel": "^2.5.0", "react": "^18.1.0", - "react-dom": "^18.1.0" + "react-dom": "^18.1.0", + "serve": "14.2.0" } } diff --git a/compat-tests/fixtures/next/package/pages/index.js b/compat-tests/fixtures/react18/package/src/App/App.tsx similarity index 60% rename from compat-tests/fixtures/next/package/pages/index.js rename to compat-tests/fixtures/react18/package/src/App/App.tsx index 154a14f..9c4d0fc 100644 --- a/compat-tests/fixtures/next/package/pages/index.js +++ b/compat-tests/fixtures/react18/package/src/App/App.tsx @@ -1,23 +1,12 @@ import {getProject} from '@theatre/core' -import React from 'react' +import React, {useEffect} 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', -) +import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' +import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f -function Plane({color, theatreKey, ...props}) { +function Plane({color, theatreKey, ...props}: any) { return ( @@ -26,7 +15,21 @@ function Plane({color, theatreKey, ...props}) { ) } -function App() { +export default function App() { + const [light2, setLight2] = React.useState(null) + + useEffect(() => { + if (!light2) return + // see the note on below to understand why we're doing this + const intensityInStateJson = 3 + const currentIntensity = light2.intensity + if (currentIntensity !== intensityInStateJson) { + console.error(`Test failed: light2.intensity is ${currentIntensity}`) + } else { + console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) + } + }, [light2]) + return ( - + {/* @ts-ignore */} - + + ) } - -export default function Home() { - return -} diff --git a/compat-tests/fixtures/react18/package/src/App/state.json b/compat-tests/fixtures/react18/package/src/App/state.json new file mode 100644 index 0000000..24328bb --- /dev/null +++ b/compat-tests/fixtures/react18/package/src/App/state.json @@ -0,0 +1,19 @@ +{ + "sheetsById": { + "R3F-Canvas": { + "staticOverrides": { + "byObject": { + "Light 2": { + "intensity": 3 + } + } + } + } + }, + "definitionVersion": "0.4.0", + "revisionHistory": [ + "jVNB3VWU34BIQK7M", + "-NXkC2GceSVBoVqa", + "Bw7ng1kdcWmMO5DN" + ] +} diff --git a/compat-tests/fixtures/react18/package/src/index.js b/compat-tests/fixtures/react18/package/src/index.js new file mode 100644 index 0000000..b5a3b54 --- /dev/null +++ b/compat-tests/fixtures/react18/package/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom/client' +import studio from '@theatre/studio' +import extension from '@theatre/r3f/dist/extension' +import App from './App/App' + +if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { + studio.extend(extension) + studio.initialize({usePersistentStorage: false}) +} + +const container = document.getElementById('root') +const root = ReactDOM.createRoot(container) +root.render() diff --git a/compat-tests/fixtures/react18/react18.compat-test.ts b/compat-tests/fixtures/react18/react18.compat-test.ts new file mode 100644 index 0000000..8f1484d --- /dev/null +++ b/compat-tests/fixtures/react18/react18.compat-test.ts @@ -0,0 +1,47 @@ +// @cspotcode/zx is zx in CommonJS +import {$, cd, path, ProcessPromise} from '@cspotcode/zx' +import {defer, testServerAndPage} from '../../utils/testUtils' + +$.verbose = false + +const PATH_TO_PACKAGE = path.join(__dirname, `./package`) + +describe(`react18`, () => { + test(`build succeeds`, async () => { + cd(PATH_TO_PACKAGE) + const {exitCode} = await $`npm run build` + // at this point, the build should have succeeded + expect(exitCode).toEqual(0) + }) + + // this one is failing for some reason, but manually running the server works fine + describe(`build`, () => { + function startServerOnPort(port: number): ProcessPromise { + cd(PATH_TO_PACKAGE) + + return $`npm start -- -p ${port}` + } + + function waitTilServerIsReady( + process: ProcessPromise, + port: number, + ): Promise<{ + url: string + }> { + const d = defer<{url: string}>() + + const url = `http://localhost:${port}` + + process.stdout.on('data', (chunk) => { + if (chunk.toString().includes('Accepting connections')) { + // server is running now + d.resolve({url}) + } + }) + + return d.promise + } + + testServerAndPage({startServerOnPort, waitTilServerIsReady}) + }) +}) diff --git a/compat-tests/fixtures/vite-react18/package/src/state.json b/compat-tests/fixtures/vite-react18/package/src/state.json deleted file mode 100644 index 5565206..0000000 --- a/compat-tests/fixtures/vite-react18/package/src/state.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "sheetsById": { - "R3F-Canvas": { - "staticOverrides": { - "byObject": { - "Camera": { - "position": { - "x": -1.9718646329873923, - "y": 0, - "z": 5.6728262108991725 - } - }, - "plane3": { - "rotation": { - "x": -1.5707963267948966, - "y": -1.6653345369377348e-16, - "z": -0.9115259508891379 - } - } - } - } - } - }, - "definitionVersion": "0.4.0", - "revisionHistory": ["-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN"] -} diff --git a/compat-tests/fixtures/vite-react18/package/.gitignore b/compat-tests/fixtures/vite2/package/.gitignore similarity index 100% rename from compat-tests/fixtures/vite-react18/package/.gitignore rename to compat-tests/fixtures/vite2/package/.gitignore diff --git a/compat-tests/fixtures/vite-react18/package/index.html b/compat-tests/fixtures/vite2/package/index.html similarity index 100% rename from compat-tests/fixtures/vite-react18/package/index.html rename to compat-tests/fixtures/vite2/package/index.html diff --git a/compat-tests/fixtures/vite-react18/package/package.json b/compat-tests/fixtures/vite2/package/package.json similarity index 94% rename from compat-tests/fixtures/vite-react18/package/package.json rename to compat-tests/fixtures/vite2/package/package.json index c5837fa..f7b3f66 100644 --- a/compat-tests/fixtures/vite-react18/package/package.json +++ b/compat-tests/fixtures/vite2/package/package.json @@ -1,5 +1,4 @@ { - "name": "@compat/vite-react18", "private": true, "version": "0.0.0", "scripts": { diff --git a/compat-tests/fixtures/vite2/package/src/App/App.tsx b/compat-tests/fixtures/vite2/package/src/App/App.tsx new file mode 100644 index 0000000..9c4d0fc --- /dev/null +++ b/compat-tests/fixtures/vite2/package/src/App/App.tsx @@ -0,0 +1,97 @@ +import {getProject} from '@theatre/core' +import React, {useEffect} from 'react' +import {Canvas} from '@react-three/fiber' +import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' +import state from './state.json' + +// credit: https://codesandbox.io/s/camera-pan-nsb7f + +function Plane({color, theatreKey, ...props}: any) { + return ( + + + + + ) +} + +export default function App() { + const [light2, setLight2] = React.useState(null) + + useEffect(() => { + if (!light2) return + // see the note on below to understand why we're doing this + const intensityInStateJson = 3 + const currentIntensity = light2.intensity + if (currentIntensity !== intensityInStateJson) { + console.error(`Test failed: light2.intensity is ${currentIntensity}`) + } else { + console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) + } + }, [light2]) + + return ( + + + {/* @ts-ignore */} + + + + + + + + + + + + + + ) +} diff --git a/compat-tests/fixtures/vite2/package/src/App/state.json b/compat-tests/fixtures/vite2/package/src/App/state.json new file mode 100644 index 0000000..24328bb --- /dev/null +++ b/compat-tests/fixtures/vite2/package/src/App/state.json @@ -0,0 +1,19 @@ +{ + "sheetsById": { + "R3F-Canvas": { + "staticOverrides": { + "byObject": { + "Light 2": { + "intensity": 3 + } + } + } + } + }, + "definitionVersion": "0.4.0", + "revisionHistory": [ + "jVNB3VWU34BIQK7M", + "-NXkC2GceSVBoVqa", + "Bw7ng1kdcWmMO5DN" + ] +} diff --git a/compat-tests/fixtures/vite-react18/package/src/main.tsx b/compat-tests/fixtures/vite2/package/src/main.tsx similarity index 93% rename from compat-tests/fixtures/vite-react18/package/src/main.tsx rename to compat-tests/fixtures/vite2/package/src/main.tsx index 13aa6a7..084c543 100644 --- a/compat-tests/fixtures/vite-react18/package/src/main.tsx +++ b/compat-tests/fixtures/vite2/package/src/main.tsx @@ -1,7 +1,7 @@ import ReactDOM from 'react-dom/client' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' -import App from './App' +import App from './App/App' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) diff --git a/compat-tests/fixtures/vite-react18/package/src/vite-env.d.ts b/compat-tests/fixtures/vite2/package/src/vite-env.d.ts similarity index 100% rename from compat-tests/fixtures/vite-react18/package/src/vite-env.d.ts rename to compat-tests/fixtures/vite2/package/src/vite-env.d.ts diff --git a/compat-tests/fixtures/vite-react18/package/tsconfig.json b/compat-tests/fixtures/vite2/package/tsconfig.json similarity index 100% rename from compat-tests/fixtures/vite-react18/package/tsconfig.json rename to compat-tests/fixtures/vite2/package/tsconfig.json diff --git a/compat-tests/fixtures/vite-react18/package/tsconfig.node.json b/compat-tests/fixtures/vite2/package/tsconfig.node.json similarity index 100% rename from compat-tests/fixtures/vite-react18/package/tsconfig.node.json rename to compat-tests/fixtures/vite2/package/tsconfig.node.json diff --git a/compat-tests/fixtures/vite-react18/package/vite.config.ts b/compat-tests/fixtures/vite2/package/vite.config.ts similarity index 100% rename from compat-tests/fixtures/vite-react18/package/vite.config.ts rename to compat-tests/fixtures/vite2/package/vite.config.ts diff --git a/compat-tests/fixtures/vite2/vite2.compat-test.ts b/compat-tests/fixtures/vite2/vite2.compat-test.ts new file mode 100644 index 0000000..b0dd4df --- /dev/null +++ b/compat-tests/fixtures/vite2/vite2.compat-test.ts @@ -0,0 +1,42 @@ +// @cspotcode/zx is zx in CommonJS +import {$, cd, path, ProcessPromise} from '@cspotcode/zx' +import {testServerAndPage} from '../../utils/testUtils' + +$.verbose = false + +const PATH_TO_PACKAGE = path.join(__dirname, `./package`) + +describe(`vite2`, () => { + test(`\`$ vite build\` should succeed`, async () => { + cd(PATH_TO_PACKAGE) + const {exitCode} = await $`npm run build` + // at this point, the build should have succeeded + expect(exitCode).toEqual(0) + }) + + describe(`vite preview`, () => { + function startServerOnPort(port: number): ProcessPromise { + cd(PATH_TO_PACKAGE) + + return $`npm run preview -- --port ${port}` + } + + async function waitTilServerIsReady( + process: ProcessPromise, + port: number, + ): Promise<{ + url: string + }> { + for await (const chunk of process.stdout) { + if (chunk.toString().includes('--host')) { + // vite's server is running now + break + } + } + + return {url: `http://localhost:${port}`} + } + + testServerAndPage({startServerOnPort, waitTilServerIsReady}) + }) +}) diff --git a/compat-tests/fixtures/vite4/package/.gitignore b/compat-tests/fixtures/vite4/package/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/compat-tests/fixtures/vite4/package/.gitignore @@ -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? diff --git a/compat-tests/fixtures/vite4/package/index.html b/compat-tests/fixtures/vite4/package/index.html new file mode 100644 index 0000000..ec41bbd --- /dev/null +++ b/compat-tests/fixtures/vite4/package/index.html @@ -0,0 +1,12 @@ + + + + + + Vite App + + +
+ + + diff --git a/compat-tests/fixtures/vite4/package/package.json b/compat-tests/fixtures/vite4/package/package.json new file mode 100644 index 0000000..b598a8e --- /dev/null +++ b/compat-tests/fixtures/vite4/package/package.json @@ -0,0 +1,26 @@ +{ + "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": "^4.4.7" + } +} diff --git a/compat-tests/fixtures/vite4/package/src/App/App.tsx b/compat-tests/fixtures/vite4/package/src/App/App.tsx new file mode 100644 index 0000000..9c4d0fc --- /dev/null +++ b/compat-tests/fixtures/vite4/package/src/App/App.tsx @@ -0,0 +1,97 @@ +import {getProject} from '@theatre/core' +import React, {useEffect} from 'react' +import {Canvas} from '@react-three/fiber' +import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' +import state from './state.json' + +// credit: https://codesandbox.io/s/camera-pan-nsb7f + +function Plane({color, theatreKey, ...props}: any) { + return ( + + + + + ) +} + +export default function App() { + const [light2, setLight2] = React.useState(null) + + useEffect(() => { + if (!light2) return + // see the note on below to understand why we're doing this + const intensityInStateJson = 3 + const currentIntensity = light2.intensity + if (currentIntensity !== intensityInStateJson) { + console.error(`Test failed: light2.intensity is ${currentIntensity}`) + } else { + console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) + } + }, [light2]) + + return ( + + + {/* @ts-ignore */} + + + + + + + + + + + + + + ) +} diff --git a/compat-tests/fixtures/vite4/package/src/App/state.json b/compat-tests/fixtures/vite4/package/src/App/state.json new file mode 100644 index 0000000..24328bb --- /dev/null +++ b/compat-tests/fixtures/vite4/package/src/App/state.json @@ -0,0 +1,19 @@ +{ + "sheetsById": { + "R3F-Canvas": { + "staticOverrides": { + "byObject": { + "Light 2": { + "intensity": 3 + } + } + } + } + }, + "definitionVersion": "0.4.0", + "revisionHistory": [ + "jVNB3VWU34BIQK7M", + "-NXkC2GceSVBoVqa", + "Bw7ng1kdcWmMO5DN" + ] +} diff --git a/compat-tests/fixtures/vite4/package/src/main.tsx b/compat-tests/fixtures/vite4/package/src/main.tsx new file mode 100644 index 0000000..084c543 --- /dev/null +++ b/compat-tests/fixtures/vite4/package/src/main.tsx @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom/client' +import studio from '@theatre/studio' +import extension from '@theatre/r3f/dist/extension' +import App from './App/App' + +if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { + studio.extend(extension) + studio.initialize({usePersistentStorage: false}) +} + +const container = document.getElementById('root')! +const root = ReactDOM.createRoot(container) +root.render() diff --git a/compat-tests/fixtures/vite4/package/src/vite-env.d.ts b/compat-tests/fixtures/vite4/package/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/compat-tests/fixtures/vite4/package/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/compat-tests/fixtures/vite4/package/tsconfig.json b/compat-tests/fixtures/vite4/package/tsconfig.json new file mode 100644 index 0000000..3d0a51a --- /dev/null +++ b/compat-tests/fixtures/vite4/package/tsconfig.json @@ -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" }] +} diff --git a/compat-tests/fixtures/vite4/package/tsconfig.node.json b/compat-tests/fixtures/vite4/package/tsconfig.node.json new file mode 100644 index 0000000..e993792 --- /dev/null +++ b/compat-tests/fixtures/vite4/package/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/compat-tests/fixtures/vite4/package/vite.config.ts b/compat-tests/fixtures/vite4/package/vite.config.ts new file mode 100644 index 0000000..7a44b51 --- /dev/null +++ b/compat-tests/fixtures/vite4/package/vite.config.ts @@ -0,0 +1,7 @@ +import {defineConfig} from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/compat-tests/fixtures/vite4/vite4.compat-test.ts b/compat-tests/fixtures/vite4/vite4.compat-test.ts new file mode 100644 index 0000000..8fc2aa4 --- /dev/null +++ b/compat-tests/fixtures/vite4/vite4.compat-test.ts @@ -0,0 +1,42 @@ +// @cspotcode/zx is zx in CommonJS +import {$, cd, path, ProcessPromise} from '@cspotcode/zx' +import {testServerAndPage} from '../../utils/testUtils' + +$.verbose = false + +const PATH_TO_PACKAGE = path.join(__dirname, `./package`) + +describe(`vite4`, () => { + test(`\`$ vite build\` should succeed`, async () => { + cd(PATH_TO_PACKAGE) + const {exitCode} = await $`npm run build` + // at this point, the build should have succeeded + expect(exitCode).toEqual(0) + }) + + describe(`vite preview`, () => { + function startServerOnPort(port: number): ProcessPromise { + cd(PATH_TO_PACKAGE) + + return $`npm run preview -- --port ${port}` + } + + async function waitTilServerIsReady( + process: ProcessPromise, + port: number, + ): Promise<{ + url: string + }> { + for await (const chunk of process.stdout) { + if (chunk.toString().includes('--host')) { + // vite's server is running now + break + } + } + + return {url: `http://localhost:${port}`} + } + + testServerAndPage({startServerOnPort, waitTilServerIsReady}) + }) +}) diff --git a/compat-tests/utils/testUtils.ts b/compat-tests/utils/testUtils.ts new file mode 100644 index 0000000..7b2323c --- /dev/null +++ b/compat-tests/utils/testUtils.ts @@ -0,0 +1,157 @@ +import {Browser, chromium, ConsoleMessage, devices, Page} from 'playwright' +import {ProcessPromise} from '@cspotcode/zx' + +export function testServerAndPage({ + startServerOnPort, + waitTilServerIsReady, +}: { + startServerOnPort: (port: number) => ProcessPromise + waitTilServerIsReady: ( + process: ProcessPromise, + port: number, + ) => Promise<{url: string}> +}) { + let browser: Browser, page: Page + beforeAll(async () => { + browser = await chromium.launch() + }) + afterAll(async () => { + await browser.close() + }) + beforeEach(async () => { + page = await browser.newPage() + }) + afterEach(async () => { + await page.close() + }) + + test('The server runs, and the r3f setup works', async () => { + // run the production server but don't wait for it to finish + + // just a random port I'm hoping is free everywhere. + const port = await findOpenPort() + let process: ProcessPromise | undefined + try { + process = startServerOnPort(port) + } catch (err) { + throw new Error(`Failed to start server: ${err}`) + } + + try { + const {url} = await waitTilServerIsReady(process, port) + + await testTheatreOnPage(page, {url}) + } finally { + // kill the server + await process.kill('SIGTERM') + } + + try { + await process + } catch (e) { + if (e.signal !== 'SIGTERM') { + console.log('process exited with error', e) + + // if it exited for any reason other than us killing it, re-throw the error + throw e + } + // otherwise, process exited because we killed it, which is what we wanted + } + }) +} + +async function testTheatreOnPage(page: Page, {url}: {url: string}) { + const d = defer() + + const processConsoleEvents = (msg: ConsoleMessage) => { + const text = msg.text() + if (text.startsWith('Test passed: light2.intensity')) { + d.resolve('Passed') + } else if (text.startsWith('Test failed: light2.intensity')) { + d.reject(text) + } + } + + page.on('console', processConsoleEvents) + + try { + await page.goto(url, { + waitUntil: 'domcontentloaded', + timeout: 3000, + }) + + // give the console listener 3 seconds to resolve, otherwise fail the test + await Promise.race([ + d.promise, + new Promise((_, reject) => setTimeout(() => reject('Timed out'), 30000)), + ]) + } finally { + page.off('console', processConsoleEvents) + } +} + +export function findOpenPort(): Promise { + return new Promise((resolve, reject) => { + const server = require('net').createServer() + server.unref() + server.on('error', reject) + server.listen(0, () => { + const {port} = server.address() as {port: number} + server.close(() => { + resolve(port) + }) + }) + }) +} + +interface Deferred { + resolve: (d: PromiseType) => void + reject: (d: unknown) => void + promise: Promise + status: 'pending' | 'resolved' | 'rejected' +} + +/** + * A simple imperative API for resolving/rejecting a promise. + * + * Example: + * ```ts + * function doSomethingAsync() { + * const deferred = defer() + * + * setTimeout(() => { + * if (Math.random() > 0.5) { + * deferred.resolve('success') + * } else { + * deferred.reject('Something went wrong') + * } + * }, 1000) + * + * // we're just returning the promise, so that the caller cannot resolve/reject it + * return deferred.promise + * } + * + * ``` + */ +export function defer(): Deferred { + let resolve: (d: PromiseType) => void + let reject: (d: unknown) => void + const promise = new Promise((rs, rj) => { + resolve = (v) => { + rs(v) + deferred.status = 'resolved' + } + reject = (v) => { + rj(v) + deferred.status = 'rejected' + } + }) + + const deferred: Deferred = { + resolve: resolve!, + reject: reject!, + promise, + status: 'pending', + } + return deferred +}