import re from uuid import uuid4 from datetime import datetime, timedelta, timezone from playwright.sync_api import expect from tests.conftest import BASE_URL, assert_share_copies from devplacepy.database import get_table from devplacepy.utils import make_combined_slug def _seed_posts(count): owner = str(uuid4()) topic = f"pag{owner[:8]}" get_table("users").insert({ "uid": owner, "username": f"pag_{owner[:8]}", "email": f"{owner[:8]}@test.devplace", "password_hash": "x", "role": "Member", "is_active": True, "created_at": datetime.now(timezone.utc).isoformat(), }) base = datetime(2026, 1, 1, tzinfo=timezone.utc) posts = get_table("posts") for i in range(count): uid = str(uuid4()) content = f"Paginated post {i}" posts.insert({ "uid": uid, "user_uid": owner, "slug": make_combined_slug(content, uid), "title": None, "content": content, "topic": topic, "project_uid": None, "image": None, "stars": 0, "created_at": (base - timedelta(seconds=i)).isoformat(), }) return topic def test_feed_vote_voted_state_persists(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").first.click() page.fill("#post-content", "Feed vote persistence test content") page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".post-action-btn.vote-up").first.click() expect(page.locator(".post-vote-count").first).to_have_text("1") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") expect(page.locator(".post-action-btn.vote-up").first).to_have_class(re.compile(r"\bvoted\b")) def test_feed_card_share_button(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").first.click() page.fill("#post-content", "Feed share button test content") page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert_share_copies(page, "/posts/") assert "/feed" in page.url def test_feed_page_loads(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible("text=Topics") assert page.is_visible("text=All") assert page.is_visible("text=Devlog") assert page.is_visible("text=Showcase") def test_feed_nav_tabs(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible("a:has-text('All')") assert page.is_visible("a:has-text('Trending')") assert page.is_visible("a:has-text('Recent')") assert page.is_visible("a:has-text('Following')") def test_feed_tab_switching(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.click("a:has-text('Trending')") page.wait_for_url(f"{BASE_URL}/feed?tab=trending", wait_until="domcontentloaded") page.click("a:has-text('Recent')") page.wait_for_url(f"{BASE_URL}/feed?tab=recent", wait_until="domcontentloaded") def test_feed_topic_filter_sidebar(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") topics = ["Devlog", "Showcase", "Question", "Rant", "Fun"] for topic in topics: link = page.locator(f"a:has-text('{topic}')").first assert link.is_visible() def test_feed_topic_filter_navigation(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.click("a:has-text('Devlog')") page.wait_for_url(f"{BASE_URL}/feed?topic=devlog", wait_until="domcontentloaded") def test_feed_community_stats(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible("text=Total Members") assert page.is_visible("text=Posts Today") assert page.is_visible("text=Total Projects") def test_feed_top_authors(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible("text=Top Authors") def test_feed_daily_topic(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible("text=Daily Topic") def test_create_post_fab(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") fab = page.locator(".feed-fab") assert fab.is_visible() fab.click() assert page.is_visible("text=Create New Post") assert page.is_visible("#post-content") assert page.is_visible("#create-post-modal button:has-text('Post')") def test_create_post_devlog(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").click() page.check("input[value='devlog']") page.fill("#post-content", "This is a test devlog post created by Playwright") page.fill("#post-title", "Test Devlog Title") page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") assert page.is_visible("text=Test Devlog Title") assert page.is_visible("text=This is a test devlog post created by Playwright") def test_create_post_showcase(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").click() page.check("input[value='showcase']") page.fill("#post-content", "Check out my new project showcase") page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") def test_create_post_without_title(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").click() page.check("input[value='rant']") page.fill("#post-content", "Rant without a title") page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") assert page.is_visible("text=Rant without a title") def test_feed_topnav_navigation(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.click("a:has-text('Projects')") page.wait_for_url(f"{BASE_URL}/projects", wait_until="domcontentloaded") page.click("a:has-text('Home')") page.wait_for_url(f"{BASE_URL}/feed", wait_until="domcontentloaded") def test_topnav_notification_bell(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") bell = page.locator(".topnav-icon[href='/notifications']") assert bell.is_visible() def test_topnav_user_menu(alice): page, user = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible(f"text={user['username']}") def test_feed_inline_comment(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").click() page.check("input[value='devlog']") page.fill("#post-content", "Post with inline comment test " + "x" * 30) page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") inline_form = page.locator(".feed-comment-form").first expect(inline_form).to_be_visible() inline_form.locator("input[name='content']").fill("Inline comment from feed") inline_form.locator(".feed-comment-submit").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") expect(page.locator(".comment-text:has-text('Inline comment from feed')")).to_be_visible() def test_feed_inline_comment_placeholder(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").first.click() page.fill("#post-content", "Placeholder check post " + "x" * 20) page.locator("#create-post-modal button.btn-primary:has-text('Post')").click() page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded") page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") inline_input = page.locator(".feed-comment-form input[name='content']").first expect(inline_input).to_be_visible() assert inline_input.get_attribute("placeholder") == "Your opinion goes here..." def test_feed_signals_topic(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") signals_link = page.locator("a:has-text('Signals')") assert signals_link.is_visible() def test_create_post_cancel_modal(alice): page, _ = alice page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") page.locator(".feed-fab").click() cancel = page.locator("#create-post-modal button:has-text('Cancel')").first cancel.click() modal = page.locator("#create-post-modal") assert not modal.is_visible() def test_feed_pagination_first_page(alice): page, _ = alice topic = _seed_posts(26) page.goto(f"{BASE_URL}/feed?topic={topic}", wait_until="domcontentloaded") assert page.locator(".post-card").count() == 25 assert page.is_visible(".load-more-wrap") def test_feed_pagination_load_more(alice): page, _ = alice topic = _seed_posts(26) page.goto(f"{BASE_URL}/feed?topic={topic}", wait_until="domcontentloaded") page.click(".load-more-wrap a") page.wait_for_url(lambda url: "before=" in url, wait_until="domcontentloaded") assert f"topic={topic}" in page.url assert page.locator(".post-card").count() == 1 assert not page.is_visible(".load-more-wrap") def test_feed_public_access(page, app_server): page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.is_visible("text=Topics") assert page.is_visible("text=All") assert page.is_visible("text=Login") def test_feed_guest_no_fab(page, app_server): page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.locator(".feed-fab").count() == 0 def test_feed_guest_no_inline_comment(page, app_server): page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded") assert page.locator(".feed-comment-form").count() == 0