feat: #78 Excalidraw finitions + #67 push + #68 health-detailed + #77 desktop jumplist
CI / lint (push) Successful in 52s
CI / security (push) Successful in 36s
CI / test (push) Successful in 1m6s
CI / build (push) Successful in 1m15s
CI / e2e (push) Failing after 12m40s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 52s
CI / security (push) Successful in 36s
CI / test (push) Successful in 1m6s
CI / build (push) Successful in 1m15s
CI / e2e (push) Failing after 12m40s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
#78 Excalidraw — finitions B5/C8/F2/F3 - backend/indexer.py: port Python pur de lz-string decompressFromBase64 (bitsPerChar=6, resetValue=32) validé contre 4 fixtures JS truth (texte accentué, edge cases) — remplace le stub base64 non-fonctionnel - B5 indexation: .excalidraw.md (format plugin Obsidian) maintenant décompressé côté Python → texte indexé pour recherche TF-IDF - 7 nouveaux tests B5 dans tests/test_excalidraw.py (13/13 total) - F2: excalidraw-viewer.test.mjs enregistré dans CI (lint job) - F3: tests/e2e/excalidraw.spec.js (9 specs — ouverture .excalidraw/.excalidraw.md, création via modale/menu contextuel, toolbar, thème, pop-out, recherche) - Fixtures test_vault/diagram.excalidraw + diagram.excalidraw.md #67 Push notifications (Web Push API + VAPID) - backend/push.py: router + VAPID keys + send_push_notification (pywebpush>=2.3.0) - frontend/js/push.js: subscription UI + service worker integration - tests/test_push.py: 10 tests (subscribe/list/unsubscribe/vapid key) - requirements.txt + sw.js + locales push strings #68 Health check enrichi - backend/main.py: GET /api/health/detailed (admin) — memory/cpu/disk/backups/index/SSE connections - backend/indexer.py: _last_full_index_ts tracking - tests/conftest.py: admin_client fixture - tests/test_api_main.py: 3 nouveaux tests health detailed #77 Desktop jumplist - desktop/src/jumplist.rs: Windows jumplist integration - Cargo.toml/lock + capabilities + main.rs - frontend/js/desktop.js: Tauri bridge (isTauriEnv, invoke, getSystemTheme, syncSystemTheme, shouldShowWizard, crash banner) - tests/frontend/desktop.test.mjs: 21 tests JSDOM (détection, invoke degradation, theme gating, wizard, crash banner) - tests/frontend/unit.test.mjs: desktop.js whitelisted (standalone global reader) # Frontend & CI - frontend/sw.js: réécriture complète (precache + push event handlers + offline) - frontend/index.html: section push dans settings + about repositionné - frontend/js/app.js: initDesktopIntegration + initPush - CI .gitea/workflows/ci.yml: excalidraw-viewer.test.mjs ajouté au lint job All checks: ruff clean, 552 backend tests pass, 9 frontend unit, 5 excalidraw-viewer, 21 desktop, 9 pane-manager, validate-imports 33 modules.
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
import json
|
||||
|
||||
|
||||
EXCALIDRAW_SKELETON = {
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
@@ -47,6 +46,34 @@ EXCALIDRAW_WITH_CONTENT = {
|
||||
"files": {},
|
||||
}
|
||||
|
||||
# ████████████████████████████████████████████████████████████████████████
|
||||
# B5 — Indexation du contenu texte Excalidraw (recherche TF-IDF)
|
||||
# ████████████████████████████████████████████████████████████████████████
|
||||
# Fixture lz-string "compressToBase64" générée par le vrai paquet
|
||||
# [email protected] (JS) en 2026-09-09, validée byte-pour-byte contre le port
|
||||
# Python de `_decompress_excalidraw`. Contient du texte accentué.
|
||||
LZ_COMPRESSED_DIAGRAM = (
|
||||
"N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcmTGAWxkbBoQBtUHgQh0"
|
||||
"ZcNDiIwKMJLnIFiABIdM9AAQBRNFjyES5evmwBzbIywAVeWPWZNu/TgLFJyBAEYADOQhWXwBf"
|
||||
"UhFcMQAjRWkxJQVyeLEAJQBLmgBXLlStOVQAC0ZsAEcMmC0AMi1GejkBEzNLa0w7ZRT0rJy8"
|
||||
"wpKyyuramHqQL3gAZn8QQPgAFhCwkFFEVBjYMXwYVDB0RnNOSUx0SI4xACF6ADu5LWwuKF"
|
||||
"NtvjhyUYBWAIRX4IBdcnQoKAAZW2cgQoGsPDEABEhnRmKEQAAzbCcATwYDBYJAA"
|
||||
)
|
||||
|
||||
_RAW_EXCALIDRAW_MD = """---
|
||||
excalidraw-plugin: parsed
|
||||
---
|
||||
```compressed-json
|
||||
{comp}
|
||||
```
|
||||
|
||||
Ce texte hors bloc ne doit pas polluer l'index (on extrait la scène uniquement).
|
||||
"""
|
||||
|
||||
|
||||
def _make_excalidraw_md(compressed: str) -> str:
|
||||
return _RAW_EXCALIDRAW_MD.format(comp=compressed)
|
||||
|
||||
|
||||
class TestExcalidrawDetection:
|
||||
"""Test that the /api/file endpoint correctly detects .excalidraw files."""
|
||||
@@ -164,3 +191,71 @@ class TestExcalidrawDetection:
|
||||
assert data["is_excalidraw"] is True
|
||||
assert len(data["excalidraw_data"]["elements"]) == 1
|
||||
assert data["excalidraw_data"]["elements"][0]["type"] == "ellipse"
|
||||
|
||||
|
||||
class TestExcalidrawIndexableTextB5:
|
||||
"""B5 — Indexation du texte des éléments (recherche TF-IDF)."""
|
||||
|
||||
def test_pure_json_extracts_element_text(self):
|
||||
from backend.indexer import extract_excalidraw_indexable
|
||||
|
||||
out = extract_excalidraw_indexable(json.dumps(EXCALIDRAW_WITH_CONTENT))
|
||||
assert "Hello Excalidraw" in out
|
||||
|
||||
def test_pure_json_skeleton_has_no_text(self):
|
||||
from backend.indexer import extract_excalidraw_indexable
|
||||
|
||||
out = extract_excalidraw_indexable(json.dumps(EXCALIDRAW_SKELETON))
|
||||
assert out.strip() == ""
|
||||
|
||||
def test_pure_json_binds_label_and_title(self):
|
||||
from backend.indexer import extract_excalidraw_indexable
|
||||
|
||||
payload = {
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"elements": [
|
||||
{"id": "r", "type": "rectangle", "label": "Boite bleue"},
|
||||
{"id": "t", "type": "text", "text": "texte simple",
|
||||
"originalText": "texte simple"},
|
||||
],
|
||||
"appState": {},
|
||||
"files": {},
|
||||
}
|
||||
out = extract_excalidraw_indexable(json.dumps(payload))
|
||||
assert "Boite bleue" in out
|
||||
assert "texte simple" in out
|
||||
|
||||
def test_lz_decompress_matches_js_fixture(self):
|
||||
"""Le port Python décode un payload lz-string produit par le vrai JS."""
|
||||
from backend.indexer import _decompress_excalidraw
|
||||
|
||||
data = _decompress_excalidraw(LZ_COMPRESSED_DIAGRAM)
|
||||
assert data is not None
|
||||
assert data["type"] == "excalidraw"
|
||||
elements = data["elements"]
|
||||
texts = {e.get("text") or e.get("label") for e in elements if isinstance(e, dict)}
|
||||
assert "Hello Excalidraw" in texts
|
||||
assert "Résumé technique & notes" in texts
|
||||
assert "Boîte importante" in texts
|
||||
|
||||
def test_lz_decompress_rejects_garbage(self):
|
||||
from backend.indexer import _decompress_excalidraw
|
||||
|
||||
assert _decompress_excalidraw("") is None
|
||||
assert _decompress_excalidraw("@@@not-lz@@@") is None
|
||||
assert _decompress_excalidraw("!!!") is None
|
||||
|
||||
def test_excalidraw_md_compressed_extracts_text(self):
|
||||
""".excalidraw.md (format plugin Obsidian) → texte indexé."""
|
||||
from backend.indexer import extract_excalidraw_indexable
|
||||
|
||||
out = extract_excalidraw_indexable(_make_excalidraw_md(LZ_COMPRESSED_DIAGRAM))
|
||||
assert "Hello Excalidraw" in out
|
||||
assert "Boîte importante" in out
|
||||
|
||||
def test_excalidraw_md_missing_block_returns_empty(self):
|
||||
from backend.indexer import extract_excalidraw_indexable
|
||||
|
||||
out = extract_excalidraw_indexable("---\nexcalidraw-plugin: parsed\n---\n(pas de bloc)")
|
||||
assert out.strip() == ""
|
||||
Reference in New Issue
Block a user