feat(ui): About page for this app, VocaHQ, and talk to us (#718)
* feat(ui): About page for this app, VocaHQ, and talk to us Settings About now has three groups. Vocalinux keeps the existing icon, version, AGPL-3.0, updater, and What's New. The website button opens vocalinux.com. Part of VocaHQ lists the family sites with PRODUCT.md status. Talk to us opens GitHub issues, Discord, X, and email. Official Design marks from VocaHQ/.github are vendored as-is and painted with iron-white ink. * fix(ui): unique About Open names and pin Design SVG hashes Open buttons on About still say Open. Each one now uses its tooltip as the accessible name so a screen reader can tell vocahq.com from vocalinux.com from Latest Release. The Talk-to-us SVG test pins sha256 of the four official marks from VocaHQ/.github 61c8eee. * fix(ui): circular app icon and About source code link The launcher and About header used the square teal fill. Switch both to the circular family logo (voca-logo.svg). Website stays vocalinux.com; Source code next to it opens the GitHub repo. * fix(appimage): pin pywhispercpp to the version install.sh declares * fix(appimage): pin pywhispercpp at both install sites * test(docs): match AGENTS.md uv.lock wording #727 dropped "unused until those phases land". The test still looked for that line, so any PR that runs Python tests against current main failed. Pin the current contract instead: uv.lock is authoritative, and requirements/*.txt are not hand-edited. --------- Co-authored-by: Sergey Silaev <hello@8am.dev>
This commit is contained in:
co-authored by
Sergey Silaev
parent
a5e723e1bd
commit
a37469c8d5
@@ -1,5 +1,6 @@
|
||||
"""Regression guards for AppImage packaging."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -67,3 +68,21 @@ def test_appimage_build_rebuilds_pywhispercpp_with_vulkan():
|
||||
assert "rebuild_pywhispercpp_vulkan" in text
|
||||
assert 'CC="${CC:-gcc}"' in text
|
||||
assert 'CXX="${CXX:-g++}"' in text
|
||||
|
||||
|
||||
def test_appimage_pins_pywhispercpp_to_the_installer_version():
|
||||
"""An unpinned source build takes whatever PyPI published that day.
|
||||
|
||||
pywhispercpp 1.5.1 landed mid-review and failed to compile with Vulkan on
|
||||
the runner, breaking both AppImage jobs while nothing in the repo changed.
|
||||
"""
|
||||
build = BUILD_SH.read_text(encoding="utf-8")
|
||||
# Both installs: the bundle install resolves the wheel's dependency range and
|
||||
# the Vulkan rebuild replaces it. Pinning one mixes two versions' libggml.
|
||||
assert build.count("pywhispercpp==$PYWHISPERCPP_VERSION") == 2
|
||||
assert 'PYWHISPERCPP_VERSION="$(sed' in build, "the pin must come from install.sh"
|
||||
|
||||
installer = (REPO_ROOT / "install.sh").read_text(encoding="utf-8")
|
||||
assert re.search(
|
||||
r'^PYWHISPERCPP_VERSION="\d+\.\d+\.\d+"', installer, re.M
|
||||
), "install.sh no longer declares the version build.sh reads"
|
||||
|
||||
@@ -170,6 +170,7 @@ def test_session_type_env_reads_use_default_expansion() -> None:
|
||||
|
||||
def test_agents_does_not_claim_requirements_are_consumed() -> None:
|
||||
text = AGENTS.read_text(encoding="utf-8")
|
||||
assert "are unused until those phases land" in text
|
||||
assert "consumed by `install.sh`" not in text
|
||||
assert "`uv.lock` is authoritative" in text
|
||||
assert "Do not edit `requirements/*.txt` by hand" in text
|
||||
assert "https://just.systems" in text
|
||||
|
||||
@@ -1020,5 +1020,194 @@ class TestSettingsNavigation(unittest.TestCase):
|
||||
self.assertIn('"escape"', body)
|
||||
|
||||
|
||||
class TestAboutPage(unittest.TestCase):
|
||||
"""About page: this app, VocaHQ family, talk-to-us. Updater stays."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.source = TestSettingsSearch._settings_source()
|
||||
cls.about = cls.source.split("def _build_about_section")[1].split("\n def ")[0]
|
||||
|
||||
def test_three_groups_keep_updater(self):
|
||||
self.assertIn('title="Vocalinux"', self.about)
|
||||
self.assertIn('title="Part of VocaHQ"', self.about)
|
||||
self.assertIn('title="Talk to us"', self.about)
|
||||
self.assertIn('title="Updates"', self.about)
|
||||
self.assertIn("What's New", self.about)
|
||||
self.assertIn("_on_check_updates_clicked", self.about)
|
||||
self.assertIn("self.update_channel_combo", self.about)
|
||||
|
||||
def test_product_status_and_honest_network_copy(self):
|
||||
self.assertIn("Available now on Linux (X11 and Wayland)", self.about)
|
||||
self.assertIn("After a model is downloaded", self.about)
|
||||
self.assertIn("unsigned developer alpha", self.about)
|
||||
self.assertIn("Android beta / iOS source build", self.about)
|
||||
self.assertIn("optional", self.about.lower())
|
||||
self.assertIn("self-hosted compute", self.about)
|
||||
self.assertNotIn("SmartScreen", self.about)
|
||||
self.assertNotIn("100% offline", self.about)
|
||||
self.assertNotIn("fully offline", self.about)
|
||||
self.assertNotIn("Coming soon", self.about)
|
||||
|
||||
def test_family_and_talk_urls(self):
|
||||
for url in (
|
||||
"https://vocalinux.com",
|
||||
"https://vocahq.com",
|
||||
"https://vocamac.com",
|
||||
"https://vocaphone.vocahq.com",
|
||||
"https://vocagateway.vocahq.com",
|
||||
"https://github.com/VocaHQ/vocalinux/issues",
|
||||
"https://discord.gg/UMJduhcqn",
|
||||
"https://x.com/vocahq",
|
||||
"mailto:hello@vocahq.com",
|
||||
):
|
||||
self.assertIn(url, self.source)
|
||||
self.assertIn('label="Report a bug or idea"', self.about)
|
||||
self.assertIn('"Discord"', self.about)
|
||||
self.assertIn('"X"', self.about)
|
||||
self.assertIn('"Email"', self.about)
|
||||
self.assertIn("https://github.com/VocaHQ/vocalinux/issues", self.about)
|
||||
self.assertIn("https://discord.gg/UMJduhcqn", self.source)
|
||||
self.assertIn("https://x.com/vocahq", self.source)
|
||||
self.assertIn("mailto:hello@vocahq.com", self.source)
|
||||
|
||||
def test_open_buttons_have_unique_accessible_names(self):
|
||||
helper = self.source.split("def _about_open_button")[1].split("\n def ")[0]
|
||||
self.assertIn("_set_accessible_name(button, tooltip)", helper)
|
||||
self.assertIn('if label == "Open"', helper)
|
||||
for name in (
|
||||
"Open vocalinux.com website",
|
||||
"Open vocahq.com",
|
||||
"Open vocalinux.com",
|
||||
"Open vocamac.com",
|
||||
"Open vocaphone.vocahq.com",
|
||||
"Open vocagateway.vocahq.com",
|
||||
"Open the Vocalinux GitHub repository",
|
||||
"Open the latest release page",
|
||||
):
|
||||
self.assertIn(name, self.source)
|
||||
self.assertIn("_set_accessible_name", self.about)
|
||||
|
||||
def test_website_is_product_site_not_only_github(self):
|
||||
self.assertIn("VOCALINUX_SITE_URL", self.about)
|
||||
self.assertIn("Product site at vocalinux.com", self.about)
|
||||
self.assertNotIn("Open the Vocalinux GitHub page", self.about)
|
||||
|
||||
def test_source_code_row_sits_beside_website(self):
|
||||
self.assertIn('title="Source code"', self.about)
|
||||
self.assertIn("GITHUB_REPO_URL", self.about)
|
||||
self.assertIn("Open the Vocalinux GitHub repository", self.about)
|
||||
self.assertLess(self.about.find("Website"), self.about.find("Source code"))
|
||||
|
||||
def test_can_open_about_urls(self):
|
||||
from vocalinux.ui.settings_dialog import (
|
||||
GITHUB_ISSUES_URL,
|
||||
GITHUB_REPO_URL,
|
||||
VOCAGATEWAY_SITE_URL,
|
||||
VOCAHQ_DISCORD_URL,
|
||||
VOCAHQ_MAILTO_URL,
|
||||
VOCAHQ_SITE_URL,
|
||||
VOCAHQ_X_URL,
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAPHONE_SITE_URL,
|
||||
_can_open_url,
|
||||
)
|
||||
|
||||
for url in (
|
||||
VOCALINUX_SITE_URL,
|
||||
VOCAHQ_SITE_URL,
|
||||
VOCAMAC_SITE_URL,
|
||||
VOCAPHONE_SITE_URL,
|
||||
VOCAGATEWAY_SITE_URL,
|
||||
GITHUB_REPO_URL,
|
||||
GITHUB_ISSUES_URL,
|
||||
VOCAHQ_DISCORD_URL,
|
||||
VOCAHQ_X_URL,
|
||||
VOCAHQ_MAILTO_URL,
|
||||
"https://github.com/VocaHQ/vocalinux/releases/tag/v0.15.0",
|
||||
):
|
||||
self.assertTrue(_can_open_url(url), url)
|
||||
self.assertFalse(_can_open_url("https://evil.example/payload"))
|
||||
self.assertFalse(_can_open_url("mailto:evil@example.com"))
|
||||
self.assertFalse(_can_open_url(""))
|
||||
|
||||
def test_talk_marks_are_official_currentcolor_svgs(self):
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
# VocaHQ/.github 61c8eee brand/vocahq/social/{discord,x,github,mail}.svg
|
||||
expected_sha256 = {
|
||||
"discord": "195092f3091352d662ceb1e9580877b03a943da0e1ec94a19f6aabac7dbf6dd7",
|
||||
"x": "0897f679c620010f09771464d39341d91f5a30a1370e126338b9a03bef47df34",
|
||||
"github": "1c1dd44a826db8b7d589cc48e9e8af2f395e960632c3ad978df8a00b2a56fc36",
|
||||
"mail": "32fd07b258990f4cba26476fb4f7b4649b9d5e0317000a8a183fa3fe29609972",
|
||||
}
|
||||
icons = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "src"
|
||||
/ "vocalinux"
|
||||
/ "resources"
|
||||
/ "icons"
|
||||
/ "scalable"
|
||||
)
|
||||
root_icons = Path(__file__).resolve().parents[1] / "resources" / "icons" / "scalable"
|
||||
for name, title in (
|
||||
("discord", "Discord"),
|
||||
("x", "X"),
|
||||
("github", "GitHub"),
|
||||
("mail", "Email"),
|
||||
):
|
||||
packaged_path = icons / f"{name}.svg"
|
||||
shipped_path = root_icons / f"{name}.svg"
|
||||
packaged = packaged_path.read_text(encoding="utf-8")
|
||||
shipped = shipped_path.read_text(encoding="utf-8")
|
||||
self.assertEqual(packaged, shipped)
|
||||
self.assertEqual(
|
||||
hashlib.sha256(packaged_path.read_bytes()).hexdigest(),
|
||||
expected_sha256[name],
|
||||
name,
|
||||
)
|
||||
self.assertEqual(
|
||||
hashlib.sha256(shipped_path.read_bytes()).hexdigest(),
|
||||
expected_sha256[name],
|
||||
name,
|
||||
)
|
||||
self.assertIn('viewBox="0 0 24 24"', packaged)
|
||||
self.assertIn('fill="currentColor"', packaged)
|
||||
self.assertIn(f"<title>{title}</title>", packaged)
|
||||
self.assertNotIn("#5865F2", packaged)
|
||||
self.assertNotIn("web-browser-symbolic", self.about)
|
||||
self.assertNotIn("mail-send-symbolic", self.about)
|
||||
|
||||
def test_launcher_icon_is_circular_family_logo(self):
|
||||
"""App/About icon is the approved teal circle lockup, not the square fill."""
|
||||
from pathlib import Path
|
||||
|
||||
packaged = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "src"
|
||||
/ "vocalinux"
|
||||
/ "resources"
|
||||
/ "icons"
|
||||
/ "scalable"
|
||||
/ "vocalinux.svg"
|
||||
)
|
||||
shipped = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "resources"
|
||||
/ "icons"
|
||||
/ "scalable"
|
||||
/ "vocalinux.svg"
|
||||
)
|
||||
packaged_svg = packaged.read_text(encoding="utf-8")
|
||||
shipped_svg = shipped.read_text(encoding="utf-8")
|
||||
self.assertEqual(packaged_svg, shipped_svg)
|
||||
self.assertIn('<circle cx="229" cy="231.5" r="221.5" fill="#0F6B57"/>', packaged_svg)
|
||||
self.assertIn('aria-label="Vocalinux"', packaged_svg)
|
||||
self.assertNotIn("<rect", packaged_svg)
|
||||
self.assertIn('get_icon_path("vocalinux")', self.about)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user