Compare commits
6
Commits
c14bf98414
...
a7f7df7714
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f7df7714 | ||
|
|
285bb25c06 | ||
|
|
338f023c0d | ||
|
|
8db0efff29 | ||
|
|
fa8751a4ca | ||
|
|
682be0861f |
@@ -57,8 +57,10 @@
|
||||
}
|
||||
|
||||
.auth-field-check input {
|
||||
width: auto;
|
||||
margin-top: 0.2rem;
|
||||
flex: none;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.auth-field-check a {
|
||||
|
||||
@@ -190,6 +190,14 @@
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.post-action-btn.vote-up::before {
|
||||
content: "+";
|
||||
}
|
||||
|
||||
.post-action-btn.vote-down::before {
|
||||
content: "\2212";
|
||||
}
|
||||
|
||||
.post-action-btn.vote-up.voted {
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
@@ -200,6 +208,14 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.post-action-btn.vote-up.voted::before {
|
||||
content: "\2295";
|
||||
}
|
||||
|
||||
.post-action-btn.vote-down.voted::before {
|
||||
content: "\2296";
|
||||
}
|
||||
|
||||
.post-votes {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -138,6 +138,14 @@
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.comment-vote-btn.vote-up::before {
|
||||
content: "+";
|
||||
}
|
||||
|
||||
.comment-vote-btn.vote-down::before {
|
||||
content: "-";
|
||||
}
|
||||
|
||||
.comment-vote-btn.vote-up.voted {
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
@@ -148,6 +156,14 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.comment-vote-btn.vote-up.voted::before {
|
||||
content: "\2295";
|
||||
}
|
||||
|
||||
.comment-vote-btn.vote-down.voted::before {
|
||||
content: "\2296";
|
||||
}
|
||||
|
||||
.comment-vote-count {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -10,6 +10,7 @@ export class DeviiTerminal {
|
||||
this.avatar = null;
|
||||
this.bindEscapeTrigger();
|
||||
this.bindDoubleClickTrigger();
|
||||
this.bindOpenTrigger();
|
||||
this.ready = this.init();
|
||||
}
|
||||
|
||||
@@ -29,16 +30,19 @@ export class DeviiTerminal {
|
||||
this.element.setAttribute("avatar", "devii");
|
||||
document.body.appendChild(this.element);
|
||||
|
||||
this.bindTriggers();
|
||||
this.maybeAutoOpen();
|
||||
}
|
||||
|
||||
bindTriggers() {
|
||||
document.querySelectorAll("[data-devii-open]").forEach((trigger) => {
|
||||
trigger.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
this.open(trigger.dataset.deviiPrompt || "");
|
||||
});
|
||||
bindOpenTrigger() {
|
||||
document.addEventListener("click", (event) => {
|
||||
const trigger = event.target.closest("[data-devii-open]");
|
||||
if (!trigger) return;
|
||||
event.preventDefault();
|
||||
this.open(trigger.dataset.deviiPrompt || "");
|
||||
});
|
||||
}
|
||||
|
||||
maybeAutoOpen() {
|
||||
const stored = this.element && this.element.hasStoredState && this.element.hasStoredState();
|
||||
if (document.querySelector("[data-devii-autoopen]") && !stored) {
|
||||
this.open();
|
||||
|
||||
@@ -57,7 +57,9 @@ export class VoteManager extends OptimisticAction {
|
||||
});
|
||||
document.querySelectorAll(`form[action="${action}"] button[type="submit"]`).forEach((button) => {
|
||||
const formValue = parseInt(button.closest("form").querySelector('input[name="value"]').value, 10);
|
||||
button.classList.toggle("voted", result.value !== 0 && formValue === result.value);
|
||||
const isVoted = result.value !== 0 && formValue === result.value;
|
||||
button.classList.toggle("voted", isVoted);
|
||||
button.setAttribute("aria-pressed", String(isVoted));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ Do NOT hand-write the overlay/header markup. Use the shared macro in `templates/
|
||||
Reuse these via `{% set _x = ... %}{% include %}` (the `_avatar_link.html` convention) instead of copy-pasting markup:
|
||||
|
||||
- `_post_composer_form.html` - the create-post form (topic selector, content/title, project select, attachments, poll builder, footer). Locals: `_composer_topic` (preselected topic, default `random`), `_composer_project` (preselected project uid or `""`). Wrapped in the `modal()` macro by `feed.html` (Create New Post) and `project_detail.html` (owner-only Post an update, preset to `devlog` + the project). Never fork a second copy of this form.
|
||||
- `_post_votes.html` - post +/- vote bar. Locals: `_uid`, `_my_vote`, `_count`.
|
||||
- `_post_votes.html` - post +/- vote bar. Locals: `_uid`, `_my_vote`, `_count`. The +/- glyphs come from the vote CSS classes via `::before` (`feed.css` for `.post-action-btn`, `post.css` for `.comment-vote-btn`), swapping to `\u2295`/`\u2296` when `.voted` - do not put literal glyphs in markup.
|
||||
- `_star_vote.html` - project/gist star button. Locals: `_type` (`project`|`gist`), `_uid`, `_my_vote`, `_count`, `_btn_class`, optional `_stop` (adds `data-stop-propagation`). The star glyph (`☆`→`★` when `.voted`) comes from the `vote-star` CSS class via `::before` (`base.css`) - do not put a literal star in markup.
|
||||
- `_post_header.html` - post author/avatar/time header (`.post-header`). Locals: `_author`, `_time`.
|
||||
- `_topic_selector.html` - topic radio group. Locals: `_topics`, `_selected`.
|
||||
@@ -89,3 +89,4 @@ The three public listings (`/feed`, `/gists`, `/projects`) share one search box:
|
||||
## Modal pattern
|
||||
|
||||
`Application.js` `initModals()` toggles a `.visible` CSS class on `.modal-overlay`; the CSS rule `.modal-overlay.visible { display: flex; }` handles visibility. Triggers usually have `href="#"`, so call `e.preventDefault()` in click handlers. `.modal-close` is wired generically - no inline JS needed.
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<div class="comment-votes" role="group" aria-label="Comment votes">
|
||||
<form method="POST" action="/votes/comment/{{ item.comment['uid'] }}">
|
||||
<input type="hidden" name="value" value="1">
|
||||
<button type="submit" class="comment-vote-btn vote-up{% if item.my_vote == 1 %} voted{% endif %}" aria-label="Upvote" title="Upvote" aria-pressed="{% if item.my_vote == 1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}>+</button>
|
||||
<button type="submit" class="comment-vote-btn vote-up{% if item.my_vote == 1 %} voted{% endif %}" aria-label="Upvote" title="Upvote" aria-pressed="{% if item.my_vote == 1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}></button>
|
||||
</form>
|
||||
<span class="comment-vote-count" data-vote-count="{{ item.comment['uid'] }}">{{ item.votes.up - item.votes.down }}</span>
|
||||
<form method="POST" action="/votes/comment/{{ item.comment['uid'] }}">
|
||||
<input type="hidden" name="value" value="-1">
|
||||
<button type="submit" class="comment-vote-btn vote-down{% if item.my_vote == -1 %} voted{% endif %}" aria-label="Downvote" title="Downvote" aria-pressed="{% if item.my_vote == -1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}>-</button>
|
||||
<button type="submit" class="comment-vote-btn vote-down{% if item.my_vote == -1 %} voted{% endif %}" aria-label="Downvote" title="Downvote" aria-pressed="{% if item.my_vote == -1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -70,3 +70,4 @@
|
||||
{{ render_comment_flat(child, depth, item.author) }}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<div class="post-votes" role="group" aria-label="Post votes">
|
||||
<form method="POST" action="/votes/post/{{ _uid }}" class="inline-form">
|
||||
<input type="hidden" name="value" value="-1">
|
||||
<button type="submit" class="post-action-btn vote-down{% if _my_vote == -1 %} voted{% endif %}" aria-label="Downvote" title="Downvote" aria-pressed="{% if _my_vote == -1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}>−</button>
|
||||
<button type="submit" class="post-action-btn vote-down{% if _my_vote == -1 %} voted{% endif %}" aria-label="Downvote" title="Downvote" aria-pressed="{% if _my_vote == -1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}></button>
|
||||
</form>
|
||||
<span class="post-vote-count" data-vote-count="{{ _uid }}">{{ _count }}</span>
|
||||
<form method="POST" action="/votes/post/{{ _uid }}" class="inline-form">
|
||||
<input type="hidden" name="value" value="1">
|
||||
<button type="submit" class="post-action-btn vote-up{% if _my_vote == 1 %} voted{% endif %}" aria-label="Upvote" title="Upvote" aria-pressed="{% if _my_vote == 1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}>+</button>
|
||||
<button type="submit" class="post-action-btn vote-up{% if _my_vote == 1 %} voted{% endif %}" aria-label="Upvote" title="Upvote" aria-pressed="{% if _my_vote == 1 %}true{% else %}false{% endif %}"{{ guest_disabled(user) }}></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<div class="auth-field auth-field-check">
|
||||
<label for="accept_terms">
|
||||
<input type="checkbox" id="accept_terms" name="accept_terms" value="1" required aria-required="true"{% if accept_terms %} checked{% endif %}>
|
||||
I accept the <a href="/docs/terms.html" target="_blank" rel="noopener">Terms of Service</a> and the <a href="/docs/community-guidelines.html" target="_blank" rel="noopener">Community Guidelines</a>, and I have read the <a href="/docs/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.
|
||||
<span>I accept the <a href="/docs/terms.html" target="_blank" rel="noopener">Terms of Service</a> and the <a href="/docs/community-guidelines.html" target="_blank" rel="noopener">Community Guidelines</a>, and I have read the <a href="/docs/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -125,6 +125,13 @@ def _port_in_use(port):
|
||||
return sock.connect_ex(("127.0.0.1", port)) == 0
|
||||
|
||||
|
||||
# Set once app_server has successfully started, so pytest_runtest_setup can
|
||||
# detect a mid-session crash (e.g. resource exhaustion on a constrained CI
|
||||
# runner) and fail every subsequent HTTP-dependent test with one clear
|
||||
# diagnostic instead of a wall of opaque connection-refused errors.
|
||||
_APP_SERVER_STATE = {"proc": None, "log_path": None}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_server(test_db_path):
|
||||
if _port_in_use(PORT):
|
||||
@@ -192,7 +199,11 @@ def app_server(test_db_path):
|
||||
_set_setting("rate_limit_per_minute", "1000000")
|
||||
_set_setting("rate_limit_window_seconds", "60")
|
||||
|
||||
_APP_SERVER_STATE["proc"] = proc
|
||||
_APP_SERVER_STATE["log_path"] = log_file.name
|
||||
|
||||
yield proc
|
||||
_APP_SERVER_STATE["proc"] = None
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=10)
|
||||
@@ -207,6 +218,27 @@ def app_server(test_db_path):
|
||||
pass
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
proc = _APP_SERVER_STATE["proc"]
|
||||
if proc is None or "tests/unit/" in item.nodeid:
|
||||
return
|
||||
exit_code = proc.poll()
|
||||
if exit_code is None:
|
||||
return
|
||||
log_path = _APP_SERVER_STATE["log_path"]
|
||||
try:
|
||||
with open(log_path, "r", errors="replace") as f:
|
||||
log_tail = f.read()[-5000:]
|
||||
except OSError:
|
||||
log_tail = "(log unavailable)"
|
||||
pytest.fail(
|
||||
f"app_server died mid-session (exit code {exit_code}). It was healthy "
|
||||
f"earlier in this run and has since crashed - this is a shared-server "
|
||||
f"failure, not a bug in {item.nodeid}. Server log tail:\n{log_tail}",
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def playwright_instance():
|
||||
from playwright.sync_api import sync_playwright
|
||||
@@ -290,6 +322,14 @@ def assert_share_copies(page, expected_fragment):
|
||||
)
|
||||
|
||||
|
||||
def vote_glyph(locator):
|
||||
value = locator.evaluate("el => getComputedStyle(el, '::before').content")
|
||||
value = value.strip().strip('"').strip("'")
|
||||
if value.startswith("\\") and len(value) == 5 and value[1:].isdigit():
|
||||
value = chr(int(value[1:], 16))
|
||||
return value
|
||||
|
||||
|
||||
def assert_no_horizontal_overflow(page, layout):
|
||||
report = page.evaluate(
|
||||
"""(selector) => {
|
||||
|
||||
@@ -128,7 +128,8 @@ def test_steal_takes_build_and_pays_thief(bob):
|
||||
steal.click()
|
||||
confirm_btn = page.locator(".dialog-confirm")
|
||||
confirm_btn.wait_for(state="visible")
|
||||
confirm_btn.click()
|
||||
with page.expect_response(lambda r: "/steal" in r.url and r.request.method == "POST"):
|
||||
confirm_btn.click()
|
||||
expect(page.locator("form[data-game-action='steal']")).to_have_count(0)
|
||||
page.wait_for_timeout(400)
|
||||
assert _farm_coins("bob_test") == 18
|
||||
@@ -165,7 +166,8 @@ def test_victim_notification_names_the_raider_and_the_amount(bob):
|
||||
steal.click()
|
||||
confirm_btn = page.locator(".dialog-confirm")
|
||||
confirm_btn.wait_for(state="visible")
|
||||
confirm_btn.click()
|
||||
with page.expect_response(lambda r: "/steal" in r.url and r.request.method == "POST"):
|
||||
confirm_btn.click()
|
||||
expect(page.locator("form[data-game-action='steal']")).to_have_count(0)
|
||||
page.wait_for_timeout(400)
|
||||
note = get_table("notifications").find_one(
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_leaderboard_ranks_after_upvote(app_server, browser, seeded_db):
|
||||
|
||||
pa.goto(post_url, wait_until="domcontentloaded")
|
||||
pa.wait_for_timeout(1000)
|
||||
vote_btn = pa.locator("button.post-action-btn").filter(has_text="+").first
|
||||
vote_btn = pa.locator("button.post-action-btn.vote-up").first
|
||||
vote_btn.wait_for(state="visible", timeout=10000)
|
||||
vote_btn.click()
|
||||
pa.wait_for_timeout(1500)
|
||||
|
||||
@@ -310,7 +310,7 @@ def test_vote_notification_on_post(app_server, browser, seeded_db):
|
||||
|
||||
pa.goto(post_url, wait_until="domcontentloaded")
|
||||
pa.wait_for_timeout(1000)
|
||||
vote_btn = pa.locator("button.post-action-btn").filter(has_text="+").first
|
||||
vote_btn = pa.locator("button.post-action-btn.vote-up").first
|
||||
vote_btn.wait_for(state="visible", timeout=10000)
|
||||
vote_btn.click()
|
||||
pa.wait_for_timeout(1500)
|
||||
@@ -796,7 +796,7 @@ def test_vote_notification_click_opens_target(app_server, browser, seeded_db):
|
||||
|
||||
pa.goto(f"{BASE_URL}{post_path}", wait_until="domcontentloaded")
|
||||
pa.wait_for_timeout(1000)
|
||||
vote_btn = pa.locator("button.post-action-btn").filter(has_text="+").first
|
||||
vote_btn = pa.locator("button.post-action-btn.vote-up").first
|
||||
vote_btn.wait_for(state="visible", timeout=10000)
|
||||
vote_btn.click()
|
||||
pa.wait_for_timeout(1500)
|
||||
|
||||
+55
-22
@@ -2,7 +2,7 @@
|
||||
|
||||
import re
|
||||
from playwright.sync_api import expect
|
||||
from tests.conftest import BASE_URL, assert_share_copies
|
||||
from tests.conftest import BASE_URL, assert_share_copies, vote_glyph
|
||||
def create_post(page, topic="random", content="Test post content", title=None):
|
||||
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
|
||||
page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
|
||||
@@ -74,35 +74,57 @@ def test_post_vote_increment(alice):
|
||||
def test_post_upvote_voted_state_persists(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "showcase", "Upvote persistence test")
|
||||
vote_up = page.locator(".post-action-btn.vote-up").first
|
||||
assert vote_glyph(vote_up) == "+"
|
||||
with page.expect_response(
|
||||
lambda r: "/votes/" in r.url and r.request.method == "POST"
|
||||
):
|
||||
page.locator(".post-action-btn.vote-up").first.click()
|
||||
vote_up.click()
|
||||
expect(page.locator(".post-vote-count").first).to_have_text("1")
|
||||
expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_up) == "\u2295"
|
||||
page.reload(wait_until="domcontentloaded")
|
||||
expect(page.locator(".post-action-btn.vote-up").first).to_have_class(
|
||||
re.compile(r"\bvoted\b")
|
||||
)
|
||||
assert "voted" not in (
|
||||
page.locator(".post-action-btn.vote-down").first.get_attribute("class") or ""
|
||||
)
|
||||
vote_up = page.locator(".post-action-btn.vote-up").first
|
||||
expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_up) == "\u2295"
|
||||
vote_down = page.locator(".post-action-btn.vote-down").first
|
||||
assert "voted" not in (vote_down.get_attribute("class") or "")
|
||||
assert vote_glyph(vote_down) == "\u2212"
|
||||
with page.expect_response(
|
||||
lambda r: "/votes/" in r.url and r.request.method == "POST"
|
||||
):
|
||||
vote_up.click()
|
||||
expect(page.locator(".post-vote-count").first).to_have_text("0")
|
||||
expect(vote_up).not_to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_up) == "+"
|
||||
|
||||
|
||||
def test_post_downvote_voted_state_persists(alice):
|
||||
page, _ = alice
|
||||
create_post(page, "showcase", "Downvote persistence test")
|
||||
vote_down = page.locator(".post-action-btn.vote-down").first
|
||||
assert vote_glyph(vote_down) == "\u2212"
|
||||
with page.expect_response(
|
||||
lambda r: "/votes/" in r.url and r.request.method == "POST"
|
||||
):
|
||||
page.locator(".post-action-btn.vote-down").first.click()
|
||||
vote_down.click()
|
||||
expect(page.locator(".post-vote-count").first).to_have_text("-1")
|
||||
expect(vote_down).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_down) == "\u2296"
|
||||
page.reload(wait_until="domcontentloaded")
|
||||
expect(page.locator(".post-action-btn.vote-down").first).to_have_class(
|
||||
re.compile(r"\bvoted\b")
|
||||
)
|
||||
assert "voted" not in (
|
||||
page.locator(".post-action-btn.vote-up").first.get_attribute("class") or ""
|
||||
)
|
||||
vote_down = page.locator(".post-action-btn.vote-down").first
|
||||
expect(vote_down).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_down) == "\u2296"
|
||||
vote_up = page.locator(".post-action-btn.vote-up").first
|
||||
assert "voted" not in (vote_up.get_attribute("class") or "")
|
||||
assert vote_glyph(vote_up) == "+"
|
||||
with page.expect_response(
|
||||
lambda r: "/votes/" in r.url and r.request.method == "POST"
|
||||
):
|
||||
vote_down.click()
|
||||
expect(page.locator(".post-vote-count").first).to_have_text("0")
|
||||
expect(vote_down).not_to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_down) == "\u2212"
|
||||
|
||||
|
||||
def test_comment_voted_state_persists(alice):
|
||||
@@ -114,14 +136,25 @@ def test_comment_voted_state_persists(alice):
|
||||
expect(
|
||||
page.locator(".comment-text:has-text('Comment whose vote should persist')")
|
||||
).to_be_visible()
|
||||
page.locator(".comment-vote-btn").first.click()
|
||||
expect(page.locator(".comment-vote-btn").first).to_have_class(
|
||||
re.compile(r"\bvoted\b")
|
||||
)
|
||||
comment_vote = page.locator(".comment-vote-btn.vote-up").first
|
||||
assert vote_glyph(comment_vote) == "+"
|
||||
with page.expect_response(
|
||||
lambda r: "/votes/" in r.url and r.request.method == "POST"
|
||||
):
|
||||
comment_vote.click()
|
||||
expect(comment_vote).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(comment_vote) == "\u2295"
|
||||
page.reload(wait_until="domcontentloaded")
|
||||
expect(page.locator(".comment-vote-btn").first).to_have_class(
|
||||
re.compile(r"\bvoted\b")
|
||||
)
|
||||
comment_vote = page.locator(".comment-vote-btn.vote-up").first
|
||||
expect(comment_vote).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(comment_vote) == "\u2295"
|
||||
with page.expect_response(
|
||||
lambda r: "/votes/" in r.url and r.request.method == "POST"
|
||||
):
|
||||
comment_vote.click()
|
||||
expect(page.locator(".comment-vote-count").first).to_have_text("0")
|
||||
expect(comment_vote).not_to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(comment_vote) == "+"
|
||||
|
||||
|
||||
def test_mention_first_match_highlighted(alice):
|
||||
|
||||
@@ -14,7 +14,7 @@ def _create_plain_post_engagement_ui(page, content):
|
||||
page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded")
|
||||
from uuid import uuid4
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from tests.conftest import BASE_URL, assert_share_copies
|
||||
from tests.conftest import BASE_URL, assert_share_copies, vote_glyph
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.utils import make_combined_slug
|
||||
def _seed_posts(count):
|
||||
@@ -404,15 +404,19 @@ def test_feed_vote_voted_state_persists(alice):
|
||||
card = page.locator(
|
||||
".post-card", has_text="Feed vote persistence test content"
|
||||
).first
|
||||
card.locator(".post-action-btn.vote-up").click()
|
||||
vote_up = card.locator(".post-action-btn.vote-up")
|
||||
assert vote_glyph(vote_up) == "+"
|
||||
vote_up.click()
|
||||
expect(card.locator(".post-vote-count")).to_have_text("1")
|
||||
expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_up) == "\u2295"
|
||||
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
|
||||
card = page.locator(
|
||||
".post-card", has_text="Feed vote persistence test content"
|
||||
).first
|
||||
expect(card.locator(".post-action-btn.vote-up")).to_have_class(
|
||||
re.compile(r"\bvoted\b")
|
||||
)
|
||||
vote_up = card.locator(".post-action-btn.vote-up")
|
||||
expect(vote_up).to_have_class(re.compile(r"\bvoted\b"))
|
||||
assert vote_glyph(vote_up) == "\u2295"
|
||||
|
||||
|
||||
def test_feed_card_share_button(alice):
|
||||
|
||||
Reference in New Issue
Block a user