Add tests for About page, header brand, and site icons (#442)

- Implement test for About page to ensure it includes the Claracle image linked to Spotify. - Create tests for the header brand to verify the use of the Claracle image asset and its existence. - Add tests for site icon metadata to confirm the correct usage of the Claracle image in favicons and web manifest, as well as the existence of required PNG files. Co-authored-by: Copilot <copilot@github.com>

Juan Manuel Servera committed Jun 12, 2026 at 22:44 UTC 598d5d063e2d3928e6f8db8ea44eab4bab4d5870
23 files changed +155 -39
.vscode/settings.json new
+7
@@ -0,0 +1,7 @@
1 +{
2 + "python.testing.pytestArgs": [
3 + "."
4 + ],
5 + "python.testing.unittestEnabled": false,
6 + "python.testing.pytestEnabled": true
7 +}
\ No newline at end of file
assets/css/common/header.css
+7
@@ -49,6 +49,13 @@
49 color: var(--color-accent);
50 }
51
52 +.site-brand__mark img {
53 + width: 100%;
54 + height: 100%;
55 + border-radius: calc(var(--radius-md) - 2px);
56 + object-fit: cover;
57 +}
58 +
59 .site-header__cluster {
60 gap: var(--space-3);
61 }
assets/images/_0e614603-592a-452d-9b27-fc552644a790.jpeg
Binary files /dev/null and b/assets/images/_0e614603-592a-452d-9b27-fc552644a790.jpeg differ
assets/images/_d2de8277-55b9-4a57-94b7-447992e757e9.jpeg
Binary files /dev/null and b/assets/images/_d2de8277-55b9-4a57-94b7-447992e757e9.jpeg differ
assets/images/_fb9af526-dbab-4b40-af04-5d8da3d2eda9.jpeg
Binary files /dev/null and b/assets/images/_fb9af526-dbab-4b40-af04-5d8da3d2eda9.jpeg differ
assets/images/claracle.jpeg
Binary files /dev/null and b/assets/images/claracle.jpeg differ
content/about/_index.md
+2
@@ -8,6 +8,8 @@ description = 'Learn what Claracle publishes and how to follow its GitHub trend
8
9 ## About Claracle
10
11 +[![Claracle visual identity](/images/claracle.jpeg)](https://open.spotify.com/show/033xdn5nDMoCWxB3bss2dB)
12 +
13 Claracle surfaces the strongest weekly signal from GitHub activity, then organizes that analysis into monthly and yearly views for long-term pattern spotting.
14
15 Use the homepage to jump straight into the latest summary, scan the archive, or move up to broader rollups when you want a bigger-picture view.
content/methodology/_index.md
+1 -1
@@ -49,7 +49,7 @@ These caveats are part of the analysis, not a disclaimer that makes the output n
49
50 ## Signal Check podcast
51
52 -Claracle publishes a companion podcast, **Signal Check**, derived from the weekly written article. Signal Check is an 8–10 minute two-host conversational episode that highlights the week's signal picks, noise calls, gaps, and predictions. It is distributed through an external podcast platform (currently Spotify); the Claracle website links to the external podcast page but does not host audio.
52 +Claracle publishes a companion podcast, **Signal Check**, derived from the weekly written article. Signal Check is an approximately 5-minute two-host conversational episode that highlights the week's signal picks, noise calls, gaps, and predictions. It is distributed through Spotify; the Claracle website links to the external podcast page but does not host audio.
53
54 ### How Signal Check is produced
55
hugo.toml
+2 -2
@@ -35,8 +35,8 @@ rssLimit = 20
35 mainSections = ['weekly', 'monthly', 'yearly']
36
37 # External podcast link (leave empty to hide)
38 - # When set, a "Podcast" link appears in the site footer navigation.
39 - podcast_url = ""
38 + # When set, a "Podcast" link appears in the site footer navigation and weekly report shortcuts.
39 + podcast_url = "https://open.spotify.com/show/033xdn5nDMoCWxB3bss2dB"
40
41 [params.homeInfoParams]
42 Title = 'Weekly tech signal from GitHub'
layouts/partials/head.html
+8 -6
@@ -86,12 +86,14 @@
86 {{- end -}}
87
88 {{- /* Favicons */}}
89 -<link rel="icon" href="/favicon.ico" sizes="any">
90 -<link rel="icon" type="image/svg+xml" href="/images/squadscope-icon.svg">
91 -<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
92 -<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
93 -<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
94 -<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#0066CC">
89 +{{- with resources.Get "images/claracle.jpeg" }}
90 +{{- $favicon16 := .Fill "16x16 png" }}
91 +{{- $favicon32 := .Fill "32x32 png" }}
92 +{{- $appleTouchIcon := .Fill "180x180 png" }}
93 +<link rel="icon" type="image/png" sizes="16x16" href="{{ $favicon16.RelPermalink }}">
94 +<link rel="icon" type="image/png" sizes="32x32" href="{{ $favicon32.RelPermalink }}">
95 +<link rel="apple-touch-icon" sizes="180x180" href="{{ $appleTouchIcon.RelPermalink }}">
96 +{{- end }}
97 <link rel="manifest" href="/site.webmanifest">
98 <meta name="theme-color" content="#0066CC">
99 <meta name="msapplication-TileColor" content="#0066CC">
layouts/partials/header.html
+15 -13
@@ -2,25 +2,16 @@
2 <nav class="header-nav" aria-label="Primary">
3 <a class="site-brand" href="{{ "" | absLangURL }}" accesskey="h" title="Claracle (Alt + H)" aria-label="Claracle home">
4 <span class="site-brand__mark" aria-hidden="true">
5 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="100%" height="100%" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
6 - <line x1="32" y1="4" x2="32" y2="14"/>
7 - <circle cx="32" cy="4" r="3" fill="currentColor" stroke="none"/>
8 - <rect x="10" y="14" width="44" height="36" rx="8" ry="8"/>
9 - <circle cx="22" cy="30" r="9"/>
10 - <circle cx="22" cy="30" r="4" fill="currentColor" stroke="none"/>
11 - <circle cx="42" cy="30" r="9"/>
12 - <circle cx="42" cy="30" r="4" fill="currentColor" stroke="none"/>
13 - <line x1="31" y1="30" x2="33" y2="30" stroke-width="4"/>
14 - <line x1="26" y1="42" x2="38" y2="42"/>
15 - <line x1="28" y1="45" x2="36" y2="45"/>
16 - <path d="M14 54 L14 58 Q14 62 18 62 L46 62 Q50 62 50 58 L50 54" stroke-width="2"/>
17 - </svg>
5 + {{- with resources.Get "images/claracle.jpeg" }}
6 + <img src="{{ .RelPermalink }}" width="32" height="32" alt="">
7 + {{- end }}
8 </span>
9 <span class="site-brand__text">Claracle</span>
10 </a>
11
12 <div class="site-header__cluster">
13 {{- $currentPage := . }}
14 + {{- $podcastURL := site.Params.podcast_url | default "" | strings.TrimSpace }}
15 {{- define "primary-menu-items" }}
16 {{- $currentPage := . }}
17 {{- range site.Menus.main }}
@@ -49,6 +40,17 @@
40 </details>
41
42 <div class="site-actions" aria-label="Site actions">
43 + {{- with $podcastURL }}
44 + <a class="icon-button spotify-button" href="{{ . | safeURL }}" target="_blank" rel="noopener noreferrer" aria-label="Spotify" title="Listen on Spotify">
45 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
46 + <circle cx="12" cy="12" r="10"></circle>
47 + <path d="M8 9.5c2.9-.9 5.8-.7 8.5.7"></path>
48 + <path d="M8.7 12.4c2.3-.7 4.7-.5 6.8.5"></path>
49 + <path d="M9.4 15.2c1.6-.4 3.2-.3 4.7.4"></path>
50 + </svg>
51 + </a>
52 + {{- end }}
53 +
54 <a class="icon-button github-button" href="https://github.com/jmservera/SquadScope" target="_blank" rel="noopener" aria-label="GitHub">
55 <svg fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
56 <path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"></path>
layouts/partials/report-shortcuts.html
+4
@@ -7,6 +7,7 @@
7 {{- $isWeekly := eq $page.Type "weekly" -}}
8 {{- $monthlyURL := "monthly/" | absURL -}}
9 {{- $yearlyURL := "yearly/" | absURL -}}
10 +{{- $podcastURL := site.Params.podcast_url | default "" | strings.TrimSpace -}}
11 {{- if $isWeekly -}}
12 {{- with site.GetPage (printf "/monthly/%s/%s" ($page.Date.Format "2006") ($page.Date.Format "01")) -}}
13 {{- $monthlyURL = .RelPermalink -}}
@@ -25,4 +26,7 @@
26 <a href="{{ $categoriesURL }}">Categories</a>
27 <a href="{{ $searchURL }}">Search</a>
28 <a href="{{ $rssURL }}">RSS</a>
29 + {{- if and $isWeekly $podcastURL }}
30 + <a href="{{ $podcastURL | safeURL }}" target="_blank" rel="noopener noreferrer">Spotify</a>
31 + {{- end }}
32 </nav>
reply_thread1.graphql deleted
-10
@@ -1,10 +0,0 @@
1 -mutation {
2 - addPullRequestReviewThreadReply(input: {
3 - threadId: "PRRT_kwDOSgq4hM6C3UXy"
4 - body: "✅ Fixed: Removed the submodule initialization instruction from the rollout checklist. The project does not use git submodules."
5 - }) {
6 - comment {
7 - id
8 - }
9 - }
10 -}
static/android-chrome-192x192.png
Binary files a/static/android-chrome-192x192.png and b/static/android-chrome-192x192.png differ
static/android-chrome-512x512.png
Binary files a/static/android-chrome-512x512.png and b/static/android-chrome-512x512.png differ
static/apple-touch-icon.png
Binary files a/static/apple-touch-icon.png and b/static/apple-touch-icon.png differ
static/favicon-16x16.png
Binary files a/static/favicon-16x16.png and b/static/favicon-16x16.png differ
static/favicon-32x32.png
Binary files a/static/favicon-32x32.png and b/static/favicon-32x32.png differ
static/site.webmanifest
+2 -1
@@ -10,7 +10,8 @@
10 {
11 "src": "/android-chrome-512x512.png",
12 "sizes": "512x512",
13 - "type": "image/png"
13 + "type": "image/png",
14 + "purpose": "any maskable"
15 }
16 ],
17 "theme_color": "#0066CC",
tests/test_about_page.py new
+15
@@ -0,0 +1,15 @@
1 +"""Tests for About page content."""
2 +
3 +from __future__ import annotations
4 +
5 +from pathlib import Path
6 +
7 +REPO_ROOT = Path(__file__).resolve().parent.parent
8 +
9 +
10 +def test_about_page_includes_claracle_image() -> None:
11 + """About page should link the full Claracle image to Spotify near the top."""
12 + about = (REPO_ROOT / "content" / "about" / "_index.md").read_text(encoding="utf-8")
13 + linked_image = "[![Claracle visual identity](/images/claracle.jpeg)](https://open.spotify.com/show/033xdn5nDMoCWxB3bss2dB)"
14 + assert linked_image in about
15 + assert about.index(linked_image) < about.index("Claracle surfaces")
\ No newline at end of file
tests/test_header_brand.py new
+20
@@ -0,0 +1,20 @@
1 +"""Tests for the site header brand mark."""
2 +
3 +from __future__ import annotations
4 +
5 +from pathlib import Path
6 +
7 +REPO_ROOT = Path(__file__).resolve().parent.parent
8 +
9 +
10 +def test_header_brand_uses_claracle_image_asset() -> None:
11 + """Header brand mark must render the Claracle image through Hugo resources."""
12 + header = (REPO_ROOT / "layouts" / "partials" / "header.html").read_text(encoding="utf-8")
13 + assert 'resources.Get "images/claracle.jpeg"' in header
14 + assert '<img src="{{ .RelPermalink }}" width="32" height="32" alt="">' in header
15 + assert "<svg" not in header.split('<span class="site-brand__text">Claracle</span>', maxsplit=1)[0]
16 +
17 +
18 +def test_claracle_brand_image_exists() -> None:
19 + """Brand image asset must exist at the path used by the header template."""
20 + assert (REPO_ROOT / "assets" / "images" / "claracle.jpeg").is_file()
\ No newline at end of file
tests/test_podcast_link.py
+22 -6
@@ -9,9 +9,10 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
9
10
11 def test_podcast_url_param_exists_in_hugo_config() -> None:
12 - """hugo.toml must define podcast_url param (can be empty)."""
12 + """hugo.toml must define the live Spotify podcast URL."""
13 config = (REPO_ROOT / "hugo.toml").read_text(encoding="utf-8")
14 assert "podcast_url" in config
15 + assert 'podcast_url = "https://open.spotify.com/show/033xdn5nDMoCWxB3bss2dB"' in config
16
17
18 def test_footer_conditionally_renders_podcast_link() -> None:
@@ -29,8 +30,23 @@ def test_footer_conditionally_renders_podcast_link() -> None:
30 ), "Footer podcast link must use dot context piped through safeURL with target=_blank and rel=noopener noreferrer"
31
32
32 -def test_podcast_url_defaults_to_empty() -> None:
33 - """When podcast_url is empty, no link should render (Hugo `with` handles this)."""
34 - config = (REPO_ROOT / "hugo.toml").read_text(encoding="utf-8")
35 - # The default value must be empty string
36 - assert 'podcast_url = ""' in config
33 +def test_report_shortcuts_link_weekly_reports_to_spotify() -> None:
34 + """Weekly report shortcuts must expose the configured Spotify URL."""
35 + shortcuts = (REPO_ROOT / "layouts" / "partials" / "report-shortcuts.html").read_text(encoding="utf-8")
36 + assert 'site.Params.podcast_url | default "" | strings.TrimSpace' in shortcuts
37 + assert "if and $isWeekly $podcastURL" in shortcuts
38 + assert re.search(
39 + r'<a\s+href="{{\s*\$podcastURL\s*\|\s*safeURL\s*}}"[^>]*target="_blank"[^>]*rel="noopener noreferrer"[^>]*>Spotify</a>',
40 + shortcuts,
41 + ), "Weekly shortcuts must link to Spotify with target=_blank and rel=noopener noreferrer"
42 +
43 +
44 +def test_header_exposes_spotify_button_when_podcast_url_is_configured() -> None:
45 + """Header actions must expose the configured Spotify URL."""
46 + header = (REPO_ROOT / "layouts" / "partials" / "header.html").read_text(encoding="utf-8")
47 + assert 'site.Params.podcast_url | default "" | strings.TrimSpace' in header
48 + assert 'class="icon-button spotify-button"' in header
49 + assert re.search(
50 + r'<a\s+class="icon-button spotify-button"\s+href="{{\s*\.\s*\|\s*safeURL\s*}}"[^>]*target="_blank"[^>]*rel="noopener noreferrer"[^>]*aria-label="Spotify"',
51 + header,
52 + ), "Header Spotify button must use configured URL with target=_blank and rel=noopener noreferrer"
tests/test_site_icons.py new
+50
@@ -0,0 +1,50 @@
1 +"""Tests for site icon metadata."""
2 +
3 +from __future__ import annotations
4 +
5 +import json
6 +from pathlib import Path
7 +
8 +REPO_ROOT = Path(__file__).resolve().parent.parent
9 +
10 +
11 +def test_head_favicons_use_claracle_image_asset() -> None:
12 + """Head template must derive browser icons from the Claracle image asset."""
13 + head = (REPO_ROOT / "layouts" / "partials" / "head.html").read_text(encoding="utf-8")
14 + assert 'resources.Get "images/claracle.jpeg"' in head
15 + assert '.Fill "16x16 png"' in head
16 + assert '.Fill "32x32 png"' in head
17 + assert '.Fill "180x180 png"' in head
18 + assert "/images/squadscope-icon.svg" not in head
19 + assert "/favicon-16x16.png" not in head
20 + assert "/favicon-32x32.png" not in head
21 + assert "/apple-touch-icon.png" not in head
22 +
23 +
24 +def test_web_manifest_uses_claracle_image_icon() -> None:
25 + """Web app manifest must point at compatible PNG site icons."""
26 + manifest = json.loads((REPO_ROOT / "static" / "site.webmanifest").read_text(encoding="utf-8"))
27 + icons = manifest["icons"]
28 + assert icons == [
29 + {"src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png"},
30 + {
31 + "src": "/android-chrome-512x512.png",
32 + "sizes": "512x512",
33 + "type": "image/png",
34 + "purpose": "any maskable",
35 + },
36 + ]
37 +
38 +
39 +def test_static_icon_png_files_exist() -> None:
40 + """All browser and app icon files must exist as PNG files."""
41 + for icon_path in (
42 + "favicon-16x16.png",
43 + "favicon-32x32.png",
44 + "apple-touch-icon.png",
45 + "android-chrome-192x192.png",
46 + "android-chrome-512x512.png",
47 + ):
48 + icon = REPO_ROOT / "static" / icon_path
49 + assert icon.is_file()
50 + assert icon.read_bytes().startswith(b"\x89PNG\r\n\x1a\n")
\ No newline at end of file