- journalArticleMeta(post) answers og:type article and the publish and update times; PostPage always sets them, the CMS-owned keys keep the no-answer fallback (L4 WR-02) - expect-acme.txt: one og:type article and the article times with a CMS answer (heading-on) and without one (uncategorised-note)
158 lines
12 KiB
Plaintext
158 lines
12 KiB
Plaintext
# SSR expectations against the acme fixtures (testing/fixtures/acme).
|
|
# Grammar: STATUS PATH [contains=TEXT] [notcontains=TEXT] [count=TEXT:N]
|
|
# [type=PREFIX] [header=NAME: VALUE] [noheader=NAME]; resolves PATH N
|
|
# The consumer enables en (default, no prefix) and pl (/pl prefix). The
|
|
# fixture SEO API (fixtures/acme/seo) answers the site URL https://acme.test.
|
|
|
|
# The post page on the shared SEO layer: exactly one H1 in both Journal H1
|
|
# modes, one canonical and one JSON-LD script (UI-SPEC L6, D-97).
|
|
200 /blog/heading-on count=<h1:1 contains=<h1 class="journal-post__title" count=application/ld+json:1 count=rel="canonical":1
|
|
200 /blog/heading-on contains=<h2>Body heading one</h2> notcontains=<p class="journal-post__title"
|
|
200 /blog/heading-off count=<h1:1 contains=<p class="journal-post__title" count=application/ld+json:1 count=rel="canonical":1
|
|
200 /blog/heading-off contains=<h1>Body heading one</h1>
|
|
# The CMS head wins: its title, no second title.
|
|
200 /blog/heading-on contains=<title>Heading On Sample | Acme Blog</title> count=<title>:1
|
|
# Without a CMS answer the post's own title and description are the fallback.
|
|
200 /blog/uncategorised-note contains=<title>Uncategorised Note</title> contains=Uncategorised Note: a short summary.
|
|
# og:type article and the article times on every post page, with a CMS
|
|
# answer (heading-on) and without one (uncategorised-note): the CMS head
|
|
# never carries them (L4 WR-02).
|
|
200 /blog/heading-on count=property="og:type":1 contains=<meta property="og:type" content="article"> contains=<meta property="article:published_time" content="2025-12-20T09:00:00+00:00"> contains=<meta property="article:modified_time" content="2025-12-20T09:00:00+00:00">
|
|
200 /blog/uncategorised-note count=property="og:type":1 contains=<meta property="og:type" content="article"> contains=<meta property="article:published_time" content="2026-01-02T09:00:00+00:00">
|
|
|
|
# Single post
|
|
200 /blog/hello-acme contains=Hello Acme
|
|
200 /blog/hello-acme contains=https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ
|
|
404 /blog/missing-post
|
|
200 /blog/unsafe-sample contains=<p>Before</p><p>After</p> notcontains=alert(
|
|
404 /blog/draft-note
|
|
|
|
# Post index and pagination (9 per page, 13 published posts: 2 pages)
|
|
200 /blog contains=Hello Acme
|
|
200 /blog?page=2
|
|
404 /blog?page=3
|
|
404 /blog?page=abc
|
|
404 /blog?page=0
|
|
200 /pl/blog contains=Najnowsze wpisy
|
|
|
|
# Categories (include_empty=1; content renders above the list)
|
|
200 /blog/category/news contains=Acme news marker
|
|
200 /blog/category/quiet contains=There are no posts in this category yet.
|
|
404 /blog/category/nope
|
|
|
|
# Related posts fall back to the latest posts for a post without a category
|
|
200 /blog/uncategorised-note contains=Hello Acme
|
|
|
|
# Author (slug and login resolve) and tag pages
|
|
200 /blog/author/jane-doe contains=Jane Doe
|
|
200 /blog/author/jdoe contains=<link rel="canonical" href="https://acme.test/blog/author/jane-doe"
|
|
200 /blog/hello-acme contains=href="/blog/author/jane-doe" notcontains=/blog/author/jdoe
|
|
404 /blog/author/nobody
|
|
200 /blog/tag/release contains=Hello Acme
|
|
404 /blog/tag/nope
|
|
|
|
# RSS, canonical and meta (sitemaps come from the CMS, not the layer)
|
|
200 /rss.xml type=application/rss+xml
|
|
200 /blog contains=href="/rss.xml"
|
|
200 /blog?page=2 contains=?page=2
|
|
200 /blog?page=2 contains=<link rel="canonical" href="https://acme.test/blog?page=2">
|
|
200 /blog/hello-acme contains=og:title
|
|
200 /blog/hello-acme contains=Acme says hello and introduces its blog.
|
|
|
|
# Search (the playground sets journal.routes.search: '/search'). 11 posts
|
|
# match "acme": 2 pages of 9. Not route-cached (the playground caches /**)
|
|
# and noindex, follow.
|
|
200 /search?q=acme contains=Hello Acme
|
|
200 /search?q=acme contains=11 results
|
|
200 /search?q=acme contains=noindex, follow header=x-robots-tag: noindex, follow
|
|
200 /search?q=acme&page=2 contains=Acme Note 12
|
|
404 /search?q=acme&page=3
|
|
404 /search?q=acme&page=abc
|
|
200 /search?q=unsafe contains=Unsafe Sample notcontains=Hello Acme
|
|
200 /search?q=unsafe contains=1 result
|
|
200 /search?q=zzzzzz contains=Nothing found for “zzzzzz”.
|
|
200 /search?q=ac contains=Type at least 3 characters.
|
|
200 /search contains=Type what you are looking for.
|
|
# The term is text everywhere it is rendered (attributes may keep a bare <).
|
|
200 /search?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E contains=Search results: <script>alert(1) notcontains=>Search results: <script>
|
|
200 /pl/search?q=acme contains=11 wyników
|
|
200 /pl/search?q=unsafe contains=1 wynik
|
|
# The header box renders its no-JS form to the search route on every page.
|
|
200 /blog contains=action="/search"
|
|
200 /pl/blog contains=action="/pl/search"
|
|
|
|
# Body links, outbound rules and images (UI-SPEC L7). hello-acme: new tab on
|
|
# with the default hint; auto-links in document order up to autolink_max 2,
|
|
# never in a heading, never to the page itself.
|
|
200 /blog/hello-acme contains=<a href="/blog/acme-note-07">anvils</a> contains=<a href="/blog/acme-note-05">rockets</a> notcontains=<a href="/blog/acme-note-03"> notcontains=<a href="/blog/acme-note-06"> notcontains=<a href="https://acme.test/blog/hello-acme"
|
|
200 /blog/hello-acme contains=<a href="https://partner.test/review" rel="ugc nofollow noopener" target="_blank">the review<span class="seo-visually-hidden"> (opens in a new tab)</span></a>
|
|
200 /blog/hello-acme contains=<a href="https://shop.ads.test/deal" target="_blank" rel="sponsored noopener">a deal<span class="seo-visually-hidden"> (opens in a new tab)</span></a>
|
|
200 /blog/hello-acme contains=<a href="/blog/acme-note-04">an older note</a>
|
|
200 /blog/hello-acme contains=<img src="/storage/app/uploads/public/acme/anvil.png" alt="" width="640" height="480" loading="lazy" decoding="async" />
|
|
# heading-off: new tab off keeps the editor rel and the domain-list nofollow,
|
|
# without target, noopener or the hint.
|
|
200 /blog/heading-off contains=<a href="https://partner.test/x" rel="ugc nofollow">the partner</a> contains=<a href="https://other.test/y">another site</a> notcontains=target="_blank" notcontains=noopener notcontains=(opens in a new tab)
|
|
|
|
# Heading convention (UI-SPEC L6) and one link per post card (L5) on every
|
|
# fixture page: exactly one <h1, no skipped level, each card one <a, post
|
|
# cards h2 in listings and h3 under the related posts' h2, no read-more link.
|
|
200 /blog headings=strict cardlinks=1 count=<h1:1 notcontains=journal-post-card__more
|
|
200 /blog?page=2 headings=strict cardlinks=1 count=<h1:1 contains=· Page 2
|
|
200 /pl/blog?page=2 headings=strict cardlinks=1 count=<h1:1 contains=· Strona 2
|
|
200 /blog/category/news headings=strict cardlinks=1 count=<h1:1
|
|
200 /blog/category/quiet headings=strict count=<h1:1
|
|
200 /blog/tag/release headings=strict cardlinks=1 count=<h1:1
|
|
200 /blog/author/jane-doe headings=strict cardlinks=1 count=<h1:1
|
|
200 /search?q=acme headings=strict cardlinks=1 count=<h1:1
|
|
200 /blog/hello-acme headings=strict cardlinks=1 count=<h1:1 contains=<h2 class="journal-related__title" contains=<h3 class="journal-post-card__title" notcontains=<h2 class="journal-post-card__title" notcontains=journal-post-card__more
|
|
200 /blog/heading-on headings=strict cardlinks=1 count=<h1:1
|
|
200 /blog/heading-off headings=strict cardlinks=1 count=<h1:1
|
|
200 /blog/uncategorised-note headings=strict cardlinks=1 count=<h1:1
|
|
|
|
# Breadcrumbs at the top of journal pages (journal.breadcrumbs, default on;
|
|
# D-86) from the resolve answer, one per page; the BreadcrumbList stays in
|
|
# the one JSON-LD graph.
|
|
200 /blog/hello-acme count=<nav class="seo-breadcrumbs":1 contains=<article class="journal-post"><nav class="seo-breadcrumbs" notcontains=playground-trail contains=BreadcrumbList count=application/ld+json:1
|
|
200 /blog count=<nav class="seo-breadcrumbs":1 contains=<section class="journal-index"><nav class="seo-breadcrumbs"
|
|
|
|
# Category pillar text (D-41, the Journal H1 setting per category row): on
|
|
# page 1 only, h1 demoted while content_h1_to_h2 is on, kept while off (two
|
|
# h1 on that page: the setting's documented consequence); the description
|
|
# on every page.
|
|
200 /blog/category/news contains=<h2>About Acme news</h2> notcontains=<h1>About Acme news count=<h1:1 headings=strict
|
|
200 /blog/category/news?page=2 notcontains=Acme news marker contains=Company news from Acme. count=<h1:1 contains=· Page 2
|
|
200 /blog/category/plain contains=<h1>Plain pillar heading</h1> count=<h1:2 contains=A category whose site keeps H1 headings as written.
|
|
|
|
# Pagination through <SeoPagination class-prefix="journal-pagination"> (L2):
|
|
# 12 one-post pages of the news category, page 1 and the last page with gap
|
|
# items around the window, rel prev/next links and head links.
|
|
200 /blog/category/news?page=6 contains=<nav class="journal-pagination" aria-label="Pagination"> count=journal-pagination__gap:2 contains=<a href="/blog/category/news" aria-label="Page 1">1</a> contains=<a href="/blog/category/news?page=12" aria-label="Page 12">12</a> contains=<span class="journal-pagination__current" aria-current="page">6</span>
|
|
200 /blog/category/news?page=6 contains=<a class="journal-pagination__prev" href="/blog/category/news?page=5" rel="prev">Previous page</a> contains=<a class="journal-pagination__next" href="/blog/category/news?page=7" rel="next">Next page</a> contains=<link rel="prev" href="/blog/category/news?page=5" contains=<link rel="next" href="/blog/category/news?page=7"
|
|
200 /blog/category/news?page=2 contains=<a class="journal-pagination__prev" href="/blog/category/news" rel="prev">
|
|
404 /blog/category/news?page=13
|
|
|
|
# Listing pages add an ItemList node to the one JSON-LD graph (D-43): with
|
|
# the CMS graph on /blog, from scratch on pages without a resolve answer.
|
|
200 /blog count=application/ld+json:1 contains="@id":"https://acme.test/blog#itemlist" contains={"@type":"ListItem","position":1,"url":"https://acme.test/blog/hello-acme"} contains="@id":"https://acme.test/blog#webpage"
|
|
200 /blog?page=2 count=application/ld+json:1 contains="@id":"https://acme.test/blog?page=2#itemlist" contains="position":10,
|
|
200 /blog/category/news count=application/ld+json:1 contains=#itemlist" contains=ItemList
|
|
200 /blog/tag/release count=application/ld+json:1 contains=ItemList
|
|
200 /blog/author/jane-doe count=application/ld+json:1 contains=ItemList
|
|
200 /blog/hello-acme notcontains=ItemList
|
|
|
|
# Category neighbours (the Journal's opt-in related_mode scored): the post
|
|
# answer's previous_in_category and next_in_category render as server-side
|
|
# links instead of the site-wide neighbours; without them the site-wide
|
|
# previous_post and next_post stay.
|
|
200 /blog/heading-on contains=href="/blog/acme-note-04" class="journal-post__previous" rel="prev"><span>Previous post</span> Acme Note 04</a> contains=href="/blog/acme-note-06" class="journal-post__next" rel="next"><span>Next post</span> Acme Note 06</a>
|
|
200 /blog/heading-off contains=href="/blog/heading-on" class="journal-post__next" rel="next"><span>Next post</span> Heading On Sample</a> notcontains=journal-post__previous
|
|
|
|
# Featured images with the API's variants (D-85, D-71): <picture> with a
|
|
# WebP source and the original format as the srcset fallback (the
|
|
# original itself as the widest candidate), the original's width and
|
|
# height, alt; the post's main image eager with fetchpriority high, cards
|
|
# lazy. An image without variants renders the single <img> as before.
|
|
200 /blog/hello-acme contains=<figure class="journal-featured-image"><picture><source contains=image/webp" srcset="/storage/app/uploads/public/acme/thumb_1_480_252_0_0_exact.webp 480w, /storage/app/uploads/public/acme/thumb_1_800_420_0_0_exact.webp 800w" sizes="(min-width: 46rem) 46rem, 100vw"><img src="/storage/app/uploads/public/acme/hello.png" srcset="/storage/app/uploads/public/acme/thumb_1_480_252_0_0_exact.png 480w, /storage/app/uploads/public/acme/thumb_1_800_420_0_0_exact.png 800w, /storage/app/uploads/public/acme/hello.png 1200w" sizes="(min-width: 46rem) 46rem, 100vw" width="1200" height="630" alt="Hello Acme" loading="eager" fetchpriority="high" decoding="async"></picture></figure> count=fetchpriority="high":1
|
|
200 /blog contains=image/webp" srcset="/storage/app/uploads/public/acme/thumb_1_480_252_0_0_exact.webp 480w, /storage/app/uploads/public/acme/thumb_1_800_420_0_0_exact.webp 800w" sizes="(min-width: 48rem) 33vw, 100vw"> contains=alt="Hello Acme" loading="lazy" decoding="async"></picture> notcontains=fetchpriority
|
|
200 /blog/acme-note-03 contains=<figure class="journal-featured-image"><img src="/storage/app/uploads/public/acme/note-03.jpg" alt="Acme Note 03" width="1200" height="630" loading="eager" decoding="async"></figure> notcontains=note-03.jpg 1200w notcontains=fetchpriority
|