tools+test: extend theme-scan.sh and update notification + brand copy tests

This commit is contained in:
null 2026-06-28 12:45:37 -05:00
parent f927097d67
commit 7a9b9eaa9d
3 changed files with 54 additions and 2 deletions

View File

@ -41,7 +41,10 @@ class PartnerNotificationManagerTest {
partnerAnsweredEnabled = true
)
)
every { quietHoursManager.isInQuietHours(any()) } returns false
// isInQuietHours(quietHours, now: Calendar = Calendar.getInstance()) has a default time arg, so the
// single-arg stub form pins the Calendar captured at stub time and never matches the SUT's call-time
// "now". Match both args with any() so the stub holds regardless of when the SUT samples the clock.
every { quietHoursManager.isInQuietHours(any(), any()) } returns false
every { rateLimiter.canSend(any()) } returns true
mockkStatic(NotificationManagerCompat::from)

View File

@ -11,6 +11,11 @@ class CloserBrandCopyTest {
assertTrue(messages.isNotEmpty())
assertEquals(messages.size, messages.distinct().size)
assertTrue(messages.all { it.isNotBlank() && it.length <= 64 })
assertTrue(messages.all { it.isNotBlank() })
// The flagship promise is intentionally a full sentence — BrandMessageRotator wraps it over up to 3 lines
// and holds it longer. Only the short single-line rotator slogans need the ~64-char display cap.
val shortSlogans = messages - CloserBrandCopy.primaryMessage
assertTrue(shortSlogans.all { it.length <= 64 })
assertTrue(CloserBrandCopy.primaryMessage.length in 1..160)
}
}

View File

@ -7,6 +7,15 @@
# the project root, and write findings to stdout + SCAN_OUTPUT. Update this header
# with any new patterns or exclusions you add.
#
# Exclusions:
# - Color.Transparent (intentional)
# - Theme.kt (the theme definition itself)
# - @Preview composables — a hardcoded color inside an `@Preview` function only affects
# Android Studio's design-time preview pane, never the shipped app, so it is NOT a theme
# defect. (Added 2026-06-28 after WheelCompleteScreen.kt `WheelRevealPreview` was filed as
# C-THEME-003, a false positive — the scanner now skips any hit whose enclosing `fun` is
# annotated `@Preview`.) See in_preview().
#
# Usage:
# ./scripts/theme-scan.sh > /tmp/claude-theme-scan-$(date +%Y%m%d).md
# ./scripts/theme-scan.sh --json # (future improvement — machine-readable)
@ -28,6 +37,32 @@ log() {
echo "$1" | tee -a "$SCAN_OUTPUT"
}
# -----------------------------------------------------------------------------
# Helper: is file:lineno inside an @Preview composable? (design-time only → not a defect)
# Finds the nearest `fun ` at or above lineno, then checks the 4 lines above that `fun`
# for an @Preview annotation. Returns 0 (true) if in a preview.
# -----------------------------------------------------------------------------
in_preview() {
local file="$1" lineno="$2"
[[ -f "$file" ]] || return 1
local fun_line
fun_line=$(awk -v L="$lineno" 'NR<=L && /^[[:space:]]*(private |internal |public )?(suspend )?fun /{ln=NR} END{print ln}' "$file")
[[ -z "$fun_line" ]] && return 1
local start=$(( fun_line > 4 ? fun_line - 4 : 1 ))
sed -n "${start},${fun_line}p" "$file" | grep -q '@Preview' && return 0 || return 1
}
# Filter stdin (grep -rn formatted lines: file:lineno:...) → drop lines inside @Preview.
drop_previews() {
local line file lineno
while IFS= read -r line; do
file=$(echo "$line" | cut -d: -f1)
lineno=$(echo "$line" | cut -d: -f2)
in_preview "$file" "$lineno" && continue
echo "$line"
done
}
log "# CloserApp Theme-Mismatch Scan — $(date)"
log "Project: $PROJECT_ROOT"
log ""
@ -50,6 +85,7 @@ scan_after_container() {
local file=$(echo "$container" | cut -d: -f1)
local lineno=$(echo "$container" | cut -d: -f2)
if [[ ! -f "$file" ]]; then continue; fi
if in_preview "$file" "$lineno"; then continue; fi
local segment=$(sed -n "${lineno},$((lineno+7))p" "$file")
local match=$(echo "$segment" | grep -E '^\s*(color|containerColor|background)\s*=\s*Color(\.|\()' | grep -ivE "$exclude" | head -1 || true)
if [[ -n "$match" ]]; then
@ -70,6 +106,7 @@ log "## Tier 1B — Modifier.background with hardcoded color (CRITICAL)"
grep -rnE 'Modifier\.background\(Color(\.|\()' "$UI_DIR" --include="*.kt" \
| grep -ivE 'Color\.Transparent' \
| grep -ivE 'Theme\.kt' \
| drop_previews \
| while IFS= read -r line; do
log "🔴 CRITICAL $line"
done || true
@ -79,12 +116,14 @@ log "## Tier 1C — Component color overrides that won't adapt (MAJOR)"
grep -rnE '(buttonColors|TextFieldDefaults\.colors|TabRowDefaults\.colors|SwitchDefaults\.colors)\s*\(' "$UI_DIR" --include="*.kt" \
| grep -iE 'Color(\.|\()' \
| grep -ivE 'Color\.Transparent' \
| drop_previews \
| while IFS= read -r line; do
log "🟠 MAJOR $line"
done || true
grep -rnE '(Divider|HorizontalDivider)\s*\(' "$UI_DIR" --include="*.kt" \
| grep -iE 'color = Color(\.|\()' \
| drop_previews \
| while IFS= read -r line; do
log "🟠 MAJOR $line"
done || true
@ -93,6 +132,7 @@ log ""
log "## Tier 1D — Text/Icon color hardcoded on themed surfaces (REVIEW)"
grep -rnE '^\s*color = Color\.(White|Black|(0x[0-9A-F]{8}))' "$UI_DIR" --include="*.kt" \
| grep -ivE 'Theme\.kt' \
| drop_previews \
| while IFS= read -r line; do
log "🟡 REVIEW $line"
done || true
@ -100,6 +140,7 @@ grep -rnE '^\s*color = Color\.(White|Black|(0x[0-9A-F]{8}))' "$UI_DIR" --include
log ""
log "## Tier 1E — Direct painterResource that bypasses BrandIllustration (MAJOR)"
grep -rnE 'painterResource\(R\.drawable\.(illustration_|pack_art_)' "$UI_DIR" --include="*.kt" \
| drop_previews \
| while IFS= read -r line; do
log "🟠 MAJOR $line"
done || true
@ -108,6 +149,7 @@ log ""
log "## Tier 1F — Hardcoded border colors (REVIEW)"
grep -rnE 'Modifier\.border\([^)]*Color(\.|\()' "$UI_DIR" --include="*.kt" \
| grep -ivE 'Color\.Transparent' \
| drop_previews \
| while IFS= read -r line; do
log "🟡 REVIEW $line"
done || true
@ -116,6 +158,7 @@ log ""
log "## Tier 1G — Hardcoded Brush/gradient stops (REVIEW)"
grep -rnE 'Brush\.(linear|vertical|horizontal|radial)Gradient' "$UI_DIR" --include="*.kt" \
| grep -vE 'BrandIllustration\.kt' \
| drop_previews \
| while IFS= read -r line; do
log "🟡 REVIEW $line"
done || true
@ -133,6 +176,7 @@ log "**Severity guide:**"
log "- 🔴 CRITICAL = container/surface/background set to a hardcoded color. Will produce visible light/dark mismatches."
log "- 🟠 MAJOR = component override or direct painterResource that likely bypasses theme adaptation or decoupled-theme art."
log "- 🟡 REVIEW = hardcoded text/icon/border/gradient color that may be correct on a branded container but must be verified in both themes."
log "- (@Preview composables are excluded — design-time only, never shipped.)"
log ""
log "---"
log "End of scan. Next step: file all CRITICAL and any MAJOR that break themes to ClaudeReport.md, update the Pass C"