fix: side peek des bases repasse en vanilla JS + largeur 1100px standard (v7.49.0)
- Panneau peek: les bindings Alpine (x-data absent du conteneur) rendaient loovverture et le redimensionnement inoperants -> cblage direct sur le document. - Helper unique window.fdWirePeekResize (app.js): pointer capture, 300px-90vw, clic=fermer, largeur persiste fd_peek_width partagee entre les 4 peeks. - database-table-container margin:0 (tableau colle a gauche, marge Library). - .lib-container remonte dans app.css (trash etait pleine largeur), .db-index 1100px. - ObsiGate verifie sans code: creation .xlsx OK (openpyxl, #186).
This commit is contained in:
+117
-34
@@ -840,28 +840,73 @@ def test_my_tasks_page(client):
|
||||
|
||||
|
||||
def test_my_tasks_cross_db(client):
|
||||
"""My Tasks API returns JSON."""
|
||||
r = client.post("/db/api", json={"name": "My Project"})
|
||||
"""My Tasks API returns JSON from every *connected* database.
|
||||
|
||||
Depuis v7.47.0, une base n'alimente My Tasks qu'après conversion
|
||||
explicite (``POST /db/{id}/task-db/api``, mapping des 3 colonnes) : la
|
||||
page n'est plus scopée par ``flowdeck_workspace``.
|
||||
"""
|
||||
import time
|
||||
|
||||
from app.db import get_conn
|
||||
col_name = f"My Project {int(time.time() * 1000)}"
|
||||
with get_conn() as conn:
|
||||
ws_row = conn.execute(
|
||||
"SELECT id FROM workspaces WHERE owner_id=(SELECT id FROM users WHERE login='tester') LIMIT 1"
|
||||
).fetchone()
|
||||
ws_id = ws_row["id"] if ws_row else conn.execute(
|
||||
"INSERT INTO workspaces (name, owner_id) VALUES (?, "
|
||||
"(SELECT id FROM users WHERE login='tester'))", ("My Tasks Cross DB WS",)
|
||||
).lastrowid
|
||||
conn.commit()
|
||||
|
||||
r = client.post("/db/api", json={"name": col_name, "workspace_id": ws_id})
|
||||
cid = r.json()["id"]
|
||||
client.post(f"/db/{cid}/pages/api", json={"title": "Task 1", "properties": {"Status": "Todo"}})
|
||||
client.post(f"/db/{cid}/pages/api", json={"title": "Task 2", "properties": {"Status": "In Progress"}})
|
||||
|
||||
# Sans conversion : la base n'émet rien.
|
||||
resp = client.get("/my-tasks/api")
|
||||
assert resp.status_code == 200
|
||||
assert cid not in [t["collection_id"] for t in resp.json()["tasks"]]
|
||||
|
||||
conv = client.post(f"/db/{cid}/task-db/api", json={})
|
||||
assert conv.status_code == 200, conv.text
|
||||
assert conv.json()["configured"] is True
|
||||
|
||||
for title in ("Task 1", "Task 2"):
|
||||
client.post(f"/db/{cid}/pages/api", json={"title": title})
|
||||
|
||||
resp = client.get("/my-tasks/api")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "tasks" in data
|
||||
assert data["total"] >= 2
|
||||
titles = [t["title"] for t in data["tasks"]]
|
||||
assert "Task 1" in titles and "Task 2" in titles
|
||||
assert col_name in [s["name"] for s in data["sources"]]
|
||||
# Le périmètre est transverse : pas de workspace_id dans le payload.
|
||||
assert "workspace_id" not in data
|
||||
|
||||
client.delete(f"/db/api/{cid}")
|
||||
|
||||
|
||||
def test_my_tasks_view_today(client):
|
||||
resp = client.get("/my-tasks?view=today")
|
||||
@pytest.mark.parametrize("view", ["table", "board", "calendar"])
|
||||
def test_my_tasks_views(client, view):
|
||||
"""Les trois vues (tableau / kanban / calendrier) sont acceptées.
|
||||
|
||||
Sans base connectée, le serveur rend son état vide (le conteneur client
|
||||
n'est chargé que lorsqu'il y a des tâches à afficher) : on vérifie donc le
|
||||
code HTTP ici, et le contenu via l'API.
|
||||
"""
|
||||
resp = client.get("/my-tasks", params={"view": view})
|
||||
assert resp.status_code == 200
|
||||
assert "My Tasks" in resp.text
|
||||
assert client.get("/my-tasks/api",
|
||||
params={"view": view}).status_code == 200
|
||||
|
||||
|
||||
def test_my_tasks_view_overdue(client):
|
||||
resp = client.get("/my-tasks?view=overdue")
|
||||
@pytest.mark.parametrize("due", ["all", "today", "overdue", "week"])
|
||||
def test_my_tasks_due_filters(client, due):
|
||||
resp = client.get("/my-tasks/api", params={"due": due})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["filters"]["due"] == due
|
||||
|
||||
|
||||
# ── v2.1.0: Workspace, Comments, Favorites, CSV ──
|
||||
@@ -1716,18 +1761,33 @@ def test_oauth_login_local_page(client):
|
||||
assert "Login" in resp.text or "login" in resp.text.lower()
|
||||
|
||||
|
||||
def test_oauth_login_gitea_redirect(client):
|
||||
"""GET /auth/login?provider=gitea — redirects to Gitea OAuth (configured in test env)."""
|
||||
def test_oauth_login_gitea_redirect(client, monkeypatch):
|
||||
"""GET /auth/login?provider=gitea — redirects to Gitea OAuth when configured.
|
||||
|
||||
v7.46.0 : les valeurs de substitution livrées dans app/config.py
|
||||
(``test-id`` / ``test-secret``) ne comptent plus comme « configuré » — sinon
|
||||
/auth/login redirigeait vers Gitea avec un client_id invalide. Le test
|
||||
configure donc de VRAIS identifiants.
|
||||
"""
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "gitea_oauth_client_id", "gitea-real-id")
|
||||
monkeypatch.setattr(settings, "gitea_oauth_client_secret", "gitea-real-secret")
|
||||
resp = client.get("/auth/login?provider=gitea", follow_redirects=False)
|
||||
# Gitea OAuth IS configured in test env → redirect to Gitea
|
||||
# Gitea OAuth configuré → redirect to Gitea
|
||||
assert resp.status_code == 302
|
||||
assert "login/oauth" in resp.headers.get("location", "").lower()
|
||||
assert "client_id=gitea-real-id" in resp.headers["location"]
|
||||
|
||||
|
||||
def test_oauth_login_with_link_mode(client):
|
||||
def test_oauth_login_with_link_mode(client, monkeypatch):
|
||||
"""GET /auth/login?provider=gitea&mode=link — link mode redirects to Gitea OAuth."""
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "gitea_oauth_client_id", "gitea-real-id")
|
||||
monkeypatch.setattr(settings, "gitea_oauth_client_secret", "gitea-real-secret")
|
||||
resp = client.get("/auth/login?provider=gitea&mode=link", follow_redirects=False)
|
||||
# Gitea OAuth IS configured → redirect to Gitea with link mode set in session
|
||||
# Gitea OAuth configuré → redirect to Gitea with link mode set in session
|
||||
assert resp.status_code == 302
|
||||
assert "login/oauth" in resp.headers.get("location", "").lower()
|
||||
|
||||
@@ -1786,8 +1846,12 @@ def test_get_redirect_uri_env_override_wins(monkeypatch):
|
||||
monkeypatch.undo()
|
||||
|
||||
|
||||
def test_oauth_login_redirect_uri_dynamic(client):
|
||||
def test_oauth_login_redirect_uri_dynamic(client, monkeypatch):
|
||||
"""The authorize URL carries the request-derived redirect_uri (encoded)."""
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "gitea_oauth_client_id", "gitea-real-id")
|
||||
monkeypatch.setattr(settings, "gitea_oauth_client_secret", "gitea-real-secret")
|
||||
resp = client.get(
|
||||
"/auth/login?provider=gitea",
|
||||
headers={"X-Forwarded-Proto": "https", "X-Forwarded-Host": "flowdeck.dracodev.net"},
|
||||
@@ -2802,28 +2866,47 @@ def test_sprint_burndown(client):
|
||||
|
||||
|
||||
def test_my_tasks_page_render(client):
|
||||
"""My Tasks page renders cross-database aggregation."""
|
||||
# Create a task collection
|
||||
resp = client.post("/db/api", json={"name": "My Tasks DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
"""My Tasks page renders the three views and its sources.
|
||||
|
||||
# Toggle to task mode
|
||||
client.put(f"/db/{coll_id}/toggle-task/api")
|
||||
Le tableau de bord est rendu côté client depuis ``/my-tasks/api`` : le HTML
|
||||
transporte la configuration (``data-config``), pas les tâches.
|
||||
"""
|
||||
import time
|
||||
|
||||
# Add pages
|
||||
client.post(f"/db/{coll_id}/pages/api", json={
|
||||
"title": "Urgent fix",
|
||||
"properties": {"Status": "Todo"},
|
||||
})
|
||||
client.post(f"/db/{coll_id}/pages/api", json={
|
||||
"title": "Deploy",
|
||||
"properties": {"Status": "Done"},
|
||||
})
|
||||
from app.db import get_conn
|
||||
name = f"My Tasks DB {int(time.time() * 1000)}"
|
||||
with get_conn() as conn:
|
||||
ws_row = conn.execute(
|
||||
"SELECT id FROM workspaces WHERE owner_id=(SELECT id FROM users WHERE login='tester') LIMIT 1"
|
||||
).fetchone()
|
||||
ws_id = ws_row["id"] if ws_row else conn.execute(
|
||||
"INSERT INTO workspaces (name, owner_id) VALUES (?, "
|
||||
"(SELECT id FROM users WHERE login='tester'))", ("Test Workspace",)
|
||||
).lastrowid
|
||||
conn.commit()
|
||||
|
||||
resp = client.get("/my-tasks?view=all")
|
||||
coll_id = client.post("/db/api",
|
||||
json={"name": name, "workspace_id": ws_id}).json()["id"]
|
||||
|
||||
# `toggle-task` reste rétrocompatible : il réalise la conversion complète.
|
||||
resp = client.put(f"/db/{coll_id}/toggle-task/api")
|
||||
assert resp.status_code == 200
|
||||
assert "Urgent fix" in resp.text
|
||||
assert "My Tasks DB" in resp.text
|
||||
assert resp.json()["is_task"] is True
|
||||
|
||||
client.post(f"/db/{coll_id}/pages/api", json={"title": "Urgent fix"})
|
||||
client.post(f"/db/{coll_id}/pages/api", json={"title": "Deploy"})
|
||||
|
||||
resp = client.get("/my-tasks")
|
||||
assert resp.status_code == 200
|
||||
assert 'id="my-tasks-app"' in resp.text
|
||||
assert "/static/js/my_tasks.js" in resp.text
|
||||
|
||||
# Les titres sont bien exposés par l'API alimentée par la page.
|
||||
titles = [t["title"] for t in client.get("/my-tasks/api").json()["tasks"]]
|
||||
assert "Urgent fix" in titles
|
||||
assert name in [s["name"] for s in client.get("/my-tasks/api").json()["sources"]]
|
||||
|
||||
client.delete(f"/db/api/{coll_id}")
|
||||
|
||||
|
||||
# ── v4.6.0: Content Blocks Enriched ──
|
||||
|
||||
Reference in New Issue
Block a user