128 lines
6.1 KiB
Python
128 lines
6.1 KiB
Python
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def test_daily_sales_canvas_is_primary_and_fallback_is_opt_in():
|
|
css = (ROOT / "web/static/css/page-daily-sales.css").read_text(encoding="utf-8")
|
|
script = (ROOT / "web/static/js/page-daily-sales.js").read_text(encoding="utf-8")
|
|
|
|
assert ".chart-container.has-html-chart canvas" not in css
|
|
assert ".chart-container.chart-fallback-active canvas" in css
|
|
assert ".chart-container.chart-empty-active canvas" in css
|
|
assert ".chart-container:not(.chart-fallback-active) .chart-fallback-list" in css
|
|
assert "node.content && node.content.textContent" in script
|
|
assert "chart-empty-active" in script
|
|
assert "Math.abs(Number(value)) > 1e-9" in script
|
|
render_body = script.split("function renderAllCharts()", 1)[1].split("function bootCharts()", 1)[0]
|
|
assert "renderHtmlChartFallbacks();" not in render_body
|
|
assert "catch(error =>" in script
|
|
assert "renderHtmlChartFallbacks();" in script.split("catch(error =>", 1)[1]
|
|
|
|
|
|
def test_growth_analysis_canvas_is_primary_and_fallback_is_opt_in():
|
|
css = (ROOT / "web/static/css/page-growth-bem.css").read_text(encoding="utf-8")
|
|
script = (ROOT / "web/static/js/page-growth.js").read_text(encoding="utf-8")
|
|
|
|
assert ".ga-chart-card__body.has-html-chart canvas" not in css
|
|
assert ".ga-chart-card__body.chart-fallback-active canvas" in css
|
|
assert ".ga-chart-card__body.chart-empty-active canvas" in css
|
|
assert ".ga-chart-card__body:not(.chart-fallback-active) .ga-chart-snapshot" in css
|
|
assert "node.content && node.content.textContent" in script
|
|
assert "chart-empty-active" in script
|
|
assert "Math.abs(Number(value)) > 1e-9" in script
|
|
render_body = script.split("function renderCharts()", 1)[1].split("function bootCharts()", 1)[0]
|
|
assert "renderHtmlChartFallbacks();" not in render_body
|
|
assert "catch(error =>" in script
|
|
assert "renderHtmlChartFallbacks();" in script.split("catch(error =>", 1)[1]
|
|
|
|
|
|
def test_chart_theme_has_cdn_timeout_and_fallback_sources():
|
|
script = (ROOT / "web/static/js" / "analysis-chart-theme.js").read_text(encoding="utf-8")
|
|
|
|
assert "CHART_LOAD_TIMEOUT_MS" in script
|
|
assert "Chart.js 載入逾時" in script
|
|
assert "cdn.jsdelivr.net/npm/chart.js@4.4.6" in script
|
|
assert "unpkg.com/chart.js@4.4.6" in script
|
|
assert "cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.6" in script
|
|
assert "!Array.isArray(ds.backgroundColor)" in script
|
|
assert "!Array.isArray(ds.borderColor)" in script
|
|
assert script.index("...originalScale") < script.index("border: { color: theme.faint")
|
|
assert "callback: v => formatNumber(v)" not in script
|
|
assert "autoSkip: true" in script
|
|
|
|
|
|
def test_sales_chart_runtime_guard_rejects_axis_only_canvases():
|
|
script = (ROOT / "scripts" / "check_sales_charts_runtime.js").read_text(encoding="utf-8")
|
|
|
|
assert "nonZeroPoints" in script
|
|
assert "colorPixels" in script
|
|
assert "has no colored chart marks" in script
|
|
assert "has no non-zero dataset values" in script
|
|
|
|
|
|
def test_all_analysis_pages_have_runtime_chart_integrity_contracts():
|
|
script = (ROOT / "scripts" / "check_analysis_chart_integrity.js").read_text(encoding="utf-8")
|
|
|
|
for route in (
|
|
"/daily_sales?month=2026-04",
|
|
"/growth_analysis?start_month=2026-04&end_month=2026-04",
|
|
"/sales_analysis?start_date=2026-04-01&end_date=2026-04-30",
|
|
"/monthly_summary_analysis?start_month=2026-04&end_month=2026-04",
|
|
):
|
|
assert route in script
|
|
for viewport in ("1440, height: 1000", "1024, height: 900", "390, height: 844"):
|
|
assert viewport in script
|
|
assert "loading overlay remains visible" in script
|
|
assert "has no colored chart marks" in script
|
|
assert "extreme percent values are not bounded with traceable markers" in script
|
|
assert "single point is not visibly emphasized" in script
|
|
assert "hides drawable payload behind an empty state" in script
|
|
assert "analysis tab lost the active period" in script
|
|
assert "rendered numeric indexes instead of product labels" in script
|
|
assert "contains labels outside the selected April period" in script
|
|
assert "explicitEmpty" in script
|
|
|
|
|
|
def test_analysis_chart_failure_states_are_explicit_and_bounded():
|
|
sales = (ROOT / "web/static/js/page-sales-analysis.js").read_text(encoding="utf-8")
|
|
daily = (ROOT / "web/static/js/page-daily-sales.js").read_text(encoding="utf-8")
|
|
growth = (ROOT / "web/static/js/page-growth.js").read_text(encoding="utf-8")
|
|
monthly = (ROOT / "web/static/js/page-monthly-summary.js").read_text(encoding="utf-8")
|
|
|
|
assert "document.documentElement.dataset.salesCharts = 'ready'" in sales
|
|
assert "series.chart_values" in sales
|
|
assert "renderChartEmpty(bcgEl" in sales
|
|
assert "renderChartEmpty(hmEl" in sales
|
|
assert "originalValue" in daily
|
|
assert "clippedCount" in daily
|
|
assert "function dateAxis(element)" in daily
|
|
assert "overlapping date ticks" in (ROOT / "scripts/check_analysis_chart_integrity.js").read_text(encoding="utf-8")
|
|
assert "beginAtZero: true" in growth
|
|
assert "pointRadiusFor" in growth
|
|
assert "REQUEST_TIMEOUT_MS" in monthly
|
|
assert "activeFetchController.abort()" in monthly
|
|
assert "const refreshForViewport = () => {" in monthly
|
|
assert "const refreshForViewport = () => fetchData()" not in monthly
|
|
assert "dataset.monthlyCharts = 'ready'" in monthly
|
|
assert "safeHtmlText(p.value[3])" in monthly
|
|
assert "safeHtmlText(ps[0].name)" in monthly
|
|
|
|
|
|
def test_analysis_tables_use_local_traditional_chinese_language_contract():
|
|
helper = (ROOT / "web/static/js/datatables-zh-hant.js").read_text(encoding="utf-8")
|
|
assert "window.EwoooCDataTableLanguage" in helper
|
|
assert "沒有符合條件的資料" in helper
|
|
|
|
assets = [
|
|
ROOT / "web/static/js/page-daily-sales.js",
|
|
ROOT / "web/static/js/page-sales-analysis.js",
|
|
ROOT / "web/static/js/page-monthly-summary.js",
|
|
ROOT / "templates/abc_analysis_detail.html",
|
|
]
|
|
for asset in assets:
|
|
source = asset.read_text(encoding="utf-8")
|
|
assert "cdn.datatables.net/plug-ins" not in source
|
|
assert "EwoooCDataTableLanguage()" in source
|