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
7 changes: 4 additions & 3 deletions app/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const TIERS = [[6, 0], [8, 5], [10, 10], [Infinity, 15]];
const TIER_LABELS = { 0: "0% share", 5: "5% share", 10: "10% share", 15: "15% share" };
const TIER_VARS = { 0: "--tier-0", 5: "--tier-5", 10: "--tier-10", 15: "--tier-15" };
const DRAWS = 4000;
const ASSET_V = "20260809a"; // bump with index.html's app.js?v= on every deploy that changes any asset
const SCEN_SCHEMA = "snap_qc_sim.model_scenarios.v1";

const fmtM = (v) => {
Expand Down Expand Up @@ -329,8 +330,8 @@ async function loadModelArtifacts() {
if (!ARTIFACTS) {
ARTIFACTS = (async () => {
const [mdRes, scRes] = await Promise.all([
fetch("model_data.json"),
fetch("model_scenarios.json"),
fetch("model_data.json?v=" + ASSET_V),
fetch("model_scenarios.json?v=" + ASSET_V),
]);
if (!mdRes.ok || !scRes.ok) throw new Error("model artifact fetch failed");
const mdBytes = await mdRes.arrayBuffer();
Expand Down Expand Up @@ -620,7 +621,7 @@ function render() {
}

async function main() {
DATA = await (await fetch("data.json")).json();
DATA = await (await fetch("data.json?v=" + ASSET_V)).json();
const sel = $("state");
for (const [code, st] of Object.entries(DATA.states)) {
const o = document.createElement("option");
Expand Down
2 changes: 1 addition & 1 deletion app/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,6 @@ <h2>All states at a glance</h2>
Built by <a href="https://policyengine.org">PolicyEngine</a> · <a href="/paper/">working paper</a> · estimates are illustrative, from public data · <a href="mailto:hello@policyengine.org">hello@policyengine.org</a>
</div></footer>

<script type="module" src="app.js"></script>
<script type="module" src="app.js?v=20260809a"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions tests/test_asset_versioning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""The deploy cache-buster: index.html's script version must match app.js's."""

import re
from pathlib import Path

PUBLIC = Path(__file__).resolve().parent.parent / "app" / "public"


def test_index_script_version_matches_app_constant():
html = (PUBLIC / "index.html").read_text()
js = (PUBLIC / "app.js").read_text()
html_v = re.search(r'src="app\.js\?v=([A-Za-z0-9._-]+)"', html)
js_v = re.search(r'const ASSET_V = "([A-Za-z0-9._-]+)"', js)
assert html_v, "index.html must load app.js with a ?v= cache-buster"
assert js_v, "app.js must define ASSET_V"
assert html_v.group(1) == js_v.group(1)


def test_all_data_fetches_are_versioned():
js = (PUBLIC / "app.js").read_text()
for name in ("data.json", "model_data.json", "model_scenarios.json"):
assert f'fetch("{name}?v=" + ASSET_V)' in js, f"{name} fetch must carry ASSET_V"
assert f'fetch("{name}")' not in js, f"unversioned {name} fetch remains"
Loading