feat(home): search-driven link library with mobile card layout

- /search/aliases/ now matches alias/url/description/text/tags and returns
  id, link_type, detail_url, edit_url, click_count for management actions
- Library filter upgraded from client-side row filtering to live search
  cards: click card -> detail page, inline edit/delete, alias jumps
- Mobile: table rows render as cards (kills 620px horizontal scroll),
  hidden cells stay hidden, no viewport overflow
- Hero search no longer autofocuses on touch devices (no keyboard pop);
  desktop keeps autofocus via hover:fine media query
- Nav bar unified to light frosted-glass (was dark green) across all pages
- Fix hero-glow decorative overflow on mobile
- Add tests/test_search_aliases.py (8 cases)
This commit is contained in:
OpenClaw Sub-agent
2026-08-02 10:58:44 +10:00
parent 74557b4c64
commit dda47ee62e
4 changed files with 373 additions and 33 deletions
+107
View File
@@ -0,0 +1,107 @@
"""Tests for the enhanced /search/aliases/ endpoint (search-driven library)."""
import pytest
from django.test import Client
from django.urls import reverse
from links.models import Link, Tag
@pytest.fixture
def db_links(db):
"""A small, deterministic link library for search tests."""
l1 = Link.objects.create(
alias="github", original_url="https://github.com", link_type="LINK", click_count=5,
description="Code hosting",
)
l2 = Link.objects.create(
alias="gitlab", original_url="https://gitlab.com", link_type="LINK", click_count=2,
description="Another code host",
)
l3 = Link.objects.create(
alias="docs", original_url="https://docs.example.com/{page,default=index}", link_type="LINK",
click_count=0, description="Template link",
)
l4 = Link.objects.create(
alias="notify", original_url="", link_type="ACTION",
action_config={"action_type": "discord_send"}, click_count=0,
)
tag = Tag.objects.create(name="dev")
l1.tags.add(tag)
return [l1, l2, l3, l4]
@pytest.mark.django_db
def test_search_aliases_matches_alias_and_returns_enriched_fields(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "git"})
assert resp.status_code == 200
data = resp.json()
aliases = {item["alias"] for item in data}
assert aliases == {"github", "gitlab"}
github = next(i for i in data if i["alias"] == "github")
# Enriched management fields are present and correct
assert github["id"] == db_links[0].id
assert github["link_type"] == "LINK"
assert github["detail_url"] == f"/link/{db_links[0].id}/"
assert github["edit_url"] == f"/link/{db_links[0].id}/edit/"
assert github["click_count"] == 5
assert github["description"] == "Code hosting"
assert github["original_url"] == "https://github.com"
# Backwards-compatible fields (hero autocomplete relies on these)
assert github["url"].endswith("/github/")
@pytest.mark.django_db
def test_search_aliases_matches_original_url(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "gitlab.com"})
assert resp.status_code == 200
assert [i["alias"] for i in resp.json()] == ["gitlab"]
@pytest.mark.django_db
def test_search_aliases_matches_description(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "template link"})
assert resp.status_code == 200
assert [i["alias"] for i in resp.json()] == ["docs"]
@pytest.mark.django_db
def test_search_aliases_matches_tag_name(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "dev"})
assert resp.status_code == 200
assert [i["alias"] for i in resp.json()] == ["github"]
@pytest.mark.django_db
def test_search_aliases_priority_exact_first(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "github"})
assert resp.status_code == 200
assert resp.json()[0]["alias"] == "github"
@pytest.mark.django_db
def test_search_aliases_empty_query_returns_recent(client, db_links):
# Touch l2 so it becomes the most recently updated (auto_now needs the
# field explicitly listed in update_fields to refresh).
db_links[1].click_count = 3
db_links[1].save(update_fields=["click_count", "updated_at"])
resp = client.get(reverse("search_aliases"))
assert resp.status_code == 200
data = resp.json()
assert len(data) >= 4
assert data[0]["alias"] == "gitlab" # most recently updated first
@pytest.mark.django_db
def test_search_aliases_action_type_surfaces(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "notify"})
assert resp.status_code == 200
item = resp.json()[0]
assert item["link_type"] == "ACTION"
assert item["detail_url"] == f"/link/{db_links[3].id}/"
@pytest.mark.django_db
def test_search_aliases_no_results(client, db_links):
resp = client.get(reverse("search_aliases"), {"q": "zzzznothing"})
assert resp.status_code == 200
assert resp.json() == []