Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(): add SSR test #9490

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codesandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ npm run sandbox
## Developing

```bash
npm start <template>
npm start <template> [-w/l/c/p]
```

### Start Flow
Expand Down
34 changes: 28 additions & 6 deletions .codesandbox/start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,29 @@ import { build } from '../scripts/build.mjs';
import { subscribe } from '../scripts/buildLock.mjs';
import { wd } from '../scripts/dirname.mjs';

const TEMPLATE_PORTS = {
vanilla: 1234,
next: 3000,
node: 8080,
ShaMan123 marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* Writes a timestamp in `package.json` file of `dest` dir
* This is done to invoke the watcher watching `dest` and serving the app from it
* I looked for other ways to tell the watcher to watch changes in fabric but I came out with this options only (symlinking and other stuff).
* @param {string} destination
*/
export function startSandbox(destination, buildAndWatch, installDeps = false) {
export function startSandbox(
destination,
{
template,
buildAndWatch = true,
installDeps = false,
port = TEMPLATE_PORTS[template] || 8000,
launchBrowser = false,
launchVSCode = false,
} = {}
) {
console.log(chalk.blue('\n> linking fabric'));
cp.execSync('npm link', { cwd: wd, stdio: 'inherit' });
cp.execSync('npm link fabric --include=dev --save', {
Expand Down Expand Up @@ -58,15 +74,21 @@ export function startSandbox(destination, buildAndWatch, installDeps = false) {
)
);

try {
cp.exec('code .', { cwd: destination });
} catch (error) {
console.log('> failed to open VSCode');
if (launchVSCode) {
try {
cp.exec('code .', { cwd: destination });
} catch (error) {
console.log('> failed to open VSCode');
}
}

return cp.spawn('npm run dev', {
const task = cp.spawn(`npm run dev -- -p ${port}`, {
cwd: destination,
stdio: 'inherit',
shell: true,
});

launchBrowser && cp.exec(`open-cli http://localhost:${port}/`);

return task;
}
3 changes: 2 additions & 1 deletion .codesandbox/templates/next/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
.next
package-lock.json
yarn.lock
yarn.lock
.instrumentation
12 changes: 12 additions & 0 deletions .codesandbox/templates/next/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { writeFileSync } from 'fs';

const PATH = './.instrumentation';

/**
* Used to hook into the server lifecycle
* An application can watch the directory for changes to {@link PATH} to listen to when the server and app are ready
* https://nextjs.org/docs/pages/building-your-application/optimizing/instrumentation
*/
export function register() {
writeFileSync(PATH, Date.now().toString());
}
7 changes: 7 additions & 0 deletions .codesandbox/templates/next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
*/
const nextConfig = {
reactStrictMode: true,

// config for the instrumentationHook
experimental: { instrumentationHook: true },
webpack: (config) => {
config.resolve.fallback = { fs: false };
return config;
},
};

export default nextConfig;
3 changes: 1 addition & 2 deletions .codesandbox/templates/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "fabric-nextjs-sandbox",
"version": "1.0.0",
"scripts": {
"dev": "open-cli http://localhost:3000/ && next dev",
"dev": "next dev",
"build": "next build",
"start": "next start",
"type-check": "tsc"
Expand All @@ -19,7 +19,6 @@
"@types/node": "^12.7.8",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"open-cli": "^7.0.1",
"typescript": "^4.7.4"
},
"license": "ISC",
Expand Down
2 changes: 1 addition & 1 deletion .codesandbox/templates/next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as fabric from 'fabric';
import { NextPage } from 'next';
import type { NextPage } from 'next';
import { useCallback } from 'react';
import { Canvas } from '../components/Canvas';

Expand Down
10 changes: 5 additions & 5 deletions .codesandbox/templates/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"name": "fabric-node-sandbox",
"version": "1.0.0",
"description": "fabric.js on node",
"main": "src/index.js",
"main": "src/index.mjs",
"scripts": {
"dev": "open-cli http://localhost:8080/ && nodemon --config nodemon.config.json src/index.mjs 8080",
"start": "node src/index.js"
"dev": "nodemon --config nodemon.config.json src/index.mjs",
"start": "node src/index.mjs"
},
"dependencies": {
"fabric": "file:../../.."
},
"devDependencies": {
"nodemon": "^2.0.19",
"open-cli": "^7.0.1"
"commander": "^11.1.0",
"nodemon": "^2.0.19"
},
"keywords": []
}
16 changes: 13 additions & 3 deletions .codesandbox/templates/node/src/index.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import http from 'http';
import * as commander from 'commander';
import * as fabric from 'fabric/node';
import http from 'http';

const program = new commander.Command()
.allowUnknownOption(false)
.allowExcessArguments(false)
.option('-p, --port <port>', 'the port to use', 8080)
.parse();

const port = Number(process.argv[2]);
const port = Number(program.opts().port);

http
.createServer((req, res) => {
const canvas = new fabric.StaticCanvas(null, { width: 100, height: 100 });
const rect = new fabric.Rect({ width: 20, height: 50, fill: '#ff0000' });
const text = new fabric.Text('fabric.js', { fill: 'blue', fontSize: 24 });
const text = new fabric.FabricText('fabric.js', {
fill: 'blue',
fontSize: 24,
});
canvas.add(rect, text);
canvas.renderAll();
if (req.url === '/download') {
Expand Down
9 changes: 4 additions & 5 deletions .codesandbox/templates/vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
"version": "1.0.0",
"description": "fabric on Vanilla.js",
"scripts": {
"dev": "open-cli http://localhost:1234/ && parcel index.html",
"dev": "parcel index.html",
"start": "parcel index.html",
"build": "parcel build index.html"
},
"dependencies": {
"fabric": "file:../../.."
},
"devDependencies": {
"@parcel/transformer-typescript-tsc": "^2.7.0",
"open-cli": "^7.0.1",
"parcel": "^2.9.3",
"typescript": "^5.0.2"
"@parcel/transformer-typescript-tsc": "^2.10.3",
"parcel": "^2.10.3",
"typescript": "^5.2.2"
},
"keywords": [
"vanilla",
Expand Down
24 changes: 24 additions & 0 deletions .codesandbox/templates/vanilla/tsconfig.json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes parcel class bug
parcel-bundler/parcel#839 (comment)

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"allowJs": true,
"alwaysStrict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "preserve",
"lib": ["dom", "es2017"],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"incremental": true
},
"exclude": ["node_modules"],
"include": ["**/*.ts"]
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- test(FabricObject): add a snapshot of the default values so that reordering and shuffling is verified. [#9492](https://github.com/fabricjs/fabric.js/pull/9492)
- feat(FabricObject, Canvas) BREAKING: remove calculate true/false from the api. [#9483](https://github.com/fabricjs/fabric.js/pull/9483)
- test(): add SSR test [#9490](https://github.com/fabricjs/fabric.js/pull/9490)
- chore(): remove some Type assertions [#8950](https://github.com/fabricjs/fabric.js/pull/8950)
- chore(): expose `sendVectorToPlane` [#9479](https://github.com/fabricjs/fabric.js/pull/9479)
- feat(FabricObject, Canvas) BREAKING: remove absolute true/false from the api. [#9395](https://github.com/fabricjs/fabric.js/pull/9395)
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ If you need to change test config ask for guidance.
### Starting an App

```bash
npm start <template>
npm start <template> -w -l
npm start -- --help
```

Expand All @@ -229,7 +229,7 @@ Refer to [`.codesandbox/README.md`](.codesandbox/README.md) for more information

You can actively develop fabric online using [Github Codespaces][github_codespaces], [Gitpod][gitpod] or CodeSandbox:

- After the Github Codespace has started run `npm start <template>` to start a prototyping app.
- After the Github Codespace has started run `npm start <template> -w -l` to start a prototyping app.
- Gitpod will start the prototyping apps and expose them as endpoints available on forwarded ports.
`A service is available on port ...` popups will show up.
- Codesandbox: _available soon_.
Expand Down
100 changes: 100 additions & 0 deletions e2e/tests/SSR/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { expect, test } from '@playwright/test';
import { spawn } from 'child_process';
import { readFileSync, watch } from 'fs';
import killPort from 'kill-port';
import path from 'path';
import type { Canvas } from '../../..';
import setupCoverage from '../../setup/setupCoverage';

setupCoverage();

test.describe('SSR', () => {
const PORT = 3000;
const nextTemplateDir = path.resolve(
process.cwd(),
'.codesandbox',
'templates',
'next'
);

let disposer: VoidFunction;

test.beforeAll(async ({}, testInfo) => {
testInfo.setTimeout(60 * 1000);
try {
await killPort(PORT);
} catch (error) {}
// import `startSandbox` directly once ESM is supported
// https://playwright.dev/docs/release-notes#improved-typescript-support
const task = spawn(`npm start next -- -p ${PORT} --no-watch`, {
cwd: process.cwd(),
shell: true,
detached: true,
});
disposer = () => {
// windows will not be able to kill the detached task but `killPort` will free the port
task.kill();
};
return new Promise<void>((resolve) => {
const watcher = watch(nextTemplateDir, (event, filename) => {
if (filename === '.instrumentation') {
watcher.close();
resolve();
}
});
});
});

test.afterAll(() => disposer());

test('SSR with Next.js', async ({ page }) => {
await page.goto(`http://localhost:${PORT}/`);
await page.waitForLoadState('load');
await test.step('edit text', async () => {
await page
.locator('canvas')
.nth(1)
.dblclick({
position: {
x: 625,
y: 50,
},
});
await page.getByRole('textbox').press('Control+a');
await page
.getByRole('textbox')
.fill('fabric can be used in SSR frameworks');
await page.evaluate(async () => {
// window.canvas is exposed in .codesandbox/templates/next/components/Canvas.tsx:33
const {
canvas,
} = // eslint-disable-next-line no-restricted-globals
window as Window & typeof globalThis & { canvas: Canvas };
const text = canvas.getActiveObject();
const text1 = await text.clone();
text1.set({ originX: 'left' });
text1.setX(0);
const text2 = await text.clone();
text2.set({ originX: 'right' });
// eslint-disable-next-line no-restricted-globals
text2.setX(window.innerWidth);
canvas.add(text1, text2);
});
expect(await page.screenshot()).toMatchSnapshot();
});

await test.step('Server side downloads', async () => {
await page.getByRole('link', { name: 'Node' }).click();
const directDownloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Direct Download' }).click();
const browserDownloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Browser Download' }).click();
expect(
readFileSync(await (await directDownloadPromise).path())
).toMatchSnapshot({ name: 'download.png' });
expect(
readFileSync(await (await browserDownloadPromise).path())
).toMatchSnapshot({ name: 'download.png' });
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading