-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.js
More file actions
227 lines (202 loc) · 25.8 KB
/
Copy pathtest.js
File metadata and controls
227 lines (202 loc) · 25.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
* test.js — Full test suite for directionmd v3
*/
import { runInference } from "./src/inference.js";
import { generateDirectionMd } from "./src/generator.js";
import { convergeExtractions } from "./src/crawler.js";
import { extractTokens } from "./src/tokens.js";
import { generateLLMBrief } from "./src/llm-inference.js";
import { generateDecisionTree, formatDecisionTreeMd } from "./src/decision-tree.js";
import { extractContentGovernance, formatGovernanceMd } from "./src/content-governance.js";
import { inferA11yPhilosophy, inferAnimationChoreography } from "./src/advanced-inference.js";
import { generatePlatformDirection, formatPlatformMd } from "./src/platform.js";
import { detectDrift, formatDriftMd } from "./src/drift.js";
let passed=0,failed=0;
function test(n,fn){try{fn();console.log(` ✓ ${n}`);passed++;}catch(e){console.log(` ✗ ${n}\n ${e.message}`);failed++;}}
function assert(c,m){if(!c)throw new Error(m||"Assertion failed");}
// ── Mock data ────────────────────────────────────────────────
const mock={
url:"https://example.com",
identity:{title:"Example - Build better",description:"Modern platform",ogSiteName:"Example",ogTitle:"Example",ogDescription:"Build better",ogImage:"/og.png",themeColor:"#5e6ad2",favicon:"/fav.ico",language:"en",canonical:"https://example.com"},
colorPatterns:{textColors:{"#1a1a1a":200,"#333":80,"#5e6ad2":20,"#fff":10},bgColors:{"#ffffff":300,"#f5f5f5":60,"#5e6ad2":15},borderColors:{"#e0e0e0":80},colorByElement:{headings:{"#1a1a1a":30},body:{"#333":100},links:{"#5e6ad2":40},buttons:{"#5e6ad2":15},nav:{},footer:{}},colorOnInteractive:{"#5e6ad2":15},gradients:{},monochromeRatio:65,colorVariety:8},
typographyPatterns:{fontsByRole:{heading:{Inter:30},body:{Inter:200},code:{"JetBrains Mono":10},nav:{Inter:20},ui:{Inter:40}},sizesByContext:{headings:[{level:1,size:48,weight:"700"},{level:2,size:32,weight:"600"},{level:3,size:24,weight:"600"}],body:[],nav:[14],buttons:[14]},weightDistribution:{"400":350,"600":80,"700":40},lineHeightBySize:{14:{total:10,count:7}},letterSpacingByContext:{headings:["-0.02em","-0.01em"],body:[],nav:[],caps:[]},textTransforms:{},headingProgression:[{level:1,size:48,weight:"700"},{level:2,size:32,weight:"600"},{level:3,size:24,weight:"600"}],bodyTextMetrics:{avgSize:16,avgLineHeight:24,avgWidth:640},fontPairCount:2,fontStacks:{},casePatterns:{headings:{upper:0,title:0,sentence:8,lower:0},nav:{upper:0,title:0,sentence:5,lower:0},buttons:{upper:0,title:0,sentence:6,lower:0}}},
spacingPatterns:{values:{"4px":80,"8px":200,"16px":300,"24px":150,"32px":100},sectionGaps:[120,128,120],paddingByContext:{cards:[24,24],buttons:[12,10],sections:[64,80],nav:[16],inputs:[12]},marginPatterns:{betweenHeadings:[24],betweenParagraphs:[16],betweenSections:[64]},gridBase:8,gridAdherence:78,whitespaceRatio:55,densityZones:{dense:30,normal:100,spacious:50}},
motionPatterns:{durations:{"0.2s":80,"0.15s":30},easings:{ease:60,"ease-in-out":30},properties:{all:50,opacity:30,transform:20},keyframes:[{name:"fadeIn",steps:2},{name:"stagger-reveal",steps:4}],animatedCount:5,hoverTransitions:25,focusTransitions:5,entranceAnimations:3,loopAnimations:1,motionPhilosophy:"functional",reducedMotionSupport:true,transitionsByContext:{buttons:["all 0.2s ease"],links:["color 0.15s ease"],cards:[],nav:[]}},
voicePatterns:{headings:[{level:1,text:"Build products that people love"},{level:2,text:"Features"},{level:2,text:"Why teams choose us"},{level:3,text:"Ship faster"}],paragraphs:["We help teams build better products by providing the tools they need.","Your team deserves better tools. We believe in simplicity."],ctas:["Get Started","Try free","Learn more"],microcopy:["No credit card required"],labels:["Email"],placeholders:["you@example.com"],sentenceLengths:[14,12,8,10],wordFrequency:{build:5,product:4,teams:3,tools:3,better:3,simplicity:1},pronouns:{we:4,you:3,they:0},punctuation:{exclamation:0,question:0,period:8,emoji:0},toneSignals:{informal:3,formal:1,technical:1,playful:0,urgent:0},capitalization:{allCaps:0,sentenceCase:10,titleCase:0},jargonCount:0,contractionCount:4,sentenceStarters:{we:3},paragraphLengths:[14,12],powerWords:1,negativeFraming:0,positiveFraming:4,activeVoice:8,passiveVoice:1},
componentPatterns:{buttons:{variants:[{bg:"rgb(94,106,210)",color:"#fff",radius:"8px",padding:"10px 20px",fontSize:"14px",fontWeight:"600",border:"none",text:"Get Started"},{bg:"transparent",color:"#333",radius:"8px",padding:"10px 20px",fontSize:"14px",fontWeight:"500",border:"1px solid #e0e0e0",text:"Learn More"}],hasIconButtons:true,hasGhostButtons:true,hasPillButtons:false,avgHeight:40},inputs:{variants:[],hasFloatingLabels:false,hasInlineValidation:true},cards:{variants:[{bg:"#fff",radius:"12px",shadow:"0 4px 12px rgba(0,0,0,0.1)",padding:"24px"}],uniform:true},navigation:{type:"sticky",position:"sticky",hasLogo:true,hasCTA:true,hasSearch:false,itemCount:5},modals:2,tooltips:5,drawers:1,toasts:0,accordions:0,tabs:1,forms:[{fields:2,submit:"Sign up"}],radiusPattern:{small:30,medium:200,large:40,pill:10,sharp:100},elevationLevels:3,borderUsage:{solid:20,none:300,subtle:80}},
layoutPatterns:{pageFlow:[{heading:"Build products that people love",height:600,aboveFold:true,hasCTA:true},{heading:"Features",height:800,aboveFold:false,hasCTA:false},{heading:"Why teams choose us",height:500,aboveFold:false,hasCTA:false}],sectionCount:4,avgSectionHeight:625,hasHero:true,heroStyle:{hasHeadline:true,hasMedia:true,hasCTA:true,height:600,isFullScreen:false},hasPricing:true,hasFAQ:false,hasTestimonials:true,hasNewsletter:true,hasFooterColumns:4,containerWidth:"1200px",contentAlignment:"left",flexVsGrid:{flex:180,grid:35},scrollDepth:4,aboveFoldElements:15,belowFoldElements:45},
a11yPatterns:{contrastIssues:1,missingAlt:2,ariaUsage:12,ariaLabels:10,focusVisible:true,skipLink:true,landmarks:{header:1,main:1,footer:1,nav:1},headingOrder:[1,2,2,3],headingOrderCorrect:true,formLabels:{labeled:6,unlabeled:1},liveRegions:1,reducedMotion:false,srOnlyText:4,langAttribute:true,colorContrastPairs:[]},
responsivePatterns:{breakpoints:{640:15,768:25,1024:30,1280:10},mobileFirst:true,desktopFirst:false,containerQueries:false,fluidType:true,clampUsage:8},
techAndSocial:{frameworks:["React","Next.js"],cssFramework:"Tailwind CSS",analytics:["Plausible"],hosting:[],socialLinks:{twitter:"https://twitter.com/example"}},
statePatterns:{errors:{messages:["Please check your email"],styling:null,tone:"instructive"},emptyStates:{found:true,hasIllustration:true,hasCTA:true,texts:["No projects yet"]},loading:{skeletons:3,spinners:0,progressBars:0,pattern:"skeleton"},success:{toasts:1},tooltipTexts:[],confirmDialogs:0},
visualHierarchy:{weightDistribution:{top:8000,middle:4000,bottom:2000},primaryAction:{text:"Get Started",score:5000,aboveFold:true},secondaryActions:[{text:"Learn More",score:2000}],prominentElements:[{tag:"h1",text:"Build products",zone:"top",area:10000,fontSize:"48px",fontWeight:"700"}]},
iconPatterns:{style:"outlined",count:18,sizes:{"16":10,"20":6},strokeWidths:{"1.5":15},hasLabels:12,iconOnly:4,consistent:true,svgStyles:{filled:2,outlined:15,duotone:1}},
imagePhilosophy:{count:8,treatments:{rounded:6,sharp:2,circular:1},objectFits:{cover:5},hasOverlayText:1,decorativeCount:2,contentCount:6,fullBleed:1,contained:7,placementPatterns:{hero:1,inline:3,background:2,avatar:1,card:3}},
compositionPatterns:{symmetry:"mixed",alignment:{left:6,center:3,right:0},columnUsage:{3:5,2:3},fullBleedSections:1,containedSections:5,verticalRhythm:[600,800,500,600],sectionAlternation:true,maxContentWidth:1200,gutterSizes:{"24px":8}},
architecture:{navLinks:[{text:"Features",href:"/features"}],footerLinks:[],sections:[{id:"hero",heading:"Build",childCount:3},{id:"features",heading:"Features",childCount:6}],internalLinks:["/page"],externalLinks:[],landmarks:{header:1,main:1},headingHierarchy:[{level:1,text:"Build"}],search:true,breadcrumbs:[],depth:12},
screenshot:"",
};
console.log("\n directionmd v3 test suite\n");
// ── Core Inference ───────────────────────────────────────────
let inf;
console.log(" ── Core Inference ────────────────────────────────\n");
test("runInference completes",()=>{inf=runInference(mock);assert(inf);});
test("all core sections",()=>{for(const k of["color","typography","spacing","motion","voice","component","layout","a11y","responsive","states","hierarchy","contentFormula","composition","iconography","imagery","microInteractions","archetype","brand","relationships","guardrails"])assert(inf[k],`missing ${k}`);});
test("creativeBrief is prose",()=>{assert(inf.creativeBrief);assert(inf.creativeBrief.length>100);assert(!inf.creativeBrief.includes("COLOR:"));});
test("archetype classified",()=>{assert(inf.archetype.primary.name);assert(inf.archetype.primary.score>0);assert(Object.keys(inf.archetype.scores).length===12);});
test("guardrails specific",()=>{assert(inf.guardrails.dos.length>3);assert(inf.guardrails.donts.length>3);assert(inf.guardrails.donts.some(d=>d.includes("jargon")||d.includes("shadow")||d.includes("exclamation")));});
// ── #5 Decision Trees ────────────────────────────────────────
console.log("\n ── #5 Decision Trees ─────────────────────────────\n");
let trees,treeMd;
test("generates decision trees",()=>{trees=generateDecisionTree(mock,inf);assert(trees);assert(trees.component);assert(trees.color);assert(trees.motion);assert(trees.spacing);});
test("component tree has branches",()=>{assert(trees.component.branches["Interactive element"]);assert(trees.component.branches["Container / wrapper"]);assert(trees.component.branches["Content display"]);assert(trees.component.branches["Feedback / overlay"]);});
test("color tree has all roles",()=>{const keys=Object.keys(trees.color.branches);assert(keys.length>=6);});
test("motion tree has UX reasons",()=>{assert(trees.motion.branches["No clear UX reason"]);});
test("formats as markdown",()=>{treeMd=formatDecisionTreeMd(trees);assert(treeMd.includes("## Design Decision Trees"));assert(treeMd.length>500);});
// ── #6 Competitive (structure only) ─────────────────────────
console.log("\n ── #6 Competitive (structure) ────────────────────\n");
import { formatCompetitiveMd } from "./src/competitive.js";
test("formats competitive report",()=>{
const mockReport={positioning:["You own the calm authority quadrant."],differentiators:[{dimension:"Color",yours:"minimal",theirs:"expressive",advice:"Protect this."}],warnings:["Motion: all products use functional."],matrix:{color:{label:"Color",primary:"minimal",competitors:[{name:"CompA",value:"expressive"}]}}};
const md=formatCompetitiveMd(mockReport,"Example");
assert(md.includes("Competitive Positioning"));assert(md.includes("Differentiators"));assert(md.includes("CompA"));
});
// ── #7 A11y Philosophy ───────────────────────────────────────
console.log("\n ── #7 A11y Philosophy ────────────────────────────\n");
let a11yPhil;
test("generates a11y philosophy",()=>{a11yPhil=inferA11yPhilosophy(mock.a11yPatterns,mock.statePatterns,mock.componentPatterns);assert(a11yPhil);assert(a11yPhil.rules.length>3);});
test("detects approach",()=>{assert(a11yPhil.philosophy.approach);assert(["inclusive design","compliance-plus","needs foundation"].includes(a11yPhil.philosophy.approach));});
test("detects error recovery",()=>{assert(a11yPhil.philosophy.errorRecovery);});
test("detects focus management",()=>{assert(a11yPhil.philosophy.focusManagement==="choreographed","expected choreographed: "+a11yPhil.philosophy.focusManagement);});
test("detects inclusion level",()=>{assert(a11yPhil.philosophy.inclusionLevel);});
test("includes philosophical rules",()=>{assert(a11yPhil.rules.some(r=>r.includes("inclusive")||r.includes("compliance")||r.includes("foundation")));});
test("mentions screen reader",()=>{assert(a11yPhil.rules.some(r=>r.toLowerCase().includes("screen reader")||r.toLowerCase().includes("assistive")||r.toLowerCase().includes("sr-only")));});
// ── #8 Animation Choreography ────────────────────────────────
console.log("\n ── #8 Animation Choreography ─────────────────────\n");
let choreo;
test("generates choreography",()=>{choreo=inferAnimationChoreography(mock.motionPatterns,mock.layoutPatterns);assert(choreo);assert(choreo.rules.length>3);});
test("detects stagger pattern",()=>{assert(choreo.philosophy.stagger);assert(["orchestrated","simple","none"].includes(choreo.philosophy.stagger));});
test("detects entrance/exit",()=>{assert(choreo.philosophy.entranceExit);});
test("detects scroll behavior",()=>{assert(choreo.philosophy.scrollBehavior);});
test("includes motion constants",()=>{assert(choreo.rules.some(r=>r.includes("duration")||r.includes("easing")||r.includes("0.2s")));});
test("mentions reduced-motion",()=>{assert(choreo.rules.some(r=>r.includes("reduced-motion")||r.includes("prefers-reduced")));});
// ── #9 Content Governance ────────────────────────────────────
console.log("\n ── #9 Content Governance ─────────────────────────\n");
let contentGov;
test("extracts content governance",()=>{contentGov=extractContentGovernance(mock.voicePatterns,mock);assert(contentGov);assert(contentGov.rules.length>3);});
test("heading length limits",()=>{assert(Object.keys(contentGov.governance.headingLimits).length>0);});
test("paragraph rules",()=>{assert(contentGov.governance.paragraphRules.avgWords>0);});
test("formatting conventions",()=>{assert(contentGov.governance.formatting.headingCase);assert(contentGov.governance.formatting.exclamationPolicy);});
test("terminology glossary",()=>{assert(contentGov.governance.terminology.length>0);assert(contentGov.governance.terminology[0].word);});
test("formats as markdown",()=>{const md=formatGovernanceMd(contentGov);assert(md.includes("Content Governance"));assert(md.includes("Terminology"));});
// ── #10 Platform Direction ───────────────────────────────────
console.log("\n ── #10 Platform Direction ────────────────────────\n");
let platforms;
test("generates platform direction",()=>{const tokens=extractTokens(mock);platforms=generatePlatformDirection(mock,inf,tokens);assert(platforms);assert(platforms.react);assert(platforms.email);assert(platforms.mobile);});
test("React has Tailwind classes",()=>{assert(platforms.react.includes("className"));assert(platforms.react.includes("rounded"));});
test("email has safe fonts",()=>{assert(platforms.email.includes("Arial")||platforms.email.includes("Helvetica"));});
test("mobile has touch targets",()=>{assert(platforms.mobile.includes("44"));});
test("mobile has nav guidance",()=>{assert(platforms.mobile.includes("Navigation")||platforms.mobile.includes("nav"));});
test("formats as markdown",()=>{const md=formatPlatformMd(platforms);assert(md.includes("Platform-Specific Direction"));assert(md.length>500);});
// ── #13 Drift Detection ─────────────────────────────────────
console.log("\n ── #13 Drift Detection ───────────────────────────\n");
const baseline={version:1,createdAt:"2026-01-01",url:"https://example.com",colors:{text:["#1a1a1a","#333","#5e6ad2","#fff"],bg:["#ffffff","#f5f5f5","#5e6ad2"],interactive:["#5e6ad2"]},typography:{fonts:["Inter","JetBrains Mono"],bodySize:16},spacing:{gridBase:8,gridAdherence:78,whitespaceRatio:55},motion:{philosophy:"functional",reducedMotion:true},a11y:{contrastIssues:1,missingAlt:2,focusVisible:true,skipLink:true,headingOrderCorrect:true},voice:{formality:"conversational",perspective:"partnership"},component:{elevationLevels:3,style:"soft"}};
test("no drift against matching data",()=>{const r=detectDrift(mock,inf,baseline);assert(r.violations.length<=2,"too many violations: "+r.violations.length);});
test("detects new color drift",()=>{
const drifted={...mock,colorPatterns:{...mock.colorPatterns,textColors:{...mock.colorPatterns.textColors,"#ff0000":50}}};
const r=detectDrift(drifted,inf,baseline);
assert(r.violations.some(v=>v.category==="color"),"no color violation");
});
test("detects new font drift",()=>{
const drifted={...mock,typographyPatterns:{...mock.typographyPatterns,fontsByRole:{...mock.typographyPatterns.fontsByRole,heading:{Poppins:30}}}};
const r=detectDrift(drifted,inf,baseline);
assert(r.violations.some(v=>v.category==="typography"),"no font violation");
});
test("detects a11y regression",()=>{
const drifted={...mock,a11yPatterns:{...mock.a11yPatterns,focusVisible:false}};
const r=detectDrift(drifted,inf,baseline);
assert(r.violations.some(v=>v.category==="a11y"),"no a11y violation");
});
test("detects reduced-motion removal",()=>{
const drifted={...mock,motionPatterns:{...mock.motionPatterns,reducedMotionSupport:false}};
const r=detectDrift(drifted,inf,baseline);
assert(r.violations.some(v=>v.message.includes("reduced-motion")),"no rm violation");
});
test("null baseline returns clean",()=>{const r=detectDrift(mock,inf,null);assert(r.score===100);assert(r.violations.length===0);});
test("formats drift report",()=>{
const drifted={...mock,colorPatterns:{...mock.colorPatterns,textColors:{...mock.colorPatterns.textColors,"#ff0000":50}}};
const r=detectDrift(drifted,inf,baseline);
const md=formatDriftMd(r,"./baseline.json");
assert(md.includes("Drift Report"));
});
// ── Generator (full output) ──────────────────────────────────
console.log("\n ── Generator (full output) ───────────────────────\n");
let md;
test("generates full DIRECTION.md",()=>{
const tokens=extractTokens(mock);
md=generateDirectionMd(mock,inf,{
a11yPhilosophy:a11yPhil,
choreography:choreo,
contentGovernance:formatGovernanceMd(contentGov),
decisionTrees:formatDecisionTreeMd(trees),
platformDirection:formatPlatformMd(platforms),
tokenMarkdown:tokens.markdown,
});
assert(typeof md==="string");assert(md.length>5000,"too short: "+md.length);
});
test("has Brand Essence",()=>{assert(md.includes("Brand Essence"));});
test("has Brand Archetype",()=>{assert(md.includes("Brand Archetype"));});
test("has all 9 core directions",()=>{for(const s of["Color Direction","Typography Direction","Spacing Direction","Motion Direction","Voice & Content","Component Direction","Layout Direction","Responsive Direction"])assert(md.includes(s),`missing: ${s}`);});
test("has A11y Philosophy (#7)",()=>{assert(md.includes("Accessibility Direction"));assert(md.includes("inclusive")||md.includes("compliance")||md.includes("foundation"));});
test("has Animation Choreography (#8)",()=>{assert(md.includes("Animation Choreography"));});
test("has Content Governance (#9)",()=>{assert(md.includes("Content Governance"));assert(md.includes("Terminology"));});
test("has Decision Trees (#5)",()=>{assert(md.includes("Decision Trees"));});
test("has Platform Direction (#10)",()=>{assert(md.includes("Platform-Specific Direction"));assert(md.includes("React"));assert(md.includes("Email"));assert(md.includes("Mobile"));});
test("has Error/Empty/Loading",()=>{assert(md.includes("Error, Empty"));});
test("has Visual Hierarchy",()=>{assert(md.includes("Visual Hierarchy"));});
test("has Composition",()=>{assert(md.includes("Composition"));});
test("has Iconography",()=>{assert(md.includes("Iconography"));});
test("has Imagery",()=>{assert(md.includes("Imagery"));});
test("has Micro-Interactions",()=>{assert(md.includes("Micro-Interactions"));});
test("has Cross-System Relationships",()=>{assert(md.includes("Cross-System"));});
test("has Creative Guardrails",()=>{assert(md.includes("### Do"));assert(md.includes("### Don't"));});
test("has Agent Prompt (brief)",()=>{assert(md.includes("Agent Prompt"));assert(md.includes("building for"));});
test("has Design Tokens",()=>{assert(md.includes("Design Tokens"));assert(md.includes("--color-"));});
test("is prose-heavy",()=>{const prose=md.split("\n").filter(l=>l.length>60&&!l.startsWith("|")&&!l.startsWith("#")&&!l.startsWith(">")&&!l.startsWith("```")&&!l.startsWith("-")).length;assert(prose>20,"too few prose: "+prose);});
test("includes disclaimer",()=>{assert(md.includes("educational"));});
// ── Crawler Convergence ──────────────────────────────────────
console.log("\n ── Crawler Convergence ───────────────────────────\n");
test("converges two extractions",()=>{const c=convergeExtractions([{...mock,url:"https://a.com"},{...mock,url:"https://b.com"}]);assert(c._crawl.pageCount===2);});
test("single returns as-is",()=>{assert(convergeExtractions([mock])===mock);});
test("empty returns null",()=>{assert(convergeExtractions([])=== null);});
// ── Tokens ───────────────────────────────────────────────────
console.log("\n ── Design Tokens ────────────────────────────────\n");
let tokens;
test("extracts tokens",()=>{tokens=extractTokens(mock);assert(tokens);});
test("color tokens",()=>{assert(Object.keys(tokens.colors).length>0);assert(Object.keys(tokens.colors).some(k=>k.startsWith("color-")));});
test("spacing tokens",()=>{assert(Object.keys(tokens.spacing).length>0);assert(tokens.spacing["space-unit"]==="8px");});
test("CSS output",()=>{assert(tokens.css.includes(":root"));});
test("Tailwind output",()=>{assert(tokens.tailwind.includes("tailwindcss"));});
// ── LLM ──────────────────────────────────────────────────────
console.log("\n ── LLM Module ───────────────────────────────────\n");
test("null without key",async()=>{assert(await generateLLMBrief(mock,inf,null)===null);});
test("null with empty key",async()=>{assert(await generateLLMBrief(mock,inf,"")===null);});
// ── Edge Cases ───────────────────────────────────────────────
console.log("\n ── Edge Cases ────────────────────────────────────\n");
test("empty color",()=>{const e={...mock,colorPatterns:{...mock.colorPatterns,textColors:{},bgColors:{},colorOnInteractive:{},gradients:{},monochromeRatio:0,colorByElement:{headings:{},body:{},links:{},buttons:{},nav:{},footer:{}}}};const r=runInference(e);assert(r.color.rules.length>0);});
test("no hero",()=>{const e={...mock,layoutPatterns:{...mock.layoutPatterns,hasHero:false,heroStyle:null,pageFlow:[]}};const r=runInference(e);const m=generateDirectionMd(e,r);assert(m.length>500);});
test("no state patterns",()=>{const r=runInference({...mock,statePatterns:undefined});assert(r.states.rules.length>0);});
test("no icons",()=>{const r=runInference({...mock,iconPatterns:undefined});assert(r.iconography.rules.length>0);});
test("a11y perfect",()=>{const a=inferA11yPhilosophy({...mock.a11yPatterns,contrastIssues:0,missingAlt:0,focusVisible:true,skipLink:true,headingOrderCorrect:true,formLabels:{labeled:5,unlabeled:0},reducedMotion:true,liveRegions:2,srOnlyText:8,langAttribute:true},mock.statePatterns,mock.componentPatterns);assert(a.philosophy.approach==="inclusive design");});
test("choreography static motion",()=>{const c=inferAnimationChoreography({...mock.motionPatterns,animatedCount:0,entranceAnimations:0,loopAnimations:0,keyframes:[],motionPhilosophy:"static",reducedMotionSupport:false},mock.layoutPatterns);assert(c.philosophy.stagger==="none");assert(c.rules.length>0);});
test("content gov empty voice",()=>{const r=extractContentGovernance({...mock.voicePatterns,headings:[],paragraphs:[],ctas:[],paragraphLengths:[],wordFrequency:{}},mock);assert(r.rules.length>=0);});
// ── Security ─────────────────────────────────────────────────
console.log("\n ── Security ──────────────────────────────────────\n");
test("no XSS via URL",()=>{const x={...mock,url:'"><script>alert(1)</script>'};const r=runInference(x);const m=generateDirectionMd(x,r);assert(!m.includes("<script>"));});
test("no XSS via name",()=>{const x={...mock,identity:{...mock.identity,ogSiteName:'<img onerror=alert(1)>'}};const r=runInference(x);const m=generateDirectionMd(x,r);assert(!m.includes("onerror"));});
test("no XSS via heading",()=>{const x={...mock,voicePatterns:{...mock.voicePatterns,headings:[{level:1,text:'<script>alert(1)</script>'}]}};const r=runInference(x);const m=generateDirectionMd(x,r);assert(!m.includes("<script>"));});
test("disclaimer",()=>{assert(md.includes("educational"));});
// ── Summary ──────────────────────────────────────────────────
console.log(`\n ────────────────────────────────────────────────\n`);
console.log(` ${passed} passed, ${failed} failed\n`);
if(failed>0)process.exit(1);