Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ jobs:
cache: maven

- name: Build and run tests
run: ./mvnw -B -ntp clean verify
# Reactor slice: the engine plus the extracted graph-compose-testing
# module and the graph-compose-qa cross-module suites (which need the
# testing module + the engine test-jar on the classpath and so cannot
# live in the engine's own test scope). -am pulls the fonts / emoji
# upstreams; render-docx / bundle / examples / benchmarks are excluded.
run: ./mvnw -B -ntp -f aggregator/pom.xml clean verify -pl :graph-compose,:graph-compose-testing,:graph-compose-qa -am

- name: Generate Javadoc
# Run only on the baseline JDK — Javadoc output is identical
Expand Down Expand Up @@ -120,6 +125,13 @@ jobs:
# dependency) so the examples build resolves it.
run: ./mvnw -B -ntp -f render-docx/pom.xml -DskipTests install

- name: Install graph-compose-testing (consumed by the examples module)
# LayoutSnapshotRegressionExample uses LayoutSnapshotJson and EngineDeckData
# reads benchmark JSON through the jackson it brings; the testing module is
# not on Central, so install it after the engine (its graph-compose
# dependency) before the examples build resolves it.
run: ./mvnw -B -ntp -f testing/pom.xml -DskipTests install

- name: Compile examples module
run: ./mvnw -B -ntp -f examples/pom.xml clean compile

Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ jobs:
CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}

- name: Publish testing to Maven Central
# Consumer testing support (LayoutSnapshotAssertions / PdfVisualRegression),
# lockstep-versioned with the engine (ships on the same v* tag). graph-compose
# resolves from the local repo installed by the engine deploy above.
run: ./mvnw -B -ntp -f testing/pom.xml -P release -DskipTests -Dgpg.skip=false deploy
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}

- name: Publish bundle to Maven Central
# The graph-compose-bundle convenience aggregate pins this engine
# version + a compatible graph-compose-fonts version. It tracks the
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ build/
.DS_Store
/logs/
benchmarks/logs/
qa/logs/
/CV_Generated.pdf
*.pdf
# Allow PDF previews that are committed README assets.
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ for this cycle.
to `com.demcha.compose.document.backend.semantic.pptx`; add
`graph-compose-render-docx` (it brings POI transitively) to export DOCX. The
`no-poi` build profile is retired.
- `graph-compose-testing` is now a separate artifact: the consumer testing
support — `LayoutSnapshotAssertions` (deterministic layout snapshots) and
`PdfVisualRegression` (pixel-diff of rendered pages) — leaves the
`graph-compose` jar together with its Jackson and PDFBox dependencies. The
`com.demcha.compose.testing.layout` / `com.demcha.compose.testing.visual`
packages are unchanged, so imports stay the same; add `graph-compose-testing`
at test scope to keep using them.

## v1.9.0 — 2026-06-29

