From a13449a2082954ee6bd26ceca3b8a9d4d1cbfdcd Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Mon, 21 Jul 2025 22:12:25 +0530 Subject: [PATCH] small proof of concept for gradle node integration --- .gitignore | 1 + build.gradle.kts | 153 +++++- package-lock.json | 502 ++++++++++++++++++ package.json | 25 + src/main/java/processing/test/NodeBridge.java | 163 ++++++ .../processing/test/NodeIntegrationTest.java | 57 ++ src/main/js/index.js | 92 ++++ 7 files changed, 992 insertions(+), 1 deletion(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/main/java/processing/test/NodeBridge.java create mode 100644 src/main/java/processing/test/NodeIntegrationTest.java create mode 100644 src/main/js/index.js diff --git a/.gitignore b/.gitignore index 26b5b97563..a34a58231c 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,4 @@ java/build/ /app/windows/obj /java/gradle/build /java/gradle/example/.processing +node_modules diff --git a/build.gradle.kts b/build.gradle.kts index 0675c2db38..1284fe634e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,8 +4,159 @@ plugins { alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.jetbrainsCompose) apply false + + id("com.github.node-gradle.node") version "7.1.0" + id("java") } // Set the build directory to not /build to prevent accidental deletion through the clean action // Can be deleted after the migration to Gradle is complete -layout.buildDirectory = file(".build") \ No newline at end of file +layout.buildDirectory = file(".build") + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.google.code.gson:gson:2.10.1") // For JSON parsing +} + +configure { + version.set("20.10.0") + npmVersion.set("10.2.3") + download.set(true) + workDir.set(file("${project.projectDir}/.build/nodejs")) + npmWorkDir.set(file("${project.projectDir}/.build/npm")) +} + +//tasks.register("testNodeSetup") { +// description = "Test that Node.js plugin is working" +// dependsOn("nodeSetup") // This task is provided by the plugin +// doLast { +// println("Node.js plugin setup completed successfully!") +// } +//} +// +//// Create a test package.json and run npm commands +//tasks.register("createTestPackage") { +// description = "Create a test package.json for Processing" +// doLast { +// val packageJson = file("package.json") +// if (!packageJson.exists()) { +// packageJson.writeText(""" +// { +// "name": "processing4-test", +// "version": "1.0.0", +// "description": "Node.js integration test for Processing 4", +// "scripts": { +// "test": "echo 'Node.js integration with Processing 4 is working!'", +// "build": "echo 'Building assets for Processing...'", +// "clean": "echo 'Cleaning build artifacts...'" +// }, +// "devDependencies": {} +// } +// """.trimIndent()) +// println("Created test package.json") +// } else { +// println("package.json already exists") +// } +// } +//} +// +//// Test npm commands +//tasks.register("testNpmCommands") { +// description = "Test npm commands with Processing" +// dependsOn("createTestPackage", "npmInstall") +// doLast { +// println("NPM integration test completed!") +// } +//} + +tasks.register("createJsStructure") { + description = "Create directory structure for JavaScript files" + doLast { + val jsDir = file("src/main/js") + jsDir.mkdirs() + println("Created directory: ${jsDir.absolutePath}") + } +} + +// Enhanced package.json creation with dependencies +tasks.register("createTestPackage") { + description = "Create a test package.json for Processing" + dependsOn("createJsStructure") + doLast { + val packageJson = file("package.json") + if (!packageJson.exists()) { + packageJson.writeText(""" + { + "name": "processing4-test", + "version": "1.0.0", + "description": "Node.js integration test for Processing 4", + "main": "src/main/js/index.js", + "scripts": { + "test": "node src/main/js/test.js", + "build": "echo 'Building assets for Processing...'", + "clean": "echo 'Cleaning build artifacts...'" + }, + "dependencies": { + "lodash": "^4.17.21" + }, + "devDependencies": {} + } + """.trimIndent()) + println("Created test package.json with lodash dependency") + } else { + println("package.json already exists") + } + } +} + +// Install npm dependencies +tasks.register("installNodeDeps") { + description = "Install Node.js dependencies" + dependsOn("createTestPackage", "npmInstall") + doLast { + println("Node.js dependencies installed successfully!") + } +} + +// Test basic Node.js functionality +tasks.register("testNodeSetup") { + description = "Test that Node.js plugin is working" + dependsOn("nodeSetup") + doLast { + println("Node.js plugin setup completed successfully!") + } +} + +// Test npm commands +tasks.register("testNpmCommands") { + description = "Test npm commands with Processing" + dependsOn("installNodeDeps") + doLast { + println("NPM integration test completed!") + } +} + +// Test Node.js function calls from Java +tasks.register("testNodeIntegration") { + description = "Test Node.js integration with Java/Processing code" + dependsOn("compileJava", "installNodeDeps") + + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("processing.test.NodeIntegrationTest") + + doFirst { + println("Testing Node.js integration with Processing...") + } +} + +// Compile and test everything +tasks.register("testAll") { + description = "Run all tests for Node.js integration" + dependsOn("testNodeSetup", "testNpmCommands", "testNodeIntegration") + doLast { + println("All integration tests completed successfully!") + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..5e3f19ac18 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,502 @@ +{ + "name": "processing4-visual-testing", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "processing4-visual-testing", + "version": "1.0.0", + "dependencies": { + "glob": "^10.3.3", + "lodash": "^4.17.21", + "pixelmatch": "^5.3.0", + "pngjs": "^7.0.0" + }, + "bin": { + "compare-images": "bin/compare-images.js" + }, + "devDependencies": { + "rimraf": "^5.0.1" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..2d185dfc96 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "processing4-visual-testing", + "version": "1.0.0", + "description": "Visual regression testing tools for Processing 4", + "private": true, + "bin": { + "compare-images": "./bin/compare-images.js" + }, + "scripts": { + "test": "node tests/run-visual-tests.js", + "compare": "node bin/compare-images.js", + "generate-baselines": "node scripts/generate-baselines.js", + "report": "node scripts/generate-report.js", + "clean": "rimraf test-results screenshots" + }, + "dependencies": { + "lodash": "^4.17.21", + "pixelmatch": "^5.3.0", + "pngjs": "^7.0.0", + "glob": "^10.3.3" + }, + "devDependencies": { + "rimraf": "^5.0.1" + } +} \ No newline at end of file diff --git a/src/main/java/processing/test/NodeBridge.java b/src/main/java/processing/test/NodeBridge.java new file mode 100644 index 0000000000..006a46e105 --- /dev/null +++ b/src/main/java/processing/test/NodeBridge.java @@ -0,0 +1,163 @@ +package processing.test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.ArrayList; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; + +/** + * Bridge class to execute Node.js functions from Java/Processing + */ +public class NodeBridge { + private static final String NODE_SCRIPT_PATH = "src/main/js/index.js"; + private final Gson gson = new Gson(); + + /** + * Execute a Node.js command and return the result + */ + private String executeNodeCommand(String command, String data) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder("node", NODE_SCRIPT_PATH, command, data); + pb.redirectErrorStream(true); + + Process process = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + StringBuilder output = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + output.append(line); + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new RuntimeException("Node.js process failed with exit code: " + exitCode); + } + + return output.toString(); + } + + /** + * Shuffle an array using lodash + */ + public List shuffleArray(List array) { + try { + String jsonData = gson.toJson(array); + String result = executeNodeCommand("shuffle", jsonData); + + JsonArray jsonArray = gson.fromJson(result, JsonArray.class); + List shuffled = new ArrayList<>(); + for (JsonElement element : jsonArray) { + shuffled.add(element.getAsInt()); + } + return shuffled; + } catch (Exception e) { + System.err.println("Error shuffling array: " + e.getMessage()); + return array; // return original on error + } + } + + /** + * Get statistical information about a number array + */ + public ArrayStats getArrayStats(List numbers) { + try { + String jsonData = gson.toJson(numbers); + String result = executeNodeCommand("stats", jsonData); + + JsonObject jsonObject = gson.fromJson(result, JsonObject.class); + return new ArrayStats( + jsonObject.get("mean").getAsDouble(), + jsonObject.get("sum").getAsDouble(), + jsonObject.get("min").getAsDouble(), + jsonObject.get("max").getAsDouble(), + jsonObject.get("size").getAsInt() + ); + } catch (Exception e) { + System.err.println("Error getting array stats: " + e.getMessage()); + return null; + } + } + + /** + * Capitalize words using lodash + */ + public String capitalizeWords(String text) { + try { + String jsonData = gson.toJson(text); + String result = executeNodeCommand("capitalize", jsonData); + return gson.fromJson(result, String.class); + } catch (Exception e) { + System.err.println("Error capitalizing words: " + e.getMessage()); + return text; // return original on error + } + } + + /** + * Generate a color palette for Processing + */ + public List generateColorPalette(int count) { + try { + String jsonData = String.valueOf(count); + String result = executeNodeCommand("colors", jsonData); + + JsonArray jsonArray = gson.fromJson(result, JsonArray.class); + List colors = new ArrayList<>(); + + for (JsonElement element : jsonArray) { + JsonObject colorObj = element.getAsJsonObject(); + colors.add(new ColorRGB( + colorObj.get("r").getAsInt(), + colorObj.get("g").getAsInt(), + colorObj.get("b").getAsInt() + )); + } + return colors; + } catch (Exception e) { + System.err.println("Error generating color palette: " + e.getMessage()); + return new ArrayList<>(); + } + } + + // Helper classes for structured data + public static class ArrayStats { + public final double mean; + public final double sum; + public final double min; + public final double max; + public final int size; + + public ArrayStats(double mean, double sum, double min, double max, int size) { + this.mean = mean; + this.sum = sum; + this.min = min; + this.max = max; + this.size = size; + } + + @Override + public String toString() { + return String.format("Stats{mean=%.2f, sum=%.2f, min=%.2f, max=%.2f, size=%d}", + mean, sum, min, max, size); + } + } + + public static class ColorRGB { + public final int r, g, b; + + public ColorRGB(int r, int g, int b) { + this.r = r; + this.g = g; + this.b = b; + } + + @Override + public String toString() { + return String.format("RGB(%d, %d, %d)", r, g, b); + } + } +} \ No newline at end of file diff --git a/src/main/java/processing/test/NodeIntegrationTest.java b/src/main/java/processing/test/NodeIntegrationTest.java new file mode 100644 index 0000000000..34fe299d61 --- /dev/null +++ b/src/main/java/processing/test/NodeIntegrationTest.java @@ -0,0 +1,57 @@ +package processing.test; + +import java.util.Arrays; +import java.util.List; +import processing.test.NodeBridge; +import processing.test.NodeBridge.ArrayStats; +import processing.test.NodeBridge.ColorRGB; + +/** + * Test class to demonstrate Node.js integration with Processing + */ +public class NodeIntegrationTest { + + public static void main(String[] args) { + NodeBridge bridge = new NodeBridge(); + + System.out.println("=== Testing Node.js Integration with Processing 4 ===\n"); + + // Test 1: Array shuffling + System.out.println("1. Testing array shuffling with lodash:"); + List originalArray = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + System.out.println("Original: " + originalArray); + List shuffled = bridge.shuffleArray(originalArray); + System.out.println("Shuffled: " + shuffled); + System.out.println(); + + // Test 2: Statistical analysis + System.out.println("2. Testing statistical analysis:"); + List numbers = Arrays.asList(10.5, 20.3, 15.7, 30.1, 25.9, 12.4, 18.6); + System.out.println("Numbers: " + numbers); + ArrayStats stats = bridge.getArrayStats(numbers); + if (stats != null) { + System.out.println("Statistics: " + stats); + } + System.out.println(); + + // Test 3: String manipulation + System.out.println("3. Testing string capitalization:"); + String originalText = "hello world from PROCESSING and node.js"; + System.out.println("Original: " + originalText); + String capitalized = bridge.capitalizeWords(originalText); + System.out.println("Capitalized: " + capitalized); + System.out.println(); + + // Test 4: Color palette generation for Processing sketches + System.out.println("4. Testing color palette generation:"); + List palette = bridge.generateColorPalette(5); + System.out.println("Generated color palette:"); + for (int i = 0; i < palette.size(); i++) { + ColorRGB color = palette.get(i); + System.out.println(" Color " + (i + 1) + ": " + color); + } + System.out.println(); + + System.out.println("=== All tests completed! ==="); + } +} \ No newline at end of file diff --git a/src/main/js/index.js b/src/main/js/index.js new file mode 100644 index 0000000000..8430c235a5 --- /dev/null +++ b/src/main/js/index.js @@ -0,0 +1,92 @@ +const _ = require('lodash'); + +// Utility functions that can be called from Java +const ProcessingUtils = { + // Array manipulation using lodash + shuffleArray: (array) => { + return _.shuffle(array); + }, + + // Statistical functions + getArrayStats: (numbers) => { + return { + mean: _.mean(numbers), + sum: _.sum(numbers), + min: _.min(numbers), + max: _.max(numbers), + size: numbers.length + }; + }, + + // String manipulation + capitalizeWords: (text) => { + return _.startCase(_.toLower(text)); + }, + + // Object utilities + deepClone: (obj) => { + return _.cloneDeep(obj); + }, + + // Generate random colors for Processing sketches + generateColorPalette: (count) => { + const colors = []; + for (let i = 0; i < count; i++) { + colors.push({ + r: _.random(0, 255), + g: _.random(0, 255), + b: _.random(0, 255) + }); + } + return colors; + } +}; + +// Export for Node.js usage +if (typeof module !== 'undefined' && module.exports) { + module.exports = ProcessingUtils; +} + +// Make available globally for other contexts +if (typeof global !== 'undefined') { + global.ProcessingUtils = ProcessingUtils; +} + +// Function to handle command line arguments and return JSON +function handleCommand(command, data) { + try { + const parsedData = JSON.parse(data); + let result; + + switch(command) { + case 'shuffle': + result = ProcessingUtils.shuffleArray(parsedData); + break; + case 'stats': + result = ProcessingUtils.getArrayStats(parsedData); + break; + case 'capitalize': + result = ProcessingUtils.capitalizeWords(parsedData); + break; + case 'colors': + result = ProcessingUtils.generateColorPalette(parsedData); + break; + default: + result = { error: 'Unknown command: ' + command }; + } + + console.log(JSON.stringify(result)); + } catch (error) { + console.log(JSON.stringify({ error: error.message })); + } +} + +// Handle command line execution +if (require.main === module) { + const args = process.argv.slice(2); + if (args.length >= 2) { + handleCommand(args[0], args[1]); + } else { + console.log(JSON.stringify({ error: 'Usage: node index.js ' })); + } +} \ No newline at end of file