Skip to content

Conversation

@JakubMastalerz
Copy link
Collaborator

@JakubMastalerz JakubMastalerz commented Oct 16, 2025

Link to Ticket

Description of Changes Made

  • Added Organization schema markup to the homepage using JSON-LD
  • Updated logo URL to use higher resolution android-chrome-512x512.png following Google's recommendations
  • Implemented comprehensive test coverage for Organization JSON-LD functionality
  • Added social media links (Bluesky, LinkedIn, Instagram) to the Organization schema
  • Refactored JSON-LD tests to use direct template rendering for better reliability
  • Cleaned up test imports and improved code organization

How to Test

  1. Visit the homepage at /
  2. View page source and verify the Organization JSON-LD is present in the <head> section
  3. Check that the logo URL points to https://torchbox.com/android-chrome-512x512.png
  4. Verify social media links are included in the sameAs array
  5. Run the test suite: fab run-test to ensure all tests pass
  6. Validate the schema using Google's Rich Results Test tool

Screenshots

Expand to see more

The Organization JSON-LD appears in the page source as:

<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Organization",
    "name": "Torchbox",
    "url": "https://torchbox.com/",
    "logo": "https://torchbox.com/android-chrome-512x512.png",
    "sameAs": [
        "https://bsky.app/profile/torchbox.com",
        "https://www.linkedin.com/company/torchbox",
        "https://www.instagram.com/torchboxltd/"
    ]
}
</script>

MR Checklist

  • Add a description of your pull request and instructions for the reviewer to verify your work.
  • If your pull request is for a specific ticket, link to it in the description.
  • Stay on point and keep it small so the merge request can be easily reviewed.
  • Tests and linting passes.

Unit tests

  • Added
  • Not required

Documentation

Browser testing

  • I have tested in the following browsers and environments (edit the list as required)
    • Latest version of Chrome on mac
    • Latest version of Firefox on mac
    • Latest version of Safari on mac
    • Safari on last two versions of iOS
    • Chrome on last two versions of Android
  • Not required

Data protection

  • Not relevant
  • This adds new sources of PII and documents it and modifies Birdbath processors accordingly

Light and dark mode

  • I have tested the changes in both light and dark mode
  • The change is not relevant to dark and light mode

Accessibility

  • Automated WCAG 2.1 tests pass
  • HTML validation passes
  • Manual WCAG 2.1 tests completed
  • I have tested in a screen reader
  • I have tested in high-contrast mode
  • Any animations removed for prefers-reduced-motion
  • Not required

Sustainability

  • Images are optimised and lazy-loading used where appropriate
  • SVGs have been optimised
  • Performance and transfer of data considered
  • If JavaScript is needed alternatives have been considered
  • Not required

Pattern library

  • The pattern library component for this template displays correctly, and does not break parent templates
  • The styleguide is updated if relevant
  • Changes are not relevant the pattern library

@JakubMastalerz JakubMastalerz self-assigned this Oct 16, 2025
"@type": "Organization",
"name": "Torchbox",
"url": "https://torchbox.com/",
"logo": "https://torchbox.com/apple-touch-icon.png",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to go with here actually

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched in f5b769c

JakubMastalerz and others added 6 commits October 16, 2025 14:55
Tests were failing due to URL generation issues in test environment.
Direct template rendering bypasses Wagtail URL resolution problems
and provides more reliable test coverage for JSON-LD functionality.
Ensures Organization schema implementation is properly validated
with tests for structure, social links, logo URL, and template inclusion.
Prevents regression of homepage JSON-LD functionality.
Switched from apple-touch-icon.png (180x180) to android-chrome-512x512.png (512x512)
for better rich results display and Google's recommended higher resolution.

Follows Google PWA standards and provides better quality for search engine rich snippets.

Standards followed:
- Google PWA Manifest requirements: https://developers.google.com/web/fundamentals/web-app-manifest
- Schema.org Organization logo recommendations: https://schema.org/Organization
- Google Rich Results guidelines: https://developers.google.com/search/docs/appearance/structured-data/logo

The 512x512px resolution exceeds Google's minimum requirement of 112x112px
and follows PWA best practices for high-quality icon display.
- Move all imports to top of file
- Remove redundant imports from test methods
- Improve code organization and readability
- Add _extract_jsonld_by_type() helper to eliminate repetitive JSON-LD extraction
- Add _get_organization_jsonld() helper for cleaner test methods
- Reduce test_organization_jsonld_social_links from 30+ lines to 8 lines
- All tests still pass with same coverage
- Much more maintainable and readable
@kevinhowbrook
Copy link
Collaborator

I've upted the PR to include tests and a more 'standard' icon.

schematest

@kevinhowbrook kevinhowbrook marked this pull request as ready for review October 23, 2025 13:29
@kevinhowbrook kevinhowbrook changed the title WIP: Add Schema.org schemas to pages Adds Schema.org schemas to pages Oct 23, 2025
Copy link
Collaborator

@bmispelon bmispelon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some suggestions for improvements, mostly in the tests.

I also notice that you're using a lot of inline imports rather than putting them all at the top of the file as is normally the case, was there a reason for that?

