diff --git a/build.gradle.kts b/build.gradle.kts index 0675c2db38..525641e505 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,4 +8,5 @@ plugins { // 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 diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8f7211b131..14f28236cc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -11,18 +11,18 @@ repositories { maven { url = uri("https://jogamp.org/deployment/maven") } } -sourceSets{ - main{ - java{ +sourceSets { + main { + java { srcDirs("src") } - resources{ + resources { srcDirs("src") exclude("**/*.java") } } - test{ - java{ + test { + java { srcDirs("test") } } @@ -33,13 +33,31 @@ dependencies { implementation(libs.gluegen) testImplementation(libs.junit) + testImplementation(libs.junitJupiter) + testImplementation(libs.junitJupiterParams) + testImplementation(libs.junitPlatformSuite) + testImplementation(libs.assertjCore) } -mavenPublishing{ +// Simple JUnit 5 configuration - let JUnit handle everything +tasks.test { + useJUnitPlatform() // JUnit discovers and runs all tests + + // Only configuration, not orchestration + outputs.upToDateWhen { false } + maxParallelForks = 1 + + testLogging { + events("passed", "skipped", "failed", "started") + showStandardStreams = true + } +} + +mavenPublishing { publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) signAllPublications() - pom{ + pom { name.set("Processing Core") description.set("Processing Core") url.set("https://processing.org") @@ -59,7 +77,7 @@ mavenPublishing{ name.set("Ben Fry") } } - scm{ + scm { url.set("https://github.com/processing/processing4") connection.set("scm:git:git://github.com/processing/processing4.git") developerConnection.set("scm:git:ssh://git@github.com/processing/processing4.git") @@ -67,13 +85,9 @@ mavenPublishing{ } } - -tasks.test { - useJUnit() -} tasks.withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } -tasks.compileJava{ +tasks.compileJava { options.encoding = "UTF-8" -} +} \ No newline at end of file diff --git a/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png b/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png new file mode 100644 index 0000000000..87bb74dfc2 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-fills-linux.png b/core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-fills-linux.png new file mode 100644 index 0000000000..608b7ffe20 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-fills-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-strokes-linux.png b/core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-strokes-linux.png new file mode 100644 index 0000000000..7270c15323 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-strokes-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png b/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png new file mode 100644 index 0000000000..e07fe529c8 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png b/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png new file mode 100644 index 0000000000..e628f40541 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/closed-curves-linux.png b/core/test/processing/visual/__screenshots__/shapes/closed-curves-linux.png new file mode 100644 index 0000000000..2851f61f95 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/closed-curves-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/closed-polylines-linux.png b/core/test/processing/visual/__screenshots__/shapes/closed-polylines-linux.png new file mode 100644 index 0000000000..43a6cc68ba Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/closed-polylines-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/contours-linux.png b/core/test/processing/visual/__screenshots__/shapes/contours-linux.png new file mode 100644 index 0000000000..032c60a753 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/contours-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/curves-linux.png b/core/test/processing/visual/__screenshots__/shapes/curves-linux.png new file mode 100644 index 0000000000..5a629e80fb Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/curves-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png b/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png new file mode 100644 index 0000000000..aecfee50ea Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/lines-linux.png b/core/test/processing/visual/__screenshots__/shapes/lines-linux.png new file mode 100644 index 0000000000..f2e42539e6 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/lines-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/points-linux.png b/core/test/processing/visual/__screenshots__/shapes/points-linux.png new file mode 100644 index 0000000000..d0aecf8d30 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/points-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/polylines-linux.png b/core/test/processing/visual/__screenshots__/shapes/polylines-linux.png new file mode 100644 index 0000000000..8f0a6ad363 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/polylines-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png b/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png new file mode 100644 index 0000000000..a0836a3a6d Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/quadratic-beziers-linux.png b/core/test/processing/visual/__screenshots__/shapes/quadratic-beziers-linux.png new file mode 100644 index 0000000000..85416ec263 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/quadratic-beziers-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/quads-linux.png b/core/test/processing/visual/__screenshots__/shapes/quads-linux.png new file mode 100644 index 0000000000..b7d2c80f9e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/quads-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/single-closed-contour-linux.png b/core/test/processing/visual/__screenshots__/shapes/single-closed-contour-linux.png new file mode 100644 index 0000000000..401b9974d1 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/single-closed-contour-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png b/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png new file mode 100644 index 0000000000..401b9974d1 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png b/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png new file mode 100644 index 0000000000..c7c2b87e64 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/triangle-strips-linux.png b/core/test/processing/visual/__screenshots__/shapes/triangle-strips-linux.png new file mode 100644 index 0000000000..14ee9cd38e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/triangle-strips-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shapes/triangles-linux.png b/core/test/processing/visual/__screenshots__/shapes/triangles-linux.png new file mode 100644 index 0000000000..0e25fdbc03 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shapes/triangles-linux.png differ diff --git a/core/test/processing/visual/src/core/BaselineManager.java b/core/test/processing/visual/src/core/BaselineManager.java new file mode 100644 index 0000000000..93123ab13c --- /dev/null +++ b/core/test/processing/visual/src/core/BaselineManager.java @@ -0,0 +1,38 @@ +package processing.visual.src.core; + +import processing.core.PImage; + +import java.util.List; + +// Baseline manager for updating reference images +public class BaselineManager { + private VisualTestRunner tester; + + public BaselineManager(VisualTestRunner tester) { + this.tester = tester; + } + + public void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + System.out.println("Updating baseline for: " + testName); + + // Capture new image + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + PImage newImage = runner.getImage(); + + // Save as baseline + String baselinePath = "__screenshots__/" + + testName.replaceAll("[^a-zA-Z0-9-_]", "-") + + "-" + detectPlatform() + ".png"; + newImage.save(baselinePath); + + System.out.println("Baseline updated: " + baselinePath); + } + + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } +} diff --git a/core/test/processing/visual/src/core/ImageComparator.java b/core/test/processing/visual/src/core/ImageComparator.java new file mode 100644 index 0000000000..2b368e8e4b --- /dev/null +++ b/core/test/processing/visual/src/core/ImageComparator.java @@ -0,0 +1,417 @@ +package processing.visual.src.core; + +import processing.core.*; +import java.util.*; + +class ComparisonResult { + public boolean passed; + public double mismatchRatio; + public boolean isFirstRun; + public PImage diffImage; + public ComparisonDetails details; + + public ComparisonResult(boolean passed, double mismatchRatio) { + this.passed = passed; + this.mismatchRatio = mismatchRatio; + this.isFirstRun = false; + } + + public ComparisonResult(boolean passed, PImage diffImage, ComparisonDetails details) { + this.passed = passed; + this.diffImage = diffImage; + this.details = details; + this.mismatchRatio = details != null ? (double) details.significantDiffPixels / (diffImage.width * diffImage.height) : 0.0; + this.isFirstRun = false; + } + + public static ComparisonResult createFirstRun() { + ComparisonResult result = new ComparisonResult(false, 0.0); + result.isFirstRun = true; + return result; + } + + public void saveDiffImage(String filePath) { + if (diffImage != null) { + diffImage.save(filePath); + System.out.println("Diff image saved: " + filePath); + } + } +} + +class ComparisonDetails { + public int totalDiffPixels; + public int significantDiffPixels; + public List clusters; + + public ComparisonDetails(int totalDiffPixels, int significantDiffPixels, List clusters) { + this.totalDiffPixels = totalDiffPixels; + this.significantDiffPixels = significantDiffPixels; + this.clusters = clusters; + } + + public void printDetails() { + System.out.println(" Total diff pixels: " + totalDiffPixels); + System.out.println(" Significant diff pixels: " + significantDiffPixels); + System.out.println(" Clusters found: " + clusters.size()); + + long lineShiftClusters = clusters.stream().filter(c -> c.isLineShift).count(); + if (lineShiftClusters > 0) { + System.out.println(" Line shift clusters (ignored): " + lineShiftClusters); + } + + // Print cluster details + for (int i = 0; i < clusters.size(); i++) { + ClusterInfo cluster = clusters.get(i); + System.out.println(" Cluster " + (i+1) + ": size=" + cluster.size + + ", lineShift=" + cluster.isLineShift); + } + } +} + +// Individual cluster information +class ClusterInfo { + public int size; + public List pixels; + public boolean isLineShift; + + public ClusterInfo(int size, List pixels, boolean isLineShift) { + this.size = size; + this.pixels = pixels; + this.isLineShift = isLineShift; + } +} + +// Simple 2D point +class Point2D { + public int x, y; + + public Point2D(int x, int y) { + this.x = x; + this.y = y; + } +} + +// Interface for pixel matching algorithms +interface PixelMatchingAlgorithm { + ComparisonResult compare(PImage baseline, PImage actual, double threshold); +} + +// Your sophisticated pixel matching algorithm +public class ImageComparator implements PixelMatchingAlgorithm { + + // Algorithm constants + private static final int MAX_SIDE = 400; + private static final int BG_COLOR = 0xFFFFFFFF; // White background + private static final int MIN_CLUSTER_SIZE = 4; + private static final int MAX_TOTAL_DIFF_PIXELS = 40; + private static final double DEFAULT_THRESHOLD = 0.5; + private static final double ALPHA = 0.1; + + private PApplet p; // Reference to PApplet for PImage creation + + public ImageComparator(PApplet p) { + this.p = p; + } + + @Override + public ComparisonResult compare(PImage baseline, PImage actual, double threshold) { + if (baseline == null || actual == null) { + return new ComparisonResult(false, 1.0); + } + + try { + return performComparison(baseline, actual, threshold); + } catch (Exception e) { + System.err.println("Comparison failed: " + e.getMessage()); + return new ComparisonResult(false, 1.0); + } + } + + private ComparisonResult performComparison(PImage baseline, PImage actual, double threshold) { + // Calculate scaling + double scale = Math.min( + (double) MAX_SIDE / baseline.width, + (double) MAX_SIDE / baseline.height + ); + + double ratio = (double) baseline.width / baseline.height; + boolean narrow = ratio != 1.0; + if (narrow) { + scale *= 2; + } + + // Resize images + PImage scaledActual = resizeImage(actual, scale); + PImage scaledBaseline = resizeImage(baseline, scale); + + // Ensure both images have the same dimensions + int width = scaledBaseline.width; + int height = scaledBaseline.height; + + // Create canvases with background color + PImage actualCanvas = createCanvasWithBackground(scaledActual, width, height); + PImage baselineCanvas = createCanvasWithBackground(scaledBaseline, width, height); + + // Create diff output canvas + PImage diffCanvas = p.createImage(width, height, PImage.RGB); + + // Run pixelmatch equivalent + int diffCount = pixelmatch(actualCanvas, baselineCanvas, diffCanvas, width, height, DEFAULT_THRESHOLD); + + // If no differences, return early + if (diffCount == 0) { + return new ComparisonResult(true, diffCanvas, null); + } + + // Post-process to identify and filter out isolated differences + Set visited = new HashSet<>(); + List clusterSizes = new ArrayList<>(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pos = y * width + x; + + // If this is a diff pixel and not yet visited + if (isDiffPixel(diffCanvas, x, y) && !visited.contains(pos)) { + ClusterInfo clusterInfo = findClusterSize(diffCanvas, x, y, width, height, visited); + clusterSizes.add(clusterInfo); + } + } + } + + // Determine if the differences are significant + List nonLineShiftClusters = clusterSizes.stream() + .filter(cluster -> !cluster.isLineShift && cluster.size >= MIN_CLUSTER_SIZE) + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + + // Calculate significant differences excluding line shifts + int significantDiffPixels = nonLineShiftClusters.stream() + .mapToInt(cluster -> cluster.size) + .sum(); + + // Determine test result + boolean passed = diffCount == 0 || + significantDiffPixels == 0 || + (significantDiffPixels <= MAX_TOTAL_DIFF_PIXELS && nonLineShiftClusters.size() <= 2); + + ComparisonDetails details = new ComparisonDetails(diffCount, significantDiffPixels, clusterSizes); + + return new ComparisonResult(passed, diffCanvas, details); + } + + private PImage resizeImage(PImage image, double scale) { + int newWidth = (int) Math.ceil(image.width * scale); + int newHeight = (int) Math.ceil(image.height * scale); + + PImage resized = p.createImage(newWidth, newHeight, PImage.RGB); + resized.copy(image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight); + + return resized; + } + + private PImage createCanvasWithBackground(PImage image, int width, int height) { + PImage canvas = p.createImage(width, height, PImage.RGB); + + // Fill with background color (white) + canvas.loadPixels(); + for (int i = 0; i < canvas.pixels.length; i++) { + canvas.pixels[i] = BG_COLOR; + } + canvas.updatePixels(); + + // Draw the image on top + canvas.copy(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height); + + return canvas; + } + + private int pixelmatch(PImage actual, PImage expected, PImage diff, int width, int height, double threshold) { + int diffCount = 0; + + actual.loadPixels(); + expected.loadPixels(); + diff.loadPixels(); + + for (int i = 0; i < actual.pixels.length; i++) { + int actualColor = actual.pixels[i]; + int expectedColor = expected.pixels[i]; + + double delta = colorDelta(actualColor, expectedColor); + + if (delta > threshold) { + // Mark as different (bright red pixel) + diff.pixels[i] = 0xFFFF0000; // Red + diffCount++; + } else { + // Mark as same (dimmed version of actual image) + int dimColor = dimColor(actualColor, ALPHA); + diff.pixels[i] = dimColor; + } + } + + diff.updatePixels(); + return diffCount; + } + + private double colorDelta(int color1, int color2) { + int r1 = (color1 >> 16) & 0xFF; + int g1 = (color1 >> 8) & 0xFF; + int b1 = color1 & 0xFF; + int a1 = (color1 >> 24) & 0xFF; + + int r2 = (color2 >> 16) & 0xFF; + int g2 = (color2 >> 8) & 0xFF; + int b2 = color2 & 0xFF; + int a2 = (color2 >> 24) & 0xFF; + + int dr = r1 - r2; + int dg = g1 - g2; + int db = b1 - b2; + int da = a1 - a2; + + return Math.sqrt(dr * dr + dg * dg + db * db + da * da) / 255.0; + } + + private int dimColor(int color, double alpha) { + int r = (int) (((color >> 16) & 0xFF) * alpha); + int g = (int) (((color >> 8) & 0xFF) * alpha); + int b = (int) ((color & 0xFF) * alpha); + int a = (int) (255 * alpha); + + r = Math.max(0, Math.min(255, r)); + g = Math.max(0, Math.min(255, g)); + b = Math.max(0, Math.min(255, b)); + a = Math.max(0, Math.min(255, a)); + + return (a << 24) | (r << 16) | (g << 8) | b; + } + + private boolean isDiffPixel(PImage image, int x, int y) { + if (x < 0 || x >= image.width || y < 0 || y >= image.height) return false; + + image.loadPixels(); + int color = image.pixels[y * image.width + x]; + + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + + return r == 255 && g == 0 && b == 0; + } + + private ClusterInfo findClusterSize(PImage diffImage, int startX, int startY, int width, int height, Set visited) { + List queue = new ArrayList<>(); + queue.add(new Point2D(startX, startY)); + + int size = 0; + List clusterPixels = new ArrayList<>(); + + while (!queue.isEmpty()) { + Point2D point = queue.remove(0); + int pos = point.y * width + point.x; + + // Skip if already visited + if (visited.contains(pos)) continue; + + // Skip if not a diff pixel + if (!isDiffPixel(diffImage, point.x, point.y)) continue; + + // Mark as visited + visited.add(pos); + size++; + clusterPixels.add(point); + + // Add neighbors to queue + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = point.x + dx; + int ny = point.y + dy; + + // Skip if out of bounds + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Skip if already visited + int npos = ny * width + nx; + if (!visited.contains(npos)) { + queue.add(new Point2D(nx, ny)); + } + } + } + } + + // Determine if this is a line shift + boolean isLineShift = detectLineShift(clusterPixels, diffImage, width, height); + + return new ClusterInfo(size, clusterPixels, isLineShift); + } + + private boolean detectLineShift(List clusterPixels, PImage diffImage, int width, int height) { + if (clusterPixels.isEmpty()) return false; + + int linelikePixels = 0; + + for (Point2D pixel : clusterPixels) { + int neighbors = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; // Skip self + + int nx = pixel.x + dx; + int ny = pixel.y + dy; + + // Skip if out of bounds + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Check if neighbor is a diff pixel + if (isDiffPixel(diffImage, nx, ny)) { + neighbors++; + } + } + } + + // Line-like pixels typically have 1-2 neighbors + if (neighbors <= 2) { + linelikePixels++; + } + } + + // If most pixels (>80%) in the cluster have ≤2 neighbors, it's likely a line shift + return (double) linelikePixels / clusterPixels.size() > 0.8; + } + + // Configuration methods + public ImageComparator setMaxSide(int maxSide) { + // For future configurability + return this; + } + + public ImageComparator setMinClusterSize(int minClusterSize) { + // For future configurability + return this; + } + + public ImageComparator setMaxTotalDiffPixels(int maxTotalDiffPixels) { + // For future configurability + return this; + } +} + +// Utility class for algorithm configuration +class ComparatorConfig { + public int maxSide = 400; + public int minClusterSize = 4; + public int maxTotalDiffPixels = 40; + public double threshold = 0.5; + public double alpha = 0.1; + public int backgroundColor = 0xFFFFFFFF; + + public ComparatorConfig() {} + + public ComparatorConfig(int maxSide, int minClusterSize, int maxTotalDiffPixels) { + this.maxSide = maxSide; + this.minClusterSize = minClusterSize; + this.maxTotalDiffPixels = maxTotalDiffPixels; + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/core/ProcessingSketch.java b/core/test/processing/visual/src/core/ProcessingSketch.java new file mode 100644 index 0000000000..e4750490b6 --- /dev/null +++ b/core/test/processing/visual/src/core/ProcessingSketch.java @@ -0,0 +1,9 @@ +package processing.visual.src.core; + +import processing.core.PApplet; + +// Interface for user sketches +public interface ProcessingSketch { + void setup(PApplet p); + void draw(PApplet p); +} diff --git a/core/test/processing/visual/src/core/TestConfig.java b/core/test/processing/visual/src/core/TestConfig.java new file mode 100644 index 0000000000..fd39bb91e7 --- /dev/null +++ b/core/test/processing/visual/src/core/TestConfig.java @@ -0,0 +1,33 @@ +package processing.visual.src.core; + +// Test configuration class +public class TestConfig { + public int width = 800; + public int height = 600; + public int[] backgroundColor = {255, 255, 255}; // RGB + public long renderWaitTime = 100; // milliseconds + public double threshold = 0.1; + + public TestConfig() {} + + public TestConfig(int width, int height) { + this.width = width; + this.height = height; + } + + public TestConfig(int width, int height, int[] backgroundColor) { + this.width = width; + this.height = height; + this.backgroundColor = backgroundColor; + } + + public TestConfig setThreshold(double threshold) { + this.threshold = threshold; + return this; + } + + public TestConfig setRenderWaitTime(long waitTime) { + this.renderWaitTime = waitTime; + return this; + } +} diff --git a/core/test/processing/visual/src/core/TestResult.java b/core/test/processing/visual/src/core/TestResult.java new file mode 100644 index 0000000000..6ff7c57ac7 --- /dev/null +++ b/core/test/processing/visual/src/core/TestResult.java @@ -0,0 +1,45 @@ +package processing.visual.src.core; + +// Enhanced test result with detailed information +public class TestResult { + public String testName; + public boolean passed; + public double mismatchRatio; + public String error; + public boolean isFirstRun; + public ComparisonDetails details; + + public TestResult(String testName, ComparisonResult comparison) { + this.testName = testName; + this.passed = comparison.passed; + this.mismatchRatio = comparison.mismatchRatio; + this.isFirstRun = comparison.isFirstRun; + this.details = comparison.details; + } + + public static TestResult createError(String testName, String error) { + TestResult result = new TestResult(); + result.testName = testName; + result.passed = false; + result.error = error; + return result; + } + + private TestResult() {} // For error constructor + + public void printResult() { + System.out.print(testName + ": "); + if (error != null) { + System.out.println("ERROR - " + error); + } else if (isFirstRun) { + System.out.println("BASELINE CREATED"); + } else if (passed) { + System.out.println("PASSED"); + } else { + System.out.println("FAILED (mismatch: " + String.format("%.4f", mismatchRatio * 100) + "%)"); + if (details != null) { + details.printDetails(); + } + } + } +} diff --git a/core/test/processing/visual/src/core/VisualTestRunner.java b/core/test/processing/visual/src/core/VisualTestRunner.java new file mode 100644 index 0000000000..7fd3a82a74 --- /dev/null +++ b/core/test/processing/visual/src/core/VisualTestRunner.java @@ -0,0 +1,263 @@ +package processing.visual.src.core; + +import processing.core.*; +import java.io.*; +import java.nio.file.*; +import java.util.*; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; + +// Core visual tester class +public class VisualTestRunner { + + private String screenshotDir; + private PixelMatchingAlgorithm pixelMatcher; + private String platform; + + public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher) { + this.pixelMatcher = pixelMatcher; + this.screenshotDir = "test/processing/visual/__screenshots__"; + this.platform = detectPlatform(); + createDirectoryIfNotExists(screenshotDir); + } + + public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher, String screenshotDir) { + this.pixelMatcher = pixelMatcher; + this.screenshotDir = screenshotDir; + this.platform = detectPlatform(); + createDirectoryIfNotExists(screenshotDir); + } + + // Main test execution method + public TestResult runVisualTest(String testName, ProcessingSketch sketch) { + return runVisualTest(testName, sketch, new TestConfig()); + } + + public TestResult runVisualTest(String testName, ProcessingSketch sketch, TestConfig config) { + try { + System.out.println("Running visual test: " + testName); + + // Capture screenshot from sketch + PImage actualImage = captureSketch(sketch, config); + + // Compare with baseline + ComparisonResult comparison = compareWithBaseline(testName, actualImage, config); + + return new TestResult(testName, comparison); + + } catch (Exception e) { + return TestResult.createError(testName, e.getMessage()); + } + } + + // Capture PImage from Processing sketch + private PImage captureSketch(ProcessingSketch sketch, TestConfig config) { + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + return runner.getImage(); + } + + // Compare actual image with baseline + private ComparisonResult compareWithBaseline(String testName, PImage actualImage, TestConfig config) { + String baselinePath = getBaselinePath(testName); + + PImage baselineImage = loadBaseline(baselinePath); + + if (baselineImage == null) { + // First run - save as baseline + saveBaseline(testName, actualImage); + return ComparisonResult.createFirstRun(); + } + + // Use your sophisticated pixel matching algorithm + ComparisonResult result = pixelMatcher.compare(baselineImage, actualImage, config.threshold); + + // Save diff images if test failed + if (!result.passed && result.diffImage != null) { + saveDiffImage(testName, result.diffImage); + } + + return result; + } + + // Save diff image for debugging + private void saveDiffImage(String testName, PImage diffImage) { + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); + String diffPath; + if (sanitizedName.contains("/")) { + diffPath = "test/processing/visual/diff_" + sanitizedName.replace("/", "_") + "-" + platform + ".png"; + } else { + diffPath = "test/processing/visual/diff_" + sanitizedName + "-" + platform + ".png"; + } + + File diffFile = new File(diffPath); + diffFile.getParentFile().mkdirs(); + + diffImage.save(diffPath); + System.out.println("Diff image saved: " + diffPath); + } + + // Utility methods + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } + + private void createDirectoryIfNotExists(String dir) { + try { + Files.createDirectories(Paths.get(dir)); + } catch (IOException e) { + System.err.println("Failed to create directory: " + dir); + } + } + + private String getBaselinePath(String testName) { + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_/]", "-"); + + return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; + } + + // Replace loadBaseline method: + private PImage loadBaseline(String path) { + File file = new File(path); + if (!file.exists()) { + System.out.println("loadBaseline: File doesn't exist: " + file.getAbsolutePath()); + return null; + } + + try { + System.out.println("loadBaseline: Loading from " + file.getAbsolutePath()); + + // Use Java ImageIO instead of PApplet + BufferedImage img = ImageIO.read(file); + + if (img == null) { + System.out.println("loadBaseline: ImageIO returned null"); + return null; + } + + // Convert BufferedImage to PImage + PImage pImg = new PImage(img.getWidth(), img.getHeight(), PImage.RGB); + img.getRGB(0, 0, pImg.width, pImg.height, pImg.pixels, 0, pImg.width); + pImg.updatePixels(); + + System.out.println("loadBaseline: ✓ Loaded " + pImg.width + "x" + pImg.height); + return pImg; + + } catch (Exception e) { + System.err.println("loadBaseline: Error loading image: " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + // Replace saveBaseline method: + private void saveBaseline(String testName, PImage image) { + String path = getBaselinePath(testName); + + if (image == null) { + System.out.println("saveBaseline: ✗ Image is null!"); + return; + } + + try { + // Convert PImage to BufferedImage + BufferedImage bImg = new BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB); + image.loadPixels(); + bImg.setRGB(0, 0, image.width, image.height, image.pixels, 0, image.width); + + // Create File object and ensure parent directories exist + File outputFile = new File(path); + outputFile.getParentFile().mkdirs(); // This creates nested directories + + // Use Java ImageIO to save + ImageIO.write(bImg, "PNG", outputFile); + + System.out.println("Baseline saved: " + path); + + } catch (Exception e) { + System.err.println("Failed to save baseline: " + path); + e.printStackTrace(); + } + } +} +class SketchRunner extends PApplet { + + private ProcessingSketch userSketch; + private TestConfig config; + private PImage capturedImage; + private volatile boolean rendered = false; + + public SketchRunner(ProcessingSketch userSketch, TestConfig config) { + this.userSketch = userSketch; + this.config = config; + } + + public void settings() { + size(config.width, config.height); + } + + public void setup() { + noLoop(); + + // Set background if specified + if (config.backgroundColor != null) { + background(config.backgroundColor[0], config.backgroundColor[1], config.backgroundColor[2]); + } + + // Call user setup + userSketch.setup(this); + } + + public void draw() { + if (!rendered) { + userSketch.draw(this); + capturedImage = get(); + rendered = true; + noLoop(); + } + } + + public void run() { + String[] args = {"SketchRunner"}; + PApplet.runSketch(args, this); + + // Simple polling with timeout + int maxWait = 100; // 10 seconds max + int waited = 0; + + while (!rendered && waited < maxWait) { + try { + Thread.sleep(100); + waited++; + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Additional wait time + try { + Thread.sleep(config.renderWaitTime); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + if (surface != null) { + surface.setVisible(false); + } + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public PImage getImage() { + return capturedImage; + } +} diff --git a/core/test/processing/visual/src/test/base/VisualTest.java b/core/test/processing/visual/src/test/base/VisualTest.java new file mode 100644 index 0000000000..55804b4acb --- /dev/null +++ b/core/test/processing/visual/src/test/base/VisualTest.java @@ -0,0 +1,61 @@ +package processing.visual.src.test.base; + +import org.junit.jupiter.api.*; +import processing.core.*; +import static org.junit.jupiter.api.Assertions.*; +import processing.visual.src.core.*; +import java.nio.file.*; +import java.io.File; + +/** + * Base class for Processing visual tests using JUnit 5 + */ +public abstract class VisualTest { + + protected static VisualTestRunner testRunner; + protected static ImageComparator comparator; + + @BeforeAll + public static void setupTestRunner() { + PApplet tempApplet = new PApplet(); + comparator = new ImageComparator(tempApplet); + testRunner = new VisualTestRunner(comparator); + + System.out.println("Visual test runner initialized"); + } + + /** + * Helper method to run a visual test + */ + protected void assertVisualMatch(String testName, ProcessingSketch sketch) { + assertVisualMatch(testName, sketch, new TestConfig()); + } + + protected void assertVisualMatch(String testName, ProcessingSketch sketch, TestConfig config) { + TestResult result = testRunner.runVisualTest(testName, sketch, config); + + // Print result for debugging + result.printResult(); + + // Handle different result types + if (result.isFirstRun) { + // First run - baseline created, mark as skipped + Assumptions.assumeTrue(false, "Baseline created for " + testName + ". Run tests again to verify."); + } else if (result.error != null) { + fail("Test error: " + result.error); + } else { + // Assert that the test passed + Assertions.assertTrue(result.passed, + String.format("Visual test '%s' failed with mismatch ratio: %.4f%%", + testName, result.mismatchRatio * 100)); + } + } + + /** + * Update baseline for a specific test (useful for maintenance) + */ + protected void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + BaselineManager manager = new BaselineManager(testRunner); + manager.updateBaseline(testName, sketch, config); + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/shapes/Shape3DTest.java b/core/test/processing/visual/src/test/shapes/Shape3DTest.java new file mode 100644 index 0000000000..7006cf329b --- /dev/null +++ b/core/test/processing/visual/src/test/shapes/Shape3DTest.java @@ -0,0 +1,84 @@ +package processing.visual.src.test.shapes; + +import org.junit.jupiter.api.*; +import processing.core.*; +import processing.visual.src.test.base.VisualTest; +import processing.visual.src.core.ProcessingSketch; +import processing.visual.src.core.TestConfig; + +@Tag("shapes") +@Tag("3d") +@Tag("p3d") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class Shape3DTest extends VisualTest { + + private ProcessingSketch create3DTest(Shape3DCallback callback) { + return new ProcessingSketch() { + @Override + public void setup(PApplet p) { + // P3D mode setup would go here if supported + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + callback.draw(p); + } + }; + } + + @FunctionalInterface + interface Shape3DCallback { + void draw(PApplet p); + } + + @Test + @DisplayName("3D vertex coordinates") + public void test3DVertexCoordinates() { + assertVisualMatch("shapes-3d/vertex-coordinates", create3DTest(p -> { + p.beginShape(PApplet.QUAD_STRIP); + p.vertex(10, 10, 0); + p.vertex(10, 40, -150); + p.vertex(40, 10, 150); + p.vertex(40, 40, 200); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @DisplayName("Per-vertex fills") + public void testPerVertexFills() { + assertVisualMatch("shapes-3d/per-vertex-fills", create3DTest(p -> { + p.beginShape(PApplet.QUAD_STRIP); + p.fill(0); + p.vertex(10, 10); + p.fill(255, 0, 0); + p.vertex(45, 5); + p.fill(0, 255, 0); + p.vertex(15, 35); + p.fill(255, 255, 0); + p.vertex(40, 45); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @DisplayName("Per-vertex strokes") + public void testPerVertexStrokes() { + assertVisualMatch("shapes-3d/per-vertex-strokes", create3DTest(p -> { + p.strokeWeight(5); + p.beginShape(PApplet.QUAD_STRIP); + p.stroke(0); + p.vertex(10, 10); + p.stroke(255, 0, 0); + p.vertex(45, 5); + p.stroke(0, 255, 0); + p.vertex(15, 35); + p.stroke(255, 255, 0); + p.vertex(40, 45); + p.endShape(); + }), new TestConfig(50, 50)); + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/shapes/ShapeTest.java b/core/test/processing/visual/src/test/shapes/ShapeTest.java new file mode 100644 index 0000000000..47ae08b5f3 --- /dev/null +++ b/core/test/processing/visual/src/test/shapes/ShapeTest.java @@ -0,0 +1,356 @@ +package processing.visual.src.test.shapes; + +import org.junit.jupiter.api.*; +import processing.core.*; +import processing.visual.src.test.base.VisualTest; +import processing.visual.src.core.ProcessingSketch; +import processing.visual.src.core.TestConfig; + +@Tag("shapes") +@Tag("rendering") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ShapeTest extends VisualTest { + + // Helper method for common setup + private ProcessingSketch createShapeTest(ShapeDrawingCallback callback) { + return new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + callback.draw(p); + } + }; + } + + @FunctionalInterface + interface ShapeDrawingCallback { + void draw(PApplet p); + } + + // ========== Polylines ========== + + @Test + @Order(1) + @Tag("polylines") + @DisplayName("Drawing polylines") + public void testPolylines() { + assertVisualMatch("shapes/polylines", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(2) + @Tag("polylines") + @DisplayName("Drawing closed polylines") + public void testClosedPolylines() { + assertVisualMatch("shapes/closed-polylines", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(15, 25); + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + // ========== Contours ========== + + @Test + @Order(3) + @Tag("contours") + @DisplayName("Drawing with contours") + public void testContours() { + assertVisualMatch("shapes/contours", createShapeTest(p -> { + p.beginShape(); + // Outer circle + vertexCircle(p, 15, 15, 10, 1); + + // Inner cutout + p.beginContour(); + vertexCircle(p, 15, 15, 5, -1); + p.endContour(); + + // Second outer shape + p.beginContour(); + vertexCircle(p, 30, 30, 8, -1); + p.endContour(); + + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(4) + @Tag("contours") + @DisplayName("Drawing with a single closed contour") + public void testSingleClosedContour() { + assertVisualMatch("shapes/single-closed-contour", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(40, 10); + p.vertex(40, 40); + p.vertex(10, 40); + + p.beginContour(); + p.vertex(20, 20); + p.vertex(20, 30); + p.vertex(30, 30); + p.vertex(30, 20); + p.endContour(); + + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + @Test + @Order(5) + @Tag("contours") + @DisplayName("Drawing with a single unclosed contour") + public void testSingleUnclosedContour() { + assertVisualMatch("shapes/single-unclosed-contour", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(40, 10); + p.vertex(40, 40); + p.vertex(10, 40); + + p.beginContour(); + p.vertex(20, 20); + p.vertex(20, 30); + p.vertex(30, 30); + p.vertex(30, 20); + p.endContour(); + + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + // ========== Triangle Shapes ========== + + @Test + @Order(6) + @Tag("triangles") + @DisplayName("Drawing triangle fans") + public void testTriangleFans() { + assertVisualMatch("shapes/triangle-fans", createShapeTest(p -> { + p.beginShape(PApplet.TRIANGLE_FAN); + p.vertex(25, 25); + for (int i = 0; i <= 12; i++) { + float angle = PApplet.map(i, 0, 12, 0, PApplet.TWO_PI); + p.vertex(25 + 10 * PApplet.cos(angle), 25 + 10 * PApplet.sin(angle)); + } + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(7) + @Tag("triangles") + @DisplayName("Drawing triangle strips") + public void testTriangleStrips() { + assertVisualMatch("shapes/triangle-strips", createShapeTest(p -> { + p.beginShape(PApplet.TRIANGLE_STRIP); + p.vertex(10, 10); + p.vertex(30, 10); + p.vertex(15, 20); + p.vertex(35, 20); + p.vertex(10, 40); + p.vertex(30, 40); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(8) + @Tag("triangles") + @DisplayName("Drawing with triangles") + public void testTriangles() { + assertVisualMatch("shapes/triangles", createShapeTest(p -> { + p.beginShape(PApplet.TRIANGLES); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(10, 10); + p.vertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Quad Shapes ========== + + @Test + @Order(9) + @Tag("quads") + @DisplayName("Drawing quad strips") + public void testQuadStrips() { + assertVisualMatch("shapes/quad-strips", createShapeTest(p -> { + p.beginShape(PApplet.QUAD_STRIP); + p.vertex(10, 10); + p.vertex(30, 10); + p.vertex(15, 20); + p.vertex(35, 20); + p.vertex(10, 40); + p.vertex(30, 40); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(10) + @Tag("quads") + @DisplayName("Drawing with quads") + public void testQuads() { + assertVisualMatch("shapes/quads", createShapeTest(p -> { + p.beginShape(PApplet.QUADS); + p.vertex(10, 10); + p.vertex(15, 10); + p.vertex(15, 15); + p.vertex(10, 15); + p.vertex(25, 25); + p.vertex(30, 25); + p.vertex(30, 30); + p.vertex(25, 30); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Curves ========== + + @Test + @Order(11) + @Tag("curves") + @DisplayName("Drawing with curves") + public void testCurves() { + assertVisualMatch("shapes/curves", createShapeTest(p -> { + p.beginShape(); + p.curveVertex(10, 10); + p.curveVertex(15, 40); + p.curveVertex(40, 35); + p.curveVertex(25, 15); + p.curveVertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(12) + @Tag("curves") + @DisplayName("Drawing closed curves") + public void testClosedCurves() { + assertVisualMatch("shapes/closed-curves", createShapeTest(p -> { + p.beginShape(); + p.curveVertex(10, 10); + p.curveVertex(15, 40); + p.curveVertex(40, 35); + p.curveVertex(25, 15); + p.curveVertex(15, 25); + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + @Test + @Order(13) + @Tag("curves") + @DisplayName("Drawing with curves with tightness") + public void testCurvesWithTightness() { + assertVisualMatch("shapes/curves-tightness", createShapeTest(p -> { + p.curveTightness(-1); + p.beginShape(); + p.curveVertex(10, 10); + p.curveVertex(15, 40); + p.curveVertex(40, 35); + p.curveVertex(25, 15); + p.curveVertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Bezier Curves ========== + + @Test + @Order(14) + @Tag("bezier") + @DisplayName("Drawing with bezier curves") + public void testBezierCurves() { + assertVisualMatch("shapes/bezier-curves", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.bezierVertex(10, 40, 40, 40, 40, 10); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(15) + @Tag("bezier") + @DisplayName("Drawing with quadratic beziers") + public void testQuadraticBeziers() { + assertVisualMatch("shapes/quadratic-beziers", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.quadraticVertex(25, 40, 40, 10); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Points and Lines ========== + + @Test + @Order(16) + @Tag("primitives") + @DisplayName("Drawing with points") + public void testPoints() { + assertVisualMatch("shapes/points", createShapeTest(p -> { + p.strokeWeight(5); + p.beginShape(PApplet.POINTS); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(17) + @Tag("primitives") + @DisplayName("Drawing with lines") + public void testLines() { + assertVisualMatch("shapes/lines", createShapeTest(p -> { + p.beginShape(PApplet.LINES); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Helper Methods ========== + + /** + * Helper method to create a circle using vertices + */ + private void vertexCircle(PApplet p, float x, float y, float r, int direction) { + for (int i = 0; i <= 12; i++) { + float angle = PApplet.map(i, 0, 12, 0, PApplet.TWO_PI) * direction; + p.vertex(x + r * PApplet.cos(angle), y + r * PApplet.sin(angle)); + } + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/suites/ShapesSuite.java b/core/test/processing/visual/src/test/suites/ShapesSuite.java new file mode 100644 index 0000000000..f45e472826 --- /dev/null +++ b/core/test/processing/visual/src/test/suites/ShapesSuite.java @@ -0,0 +1,12 @@ +package processing.visual.src.test.suites; + +import org.junit.platform.suite.api.*; + +@Suite +@SuiteDisplayName("Basic Shapes Visual Tests") +@SelectPackages("processing.visual.src.test.shapes") +@ExcludePackages("processing.visual.src.test.suites") +@IncludeTags("shapes") +public class ShapesSuite { + // Empty class - just holds annotations +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfacae1ead..ccd1f5c6d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,8 @@ kotlin = "2.0.20" compose-plugin = "1.7.1" jogl = "2.5.0" jupiter = "5.12.0" +junitPlatform = "1.12.0" +assertj = "3.24.2" [libraries] jogl = { module = "org.jogamp.jogl:jogl-all-main", version.ref = "jogl" } @@ -29,6 +31,8 @@ markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" } clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" } kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } +junitPlatformSuite = { module = "org.junit.platform:junit-platform-suite", version.ref = "junitPlatform" } +assertjCore = { module = "org.assertj:assertj-core", version.ref = "assertj" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f8cb74c7f..4d24671266 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,6 @@ include( "java:libraries:net", "java:libraries:pdf", "java:libraries:serial", - "java:libraries:svg", + "java:libraries:svg" ) include("app:utils") \ No newline at end of file