Upload tests/test_frontend_assets.py
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
"""Frontend-Asset Tests: verify JS modules and CSS are syntactically valid
|
||||
and export / register the expected symbols.
|
||||
|
||||
These tests perform static text-level checks on the JS / CSS files — no
|
||||
runtime execution, no nodejs dependency. They are sufficient to catch:
|
||||
- Missing exports (e.g. `export { api, ApiError }` removed from api.js)
|
||||
- Missing global Alpine.data() registrations for the components
|
||||
- CSS variables removed from the custom-overrides file
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
WEBUI = Path(__file__).resolve().parent.parent / "app" / "webui"
|
||||
|
||||
|
||||
def _read(rel: str) -> str:
|
||||
p = WEBUI / rel
|
||||
assert p.exists(), f"Missing asset: {p}"
|
||||
return p.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# api.js
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_api_js_exports_api_and_ApiError():
|
||||
content = _read("js/api.js")
|
||||
assert "export class ApiError" in content
|
||||
assert "export const api" in content or "export default api" in content
|
||||
|
||||
|
||||
def test_api_js_uses_localStorage_for_jwt():
|
||||
"""R-1 / architecture: JWT must live in localStorage."""
|
||||
content = _read("js/api.js")
|
||||
assert "localStorage" in content
|
||||
assert "jwt" in content
|
||||
|
||||
|
||||
def test_api_js_handles_401_redirect():
|
||||
"""401 must clear the token and redirect to /index.html."""
|
||||
content = _read("js/api.js")
|
||||
assert "401" in content
|
||||
assert "/index.html" in content
|
||||
|
||||
|
||||
def test_api_js_handles_204_no_content():
|
||||
content = _read("js/api.js")
|
||||
assert "204" in content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# store.js
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_store_js_defines_auth_and_notifications():
|
||||
content = _read("js/store.js")
|
||||
assert "Alpine.store('auth'" in content
|
||||
assert "Alpine.store('notifications'" in content
|
||||
|
||||
|
||||
def test_store_js_auth_has_login_and_logout():
|
||||
content = _read("js/store.js")
|
||||
assert "login" in content
|
||||
assert "logout" in content
|
||||
assert "fetchUser" in content
|
||||
|
||||
|
||||
def test_store_js_notifications_has_push_and_dismiss():
|
||||
content = _read("js/store.js")
|
||||
assert "push" in content
|
||||
assert "dismiss" in content
|
||||
assert "success" in content
|
||||
assert "error" in content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# app.css
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_app_css_contains_tailwind_overrides():
|
||||
content = _read("css/app.css")
|
||||
# The file defines custom CSS variables (--crm-primary, etc.)
|
||||
assert "--crm-primary" in content
|
||||
assert "--crm-success" in content
|
||||
assert "--crm-danger" in content
|
||||
|
||||
|
||||
def test_app_css_has_loading_spinner():
|
||||
content = _read("css/app.css")
|
||||
assert "crm-spinner" in content
|
||||
assert "@keyframes crm-spin" in content
|
||||
|
||||
|
||||
def test_app_css_has_toast_styles():
|
||||
content = _read("css/app.css")
|
||||
assert "crm-toast" in content
|
||||
assert "toast-success" in content
|
||||
assert "toast-error" in content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Alpine components
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
COMPONENTS = [
|
||||
("account-list.js", "accountList"),
|
||||
("deal-kanban.js", "dealKanban"),
|
||||
("activity-list.js", "activityList"),
|
||||
("dashboard-kpis.js", "dashboardKpis"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename,factory", COMPONENTS)
|
||||
def test_alpine_component_defined(filename, factory):
|
||||
"""Each component file must define a named factory and register it
|
||||
globally with Alpine.data(name, factory)."""
|
||||
content = _read(f"components/{filename}")
|
||||
# factory function definition
|
||||
assert f"export function {factory}" in content, \
|
||||
f"{filename} missing `export function {factory}()`"
|
||||
# Alpine.data() registration
|
||||
assert f"Alpine.data('{factory}'" in content, \
|
||||
f"{filename} missing `Alpine.data('{factory}', …)` registration"
|
||||
|
||||
|
||||
def test_deal_kanban_handles_drag_and_drop():
|
||||
content = _read("components/deal-kanban.js")
|
||||
assert "draggable" in content or "dataTransfer" in content
|
||||
assert "onDrop" in content
|
||||
assert "/deals/" in content and "/stage" in content
|
||||
|
||||
|
||||
def test_activity_list_supports_overdue_filter():
|
||||
content = _read("components/activity-list.js")
|
||||
assert "overdue" in content
|
||||
assert "/activities/" in content
|
||||
assert "/complete" in content
|
||||
|
||||
|
||||
def test_dashboard_kpis_loads_both_kpis_and_feed():
|
||||
content = _read("components/dashboard-kpis.js")
|
||||
assert "/dashboard/kpis" in content
|
||||
assert "/dashboard/feed" in content
|
||||
|
||||
|
||||
def test_account_list_uses_pagination():
|
||||
content = _read("components/account-list.js")
|
||||
assert "skip" in content
|
||||
assert "limit" in content
|
||||
assert "q" in content
|
||||
assert "/accounts/" in content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# notifications.js (toast component)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_notifications_js_registers_toast_container():
|
||||
content = _read("js/components/notifications.js")
|
||||
assert "toastContainer" in content
|
||||
assert "Alpine.data" in content
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# auth.js
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_auth_js_exports_login_logout_register():
|
||||
content = _read("js/auth.js")
|
||||
assert "export async function login" in content
|
||||
assert "export async function logout" in content
|
||||
assert "export async function register" in content
|
||||
assert "export async function refreshToken" in content
|
||||
Reference in New Issue
Block a user