mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
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
43 lines
2.0 KiB
TypeScript
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()
|
|
})
|