You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/08/30 11:25:14 UTC

(juneau) branch master updated: Fix nav-tab .active contrast, widen CI js-tests to the full browser suite, and score ChromeScaleScanner's remaining families

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 09a5661ed0 Fix nav-tab .active contrast, widen CI js-tests to the full browser suite, and score ChromeScaleScanner's remaining families
09a5661ed0 is described below

commit 09a5661ed0cc29190f4fe92f8b31d0d399b0d29e
Author: James Bognar <ja...@apache.org>
AuthorDate: Sun Aug 30 07:25:08 2026 -0400

    Fix nav-tab .active contrast, widen CI js-tests to the full browser suite, and score ChromeScaleScanner's remaining families
    
    [J0484] nav-tab .active solid-fill contrast reusing --jc-accent-selected/--jc-on-accent (incl. j04 count-guard 2->3 reconciliation); [J0481] widen CI js-tests to the full 20-class browser suite (folds J0473); [J0476] wire CONTROL_PADDING_X + LINE_HEIGHT into ChromeScaleScanner scoring.
---
 .github/workflows/maven.yml                        |  9 ++--
 .../resources/org/apache/juneau/console/chrome.css |  4 +-
 .../server/console/ConsoleChromeMixin_Test.java    | 18 ++++++--
 .../rest/server/views/ChromeScaleScanner.java      | 48 +++++++++++++++++++---
 .../views/ChromeScale_ContractScan_Test.java       | 25 +++++++++++
 5 files changed, 89 insertions(+), 15 deletions(-)

diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index a356c42ba6..2439cb454e 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -109,16 +109,15 @@ jobs:
     - name: Show Node version
       run: node --version && npm --version
     # -pl/-am rather than -f: -f would resolve the module's SNAPSHOT dependencies from ~/.m2, which a cold runner does
-    # not have.  -Dtest= narrows Surefire to the harness so the upstream modules are built but not re-tested; the
-    # matrix job above is what runs their suites, including the always-on markup/CSS half of this same contract.
+    # not have.  -pl -am builds the upstream modules but does not re-test them; the matrix job above is what runs
+    # their suites, including the always-on markup/CSS half of this same contract.  This step runs the module's
+    # full *_BrowserTest suite.
     - name: Run JS harness
       run: >
         mvn --no-transfer-progress --batch-mode --show-version
         -Pjs-tests
         -pl juneau-rest/juneau-rest-server-views -am
         test
-        -Dtest=PagePanelVisibility_BrowserTest
-        -Dsurefire.failIfNoSpecifiedTests=false
         -Djuneau.jsTests.browsers="${HOME}/.cache/ms-playwright"
     # Surefire already prints each failed assertion with the full rendered-state JSON attached, which is the actual
     # diagnosis (a panel reported as "jc-subpanel jc-active" with "visible":false is the blank-tab defect, stated).
@@ -141,6 +140,6 @@ jobs:
         echo 'Contract being asserted: PageTable.java javadoc, "Panel markup contract".'
         echo '================================================================================'
         echo '--- Surefire report ---'