Comment on lines +38 to +60
start_marker = '<script type="application/ld+json">'
end_marker = "</script>"

json_scripts = []
start_idx = 0
while True:
start_idx = content.find(start_marker, start_idx)
if start_idx == -1:
break
end_idx = content.find(end_marker, start_idx)
if end_idx == -1:
break

json_content = content[start_idx + len(start_marker) : end_idx].strip()
try:
json_data = json.loads(json_content)
if json_data.get("@type") == jsonld_type:
json_scripts.append(json_data)
except json.JSONDecodeError:
pass
start_idx = end_idx + len(end_marker)

return json_scripts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Use bs4.BeautifulSoup instead of string manipulation:

soup = BeautifulSoup(content, "html.parser")
scripts = [
    json.loads(tag.string)
    for tag in soup.find_all("script", type="application/ld+json")
]
return [script for script in scripts if script["@type"] == jsonld_type]

Comment on lines +64 to +65
context = self._get_template_context(self.homepage)
content = render_to_string("patterns/pages/home/home_page.html", context)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Use the builtin test client instead:

Suggested change
context = self._get_template_context(self.homepage)
content = render_to_string("patterns/pages/home/home_page.html", context)
response = self.client.get(self.homepage.url)
# then use `response.content` where you'd use `content` before

Comment on lines +92 to +104
self.assertIn("sameAs", org_data)
same_as = org_data["sameAs"]
self.assertIsInstance(same_as, list)
self.assertGreater(len(same_as), 0)

# Check for expected social media links
expected_links = [
"https://bsky.app/profile/torchbox.com",
"https://www.linkedin.com/company/torchbox",
"https://www.instagram.com/torchboxltd/",
]
for link in expected_links:
self.assertIn(link, same_as)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: test the list directly:

Suggested change
self.assertIn("sameAs", org_data)
same_as = org_data["sameAs"]
self.assertIsInstance(same_as, list)
self.assertGreater(len(same_as), 0)
# Check for expected social media links
expected_links = [
"https://bsky.app/profile/torchbox.com",
"https://www.linkedin.com/company/torchbox",
"https://www.instagram.com/torchboxltd/",
]
for link in expected_links:
self.assertIn(link, same_as)
self.assertEqual(
org_data["sameAs"],
[...],
)

Or if you don't want to test the exact order, you can use assertCountEqual instead of assertEqual.

Comment on lines +106 to +116
def test_organization_jsonld_social_links(self):
"""Test that Organization JSON-LD includes correct social media links."""
org_data = self._get_organization_jsonld()
same_as = org_data["sameAs"]

# Test that all social links are valid URLs
for link in same_as:
self.assertTrue(link.startswith("http"), f"Invalid social link: {link}")

# Test that we have the expected number of social links
self.assertGreaterEqual(len(same_as), 3)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should already be covered by the previous test, no?


# Test that logo URL is correct
self.assertEqual(logo_url, "https://torchbox.com/android-chrome-512x512.png")
self.assertTrue(logo_url.startswith("https://"), "Logo URL should be HTTPS")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion is not needed since the previous line already guarantees the URL starts with https://.

Comment on lines +202 to +208
# This test would need to be run on an actual blog page
# For now, we'll just verify the template exists
try:
template = get_template("patterns/pages/blog/blog-posting-jsonld.html")
self.assertIsNotNone(template)
except Exception as e:
self.fail(f"Blog posting JSON-LD template not found: {e}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: test against an actual blog page:

page = BlogPageFactory(parent=self.homepage)
response = self.client.get(page.url)
schema = self._extract_jsonld_by_type(response.content, "BlogPosting")
self.assertEqual(schema["headline"], ...)

Comment on lines +210 to +218
def test_breadcrumb_template_exists(self):
"""Test that breadcrumb JSON-LD template exists."""
try:
template = get_template(
"patterns/navigation/components/breadcrumbs-jsonld.html"
)
self.assertIsNotNone(template)
except Exception as e:
self.fail(f"Breadcrumb JSON-LD template not found: {e}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already covered by test_breadcrumb_template_included(), no?

Comment on lines +220 to +223
def test_jsonld_script_tags_present(self):
"""Test that JSON-LD script tags are present in the rendered HTML."""
org_data = self._get_organization_jsonld()
self.assertIsNotNone(org_data)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already covered by test_base_template_includes_jsonld_block()?

Comment on lines +225 to +228
def test_multiple_jsonld_scripts(self):
"""Test that multiple JSON-LD scripts can be present on a page."""
org_data = self._get_organization_jsonld()
self.assertEqual(org_data["@type"], "Organization")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this test makes sure that multiple scripts can be on a page. Its code is the same as test_base_template_includes_jsonld_block().

@@ -0,0 +1,29 @@
{% load wagtailcore_tags wagtailimages_tags %}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template shows the limits of using templates to generate JSON. It seems very fragile to me:

  • Everything has to use |escapejs
  • tags like {% if ... %} need to go on the line above where they'd normally go to avoid extra blank lines
  • Similarly you have to be careful about trailing commas.

It would be a lot more robust if we were to turn this into a template tag that would build the right dict in Python then use json.dumps to generate valid JSON. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants