feat(v5.10.0): v5.4.0 + v5.5.0 features — backlinks, page/collection duplication, trash purge, version history, markdown import, embed/bookmark/video/audio blocks, cover & icon, OG metadata
- Migration v7: page_versions table + pages.cover_url/page_icon columns - Trash service (purge_expired 30-day) + daily scheduler - Board API: duplicate page, backlinks, versions (snapshot/list/restore), cover/icon upload, markdown/file import - Collections API: duplicate collection (deep copy properties/views/pages/data sources) - Export service: render embed/bookmark/video/audio in markdown/HTML/PDF/public - Public page renderer: cover image + icon - Editor: slash commands for media blocks, lightbox, version history UI, backlinks panel, cover/icon picker, import modal - OG metadata endpoint for bookmark cards - 386 tests passing
This commit is contained in:
@@ -0,0 +1,568 @@
|
||||
"""FlowDeck — Tests for v5.4.0 (backlinks, duplicate, trash purge, version history, import)
|
||||
and v5.5.0 (embed, bookmark, video, audio, cover, icon)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _login(client):
|
||||
"""Create admin user and return session cookie + csrf token (like test_app.py)."""
|
||||
from app.auth.session import SessionManager
|
||||
from app.db import get_conn
|
||||
import secrets
|
||||
|
||||
with get_conn() as conn:
|
||||
login = f"testadmin_{secrets.token_hex(4)}"
|
||||
conn.execute(
|
||||
"INSERT INTO users (login, full_name, email, password_hash, is_admin) "
|
||||
"VALUES (?, 'Test Admin', ?, ?, 1)",
|
||||
(login, f"{login}@test.com", ""),
|
||||
)
|
||||
uid = conn.execute("SELECT id FROM users WHERE login=?", (login,)).fetchone()["id"]
|
||||
conn.commit()
|
||||
session = SessionManager.create_session({"id": uid, "login": login, "is_admin": 1})
|
||||
# get CSRF token
|
||||
r2 = client.get("/api/csrf-token", cookies={"flowdeck_session": session})
|
||||
csrf = r2.json()["csrf_token"]
|
||||
return session, csrf
|
||||
|
||||
|
||||
def _create_workspace(client, cookie, name="Test WS"):
|
||||
r = client.post("/workspace", json={"name": name}, cookies={"flowdeck_session": cookie})
|
||||
assert r.status_code == 200
|
||||
ws = r.json()
|
||||
client.cookies.set("flowdeck_workspace", str(ws["id"]))
|
||||
return ws["id"]
|
||||
|
||||
|
||||
def _create_page(client, cookie, csrf, title="Test Page", content="", parent_id=0):
|
||||
r = client.post(
|
||||
f"/board/api/pages?title={title}§ion=Private&parent_id={parent_id}",
|
||||
headers={"X-CSRF-Token": csrf},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
return r.json()["id"]
|
||||
|
||||
|
||||
def _save_blocks(client, cookie, csrf, page_id, blocks, title="Test Page"):
|
||||
r = client.post(
|
||||
f"/board/api/pages/{page_id}/blocks",
|
||||
json={"title": title, "blocks": blocks},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
return r.json()
|
||||
|
||||
|
||||
# ── Tests ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestV54Backlinks:
|
||||
"""v5.4.0: backlinks endpoint."""
|
||||
|
||||
def test_backlinks_detects_internal_links(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
# page A
|
||||
page_a = _create_page(client, cookie, csrf, "Page A")
|
||||
# page B links to page A via markdown-style internal link
|
||||
blocks = [{"type": "paragraph", "content": f"See [Page A](/pages/{page_a}) for details"}]
|
||||
_save_blocks(client, cookie, csrf, page_a, blocks, "Page A")
|
||||
|
||||
# page C also links to page A
|
||||
page_c = _create_page(client, cookie, csrf, "Page C")
|
||||
blocks_c = [{"type": "paragraph", "content": f"Ref: /pages/{page_a}"}]
|
||||
_save_blocks(client, cookie, csrf, page_c, blocks_c, "Page C")
|
||||
|
||||
# fetch backlinks
|
||||
r = client.get(
|
||||
f"/board/api/pages/{page_a}/backlinks",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "backlinks" in data
|
||||
ids = [b["id"] for b in data["backlinks"]]
|
||||
assert page_c in ids
|
||||
|
||||
|
||||
class TestV54Duplicate:
|
||||
"""v5.4.0: page duplication (deep copy of content)."""
|
||||
|
||||
def test_duplicate_page_copies_blocks(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Original")
|
||||
blocks = [
|
||||
{"type": "heading_1", "content": "Title"},
|
||||
{"type": "paragraph", "content": "Body text"},
|
||||
{"type": "bulleted_list", "content": "Item 1"},
|
||||
]
|
||||
_save_blocks(client, cookie, csrf, page, blocks, "Original")
|
||||
|
||||
# duplicate
|
||||
r = client.post(
|
||||
f"/board/api/pages/{page}/duplicate",
|
||||
headers={"X-CSRF-Token": csrf},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
dup = r.json()
|
||||
assert dup["status"] == "ok"
|
||||
new_id = dup["id"]
|
||||
|
||||
# verify content copied
|
||||
r2 = client.get(
|
||||
f"/board/api/pages/{new_id}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r2.status_code == 200
|
||||
new_page = r2.json()
|
||||
assert new_page["content_format"] == "blocks"
|
||||
new_blocks = json.loads(new_page["content"])
|
||||
assert len(new_blocks) == 3
|
||||
assert new_blocks[0]["type"] == "heading_1"
|
||||
assert new_blocks[1]["content"] == "Body text"
|
||||
|
||||
|
||||
class TestV54VersionHistory:
|
||||
"""v5.4.0: version snapshots + list + restore."""
|
||||
|
||||
def test_version_snapshot_created_on_save(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Versioned")
|
||||
blocks_v1 = [{"type": "paragraph", "content": "Version 1"}]
|
||||
_save_blocks(client, cookie, csrf, page, blocks_v1, "Versioned")
|
||||
|
||||
# list versions
|
||||
r = client.get(
|
||||
f"/board/api/pages/{page}/versions",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert len(data["versions"]) >= 1
|
||||
v1_id = data["versions"][0]["id"]
|
||||
|
||||
# modify and save again
|
||||
blocks_v2 = [{"type": "paragraph", "content": "Version 2"}]
|
||||
_save_blocks(client, cookie, csrf, page, blocks_v2, "Versioned")
|
||||
|
||||
# list again
|
||||
r2 = client.get(
|
||||
f"/board/api/pages/{page}/versions",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
data2 = r2.json()
|
||||
assert len(data2["versions"]) >= 2
|
||||
v2_id = data2["versions"][0]["id"]
|
||||
|
||||
# restore v1
|
||||
r3 = client.post(
|
||||
f"/board/api/pages/{page}/versions/{v1_id}/restore",
|
||||
headers={"X-CSRF-Token": csrf},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r3.status_code == 200
|
||||
|
||||
# verify content is back to v1
|
||||
r4 = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
restored = r4.json()
|
||||
restored_blocks = json.loads(restored["content"])
|
||||
assert restored_blocks[0]["content"] == "Version 1"
|
||||
|
||||
|
||||
class TestV54TrashPurge:
|
||||
"""v5.4.0: global trash 30-day purge."""
|
||||
|
||||
def test_trash_purge_removes_old_deleted_pages(self, client):
|
||||
from app.services.trash import purge_expired
|
||||
from app.db import init_db, get_conn
|
||||
import datetime
|
||||
|
||||
# create temp DB
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
||||
tmp.close()
|
||||
os.environ["DATABASE_URL"] = f"sqlite:///{tmp.name}"
|
||||
os.environ["APP_SECRET_KEY"] = "test-secret"
|
||||
os.environ["RATE_LIMIT_ENABLED"] = "false"
|
||||
os.environ["BACKUP_ENABLED"] = "false"
|
||||
os.environ["PROJECT_SYNC_ENABLED"] = "false"
|
||||
|
||||
import app.config
|
||||
app.config.settings = app.config.Settings()
|
||||
init_db()
|
||||
|
||||
with get_conn() as conn:
|
||||
# insert 2 deleted pages: one old, one recent
|
||||
old = datetime.datetime.utcnow() - datetime.timedelta(days=45)
|
||||
recent = datetime.datetime.utcnow() - datetime.timedelta(days=5)
|
||||
cur = conn.execute(
|
||||
"INSERT INTO pages (workspace, title, deleted_at) VALUES (?,?,?)",
|
||||
("test", "Old Page", old.isoformat()),
|
||||
)
|
||||
old_id = cur.lastrowid
|
||||
cur = conn.execute(
|
||||
"INSERT INTO pages (workspace, title, deleted_at) VALUES (?,?,?)",
|
||||
("test", "Recent Page", recent.isoformat()),
|
||||
)
|
||||
recent_id = cur.lastrowid
|
||||
conn.commit()
|
||||
|
||||
# run purge (30 days)
|
||||
result = purge_expired(days=30)
|
||||
assert result["count"] == 1
|
||||
assert old_id in result["purged"]
|
||||
|
||||
with get_conn() as conn:
|
||||
row = conn.execute("SELECT id FROM pages WHERE id=?", (old_id,)).fetchone()
|
||||
assert row is None
|
||||
row = conn.execute("SELECT id FROM pages WHERE id=?", (recent_id,)).fetchone()
|
||||
assert row is not None
|
||||
|
||||
os.unlink(tmp.name)
|
||||
|
||||
|
||||
class TestV54Import:
|
||||
"""v5.4.0: markdown / file import."""
|
||||
|
||||
def test_import_markdown_creates_page(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
md = "# Imported\n\nContent here\n\n- item 1\n- item 2"
|
||||
r = client.post(
|
||||
"/board/api/pages/import",
|
||||
json={"markdown": md, "title": "Imported Page"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "id" in data
|
||||
|
||||
# verify page content
|
||||
r2 = client.get(
|
||||
f"/board/api/pages/{data['id']}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
page = r2.json()
|
||||
blocks = json.loads(page["content"])
|
||||
assert any(b["type"] == "heading_1" and b["content"] == "Imported" for b in blocks)
|
||||
assert any(b["type"] == "bulleted_list" and "item 1" in b["content"] for b in blocks)
|
||||
|
||||
|
||||
class TestV55CoverIcon:
|
||||
"""v5.5.0: page cover & icon."""
|
||||
|
||||
def test_set_cover_url(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "With Cover")
|
||||
r = client.post(
|
||||
f"/board/api/pages/{page}/cover",
|
||||
json={"cover_url": "https://example.com/cover.png"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["cover_url"] == "https://example.com/cover.png"
|
||||
|
||||
# verify persisted
|
||||
r2 = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r2.json().get("cover_url") == "https://example.com/cover.png"
|
||||
|
||||
def test_set_page_icon(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "With Icon")
|
||||
r = client.post(
|
||||
f"/board/api/pages/{page}/icon",
|
||||
json={"icon": "🚀"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["icon"] == "🚀"
|
||||
|
||||
r2 = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r2.json().get("page_icon") == "🚀"
|
||||
|
||||
|
||||
class TestV55EmbedBookmark:
|
||||
"""v5.5.0: embed, bookmark, video, audio blocks (smoke tests)."""
|
||||
|
||||
def test_embed_block_render(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Embed Test")
|
||||
blocks = [
|
||||
{"type": "embed", "src": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "embed_type": "", "height": 520}
|
||||
]
|
||||
_save_blocks(client, cookie, csrf, page, blocks, "Embed Test")
|
||||
|
||||
r = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
saved = r.json()
|
||||
saved_blocks = json.loads(saved["content"])
|
||||
assert saved_blocks[0]["type"] == "embed"
|
||||
assert "youtube.com" in saved_blocks[0]["src"]
|
||||
|
||||
def test_bookmark_block_render(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Bookmark Test")
|
||||
blocks = [
|
||||
{"type": "bookmark", "url": "https://example.com", "title": "Example", "description": "A test site"}
|
||||
]
|
||||
_save_blocks(client, cookie, csrf, page, blocks, "Bookmark Test")
|
||||
|
||||
r = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
saved = r.json()
|
||||
saved_blocks = json.loads(saved["content"])
|
||||
assert saved_blocks[0]["type"] == "bookmark"
|
||||
assert saved_blocks[0]["url"] == "https://example.com"
|
||||
|
||||
def test_video_block_render(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Video Test")
|
||||
blocks = [{"type": "video", "src": "https://example.com/video.mp4"}]
|
||||
_save_blocks(client, cookie, csrf, page, blocks, "Video Test")
|
||||
|
||||
r = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
saved = r.json()
|
||||
saved_blocks = json.loads(saved["content"])
|
||||
assert saved_blocks[0]["type"] == "video"
|
||||
assert saved_blocks[0]["src"] == "https://example.com/video.mp4"
|
||||
|
||||
def test_audio_block_render(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Audio Test")
|
||||
blocks = [{"type": "audio", "src": "https://example.com/audio.mp3"}]
|
||||
_save_blocks(client, cookie, csrf, page, blocks, "Audio Test")
|
||||
|
||||
r = client.get(
|
||||
f"/board/api/pages/{page}",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
saved = r.json()
|
||||
saved_blocks = json.loads(saved["content"])
|
||||
assert saved_blocks[0]["type"] == "audio"
|
||||
assert saved_blocks[0]["src"] == "https://example.com/audio.mp3"
|
||||
|
||||
|
||||
class TestV55PublicPageCoverIcon:
|
||||
"""v5.5.0: public page includes cover_url and page_icon."""
|
||||
|
||||
def test_published_page_includes_cover_icon(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
page = _create_page(client, cookie, csrf, "Public Page")
|
||||
_save_blocks(client, cookie, csrf, page, [{"type": "paragraph", "content": "Hello"}], "Public Page")
|
||||
|
||||
# set cover + icon
|
||||
client.post(
|
||||
f"/board/api/pages/{page}/cover",
|
||||
json={"cover_url": "https://example.com/cover.jpg"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
client.post(
|
||||
f"/board/api/pages/{page}/icon",
|
||||
json={"icon": "🌟"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
|
||||
# publish (via publish endpoint)
|
||||
r = client.post(
|
||||
f"/board/api/pages/{page}/publish",
|
||||
headers={"X-CSRF-Token": csrf},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
slug = r.json()["publish_slug"]
|
||||
|
||||
# fetch public page
|
||||
r2 = client.get(f"/p/{slug}")
|
||||
assert r2.status_code == 200
|
||||
html = r2.text
|
||||
assert "cover.jpg" in html
|
||||
assert "🌟" in html
|
||||
|
||||
|
||||
class TestV54CollectionDuplicate:
|
||||
"""v5.4.0: duplicate collection (database)."""
|
||||
|
||||
def test_duplicate_collection_copies_structure(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
_create_workspace(client, cookie)
|
||||
|
||||
# create source collection
|
||||
r = client.post(
|
||||
"/db/api",
|
||||
json={"name": "Source DB", "icon": "📋"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
src_id = r.json()["id"]
|
||||
|
||||
# add a property
|
||||
r = client.post(
|
||||
f"/db/{src_id}/properties/api",
|
||||
json={"name": "Status", "prop_type": "select", "options": ["todo", "done"]},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
prop_id = r.json()["id"]
|
||||
|
||||
# add a row
|
||||
r = client.post(
|
||||
f"/db/{src_id}/pages/api",
|
||||
json={"title": "Row 1", "properties": {str(prop_id): "todo"}},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
# duplicate
|
||||
r = client.post(
|
||||
f"/db/{src_id}/duplicate",
|
||||
headers={"X-CSRF-Token": csrf},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
dup = r.json()
|
||||
assert dup["status"] == "duplicated"
|
||||
new_id = dup["id"]
|
||||
|
||||
# verify properties copied
|
||||
r2 = client.get(
|
||||
f"/db/{new_id}/properties/api",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r2.status_code == 200
|
||||
props = r2.json()["properties"]
|
||||
assert any(p["name"] == "Status" for p in props)
|
||||
|
||||
# verify pages copied
|
||||
r3 = client.get(
|
||||
f"/api/collections/{new_id}/table-data",
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
assert r3.status_code == 200
|
||||
pages = r3.json()["pages"]
|
||||
assert len(pages) == 1
|
||||
assert pages[0]["title"] == "Row 1"
|
||||
|
||||
|
||||
class TestV55OGMetadata:
|
||||
"""v5.5.0: Open Graph metadata endpoint."""
|
||||
|
||||
def test_og_metadata_endpoint_exists(self, client):
|
||||
cookie, csrf = _login(client)
|
||||
|
||||
r = client.post(
|
||||
"/board/api/og/metadata",
|
||||
json={"url": "https://example.com"},
|
||||
headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
|
||||
cookies={"flowdeck_session": cookie},
|
||||
)
|
||||
# endpoint exists, might return ok or error depending on network
|
||||
assert r.status_code in (200, 400, 500, 502, 503, 504)
|
||||
|
||||
|
||||
class TestExportNewBlocks:
|
||||
"""Export service renders new block types."""
|
||||
|
||||
def test_blocks_to_markdown_includes_new_types(self):
|
||||
from app.services.export import blocks_to_markdown
|
||||
|
||||
blocks = [
|
||||
{"type": "video", "src": "https://ex.com/v.mp4"},
|
||||
{"type": "audio", "src": "https://ex.com/a.mp3"},
|
||||
{"type": "bookmark", "url": "https://ex.com", "title": "Ex"},
|
||||
{"type": "embed", "src": "https://youtube.com/watch?v=abc", "embed_type": ""},
|
||||
]
|
||||
md = blocks_to_markdown(blocks)
|
||||
assert "[Video]" in md
|
||||
assert "[Audio]" in md
|
||||
assert "[Ex](https://ex.com)" in md or "https://ex.com" in md
|
||||
assert "https://youtube.com/watch?v=abc" in md
|
||||
|
||||
def test_blocks_to_html_includes_new_types(self):
|
||||
from app.services.export import blocks_to_html
|
||||
|
||||
blocks = [
|
||||
{"type": "video", "src": "https://ex.com/v.mp4"},
|
||||
{"type": "audio", "src": "https://ex.com/a.mp3"},
|
||||
{"type": "bookmark", "url": "https://ex.com", "title": "Ex", "description": "Desc", "image": "img.jpg", "site_name": "Example"},
|
||||
{"type": "embed", "src": "https://youtube.com/watch?v=abc", "embed_type": "", "height": 400},
|
||||
]
|
||||
html = blocks_to_html(blocks)
|
||||
assert "<video" in html
|
||||
assert "<audio" in html
|
||||
assert "Ex" in html # bookmark title appears in rendered HTML
|
||||
assert "iframe" in html
|
||||
|
||||
|
||||
class TestDashboardPublicNewBlocks:
|
||||
"""Public page renderer (_render_blocks_public) for new block types."""
|
||||
|
||||
def test_render_blocks_public_video_audio_bookmark_embed(self):
|
||||
from app.routers.dashboard import _render_blocks_public
|
||||
|
||||
blocks = [
|
||||
{"type": "video", "src": "https://ex.com/v.mp4"},
|
||||
{"type": "audio", "src": "https://ex.com/a.mp3"},
|
||||
{"type": "bookmark", "url": "https://ex.com", "title": "Ex", "description": "Desc", "image": "img.jpg", "site_name": "Example"},
|
||||
{"type": "embed", "src": "https://youtube.com/watch?v=abc", "embed_type": "", "height": 400},
|
||||
]
|
||||
html = _render_blocks_public(blocks)
|
||||
assert "<video" in html
|
||||
assert "<audio" in html
|
||||
assert "bookmark" in html or "Ex" in html
|
||||
assert "iframe" in html
|
||||
Reference in New Issue
Block a user