-        cat "${VIEWS}"/target/surefire-reports/*PagePanelVisibility_BrowserTest.txt 2>/dev/null || echo '(none - the failure was before the tests ran)'
+        cat "${VIEWS}"/target/surefire-reports/*_BrowserTest.txt 2>/dev/null || echo '(none - the failure was before the tests ran)'
         echo '--- Prober stderr (non-empty only if Chromium or Node itself failed) ---'
         cat "${VIEWS}/target/js/prober-stderr.txt" 2>/dev/null || echo '(none)'
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css b/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css
index 36f552478e..6eb3e20fc5 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css
+++ b/juneau-rest/juneau-rest-server-console-ui/src/main/resources/org/apache/juneau/console/chrome.css
@@ -324,9 +324,9 @@ a:hover { text-decoration: underline; }
 }
 
 .jc-nav-tab.active {
-  background-color: var(--jc-accent-wash);
+  background-color: var(--jc-accent-selected);
   border-top-color: var(--jc-accent);
-  color: var(--jc-text);
+  color: var(--jc-on-accent);
 }
 
 /* ==========================================================================
diff --git a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java
index e91fe11682..b48ee5a1f7 100644
--- a/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java
+++ b/juneau-rest/juneau-rest-server-console-ui/src/test/java/org/apache/juneau/rest/server/console/ConsoleChromeMixin_Test.java
@@ -546,12 +546,24 @@ class ConsoleChromeMixin_Test extends TestBase {
 		assertTrue(onAccentVsFace >= 3.0, () -> "--jc-on-accent/selected-face contrast " + onAccentVsFace + ":1 is below WCAG 1.4.11's 3:1 floor");
 
 		// Substring pin: both rewritten selected-state selectors consume var(--jc-accent-selected), with no
-		// var() fallback (requirement 3) - exactly two "background-color: var(--jc-accent-selected)" sinks.
+		// var() fallback (requirement 3) - exactly three "background-color: var(--jc-accent-selected)" sinks.
 		var css = readChromeCss();
 		assertTrue(css.contains(".jc-tab.jc-tab-active,\n.jc-subtab.jc-subtab-active {"), () -> "missing raised-specificity selected selector, css:\n" + css);
 		assertTrue(css.contains(".juneau-view-ribbon-group[data-juneau-strip-mode=\"tab\"] .juneau-view-ribbon-btn[aria-selected=\"true\"] {"), () -> "missing widget selected selector, css:\n" + css);
-		assertEquals(2, countOccurrences(css, "background-color: var(--jc-accent-selected)"),
-			() -> "expected exactly the two selected-face rules to consume --jc-accent-selected, css:\n" + css);
+		assertEquals(3, countOccurrences(css, "background-color: var(--jc-accent-selected)"),
+			() -> "expected exactly the three consumers of --jc-accent-selected: the two ribbon-format selected-state selectors plus .jc-nav-tab.active (added by J0484), css:\n" + css);
+	}
+
+	@Test void j05_navTabActive_consumesAccentSelectedToken_andNoLongerConsumesAccentWash() throws Exception {
+		var css = readChromeCss();
+		var navTabActiveStart = css.indexOf(".jc-nav-tab.active {");
+		assertTrue(navTabActiveStart != -1, () -> "missing .jc-nav-tab.active rule, css:\n" + css);
+		var navTabActiveEnd = css.indexOf("}", navTabActiveStart);
+		var navTabActiveBlock = css.substring(navTabActiveStart, navTabActiveEnd);
+		assertTrue(navTabActiveBlock.contains("background-color: var(--jc-accent-selected)"),
+			() -> "expected .jc-nav-tab.active to consume --jc-accent-selected, block:\n" + navTabActiveBlock);
+		assertFalse(navTabActiveBlock.contains("var(--jc-accent-wash)"),
+			() -> "expected .jc-nav-tab.active to no longer consume --jc-accent-wash, block:\n" + navTabActiveBlock);
 	}
 
 	/** WCAG 2.x contrast ratio between two {@code "#rrggbb"} literals: {@code (lighter+0.05)/(darker+0.05)}. */
diff --git a/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScaleScanner.java b/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScaleScanner.java
index d6fe4892a4..09afd9731a 100644
--- a/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScaleScanner.java
+++ b/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScaleScanner.java
@@ -157,14 +157,23 @@ final class ChromeScaleScanner {
 			"a third small-glyph consumer; enrolling it widens a named glyph role and is not this contract's call")
 	);
 
-	/** Properties a chrome surface sizes with, and the step families each may legitimately spend. */
+	/**
+	 * Properties a chrome surface sizes with, and the step families each may legitimately spend.
+	 *
+	 * <p>
+	 * {@code padding-left}/{@code padding-right} are the longhand-only reading of {@code CONTROL_PADDING_X}
+	 * (the {@code padding} shorthand is not parsed to isolate its horizontal component - a shorthand
+	 * declaration hiding the value is an accepted, stated limitation).
+	 */
 	private static final Map<String,Set<Family>> CHECKED_PROPERTIES = Map.of(
 		"height", Set.of(Family.CONTROL_HEIGHT, Family.SPACE, Family.GLYPH),
 		"min-height", Set.of(Family.CONTROL_HEIGHT, Family.SPACE),
 		"font-size", Set.of(Family.FONT_SIZE),
 		"gap", Set.of(Family.SPACE),
 		"line-height", Set.of(Family.LINE_HEIGHT),
-		"width", Set.of(Family.GLYPH)
+		"width", Set.of(Family.GLYPH),
+		"padding-left", Set.of(Family.CONTROL_PADDING_X),
+		"padding-right", Set.of(Family.CONTROL_PADDING_X)
 	);
 
 	/** {@code width} is only a scale property on an SVG glyph; elsewhere it is a content measure. */
