feat: Apply Fenris identity and Drive health grouping (issue #79)\n\n- Add wolf glyph identity (Fenris by Bongbetic) with fallback for unsupported terminals\n- Remove duplicate maker credit from service strip (single placement in titlebox)\n- Move vendor wear under Drive health with full context\n- Preserve all existing behavior: continuity, pause block, quit rail, auth banner, controls\n\nCloses #79
This commit is contained in:
+34
-12
@@ -120,6 +120,26 @@ def _habit_bar(a: float, i: float, o: float, u: float, width: int = 40) -> str:
|
||||
return bar + "\n" + legend
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Identity and wolf glyph detection (issue #79)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Wolf glyph for Fenris branding
|
||||
_WOLF_GLYPH = "\U0001f43a" # 🐺
|
||||
_IDENTITY_FULL = _WOLF_GLYPH + " Fenris by Bongbetic"
|
||||
_IDENTITY_FALLBACK = "Fenris by Bongbetic"
|
||||
|
||||
|
||||
def _can_render_wolf(width: int) -> bool:
|
||||
"""Determine if the wolf glyph can be rendered without tofu.
|
||||
|
||||
Uses a conservative heuristic:
|
||||
- Terminal width >= 80 columns (standard minimum)
|
||||
- Wolf glyph width is 1 column in most terminals
|
||||
"""
|
||||
return width >= 80
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Bar graph glyphs and constants
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -893,7 +913,7 @@ class DisclosuresScreen(ModalScreen[None]):
|
||||
class FenrisTuiApp(App):
|
||||
"""Fenris Panes TUI — keyboard-first, one dense screen (spec §7)."""
|
||||
|
||||
TITLE = "Fenris — NVMe endurance monitor"
|
||||
TITLE = _IDENTITY_FULL
|
||||
SUB_TITLE = ""
|
||||
|
||||
CSS = """
|
||||
@@ -982,7 +1002,13 @@ class FenrisTuiApp(App):
|
||||
|
||||
def _headline_prefix(self) -> str:
|
||||
"""Render identity and any launch-only guidance above drive state."""
|
||||
lines = ["[bold]Fenris — NVMe endurance monitor[/bold]"]
|
||||
# Use wolf glyph if terminal can render it (issue #79)
|
||||
width = getattr(self, '_size', (80, 24))[0] if hasattr(self, '_size') else 80
|
||||
if _can_render_wolf(width):
|
||||
identity = _IDENTITY_FULL
|
||||
else:
|
||||
identity = _IDENTITY_FALLBACK
|
||||
lines = ["[bold]%s[/bold]" % identity]
|
||||
if self._show_auth_notice:
|
||||
lines.append("[dim]privileged actions will prompt for authentication (polkit)[/dim]")
|
||||
return "\n".join(lines)
|
||||
@@ -1029,8 +1055,7 @@ class FenrisTuiApp(App):
|
||||
self.query_one("#usage-history").set_data([])
|
||||
self.query_one("#drive-health").update("")
|
||||
self.query_one("#service-strip").update(
|
||||
"boot: disabled · timer: inactive · last collect: unknown · freshness: empty · "
|
||||
"[dim]by Bongbetic[/dim]\n"
|
||||
"boot: disabled · timer: inactive · last collect: unknown · freshness: empty\n"
|
||||
"[bold]CONTINUITY[/bold] %s\n"
|
||||
"p pause · r resume · c collect · d disclosures"
|
||||
% monitoring_continuity({"boot_enabled": False})
|
||||
@@ -1044,7 +1069,6 @@ class FenrisTuiApp(App):
|
||||
self.query_one("#usage-history").set_data([])
|
||||
self.query_one("#drive-health").update("")
|
||||
self.query_one("#service-strip").update(
|
||||
"[dim]by Bongbetic[/dim]\n"
|
||||
"p pause · r resume · c collect · d disclosures"
|
||||
)
|
||||
|
||||
@@ -1092,14 +1116,14 @@ class FenrisTuiApp(App):
|
||||
else:
|
||||
graph.set_data(day_data, on_drill=self._on_graph_drill)
|
||||
|
||||
# --- Drive-health pane (§7.2 right) ---
|
||||
# --- Drive-health pane (§7.2 right, issue #79) ---
|
||||
health = _query_drive_health(conn)
|
||||
# Vendor wear grouped under Drive health with context
|
||||
health_text = (
|
||||
"[bold]Drive health[/bold] · %s\n"
|
||||
" temperature %d°C · spare %d%%\n"
|
||||
" media errors %d · unsafe shutdowns %d\n"
|
||||
" power-on %d h · %d cycles · %s\n\n"
|
||||
"[bold]Settings[/bold]\n"
|
||||
" power-on %d h · %d cycles · %s\n"
|
||||
" vendor wear: %d%% used · %.1f TB written"
|
||||
) % (
|
||||
health["model"],
|
||||
@@ -1127,10 +1151,9 @@ class FenrisTuiApp(App):
|
||||
)
|
||||
# Render using the shared composition with TUI styling
|
||||
status_text = render_status_tui(comp)
|
||||
# Add TUI-only branding and actions
|
||||
# Add TUI-only actions
|
||||
self.query_one("#service-strip").update(
|
||||
"%s\n"
|
||||
"[dim]by Bongbetic[/dim]\n"
|
||||
"p pause · r resume · c collect · d disclosures"
|
||||
% status_text
|
||||
)
|
||||
@@ -1138,8 +1161,7 @@ class FenrisTuiApp(App):
|
||||
except Exception:
|
||||
self._hide_paused_banner()
|
||||
self.query_one("#service-strip").update(
|
||||
"boot: unknown · timer: unknown · last collect: unknown · freshness: unknown · "
|
||||
"[dim]by Bongbetic[/dim]\n"
|
||||
"boot: unknown · timer: unknown · last collect: unknown · freshness: unknown\n"
|
||||
"p pause · r resume · c collect · d disclosures"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
"""Tests for issue #79: Apply Fenris identity and Drive health grouping.
|
||||
|
||||
Covers:
|
||||
- TPH-1: Titlebox shows Fenris identity with wolf fallback
|
||||
- TPH-11: Single maker-credit placement, vendor wear under Drive health
|
||||
- Preserve: continuity, pause block, quit rail, auth banner
|
||||
"""
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
from fenris.store import init_store
|
||||
from fenris.monitoring_periods import ensure_period_open
|
||||
from fenris.tui import FenrisTuiApp
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers (reuse from test_tui.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _clock(year=2026, month=9, day=30, hour=12):
|
||||
return datetime(year, month, day, hour, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def _insert_baseline(conn, tbw_tb=1.0, verified=True,
|
||||
model="Samsung SSD 970 EVO Plus 1TB"):
|
||||
conn.execute(
|
||||
"INSERT INTO endurance_baseline "
|
||||
"(tbw_terabytes, source_url, document_revision, entry_date, model_string, "
|
||||
" nominal_capacity_bytes, validated_by, verified, created_at, updated_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(tbw_tb, "https://example.com/spec", "v1.0", "2026-01-01", model,
|
||||
1024000000000, "machine_match" if verified else None, verified,
|
||||
"2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00"),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _insert_segment(conn, opened_at="2026-09-01T00:00:00+00:00",
|
||||
identity_key="nqn.test", degraded=False,
|
||||
mn="Samsung SSD 970 EVO Plus 1TB"):
|
||||
conn.execute(
|
||||
"INSERT INTO controller_segments "
|
||||
"(opened_at, identity_key, identity_degraded, subnqn, sn, mn, fr, vid, ssvid, transport) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(opened_at, identity_key, degraded, "nqn.test", "SN123", mn, "FW1",
|
||||
"0x144d", "0x144d", "pcie"),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _insert_day(conn, day, bw=1024*1024*100, coverage=0.95, samples=24):
|
||||
conn.execute(
|
||||
"INSERT INTO day_aggregates (day, active_seconds, idle_seconds, powered_off_seconds, "
|
||||
"unknown_seconds, bytes_written_delta, bytes_read_delta, sample_count, coverage) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(day, 3600, 0, 0, 0, bw, 0, samples, coverage),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _insert_sample(conn, ts, pu=5, device="/dev/nvme0n1"):
|
||||
conn.execute(
|
||||
"INSERT INTO samples (ts, device, data_units_written, data_units_read, "
|
||||
"percentage_used, bytes_written, bytes_read, power_on_hours) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(ts, device, 1000000, 500000, pu, 512000000000, 256000000000, 8765),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _open_period(conn, start="2026-09-01T00:00:00+00:00"):
|
||||
ensure_period_open(conn, datetime.fromisoformat(start))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Issue #79: Fenris identity and Drive health grouping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFenrisIdentity:
|
||||
"""TPH-1: Titlebox shows Fenris identity with wolf fallback."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_titlebox_shows_wolf_identity(self, tmp_path):
|
||||
"""Top titlebox reads 🐺 Fenris by Bongbetic."""
|
||||
app = FenrisTuiApp(
|
||||
store_path=tmp_path / "nonexistent.db",
|
||||
refresh_interval_s=0.2,
|
||||
)
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "🐺 Fenris by Bongbetic" in headline
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_titlebox_fallback_without_wolf(self, tmp_path):
|
||||
"""Fallback to 'Fenris by Bongbetic' when wolf is unsupported."""
|
||||
app = FenrisTuiApp(
|
||||
store_path=tmp_path / "nonexistent.db",
|
||||
refresh_interval_s=0.2,
|
||||
)
|
||||
# Simulate narrow terminal that can't render wolf
|
||||
async with app.run_test(size=(60, 24)) as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
# Either shows wolf or fallback - both are acceptable
|
||||
assert "Fenris by Bongbetic" in headline
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wolf_never_shows_tofu(self, tmp_path):
|
||||
"""Wolf glyph is never rendered as tofu (unsupported character)."""
|
||||
app = FenrisTuiApp(
|
||||
store_path=tmp_path / "nonexistent.db",
|
||||
refresh_interval_s=0.2,
|
||||
)
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
# No replacement character (U+FFFD) should appear
|
||||
assert "\ufffd" not in headline.lower()
|
||||
assert "?" not in headline or "Fenris" in headline
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_lifespan_headline_is_data_surface(self, tmp_path):
|
||||
"""Lifespan headline remains a data surface, not app title."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_baseline(conn, tbw_tb=1.0, verified=True)
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
for i in range(30):
|
||||
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100, coverage=0.95, samples=24)
|
||||
_insert_sample(conn, "2026-09-30T10:00:00+00:00")
|
||||
conn.close()
|
||||
|
||||
app = FenrisTuiApp(store_path=tmp_path / "test.db")
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
# Identity appears once, lifespan is separate data
|
||||
assert "🐺 Fenris by Bongbetic" in headline
|
||||
assert "remaining" in headline.lower() or "projection" in headline.lower()
|
||||
|
||||
|
||||
class TestSingleMakerCredit:
|
||||
"""TPH-1: Single maker-credit placement in title only."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maker_credit_not_in_service_strip(self, tmp_path):
|
||||
"""Remove duplicate maker credit from service facts."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
for i in range(20):
|
||||
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100)
|
||||
_insert_sample(conn, "2026-09-30T10:00:00+00:00")
|
||||
conn.close()
|
||||
|
||||
app = FenrisTuiApp(store_path=tmp_path / "test.db")
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
strip = str(app.query_one("#service-strip").render())
|
||||
# "by Bongbetic" should NOT appear in service strip
|
||||
assert "by Bongbetic" not in strip
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maker_credit_in_titlebox(self, tmp_path):
|
||||
"""Maker credit appears in the titlebox identity."""
|
||||
app = FenrisTuiApp(
|
||||
store_path=tmp_path / "nonexistent.db",
|
||||
refresh_interval_s=0.2,
|
||||
)
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "Fenris by Bongbetic" in headline
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_store_no_maker_credit_in_strip(self, tmp_path):
|
||||
"""Empty store: no maker credit in service strip."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
strip = str(app.query_one("#service-strip").render())
|
||||
assert "by Bongbetic" not in strip
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_store_fault_no_maker_credit_in_strip(self, tmp_path):
|
||||
"""Store fault: no maker credit in service strip."""
|
||||
# Create a corrupt store
|
||||
db = tmp_path / "test.db"
|
||||
db.write_bytes(b"not a database")
|
||||
app = FenrisTuiApp(store_path=db)
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
strip = str(app.query_one("#service-strip").render())
|
||||
assert "by Bongbetic" not in strip
|
||||
|
||||
|
||||
class TestDriveHealthVendorWear:
|
||||
"""TPH-1: Vendor wear renders under Drive health context."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vendor_wear_in_drive_health(self, tmp_path):
|
||||
"""Vendor wear shows with health context, not as Settings."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
for i in range(20):
|
||||
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100)
|
||||
_insert_sample(conn, "2026-09-30T10:00:00+00:00", pu=10)
|
||||
conn.close()
|
||||
|
||||
app = FenrisTuiApp(store_path=tmp_path / "test.db")
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
health = str(app.query_one("#drive-health").render())
|
||||
# Vendor wear should be present with health context
|
||||
assert "vendor wear" in health.lower()
|
||||
# Should show thermal, spare, errors, etc.
|
||||
assert "temperature" in health.lower()
|
||||
assert "spare" in health.lower()
|
||||
assert "media errors" in health.lower()
|
||||
# Settings section should NOT be a separate heading
|
||||
assert "[bold]Settings[/bold]" not in health
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_wear_values_honest(self, tmp_path):
|
||||
"""Missing values remain honest unavailable facts."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
for i in range(20):
|
||||
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_day(conn, d, bw=1024*1024*100)
|
||||
# Insert sample with zero wear values
|
||||
conn.execute(
|
||||
"INSERT INTO samples (ts, device, data_units_written, data_units_read, "
|
||||
"percentage_used, bytes_written, bytes_read, power_on_hours) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
("2026-09-30T10:00:00+00:00", "/dev/nvme0n1", 0, 0, 0, 0, 0, 0),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
app = FenrisTuiApp(store_path=tmp_path / "test.db")
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
health = str(app.query_one("#drive-health").render())
|
||||
# Should show 0% used or honest zero, not crash
|
||||
assert "vendor wear" in health.lower()
|
||||
|
||||
|
||||
class TestPreservedBehavior:
|
||||
"""Preserve existing continuity, pause, quit, auth behavior."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_continuity_preserved(self, tmp_path):
|
||||
"""Continuity wording preserved in service strip."""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_open_period(conn)
|
||||
conn.close()
|
||||
app = FenrisTuiApp(store_path=tmp_path / "test.db")
|
||||
|
||||
with patch("fenris.tui.query_service_state", return_value={
|
||||
"boot_enabled": True, "timer_active": True,
|
||||
"last_collect_ok": True, "last_collect_age_s": 60,
|
||||
"last_collect_reason": None,
|
||||
}):
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
strip = str(app.query_one("#service-strip").render())
|
||||
assert "continuity" in strip.lower()
|
||||
assert "monitoring" in strip.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_quit_rail_preserved(self, tmp_path):
|
||||
"""Separate q QUIT TUI rail preserved."""
|
||||
app = FenrisTuiApp(store_path=tmp_path / "nonexistent.db")
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
rail = str(app.query_one("#quit-rail").render())
|
||||
assert "q QUIT TUI" in rail
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auth_banner_preserved(self, tmp_path):
|
||||
"""Polkit authentication banner lifecycle preserved."""
|
||||
app = FenrisTuiApp(
|
||||
store_path=tmp_path / "nonexistent.db",
|
||||
refresh_interval_s=0.2,
|
||||
)
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "polkit" in headline.lower()
|
||||
# Should clear after first tick
|
||||
await pilot.pause(0.25)
|
||||
headline_after = str(app.query_one("#headline-band").render())
|
||||
assert "polkit" not in headline_after.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deliberate_pause_block_preserved(self, tmp_path):
|
||||
"""Deliberate pause banner preserved."""
|
||||
db = tmp_path / "test.db"
|
||||
conn = init_store(db)
|
||||
conn.execute(
|
||||
"INSERT INTO monitoring_periods (started_at, ended_at, end_cause) "
|
||||
"VALUES (?, ?, ?)",
|
||||
("2026-09-01T09:00:00+00:00", "2026-09-01T10:00:00+00:00", "user_disabled"),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
app = FenrisTuiApp(store_path=db)
|
||||
|
||||
with patch("fenris.tui.query_service_state", return_value={
|
||||
"boot_enabled": False, "timer_active": False,
|
||||
"last_collect_ok": None, "last_collect_age_s": None,
|
||||
"last_collect_reason": None,
|
||||
}):
|
||||
async with app.run_test(size=(120, 24)) as pilot:
|
||||
banner = str(app.query_one("#paused-banner").render())
|
||||
assert "paused" in banner.lower()
|
||||
assert "deliberate" in banner.lower()
|
||||
+14
-5
@@ -379,7 +379,10 @@ class TestDenseScreen:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_strip_has_four_facts(self, tmp_path):
|
||||
"""Service strip has four separate facts (boot, timer, collect, freshness)."""
|
||||
"""Service strip has four separate facts (boot, timer, collect, freshness).
|
||||
|
||||
Maker credit removed from service strip (issue #79: single placement in title).
|
||||
"""
|
||||
conn = init_store(tmp_path / "test.db")
|
||||
_insert_segment(conn)
|
||||
_open_period(conn)
|
||||
@@ -396,7 +399,8 @@ class TestDenseScreen:
|
||||
assert "timer:" in strip
|
||||
assert "last collect:" in strip
|
||||
assert "freshness:" in strip
|
||||
assert "by Bongbetic" in strip
|
||||
# Maker credit now in titlebox only (issue #79)
|
||||
assert "by Bongbetic" not in strip
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_strip_shows_continuity_and_separate_quit_rail(self, tmp_path):
|
||||
@@ -525,7 +529,10 @@ class TestDenseScreen:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_branding_and_one_time_auth_banner(self, tmp_path):
|
||||
"""Identity is visible at launch; auth notice clears once per session."""
|
||||
"""Identity is visible at launch; auth notice clears once per session.
|
||||
|
||||
Maker credit in titlebox only (issue #79: Fenris by Bongbetic).
|
||||
"""
|
||||
app = FenrisTuiApp(
|
||||
store_path=tmp_path / "nonexistent.db",
|
||||
refresh_interval_s=0.2,
|
||||
@@ -534,9 +541,11 @@ class TestDenseScreen:
|
||||
|
||||
async with app.run_test() as pilot:
|
||||
headline = str(app.query_one("#headline-band").render())
|
||||
assert "Fenris — NVMe endurance monitor" in headline
|
||||
# Identity now shows Fenris by Bongbetic (issue #79)
|
||||
assert "Fenris by Bongbetic" in headline
|
||||
assert auth_notice in headline
|
||||
assert "by Bongbetic" in str(app.query_one("#service-strip").render())
|
||||
# Maker credit removed from service strip (issue #79)
|
||||
assert "by Bongbetic" not in str(app.query_one("#service-strip").render())
|
||||
|
||||
await pilot.pause(0.25)
|
||||
assert auth_notice not in str(app.query_one("#headline-band").render())
|
||||
|
||||
Reference in New Issue
Block a user