# retoor from uuid import uuid4 from datetime import datetime, timezone, timedelta from playwright.sync_api import expect from tests.conftest import BASE_URL from devplacepy.database import get_table from devplacepy.utils import make_combined_slug def _seed_project(): """Create a project seeded directly into DB and return (slug, uid, user_uid).""" owner = str(uuid4()) get_table("users").insert( { "uid": owner, "username": f"dlowner_{owner[:8]}", "email": f"{owner[:8]}@dl.test", "password_hash": "x", "role": "Member", "is_active": True, "created_at": datetime.now(timezone.utc).isoformat(), } ) uid = str(uuid4()) slug = make_combined_slug("E2E Devlog Project", uid) get_table("projects").insert( { "deleted_at": None, "deleted_by": None, "uid": uid, "user_uid": owner, "slug": slug, "title": "E2E Devlog Project", "description": "Project for devlog browser tests.", "project_type": "software", "platforms": "", "status": "In Development", "stars": 0, "created_at": datetime.now(timezone.utc).isoformat(), } ) return slug, uid, owner def _seed_project_post(project_uid, user_uid, order, title=None): """Insert a post linked to a project with precise ordering.""" uid = str(uuid4()) marker = title or f"dlpost-{uid[:8]}" get_table("posts").insert( { "deleted_at": None, "deleted_by": None, "uid": uid, "user_uid": user_uid, "slug": make_combined_slug(marker, uid), "title": marker, "content": f"Devlog post content {order}", "topic": "devlog", "project_uid": project_uid, "image": None, "stars": 0, "created_at": (datetime(2026, 1, 1, tzinfo=timezone.utc) - timedelta(seconds=order)).isoformat(), } ) return marker, uid def _create_project_ui(page, title): """Create a project via the UI.""" page.goto(f"{BASE_URL}/projects", wait_until="domcontentloaded") page.locator("#create-project-btn").click() page.fill("#title", title) page.fill("#description", "Project for devlog UI test") page.click("button:has-text('Create Project')") page.wait_for_url(f"{BASE_URL}/projects/*", wait_until="domcontentloaded") return page.url def test_devlog_empty_state_on_project_page(alice): """Project with no linked posts displays 'No devlog posts yet'.""" page, _ = alice _create_project_ui(page, "Empty Devlog Project") devlog_section = page.locator(".project-devlog") expect(devlog_section).to_be_visible() expect(devlog_section.locator("h3:has-text('Devlog')")).to_be_visible() expect(page.locator(".empty-state:has-text('No devlog posts yet.')")).to_be_visible() def test_devlog_shows_linked_post_title(alice): """Linked post title renders in the devlog section.""" page, _ = alice slug, project_uid, owner_uid = _seed_project() marker = f"visible-dl-{uuid4().hex[:8]}" _seed_project_post(project_uid, owner_uid, 0, title=marker) page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded") devlog = page.locator(".project-devlog") expect(devlog.locator(f"h3:has-text('{marker}')")).to_be_visible() def test_devlog_shows_author_info(alice): """Author avatar, name and level appear on devlog posts.""" page, _ = alice slug, project_uid, owner_uid = _seed_project() marker = f"auth-dl-{uuid4().hex[:8]}" _seed_project_post(project_uid, owner_uid, 0, title=marker) page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded") post_card = page.locator(".project-devlog .post-card").first expect(post_card.locator(".post-header")).to_be_visible() expect(post_card.locator(".post-author-link")).to_be_visible() expect(post_card.locator(".post-time")).to_be_visible() def test_devlog_shows_vote_and_comment_buttons(alice): """Devlog post renders vote buttons and comment count.""" page, _ = alice slug, project_uid, owner_uid = _seed_project() marker = f"action-dl-{uuid4().hex[:8]}" _seed_project_post(project_uid, owner_uid, 0, title=marker) page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded") post_card = page.locator(".project-devlog .post-card").first expect(post_card.locator(".post-action-btn")).to_be_visible() expect(post_card.locator("form[action*='/posts/delete/']")).to_be_visible() def test_devlog_load_more_appears_with_many_posts(alice): """More than PAGE_SIZE posts produces a Load More link.""" from devplacepy.database.pagination import PAGE_SIZE page, _ = alice slug, project_uid, owner_uid = _seed_project() count = PAGE_SIZE + 1 for i in range(count): _seed_project_post(project_uid, owner_uid, i, title=f"loadmore-{i}") page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded") devlog = page.locator(".project-devlog") expect(devlog.locator(".post-card")).to_have_count(PAGE_SIZE) expect(devlog.locator(".load-more-wrap")).to_be_visible() def test_devlog_guest_sees_devlog_section(app_server): """Unauthenticated visitors can see the devlog section.""" import requests slug, _, _ = _seed_project() r = requests.get(f"{BASE_URL}/projects/{slug}") assert r.status_code == 200, r.text[:200] assert "No devlog posts yet." in r.text assert 'class="project-devlog"' in r.text def test_devlog_multiple_posts_order(alice): """Posts appear newest-first in the devlog.""" page, _ = alice slug, project_uid, owner_uid = _seed_project() markers = [] for i in range(3): marker = f"order-{i}-{uuid4().hex[:8]}" markers.append(marker) _seed_project_post(project_uid, owner_uid, i, title=marker) page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded") titles = page.locator(".project-devlog .post-card .post-title") expect(titles).to_have_count(3) first_text = titles.nth(0).inner_text() assert markers[-1] == first_text, ( f"Expected newest post first: {markers[-1]}, got: {first_text}" )