@@ -179,6 +188,14 @@ final class ChromeScaleScanner {
 	/** Matches an absolute length/size token. */
 	private static final Pattern ABSOLUTE = Pattern.compile("(?<![\\w.-])(\\d*\\.?\\d+)(px|rem)(?![\\w-])");
 
+	/**
+	 * Matches a bare, unitless decimal. {@code line-height}'s scale step ({@code --jc-chrome-line-height},
+	 * {@code 1.2}) is the only step value in the ladder with no {@code px}/{@code rem} unit, so this pattern is
+	 * used only for the {@code line-height} property (see {@link #findDuplicatedStep}) - it must never widen
+	 * matching for any other property.
+	 */
+	private static final Pattern BARE_NUMBER = Pattern.compile("(?<![\\w.-])(\\d*\\.?\\d+)(?![\\w.%-])");
+
 	/** The steps this contract declares. */
 	static List<Step> scale() {
 		return SCALE;
@@ -278,15 +295,18 @@ final class ChromeScaleScanner {
 	 * are violations either way; naming the apter one is what makes the message actionable.
 	 */
 	private static Step findDuplicatedStep(Decl d, Set<Family> families, List<Step> scale) {
-		var literals = absoluteValues(d.value());
+		// line-height's step value is unitless, so it needs bare-number matching instead of the general
+		// px/rem literal matcher - scoped to this one property so no other property's matching widens (LD-2).
+		var lineHeight = "line-height".equals(d.property());
+		var literals = lineHeight ? bareNumberValues(d.value()) : absoluteValues(d.value());
 		if (literals.isEmpty())
 			return null;
 		for (var roleNamed : List.of(true, false)) {
 			for (var s : scale) {
 				if (!s.confirmed() || !families.contains(s.family()) || (s.family() == Family.SPACE) == roleNamed)
 					continue;
-				var stepPx = toPx(s.value());
-				if (stepPx != null && literals.contains(stepPx))
+				var stepValue = lineHeight ? bareValue(s.value()) : toPx(s.value());
+				if (stepValue != null && literals.contains(stepValue))
 					return s;
 			}
 		}
@@ -326,6 +346,24 @@ final class ChromeScaleScanner {
 		return m.find() ? px(Double.parseDouble(m.group(1)), m.group(2)) : null;
 	}
 
+	/**
+	 * Every bare unitless number in a declaration's value, {@code var(...)} fallbacks excluded. Only ever called
+	 * for the {@code line-height} property (see {@link #findDuplicatedStep}).
+	 */
+	private static Set<Double> bareNumberValues(String value) {
+		var out = new LinkedHashSet<Double>();
+		var m = BARE_NUMBER.matcher(VAR_REF.matcher(value).replaceAll(" "));
+		while (m.find())
+			out.add(Double.parseDouble(m.group(1)));
+		return out;
+	}
+
+	/** The step-value counterpart to {@link #bareNumberValues}: a step's own value read as a bare number. */
+	private static Double bareValue(String value) {
+		var m = BARE_NUMBER.matcher(value);
+		return m.find() ? Double.parseDouble(m.group(1)) : null;
+	}
+
 	/** Normalises to px. {@code rem} resolves at the browser default because nothing here sets a root font-size. */
 	private static double px(double n, String unit) {
 		return "rem".equals(unit) ? n * 16.0 : n;
diff --git a/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScale_ContractScan_Test.java b/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScale_ContractScan_Test.java
index 61376231f0..0553a3e91b 100644
--- a/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScale_ContractScan_Test.java
+++ b/juneau-rest/juneau-rest-server-views/src/test/java/org/apache/juneau/rest/server/views/ChromeScale_ContractScan_Test.java
@@ -152,4 +152,29 @@ class ChromeScale_ContractScan_Test extends TestBase {
 		assertTrue(ChromeScaleScanner.scan(".synthetic-cell { width: 16px; }").violations().isEmpty());
 		assertEquals(1, ChromeScaleScanner.scan(".synthetic-btn svg { width: 16px; }").violations().size());
 	}
+
+	/**
+	 * {@code CONTROL_PADDING_X} was structurally unscored before this fix - no property mapped onto it in
+	 * {@code CHECKED_PROPERTIES}, so a duplicated value could never be caught regardless of a step's
+	 * {@code confirmed} flag. Exercises both newly-wired longhand properties.
+	 */
+	@Test void b10_controlPaddingXCatchesADuplicatedValue() {
+		var r = ChromeScaleScanner.scan(".synthetic-control { padding-left: 10px; padding-right: 14px; }");
+		assertEquals(2, r.violations().size(), () -> "expected both padding-left and padding-right to be caught: "
+			+ r.violations());
+		assertTrue(r.violations().get(0).contains("--jc-chrome-control-padding-x (10px)"), r.violations().get(0));
+		assertTrue(r.violations().get(1).contains("--jc-chrome-control-padding-x-wide (14px)"), r.violations().get(1));
+	}
+
+	/**
+	 * {@code LINE_HEIGHT}'s only step value is unitless, so the general px/rem literal matcher could never bind
+	 * to it before this fix - {@code line-height} was already mapped in {@code CHECKED_PROPERTIES}, but nothing
+	 * could ever match. Proves the widened, {@code line-height}-scoped value-matching now catches it.
+	 */
+	@Test void b11_lineHeightCatchesADuplicatedValue() {
+		var r = ChromeScaleScanner.scan(".synthetic-control { line-height: 1.2; }");
+		assertEquals(1, r.violations().size(), () -> "expected the duplicated line-height value to be caught: "
+			+ r.violations());
+		assertTrue(r.violations().get(0).contains("--jc-chrome-line-height (1.2)"), r.violations().get(0));
+	}
 }