Files
hanzo.ai/e2e/unit/solutions.spec.ts
zeekay bc6b07a1df Build Solutions from one list
Every team, industry and use case, plus the reserved tier, is an entry
in lib/data/solutions.ts.  /solutions draws the index,
/solutions/<slug> draws one entry through
components/solutions/Solution.tsx, and the sitemap reads the same
list, so a solution is added in one place.

Each page names three to five workflows, links the products it runs
on, and states security only as /trust and /legal do: controls
compatible with SOC 2 with the report on request, and PHI, card data
and student records only under written terms or self-hosted.  The
reserved tier's GPU rates are read from pricing.json, never typed.

The old section is gone: the capability, stack and industry pages,
their components and their data.  Their addresses, and /fintech,
forward through MOVED to the page that answers for each.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-27 14:33:40 -07:00

43 lines
2.0 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { INDEX, KINDS, SOLUTIONS, href, ofKind } from '../../lib/data/solutions'
import { MOVED } from '../../lib/publish'
/**
* The Solutions section is one list, lib/data/solutions.ts, and these are the
* rules the list keeps. The render — every page answers, every link resolves,
* every page is in the sitemap — is the gates' to measure over the export.
*/
test('every slug is one address, and every title one page', () => {
const slugs = SOLUTIONS.map((s) => s.slug)
expect(new Set(slugs).size, 'a slug names one solution').toBe(slugs.length)
expect(slugs.filter((s) => !/^[a-z]+(-[a-z]+)*$/.test(s)), 'lower case, hyphen between words').toEqual([])
const titles = SOLUTIONS.map((s) => s.title)
expect(new Set(titles).size, 'a title names one page').toBe(titles.length)
})
test('every kind has entries, and the menu is every entry', () => {
for (const k of KINDS) expect(ofKind(k.id).length, `${k.label} is empty`).toBeGreaterThan(0)
expect(KINDS.flatMap((k) => ofKind(k.id)).length).toBe(SOLUTIONS.length)
})
test('a solution says three to five things it does', () => {
const off = SOLUTIONS.filter((s) => s.work.length < 3 || s.work.length > 5).map((s) => s.slug)
expect(off).toEqual([])
})
test('the regulated industries say what is true about their data', () => {
for (const slug of ['healthcare', 'financial-services', 'law', 'government']) {
expect(SOLUTIONS.find((s) => s.slug === slug)?.trust?.length, `${slug} carries no security section`).toBeGreaterThan(0)
}
})
test('a retired solutions address forwards to a page that answers', () => {
const live = new Set([INDEX, ...SOLUTIONS.map(href)])
const dead = Object.entries(MOVED)
.filter(([from, to]) => from.startsWith(INDEX + '/') && to.startsWith(INDEX) && !live.has(to))
.map(([from, to]) => `${from} -> ${to}`)
expect(dead).toEqual([])
for (const s of SOLUTIONS) expect(MOVED[href(s)], `${href(s)} is a page and a forward`).toBeUndefined()
})