Expand Down
2 changes: 2 additions & 0 deletions aggregator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@
<module>../fonts</module>
<module>../emoji</module>
<module>../render-docx</module>
<module>../testing</module>
<module>../bundle</module>
<module>../examples</module>
<module>../benchmarks</module>
<module>../qa</module>
</modules>
</project>
2 changes: 1 addition & 1 deletion docs/operations/layout-snapshot-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ Or update a focused test only:
./mvnw "-Dgraphcompose.updateSnapshots=true" "-Dtest=MyFeatureLayoutSnapshotTest" test
```

The same property works for downstream projects that use `LayoutSnapshotAssertions` from the published GraphCompose artifact.
The same property works for downstream projects that use `LayoutSnapshotAssertions` from the published `graph-compose-testing` artifact.

In normal mode:

Expand Down
2 changes: 1 addition & 1 deletion docs/operations/visual-regression-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The semantic layer is cheap, deterministic, and cross-platform stable. The pixel

## Public API

The harness is `com.demcha.compose.testing.visual.PdfVisualRegression` (`@since 1.6.9`), a sibling to the semantic `com.demcha.compose.testing.layout.*` helpers. It ships in the main artifact, so library consumers use the exact helpers GraphCompose uses in its own tests.
The harness is `com.demcha.compose.testing.visual.PdfVisualRegression` (`@since 1.6.9`), a sibling to the semantic `com.demcha.compose.testing.layout.*` helpers. It ships in the `graph-compose-testing` artifact (split out of the engine jar in 2.0) — add it at test scope, and consumers use the exact helpers GraphCompose uses in its own tests.

### Assert a committed baseline

Expand Down
6 changes: 4 additions & 2 deletions docs/recipes/snapshot-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ void invoiceLayoutIsStable() throws Exception {

The slash-delimited key is a logical path: this example compares against
`src/test/resources/layout-snapshots/templates/invoice/invoice_baseline.json`.
`LayoutSnapshotAssertions` ships in the main GraphCompose artifact, so
consumer projects can use it without any extra test dependency.
`LayoutSnapshotAssertions` ships in the `graph-compose-testing` artifact
(`io.github.demchaav:graph-compose-testing`), split out of the engine jar in
2.0. Add it at test scope alongside `graph-compose`; the import path is
unchanged.

## First run and updates

Expand Down
11 changes: 11 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@
<version>${graphcompose.version}</version>
</dependency>

<!-- Snapshot-regression demo (LayoutSnapshotRegressionExample) uses
LayoutSnapshotJson, which ships in graph-compose-testing since 2.0.
It also brings jackson-databind transitively, which EngineDeckData
uses to read the comparative-benchmark JSON — jackson left the
engine jar in 2.0. -->
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-testing</artifactId>
<version>${graphcompose.version}</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down
25 changes: 1 addition & 24 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@

<!-- Runtime / library dependencies -->
<flexmark.version>0.64.8</flexmark.version>
<jackson.bom.version>2.21.4</jackson.bom.version>
<logback.version>1.5.37</logback.version>
<lombok.version>1.18.46</lombok.version>
<pdfbox.version>3.0.7</pdfbox.version>
Expand Down Expand Up @@ -103,13 +102,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down Expand Up @@ -181,21 +173,6 @@
<version>${flexmark.version}</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
Expand Down Expand Up @@ -461,7 +438,7 @@
<failOnError>true</failOnError>
<show>public</show>
<quiet>true</quiet>
<subpackages>com.demcha.compose.document:com.demcha.compose.testing</subpackages>
<subpackages>com.demcha.compose.document</subpackages>
</configuration>
</plugin>
</plugins>
Expand Down
154 changes: 154 additions & 0 deletions qa/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<!--
Non-published cross-module test harness (2.0 module line). Home for the
suites that exercise more than one published artifact at once — the
snapshot/visual dogfood tests, the render-heavy integration tests, the
documentation guards, and the version-consistency guard — plus the shared
layout-snapshot and visual-baseline resources they compare against.

These suites cannot live in the engine's own test scope: they need
graph-compose-testing (LayoutSnapshotAssertions / PdfVisualRegression) on
the classpath, and the engine cannot test-depend on a module that
compile-depends on it (a reactor cycle). This module sits at the reactor
tail as an aggregator child — so its version tracks the engine line
automatically — depends on everything at test scope, and never
publishes (maven.deploy.skip=true, inherited from the aggregator).
-->
<parent>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-build</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../aggregator/pom.xml</relativePath>
</parent>

<artifactId>graph-compose-qa</artifactId>
<name>GraphCompose QA</name>
<description>Non-published cross-module test suites and shared fixtures for GraphCompose.</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>

<maven.compiler.plugin.version>3.15.0</maven.compiler.plugin.version>
<maven.surefire.plugin.version>3.5.6</maven.surefire.plugin.version>

<jackson.bom.version>2.21.4</jackson.bom.version>
<junit.bom.version>6.1.1</junit.bom.version>
<assertj.version>3.27.7</assertj.version>
<mockito.version>5.23.0</mockito.version>
<logback.version>1.5.37</logback.version>
<pdfbox.version>3.0.7</pdfbox.version>
<!-- graphcompose.fonts.version / graphcompose.emoji.version and
maven.deploy.skip=true are inherited from the aggregator parent. -->
</properties>

<dependencies>
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!--
The engine's shared test-only scaffolding — com.demcha.mock.*,
com.demcha.compose.testsupport.*, com.demcha.testing.* — that the
relocated cross-module suites reuse alongside the engine's own
tests. Pulled from the tests-classifier jar built by the engine's
maven-jar-plugin test-jar execution (a local build aid only; the
release profile disables it, so it is never deployed).
-->
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-fonts</artifactId>
<version>${graphcompose.fonts.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-emoji</artifactId>
<version>${graphcompose.emoji.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${pdfbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.bom.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.bom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,31 @@
import org.junit.jupiter.api.Test;

import java.awt.Color;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class RepositoryShowcaseRenderTest {
private static final Path COVER_ASSET = Path.of("assets", "GraphComposeCover.png");
private static final Path COVER_ASSET = locateRepoAsset(Path.of("assets", "GraphComposeCover.png"));

/**
* Resolves a repository-root asset independently of which module runs the
* test. The suite moved from the engine module (working dir = repo root) to
* the qa reactor tail (working dir = {@code qa/}); walking up from the
* working directory until the relative path resolves keeps the cover image
* reachable from either location.
*/
private static Path locateRepoAsset(Path relative) {
for (Path dir = Path.of("").toAbsolutePath(); dir != null; dir = dir.getParent()) {
Path candidate = dir.resolve(relative);
if (Files.exists(candidate)) {
return candidate;
}
}
return relative;
}
private static final Color TITLE_COLOR = new Color(18, 40, 74);
private static final Color ACCENT_COLOR = new Color(37, 128, 197);
private static final Color MUTED_COLOR = new Color(86, 98, 120);
Expand Down
Loading