fix(r3): fold heid's code review — equal stage widths, the axis guard, players, and tests that read the observable
The one drift: the separator was a border on B, making B's stage 1px narrower than A's; it is now a 1px column gap, so the stages are the same size to the pixel. Tests now read what the contract promises instead of a proxy: the strip's ring order, the full bakeoff sequence, 1:1 and Fit by geometry, the 900px break from both sides, A wrapping, each form naming its own item, a sibling-prefix symlink, both reveals, the strip's flag, the back arrow unlinked, a one-axis picture, a focused player, two videos with no toggle. The contract names .cmp-cap, a press on a stage, INV-4's URL-driven picker and the redirect branch's isinstance check. r3.toml gains ten rows.
This commit is contained in:
+95
-2
@@ -182,9 +182,9 @@ label = "C4 a caption takes its height from one stage only (Fit draws the two at
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_compare_browser.py::test_two_stages_side_by_side_wide_and_stacked_narrow"
|
||||
old = '''
|
||||
.cmp-side{display:grid;grid-row:1 / -1;grid-template-rows:subgrid;min-width:0;min-height:0}'''
|
||||
.cmp-side{display:grid;grid-row:1 / -1;grid-template-rows:subgrid;'''
|
||||
new = '''
|
||||
.cmp-side{display:grid;grid-row:1 / -1;grid-template-rows:auto minmax(0,1fr) auto;min-width:0;min-height:0}'''
|
||||
.cmp-side{display:grid;grid-row:1 / -1;grid-template-rows:auto minmax(0,1fr) auto;'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C4 stacked, a stage keeps the review's 60vh"
|
||||
@@ -418,6 +418,99 @@ old = '''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
# ---- the heid code-review fold (01M3AJXX8RH4QZPY5D1DQWQQGQ)
|
||||
|
||||
[[mutation]]
|
||||
label = "C4 the separator is a border on B (B's stage 1px narrower than A's)"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_compare_browser.py::test_two_stages_side_by_side_wide_and_stacked_narrow"
|
||||
old = '''
|
||||
background:var(--surface-sunken)}'''
|
||||
new = '''
|
||||
background:var(--surface-sunken)}
|
||||
.cmp-side+.cmp-side{border-left:1px solid var(--border-subtle)}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C4 the stack break drifts to 1000px"
|
||||
file = "booth/templates/base.html"
|
||||
test = "tests/test_compare_browser.py::test_two_stages_side_by_side_wide_and_stacked_narrow"
|
||||
old = '''
|
||||
@media (max-width:900px){
|
||||
.viewer.review.compare{display:block}'''
|
||||
new = '''
|
||||
@media (max-width:1000px){
|
||||
.viewer.review.compare{display:block}'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C4 a side with nothing to scroll on an axis does not ignore it (0/0 resets the other)"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_compare_browser.py::test_an_axis_with_nothing_to_scroll_is_ignored"
|
||||
old = '''
|
||||
if (fy > 0 && ty > 0) t = from.scrollTop / fy * ty;'''
|
||||
new = '''
|
||||
if (ty > 0) t = from.scrollTop / fy * ty;'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C4 two videos get a Fit | 1:1 toggle"
|
||||
file = "booth/app.py"
|
||||
test = "tests/test_compare.py::test_a_video_or_track_plays_in_its_own_stage_and_two_get_no_toggle"
|
||||
old = '''
|
||||
"any_image": any(by_rel[r].kind == "image" for r in (a, b)),'''
|
||||
new = '''
|
||||
"any_image": True,'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C3 Space on a focused player steps the pair"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_compare_browser.py::test_the_keys_keep_the_reviews_guards_and_c_toggles_the_view"
|
||||
old = '''e.target.closest('button, a, summary, video, audio')'''
|
||||
new = '''e.target.closest('button, a, summary')'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C3 there is no back arrow"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_compare_browser.py::test_unlinked_moves_only_the_active_side_and_the_strip_picks_it"
|
||||
old = '''
|
||||
else if (k === 'ArrowLeft') step(-1);'''
|
||||
new = '''
|
||||
'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C1 containment is a bare prefix (a sibling booth sharing the name opens)"
|
||||
file = "booth/app.py"
|
||||
test = "tests/test_compare.py::test_an_outside_symlink_in_the_ring_is_404"
|
||||
old = '''
|
||||
if not str(target).startswith(str(booth) + os.sep) or not target.is_file():
|
||||
raise HTTPException(status_code=404, detail="no such item")'''
|
||||
new = '''
|
||||
if not str(target).startswith(str(booth)) or not target.is_file():
|
||||
raise HTTPException(status_code=404, detail="no such item")'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C1 the strip is not in ring order"
|
||||
file = "booth/app.py"
|
||||
test = "tests/test_compare.py::test_compare_renders_the_pair"
|
||||
old = '''
|
||||
"pick_a": url(r, b), "pick_b": url(a, r)} for r in ring]'''
|
||||
new = '''
|
||||
"pick_a": url(r, b), "pick_b": url(a, r)} for r in ring[::-1]]'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C6 each reveal lifts B's blur whichever side it sits on"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_compare_browser.py::test_blur_is_honest_on_both_sides"
|
||||
old = '''
|
||||
var stage = sides[btn.getAttribute('data-side')].querySelector('.vstage');'''
|
||||
new = '''
|
||||
var stage = sides.b.querySelector('.vstage');'''
|
||||
|
||||
[[mutation]]
|
||||
label = "C5 the strip does not show a flag made in place"
|
||||
file = "booth/templates/compare.html"
|
||||
test = "tests/test_compare_browser.py::test_a_flags_A_in_place_and_the_stages_survive"
|
||||
old = '''<a class="film-f{% if x.flagged %} is-flagged{% endif %}'''
|
||||
new = '''<a class="film-f'''
|
||||
|
||||
# Accepted, not rowed: the mode's `onChange: settleAll` (re-deciding which stage
|
||||
# can pan) is redundant with compare's ResizeObserver — 1:1 drops the stage's
|
||||
# padding, so every mode change resizes the stage's content box and the
|
||||
|
||||
+33
-3
@@ -61,6 +61,11 @@ def test_compare_renders_the_pair(tmp_path):
|
||||
assert "p.png" in label_a and "#1" in label_a, label_a
|
||||
assert "r.png" in label_b and "#3" in label_b, label_b
|
||||
assert _frames(body) == {"p.png": "A", "q.png": "", "r.png": "B", "s.png": ""}
|
||||
assert list(_frames(body)) == ["p.png", "q.png", "r.png", "s.png"], "the strip is in RING order"
|
||||
# each side's flag form names its OWN item
|
||||
for key, rel in (("a", "p.png"), ("b", "r.png")):
|
||||
form = re.search(r'data-region="flag-%s".*?</form>' % key, body, re.S).group(0)
|
||||
assert f'name="target" value="{rel}"' in form, (key, form)
|
||||
|
||||
|
||||
def _four(root: pathlib.Path) -> pathlib.Path:
|
||||
@@ -103,9 +108,16 @@ def test_an_outside_symlink_in_the_ring_is_404(tmp_path):
|
||||
outside.write_bytes(PNG)
|
||||
(b / "zz-link.png").symlink_to(outside)
|
||||
assert "zz-link.png" in review_chain(booth_items(b)), "the fixture must put it in the ring"
|
||||
# a SIBLING whose name shares the booth's prefix is outside too (the
|
||||
# containment check compares with the separator, never a bare prefix)
|
||||
sib = tmp_path / "g-extra"
|
||||
sib.mkdir()
|
||||
(sib / "x.png").write_bytes(PNG)
|
||||
(b / "zz-sib.png").symlink_to(sib / "x.png")
|
||||
c = _client(tmp_path)
|
||||
assert c.get("/b/g/compare?a=zz-link.png&b=p.png").status_code == 404
|
||||
assert c.get("/b/g/compare?a=p.png&b=zz-link.png").status_code == 404
|
||||
for rel in ("zz-link.png", "zz-sib.png"):
|
||||
assert c.get(f"/b/g/compare?a={rel}&b=p.png").status_code == 404, rel
|
||||
assert c.get(f"/b/g/compare?a=p.png&b={rel}").status_code == 404, rel
|
||||
|
||||
|
||||
def test_a_look_records_both_seen(tmp_path):
|
||||
@@ -144,6 +156,18 @@ def test_no_data_region_repeats(tmp_path):
|
||||
assert _frames(body)["q.png"] == "AB", "a == b marks the one frame both ways"
|
||||
|
||||
|
||||
def test_a_video_or_track_plays_in_its_own_stage_and_two_get_no_toggle(tmp_path):
|
||||
"""C4: video and audio play in their own stage; the Fit | 1:1 toggle is
|
||||
bound only when a side is a picture, so two videos get none."""
|
||||
_booth(tmp_path, "g", {"a.webm": b"\x1aE\xdf\xa3", "b.mp3": b"ID3", "c.png": PNG})
|
||||
c = _client(tmp_path)
|
||||
body = c.get("/b/g/compare?a=a.webm&b=b.mp3").text
|
||||
assert re.search(r'<video class="cmp-media"[^>]*src="a.webm"', body)
|
||||
assert re.search(r'<audio class="cmp-media"[^>]*src="b.mp3"', body)
|
||||
assert 'id="vtoggle"' not in body
|
||||
assert 'id="vtoggle"' in c.get("/b/g/compare?a=a.webm&b=c.png").text
|
||||
|
||||
|
||||
# ---- C3: stepping --------------------------------------------------------------
|
||||
|
||||
def _ring6(root: pathlib.Path) -> pathlib.Path:
|
||||
@@ -170,6 +194,8 @@ def test_linked_steps_keep_the_distance_and_wrap(tmp_path):
|
||||
assert _step(body, "both-next") == ("04.png", "07.png")
|
||||
body = c.get("/b/g/compare?a=04.png&b=07.png").text
|
||||
assert _step(body, "both-next") == ("05.png", "01.png")
|
||||
body = c.get("/b/g/compare?a=07.png&b=02.png").text
|
||||
assert _step(body, "both-next") == ("01.png", "04.png"), "A wraps as B does"
|
||||
body = c.get("/b/g/compare?a=01.png&b=05.png").text
|
||||
assert _step(body, "both-prev") == ("07.png", "04.png")
|
||||
# each side on its own moves only itself, and wraps the same way
|
||||
@@ -214,6 +240,8 @@ def test_view_state_rides_the_links(tmp_path):
|
||||
frame = re.search(r'<a class="film-f[^"]*"\s+href="([^"]+)" data-rel="q.png"', body).group(1)
|
||||
assert parse_qs(urlsplit(frame.replace("&", "&")).query) == {
|
||||
"a": ["q.png"], "b": ["r.png"], "side": ["a"], "link": ["0"]}
|
||||
links = _compare_links(c.get("/b/g/compare?a=p.png&b=r.png&link=0").text)
|
||||
assert links and all(h.endswith("&link=0") and "side=" not in h for h in links), links
|
||||
for odd in ("side=z&link=maybe", "side=A&link=00", "side=&link="):
|
||||
r = c.get(f"/b/g/compare?a=p.png&b=r.png&{odd}")
|
||||
assert r.status_code == 200, odd
|
||||
@@ -227,7 +255,9 @@ def test_view_state_rides_the_links(tmp_path):
|
||||
|
||||
def _flag(c: TestClient, form: dict, accept: str | None = None):
|
||||
headers = {"accept": accept} if accept else {}
|
||||
return c.post("/b/g/flag", data={"target": "q.png", "on": "1", **form}, headers=headers)
|
||||
r = c.post("/b/g/flag", data={"target": "q.png", "on": "1", **form}, headers=headers)
|
||||
assert r.status_code == (204 if accept else 303), (form, r.status_code)
|
||||
return r
|
||||
|
||||
|
||||
def test_a_flag_without_js_lands_on_the_same_pair(tmp_path):
|
||||
|
||||
@@ -39,15 +39,17 @@ _BOXES = """() => [...document.querySelectorAll('.cmp-side .vstage')].map(s => {
|
||||
|
||||
|
||||
def test_two_stages_side_by_side_wide_and_stacked_narrow(browser, live):
|
||||
"""The tracer. At 1440 A and B share one row, A on the left, each about
|
||||
half the body. At 390 they stack, A above B, each at most 45vh tall."""
|
||||
"""The tracer. Above 900px A and B share one row, A on the left, each
|
||||
exactly half the body — the SAME width, so equal pictures have equal
|
||||
ranges. At 900px and below (the review's break) they stack, A above B,
|
||||
each at most 45vh tall."""
|
||||
base, root = live
|
||||
from booth.app import set_blurred
|
||||
b = _pics(root, {"a.png": (800, 600), "b.png": (800, 600)})
|
||||
(b / "a.png.txt").write_text("a caption on A only, " * 8)
|
||||
set_blurred(b, "b.png", True) # the bar carries Reveal all too: its fullest
|
||||
got = {}
|
||||
for w, h in ((1440, 900), (390, 844)):
|
||||
for w, h in ((1440, 900), (901, 800), (900, 800), (390, 844)):
|
||||
page = browser.new_page(viewport={"width": w, "height": h})
|
||||
_open(page, f"{base}/b/g/compare?a=a.png&b=b.png")
|
||||
got[w] = (page.evaluate(_BOXES), h,
|
||||
@@ -67,7 +69,13 @@ def test_two_stages_side_by_side_wide_and_stacked_narrow(browser, live):
|
||||
# the SAME stage for both, or Fit draws one smaller: A's caption must not
|
||||
# take its height from A's stage alone
|
||||
assert abs(a["h"] - b["h"]) <= 1 and abs(a["b"] - b["b"]) <= 1, (a, b)
|
||||
# ...and the same width: a separator must not come out of one side alone
|
||||
assert abs((a["r"] - a["l"]) - (b["r"] - b["l"])) <= 0.5, (a, b)
|
||||
assert over <= 0 and tallest[0] <= 40 and tallest[1] <= 1, (over, tallest)
|
||||
(a, b), _, _, _ = got[901]
|
||||
assert abs(a["t"] - b["t"]) <= 1 and a["r"] <= b["l"], ("side by side at 901", a, b)
|
||||
(a, b), _, _, _ = got[900]
|
||||
assert a["b"] <= b["t"], ("stacked at 900", a, b)
|
||||
(a, b), vh, over, tallest = got[390]
|
||||
assert a["b"] <= b["t"], ("A above B", a, b)
|
||||
assert a["h"] <= 0.45 * vh + 1 and b["h"] <= 0.45 * vh + 1, (a, b)
|
||||
@@ -101,7 +109,10 @@ def test_one_mode_for_both_and_for_the_review(browser, live):
|
||||
stored = page.evaluate("localStorage.getItem('booth.fit')")
|
||||
pressed = page.locator("#btn-one").get_attribute("aria-pressed")
|
||||
page.goto(f"{base}/b/g/view?f=a.png", wait_until="networkidle")
|
||||
review_one = page.evaluate("document.documentElement.classList.contains('stage-one')")
|
||||
page.wait_for_function("document.getElementById('vimg').complete && document.getElementById('vimg').naturalWidth > 0")
|
||||
review_one = page.evaluate("""() => { const i = document.getElementById('vimg'), b = i.getBoundingClientRect();
|
||||
return document.documentElement.classList.contains('stage-one') &&
|
||||
Math.round(b.width) === i.naturalWidth && Math.round(b.height) === i.naturalHeight; }""")
|
||||
page.goto(f"{base}/b/g/compare?a=a.png&b=b.png", wait_until="networkidle")
|
||||
page.keyboard.press("Z")
|
||||
back = page.evaluate("[document.documentElement.classList.contains('stage-one'), localStorage.getItem('booth.fit')]")
|
||||
@@ -202,6 +213,28 @@ def test_synced_pan_by_fraction_for_different_sizes(browser, live):
|
||||
assert quarter[1] == [x, y] and idle == quarter, (x, y, quarter, idle)
|
||||
|
||||
|
||||
def test_an_axis_with_nothing_to_scroll_is_ignored(browser, live):
|
||||
"""A side with nothing to scroll on an axis ignores that axis, each axis
|
||||
on its own. A is wide and short (it scrolls across only), B is large. B
|
||||
scrolled down stays down when A pans across: B's x follows, B's y is B's."""
|
||||
base, root = live
|
||||
_pics(root, {"a.png": (3000, 200), "b.png": (3000, 3000)})
|
||||
page = browser.new_page(viewport={"width": 1440, "height": 900})
|
||||
_one_to_one(page, f"{base}/b/g/compare?a=a.png&b=b.png")
|
||||
(rax, ray), (rbx, rby) = page.evaluate(_RANGES)
|
||||
page.evaluate("document.querySelector('.cmp-side[data-side=\"b\"] .vstage').scrollTo(0, 500)")
|
||||
page.wait_for_timeout(250)
|
||||
down = page.evaluate(_SCROLLS)
|
||||
page.evaluate("document.querySelector('.cmp-side[data-side=\"a\"] .vstage').scrollTo(800, 0)")
|
||||
page.wait_for_timeout(250)
|
||||
across = page.evaluate(_SCROLLS)
|
||||
page.close()
|
||||
assert ray == 0 and rax > 0 and rby > 0, (rax, ray, rbx, rby)
|
||||
assert down == [[0, 0], [0, 500]], down
|
||||
assert across[0] == [800, 0], across
|
||||
assert abs(across[1][0] - 800 / rax * rbx) <= 1 and across[1][1] == 500, across
|
||||
|
||||
|
||||
def test_a_flags_A_in_place_and_the_stages_survive(browser, live):
|
||||
"""`A` flags A in place: no navigation, A's control and label show the
|
||||
flag, B's do not, and both stages are the SAME nodes — a save swaps the
|
||||
@@ -221,6 +254,7 @@ def test_a_flags_A_in_place_and_the_stages_survive(browser, live):
|
||||
a: document.querySelector('[data-region="label-a"]').textContent,
|
||||
b: document.querySelector('[data-region="label-b"]').textContent,
|
||||
bflag: document.getElementById('cmp-flag-b').classList.contains('is-flagged'),
|
||||
strip: [...document.querySelectorAll('.film-f.is-flagged')].map(f => f.dataset.rel),
|
||||
})""")
|
||||
flagged = [m.target for m in marks_for(b) if m.shape == "flag"]
|
||||
# the save REPLACED the buttons: each key must find the fresh one
|
||||
@@ -233,6 +267,7 @@ def test_a_flags_A_in_place_and_the_stages_survive(browser, live):
|
||||
page.close()
|
||||
assert not got["reload"] and got["same"], got
|
||||
assert "flagged" in got["a"] and "flagged" not in got["b"] and not got["bflag"], got
|
||||
assert got["strip"] == ["a.png"], got
|
||||
assert flagged == ["a.png"] and after == ["b.png"] and not reloaded, (flagged, after, reloaded)
|
||||
|
||||
|
||||
@@ -267,9 +302,9 @@ def test_linked_arrow_walks_a_bakeoff(browser, live):
|
||||
_press_and_wait(page, "ArrowRight")
|
||||
pairs.append(_pair(page))
|
||||
page.close()
|
||||
assert [(a[2:], b[2:]) for a, b in pairs] == [(a[2:], a[2:]) for a, _ in pairs], pairs
|
||||
assert [a[0] + b[0] for a, b in pairs] == ["mr"] * 4, pairs
|
||||
assert pairs[-1] == ("m-4-tower-s42.png", "r-4-tower-s42.png"), pairs
|
||||
# every pair, in order: no pair skipped and none repeated
|
||||
scenes = ("dock-s11", "forge-s23", "marsh-s37", "tower-s42")
|
||||
assert pairs == [(f"m-{i}-{sc}.png", f"r-{i}-{sc}.png") for i, sc in enumerate(scenes, 1)], pairs
|
||||
|
||||
|
||||
def test_unlinked_moves_only_the_active_side_and_the_strip_picks_it(browser, live):
|
||||
@@ -286,6 +321,9 @@ def test_unlinked_moves_only_the_active_side_and_the_strip_picks_it(browser, liv
|
||||
one = _pair(page)
|
||||
_press_and_wait(page, "ArrowRight")
|
||||
two = _pair(page)
|
||||
_press_and_wait(page, "ArrowLeft")
|
||||
left = _pair(page)
|
||||
_press_and_wait(page, "ArrowRight")
|
||||
with page.expect_navigation(wait_until="networkidle"):
|
||||
page.locator('.film-f[data-rel="m-3-marsh-s37.png"]').click()
|
||||
picked = _pair(page)
|
||||
@@ -301,6 +339,7 @@ def test_unlinked_moves_only_the_active_side_and_the_strip_picks_it(browser, liv
|
||||
assert linked == "false", linked
|
||||
assert one == ("m-1-dock-s11.png", "r-2-forge-s23.png"), one
|
||||
assert two == ("m-1-dock-s11.png", "r-3-marsh-s37.png"), two
|
||||
assert left == one, ("← moves only the active side back", left)
|
||||
assert picked == ("m-1-dock-s11.png", "m-3-marsh-s37.png"), picked
|
||||
assert active == ["a", ["m-1-dock-s11.png"]], active
|
||||
assert stepped == ("m-2-forge-s23.png", "m-3-marsh-s37.png"), stepped
|
||||
@@ -328,6 +367,10 @@ def test_blur_is_honest_on_both_sides(browser, live):
|
||||
page.wait_for_timeout(400) # the filter transition
|
||||
own = page.evaluate(_FILTERS)
|
||||
page.locator('.cmp-reveal[data-side="b"]').click()
|
||||
page.locator('.cmp-reveal[data-side="a"]').click()
|
||||
page.wait_for_timeout(400)
|
||||
own_a = page.evaluate(_FILTERS)
|
||||
page.locator('.cmp-reveal[data-side="a"]').click()
|
||||
page.locator("[data-reveal-all]").click()
|
||||
page.wait_for_timeout(400)
|
||||
everything = page.evaluate(_FILTERS)
|
||||
@@ -336,6 +379,7 @@ def test_blur_is_honest_on_both_sides(browser, live):
|
||||
assert all("blur(" in f for f in before), before
|
||||
assert shown == [True, True], shown
|
||||
assert "blur(" in own[0] and "blur(" not in own[1], own
|
||||
assert "blur(" not in own_a[0] and "blur(" in own_a[1], own_a
|
||||
assert all("blur(" not in f for f in everything), everything
|
||||
assert stood_down == [False, False], stood_down
|
||||
|
||||
@@ -385,7 +429,9 @@ def test_without_js_every_judgment_and_step_still_works(browser, live):
|
||||
ctx = browser.new_context(java_script_enabled=False, viewport={"width": 1440, "height": 900})
|
||||
page = ctx.new_page()
|
||||
page.goto(f"{base}/b/g/compare?a=m-1-dock-s11.png&b=r-1-dock-s11.png", wait_until="networkidle")
|
||||
fit = page.evaluate("document.documentElement.classList.contains('stage-one')")
|
||||
fit = page.evaluate("""() => document.documentElement.classList.contains('stage-one') ||
|
||||
[...document.querySelectorAll('.cmp-side .vstage img')].some(i =>
|
||||
Math.round(i.getBoundingClientRect().width) === i.naturalWidth)""")
|
||||
hidden = [page.locator(s).is_visible() for s in ("#cmp-link", "#vtoggle")]
|
||||
forms = page.locator(".cmp-flag form").count()
|
||||
page.locator('a[data-step="both-next"]').click()
|
||||
@@ -417,6 +463,8 @@ def test_the_keys_keep_the_reviews_guards_and_c_toggles_the_view(browser, live):
|
||||
item; `Esc` (or `C`) in compare returns to the review of A."""
|
||||
base, root = live
|
||||
_bakeoff(root)
|
||||
v = _pics(root, {"a.png": (400, 300)}, name="v")
|
||||
(v / "b.webm").write_bytes(b"\x1aE\xdf\xa3")
|
||||
page = browser.new_page(viewport={"width": 1440, "height": 900})
|
||||
_open(page, f"{base}/b/g/compare?a=m-2-forge-s23.png&b=r-2-forge-s23.png")
|
||||
start = page.url
|
||||
@@ -432,6 +480,13 @@ def test_the_keys_keep_the_reviews_guards_and_c_toggles_the_view(browser, live):
|
||||
pressed = (page.url.split("?")[1], page.locator("#cmp-link").get_attribute("aria-pressed"))
|
||||
page.locator("#cmp-link").press(" ") # linked again
|
||||
page.evaluate("document.activeElement.blur()")
|
||||
# ...and never from a player on either stage: Space is the player's
|
||||
page.goto(f"{base}/b/v/compare?a=a.png&b=b.webm", wait_until="networkidle")
|
||||
page.locator(".cmp-media").focus()
|
||||
page.keyboard.press(" ")
|
||||
page.wait_for_timeout(300)
|
||||
player = page.url.endswith("/b/v/compare?a=a.png&b=b.webm")
|
||||
page.goto(start, wait_until="networkidle")
|
||||
_press_and_wait(page, " ")
|
||||
fwd = _pair(page)
|
||||
_press_and_wait(page, "Shift+ ")
|
||||
@@ -444,6 +499,7 @@ def test_the_keys_keep_the_reviews_guards_and_c_toggles_the_view(browser, live):
|
||||
c_back = page.url
|
||||
page.close()
|
||||
assert inert == (True, "b", "true"), inert
|
||||
assert player, "Space on a focused player stepped the pair"
|
||||
assert pressed == ("a=m-2-forge-s23.png&b=r-2-forge-s23.png&link=0", "false"), pressed
|
||||
assert fwd == ("m-3-marsh-s37.png", "r-3-marsh-s37.png") and back == ("m-2-forge-s23.png", "r-2-forge-s23.png"), (fwd, back)
|
||||
assert esc.endswith("/b/g/view?f=m-2-forge-s23.png"), esc
|
||||
|
||||
Reference in New Issue
Block a user