diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 885e6867059..8ead281e0be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,6 +91,27 @@ jobs: uses: ./.github/actions/build-fabric-cached - name: Run ${{ matrix.suite }} tests run: npm run test -- -c node -s ${{ matrix.suite }} + ts: + runs-on: ubuntu-latest + name: TS ${{ matrix.ts }} tests + strategy: + fail-fast: false + matrix: + node-version: [18.x] + ts: [4.x, latest] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: | + npm ci + npm i typescript@${{ matrix.ts }} + - name: Build + run: npm run build -- -f + - name: Run tests + run: npm run test -- -s ts jest: name: Jest tests runs-on: ubuntu-latest diff --git a/.prettierignore b/.prettierignore index 833acfada8e..9bc4a24afdb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,11 +11,12 @@ before_commit /scripts/cli_cache.json **/dist/ /lib/ -/test/ +test/* +!/test/ts /website/ .next/ .parcel-cache/ /docs/ /e2e/test-results/ /e2e/test-report/ -/e2e/tests/**/*-snapshots/*.json \ No newline at end of file +/e2e/tests/**/*-snapshots/*.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c0f4a5b7b..7bfb7088bbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- ci(): add TS test suite [#8394](https://github.com/fabricjs/fabric.js/pull/8394) - chore(): Upgrade Rollup to 4.9.5 [#9613](https://github.com/fabricjs/fabric.js/pull/9613) - chore(): Upgrade rollup and plugins at latest 3 [#9612](https://github.com/fabricjs/fabric.js/pull/9612) - fix(WebGLFilterBackend) Destroy the context of queryWebgl test function, remove automatic perf checkup, make it explicit with a function [#8932](https://github.com/fabricjs/fabric.js/pull/8932) diff --git a/package-lock.json b/package-lock.json index 41d78c60f7d..2d4ea9aca53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "busboy": "^1.6.0", "chalk": "^2.4.1", "commander": "^9.1.0", + "conditional-type-checks": "^1.0.6", "eslint": "8.40", "eslint-config-prettier": "^8.6.0", "fireworm": "^0.7.2", @@ -52,6 +53,7 @@ "ps-list": "^8.1.0", "qunit": "^2.17.2", "rollup": "^4.9.5", + "rollup-plugin-no-emit": "^1.1.1", "semver": "^7.3.8", "source-map-support": "^0.5.21", "testem": "^3.8.0", @@ -4842,6 +4844,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/conditional-type-checks": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/conditional-type-checks/-/conditional-type-checks-1.0.6.tgz", + "integrity": "sha512-3vyi+yNcmKq+xl1sTX7Ta+4pUvjusMYbC6FSbrS6YJV8TI51wiRn24u4bfdFVhDKKH5GtpKQzxW7bqXbPWllgQ==", + "dev": true + }, "node_modules/console-control-strings": { "version": "1.1.0", "devOptional": true, @@ -11054,6 +11062,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-no-emit": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-no-emit/-/rollup-plugin-no-emit-1.1.1.tgz", + "integrity": "sha512-S3GshU1UXYk/LHcmLDb/aFk/LPYI34KcJ+AmhA9TuW3p5lJLUok+VnT3hvRPoqpfw83TrdYhOuokJj8remp9qQ==", + "dev": true, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/run-async": { "version": "2.4.1", "dev": true, @@ -15802,6 +15824,12 @@ "version": "0.0.1", "devOptional": true }, + "conditional-type-checks": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/conditional-type-checks/-/conditional-type-checks-1.0.6.tgz", + "integrity": "sha512-3vyi+yNcmKq+xl1sTX7Ta+4pUvjusMYbC6FSbrS6YJV8TI51wiRn24u4bfdFVhDKKH5GtpKQzxW7bqXbPWllgQ==", + "dev": true + }, "console-control-strings": { "version": "1.1.0", "devOptional": true @@ -20020,6 +20048,13 @@ "fsevents": "~2.3.2" } }, + "rollup-plugin-no-emit": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-no-emit/-/rollup-plugin-no-emit-1.1.1.tgz", + "integrity": "sha512-S3GshU1UXYk/LHcmLDb/aFk/LPYI34KcJ+AmhA9TuW3p5lJLUok+VnT3hvRPoqpfw83TrdYhOuokJj8remp9qQ==", + "dev": true, + "requires": {} + }, "run-async": { "version": "2.4.1", "dev": true diff --git a/package.json b/package.json index ec683bc6dba..d2ec96909fe 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "busboy": "^1.6.0", "chalk": "^2.4.1", "commander": "^9.1.0", + "conditional-type-checks": "^1.0.6", "eslint": "8.40", "eslint-config-prettier": "^8.6.0", "fireworm": "^0.7.2", @@ -117,6 +118,7 @@ "prettier": "2.7.1", "ps-list": "^8.1.0", "qunit": "^2.17.2", + "rollup-plugin-no-emit": "^1.1.1", "rollup": "^4.9.5", "semver": "^7.3.8", "source-map-support": "^0.5.21", diff --git a/rollup.config.mjs b/rollup.config.mjs index a2974ae4bc3..c697fc0f878 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -4,10 +4,11 @@ import ts from '@rollup/plugin-typescript'; import { babel } from '@rollup/plugin-babel'; import path from 'path'; import chalk from 'chalk'; -// import dts from "rollup-plugin-dts"; +import noEmit from 'rollup-plugin-no-emit'; const splitter = /\n|\s|,/g; - +const input = process.env.BUILD_INPUT?.split(splitter) || ['./index.ts']; +const outputType = input.length > 1 ? 'dir' : 'file'; const buildOutput = process.env.BUILD_OUTPUT || './dist/index.js'; const dirname = path.dirname(buildOutput); @@ -20,6 +21,7 @@ const plugins = [ tsconfig: './tsconfig.json', exclude: ['dist', '**/**.spec.ts', '**/**.test.ts'], }), + noEmit({ emit: !Number(process.env.NO_EMIT) }), babel({ extensions: ['.ts', '.js'], babelHelpers: 'bundled', @@ -47,14 +49,16 @@ function onwarn(warning, warn) { } warn(warning); } - // https://rollupjs.org/guide/en/#configuration-files -export default [ +const config = [ { - input: process.env.BUILD_INPUT?.split(splitter) || ['./index.ts'], + input, output: [ { - file: path.resolve(dirname, `${basename}.mjs`), + [outputType]: + outputType === 'file' + ? path.resolve(dirname, `${basename}.mjs`) + : path.resolve(dirname), name: 'fabric', format: 'es', sourcemap: true, @@ -65,7 +69,7 @@ export default [ format: 'umd', sourcemap: true, }, - Number(process.env.MINIFY) + Number(process.env.MINIFY) && outputType === 'file' ? { file: path.resolve(dirname, `${basename}.min.js`), name: 'fabric', @@ -98,3 +102,5 @@ export default [ external: ['jsdom', 'jsdom/lib/jsdom/living/generated/utils.js', 'canvas'], }, ]; +console.log(config[0].output); +export default config; diff --git a/scripts/build.mjs b/scripts/build.mjs index 26e8c8bbcac..cf4bf8b6839 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -10,18 +10,28 @@ import { wd } from './dirname.mjs'; * @see https://rollupjs.org/guide/en/#--watchonstart-cmd---watchonbundlestart-cmd---watchonbundleend-cmd---watchonend-cmd---watchonerror-cmd * @param {*} options */ -export function build({ watch, fast, input, output, stats = false } = {}) { +export function build({ + watch, + fast, + input, + output, + report = true, + emit = true, + stats = false, +} = {}) { const cmd = [ 'rollup', '-c', watch ? '--watch' : '', '--no-watch.clearScreen', - ...['onStart', 'onError', 'onEnd'].map( - (type) => - `--watch.${type} "node ./scripts/buildReporter.mjs ${type - .toLowerCase() - .slice(2)}"` - ), + ...(report + ? [('onStart', 'onError', 'onEnd')].map( + (type) => + `--watch.${type} "node ./scripts/buildReporter.mjs ${type + .toLowerCase() + .slice(2)}"` + ) + : []), ].join(' '); const processOptions = { stdio: 'inherit', @@ -32,6 +42,14 @@ export function build({ watch, fast, input, output, stats = false } = {}) { MINIFY: Number(!fast), BUILD_INPUT: Array.isArray(input) ? input.join(' ') : input, BUILD_OUTPUT: output, + BUILD_MIN_OUTPUT: + output && !Array.isArray(input) && !fast + ? path.resolve( + path.dirname(output), + `${path.basename(output, '.js')}.min.js` + ) + : undefined, + NO_EMIT: Number(!emit), BUILD_STATS: Number(stats), }, }; diff --git a/scripts/index.mjs b/scripts/index.mjs index f762cef9c7e..a88f77d2d22 100644 --- a/scripts/index.mjs +++ b/scripts/index.mjs @@ -26,7 +26,7 @@ import process from 'node:process'; import os from 'os'; import { build } from './build.mjs'; import { awaitBuild } from './buildLock.mjs'; -import { CLI_CACHE, wd } from './dirname.mjs'; +import { CLI_CACHE, dumpsPath, wd } from './dirname.mjs'; const program = new commander.Command() .showHelpAfterError() @@ -35,6 +35,8 @@ const program = new commander.Command() const websiteDir = path.resolve(wd, '../fabricjs.com'); +const TEST_SUITES = ['unit', 'visual', 'ts']; + class ICheckbox extends Checkbox { constructor(questions, rl, answers) { super(questions, rl, answers); @@ -267,7 +269,7 @@ async function runTestem({ /** * - * @param {'unit' | 'visual'} suite + * @param {'unit' | 'visual' | 'ts'} suite * @param {string[] | null} tests file paths * @param {{debug?:boolean,recreate?:boolean,verbose?:boolean,filter?:string}} [options] * @returns {Promise} true if some tests failed @@ -275,6 +277,19 @@ async function runTestem({ async function test(suite, tests, options = {}) { let failed = false; await awaitBuild(); + + if (suite === 'ts') { + console.log(chalk.bold(chalk.blue(`running TS test suite`))); + build({ + fast: true, + input: tests || listTestFiles('ts').map((file) => `test/ts/${file}`), + output: path.resolve(dumpsPath, 'ts_tests'), + report: false, + emit: false, + }); + return false; + } + const qunitEnv = { QUNIT_DEBUG_VISUAL_TESTS: Number(options.debug), QUNIT_RECREATE_VISUAL_REFS: Number(options.recreate), @@ -299,7 +314,7 @@ async function test(suite, tests, options = {}) { cwd: wd, env: { ...env, - // browser takes precendence in golden ref generation + // browser takes precedence in golden ref generation ...(browserContexts.length === 0 ? qunitEnv : {}), }, shell: true, @@ -333,7 +348,7 @@ async function test(suite, tests, options = {}) { /** * - * @param {'unit'|'visual'} type corresponds to the test directories + * @param {'unit'|'visual'|'ts'} type corresponds to the test directories * @returns */ function listTestFiles(type) { @@ -364,11 +379,8 @@ function createChoiceData(type, file) { async function selectTestFile() { const selected = readCLIFile(); - const unitTests = listTestFiles('unit').map((file) => - createChoiceData('unit', file) - ); - const visualTests = listTestFiles('visual').map((file) => - createChoiceData('visual', file) + const testChoices = TEST_SUITES.map((suite) => + listTestFiles(suite).map((file) => createChoiceData(suite, file)) ); const { tests: filteredTests } = await inquirer.prompt([ { @@ -381,32 +393,24 @@ async function selectTestFile() { pageSize: Math.max(10, selected.length), source(answersSoFar, input = '') { return new Promise((resolve) => { - const tests = _.concat(unitTests, visualTests); + const tests = _.concat(...Object.values(testChoices)); const value = _.map(this.getCurrentValue(), (value) => createChoiceData(value.type, value.file) ); - if (value.length > 0) { - if ( - value.find( - (v) => v.value && v.value.type === 'unit' && !v.value.file - ) - ) { - _.pullAll(tests, unitTests); - } - if ( - value.find( - (v) => v.value && v.value.type === 'visual' && !v.value.file - ) - ) { - _.pullAll(tests, visualTests); - } - } - const unitChoice = createChoiceData('unit', ''); - const visualChoice = createChoiceData('visual', ''); - !value.find((v) => _.isEqual(v, unitChoice)) && - value.push(unitChoice); - !value.find((v) => _.isEqual(v, visualChoice)) && - value.push(visualChoice); + value.length > 0 && + TEST_SUITES.forEach((suite) => { + if ( + value.find( + (v) => v.value && v.value.type === suite && !v.value.file + ) + ) { + _.pullAll(tests, testChoices[suite]); + } + }); + TEST_SUITES.forEach((suite) => { + const choice = createChoiceData(suite, ''); + !value.find((v) => _.isEqual(v, choice)) && value.push(choice); + }); if (value.length > 0) { value.unshift(new inquirer.Separator()); value.push(new inquirer.Separator()); @@ -438,7 +442,10 @@ async function runInteractiveTestSuite(options) { } return acc; }, - { unit: [], visual: [] } + _.zipObject( + TEST_SUITES, + TEST_SUITES.map(() => []) + ) ); return Promise.all( _.map(tests, (files, suite) => { @@ -484,7 +491,7 @@ program .description('run test suite') .addOption( new commander.Option('-s, --suite ', 'test suite to run').choices( - ['unit', 'visual'] + TEST_SUITES ) ) .option('-f, --file ', 'run a specific test file') @@ -513,7 +520,7 @@ program fs.removeSync(CLI_CACHE); } if (options.all) { - options.suite = ['unit', 'visual']; + options.suite = TEST_SUITES; } const results = []; if (options.suite) { @@ -527,7 +534,7 @@ program } else if (options.file) { results.push( await test( - options.file.startsWith('visual') ? 'visual' : 'unit', + options.file.split(/\\|\//)[0], [`test/${options.file}`], options ) diff --git a/test/ts/index.ts b/test/ts/index.ts new file mode 100644 index 00000000000..0a43c1309d6 --- /dev/null +++ b/test/ts/index.ts @@ -0,0 +1,5 @@ +import * as fabric from '../../'; +import { IsAny, assert } from 'conditional-type-checks'; + +assert>(false); +assert>(false);