tools+test: extend theme-scan.sh and update notification + brand copy tests
This commit is contained in:
parent
f927097d67
commit
7a9b9eaa9d
|
|
@ -41,7 +41,10 @@ class PartnerNotificationManagerTest {
|
||||||
partnerAnsweredEnabled = true
|
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
|
every { rateLimiter.canSend(any()) } returns true
|
||||||
|
|
||||||
mockkStatic(NotificationManagerCompat::from)
|
mockkStatic(NotificationManagerCompat::from)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ class CloserBrandCopyTest {
|
||||||
|
|
||||||
assertTrue(messages.isNotEmpty())
|
assertTrue(messages.isNotEmpty())
|
||||||
assertEquals(messages.size, messages.distinct().size)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,15 @@
|
||||||
# the project root, and write findings to stdout + SCAN_OUTPUT. Update this header
|
# the project root, and write findings to stdout + SCAN_OUTPUT. Update this header
|
||||||
# with any new patterns or exclusions you add.
|
# 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:
|
# Usage:
|
||||||
# ./scripts/theme-scan.sh > /tmp/claude-theme-scan-$(date +%Y%m%d).md
|
# ./scripts/theme-scan.sh > /tmp/claude-theme-scan-$(date +%Y%m%d).md
|
||||||
# ./scripts/theme-scan.sh --json # (future improvement — machine-readable)
|
# ./scripts/theme-scan.sh --json # (future improvement — machine-readable)
|
||||||
|
|
@ -28,6 +37,32 @@ log() {
|
||||||
echo "$1" | tee -a "$SCAN_OUTPUT"
|
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 "# CloserApp Theme-Mismatch Scan — $(date)"
|
||||||
log "Project: $PROJECT_ROOT"
|
log "Project: $PROJECT_ROOT"
|
||||||
log ""
|
log ""
|
||||||
|
|
@ -50,6 +85,7 @@ scan_after_container() {
|
||||||
local file=$(echo "$container" | cut -d: -f1)
|
local file=$(echo "$container" | cut -d: -f1)
|
||||||
local lineno=$(echo "$container" | cut -d: -f2)
|
local lineno=$(echo "$container" | cut -d: -f2)
|
||||||
if [[ ! -f "$file" ]]; then continue; fi
|
if [[ ! -f "$file" ]]; then continue; fi
|
||||||
|
if in_preview "$file" "$lineno"; then continue; fi
|
||||||
local segment=$(sed -n "${lineno},$((lineno+7))p" "$file")
|
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)
|
local match=$(echo "$segment" | grep -E '^\s*(color|containerColor|background)\s*=\s*Color(\.|\()' | grep -ivE "$exclude" | head -1 || true)
|
||||||
if [[ -n "$match" ]]; then
|
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 -rnE 'Modifier\.background\(Color(\.|\()' "$UI_DIR" --include="*.kt" \
|
||||||
| grep -ivE 'Color\.Transparent' \
|
| grep -ivE 'Color\.Transparent' \
|
||||||
| grep -ivE 'Theme\.kt' \
|
| grep -ivE 'Theme\.kt' \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🔴 CRITICAL $line"
|
log "🔴 CRITICAL $line"
|
||||||
done || true
|
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 -rnE '(buttonColors|TextFieldDefaults\.colors|TabRowDefaults\.colors|SwitchDefaults\.colors)\s*\(' "$UI_DIR" --include="*.kt" \
|
||||||
| grep -iE 'Color(\.|\()' \
|
| grep -iE 'Color(\.|\()' \
|
||||||
| grep -ivE 'Color\.Transparent' \
|
| grep -ivE 'Color\.Transparent' \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🟠 MAJOR $line"
|
log "🟠 MAJOR $line"
|
||||||
done || true
|
done || true
|
||||||
|
|
||||||
grep -rnE '(Divider|HorizontalDivider)\s*\(' "$UI_DIR" --include="*.kt" \
|
grep -rnE '(Divider|HorizontalDivider)\s*\(' "$UI_DIR" --include="*.kt" \
|
||||||
| grep -iE 'color = Color(\.|\()' \
|
| grep -iE 'color = Color(\.|\()' \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🟠 MAJOR $line"
|
log "🟠 MAJOR $line"
|
||||||
done || true
|
done || true
|
||||||
|
|
@ -93,6 +132,7 @@ log ""
|
||||||
log "## Tier 1D — Text/Icon color hardcoded on themed surfaces (REVIEW)"
|
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 -rnE '^\s*color = Color\.(White|Black|(0x[0-9A-F]{8}))' "$UI_DIR" --include="*.kt" \
|
||||||
| grep -ivE 'Theme\.kt' \
|
| grep -ivE 'Theme\.kt' \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🟡 REVIEW $line"
|
log "🟡 REVIEW $line"
|
||||||
done || true
|
done || true
|
||||||
|
|
@ -100,6 +140,7 @@ grep -rnE '^\s*color = Color\.(White|Black|(0x[0-9A-F]{8}))' "$UI_DIR" --include
|
||||||
log ""
|
log ""
|
||||||
log "## Tier 1E — Direct painterResource that bypasses BrandIllustration (MAJOR)"
|
log "## Tier 1E — Direct painterResource that bypasses BrandIllustration (MAJOR)"
|
||||||
grep -rnE 'painterResource\(R\.drawable\.(illustration_|pack_art_)' "$UI_DIR" --include="*.kt" \
|
grep -rnE 'painterResource\(R\.drawable\.(illustration_|pack_art_)' "$UI_DIR" --include="*.kt" \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🟠 MAJOR $line"
|
log "🟠 MAJOR $line"
|
||||||
done || true
|
done || true
|
||||||
|
|
@ -108,6 +149,7 @@ log ""
|
||||||
log "## Tier 1F — Hardcoded border colors (REVIEW)"
|
log "## Tier 1F — Hardcoded border colors (REVIEW)"
|
||||||
grep -rnE 'Modifier\.border\([^)]*Color(\.|\()' "$UI_DIR" --include="*.kt" \
|
grep -rnE 'Modifier\.border\([^)]*Color(\.|\()' "$UI_DIR" --include="*.kt" \
|
||||||
| grep -ivE 'Color\.Transparent' \
|
| grep -ivE 'Color\.Transparent' \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🟡 REVIEW $line"
|
log "🟡 REVIEW $line"
|
||||||
done || true
|
done || true
|
||||||
|
|
@ -116,6 +158,7 @@ log ""
|
||||||
log "## Tier 1G — Hardcoded Brush/gradient stops (REVIEW)"
|
log "## Tier 1G — Hardcoded Brush/gradient stops (REVIEW)"
|
||||||
grep -rnE 'Brush\.(linear|vertical|horizontal|radial)Gradient' "$UI_DIR" --include="*.kt" \
|
grep -rnE 'Brush\.(linear|vertical|horizontal|radial)Gradient' "$UI_DIR" --include="*.kt" \
|
||||||
| grep -vE 'BrandIllustration\.kt' \
|
| grep -vE 'BrandIllustration\.kt' \
|
||||||
|
| drop_previews \
|
||||||
| while IFS= read -r line; do
|
| while IFS= read -r line; do
|
||||||
log "🟡 REVIEW $line"
|
log "🟡 REVIEW $line"
|
||||||
done || true
|
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 "- 🔴 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 "- 🟠 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 "- 🟡 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 "---"
|
log "---"
|
||||||
log "End of scan. Next step: file all CRITICAL and any MAJOR that break themes to ClaudeReport.md, update the Pass C"
|
log "End of scan. Next step: file all CRITICAL and any MAJOR that break themes to ClaudeReport.md, update the Pass C"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue