| 1 | import { readFileSync } from 'fs' |
| 2 | import path from 'path' |
| 3 | import { describe, expect, it } from 'vitest' |
| 4 | |
| 5 | const projectRoot = process.cwd() |
| 6 | |
| 7 | const readProjectFile = (filePath: string) => |
| 8 | readFileSync(path.join(projectRoot, filePath), 'utf-8') |
| 9 | |
| 10 | const sizeLayoutSkills = [ |
| 11 | { |
| 12 | name: 'vertical-9-16-layout-skill', |
| 13 | dir: 'resources/skills/vertical-9-16-layout-skill', |
| 14 | patterns: ['hook-value-takeaway', 'vertical-step-story', 'data-takeaway'] |
| 15 | }, |
| 16 | { |
| 17 | name: 'standard-4-3-layout-skill', |
| 18 | dir: 'resources/skills/standard-4-3-layout-skill', |
| 19 | patterns: ['title-plus-two-zone', 'chart-insight-pair', 'matrix-2x2'] |
| 20 | }, |
| 21 | { |
| 22 | name: 'square-1-1-layout-skill', |
| 23 | dir: 'resources/skills/square-1-1-layout-skill', |
| 24 | patterns: ['center-hero-orbit', 'quadrant-card', 'square-data-card'] |
| 25 | }, |
| 26 | { |
| 27 | name: 'vertical-3-4-layout-skill', |
| 28 | dir: 'resources/skills/vertical-3-4-layout-skill', |
| 29 | patterns: ['poster-hero-proof', 'vertical-process', 'two-column-pocket'] |
| 30 | }, |
| 31 | { |
| 32 | name: 'red-layout-skill', |
| 33 | dir: 'resources/skills/red-layout-skill', |
| 34 | patterns: ['cover-hook', 'saveable-checklist', 'template-note'] |
| 35 | } |
| 36 | ] as const |
| 37 | |
| 38 | describe('size layout skills', () => { |
| 39 | it('use progressive disclosure with catalog and checklist references', () => { |
| 40 | for (const skill of sizeLayoutSkills) { |
| 41 | const source = readProjectFile(`${skill.dir}/SKILL.md`) |
| 42 | const catalog = readProjectFile(`${skill.dir}/references/catalog.md`) |
| 43 | const checklist = readProjectFile(`${skill.dir}/references/checklist.md`) |
| 44 | |
| 45 | expect(source).toContain('references/catalog.md') |
| 46 | expect(source).toContain('references/checklist.md') |
| 47 | expect(source).toContain('Pattern Quick Lookup') |
| 48 | expect(source).not.toContain('## Structure Patterns') |
| 49 | |
| 50 | expect(catalog).toContain('## Zone Skeletons') |
| 51 | expect(catalog).toContain('## Patterns') |
| 52 | expect(catalog).toContain('Structure recipe') |
| 53 | expect(catalog).toContain('Budget rule') |
| 54 | expect(catalog).toContain('Failure signs') |
| 55 | for (const pattern of skill.patterns) { |
| 56 | expect(catalog).toContain(pattern) |
| 57 | } |
| 58 | |
| 59 | expect(checklist).toContain('P0 - Not Deliverable') |
| 60 | expect(checklist).toContain('P1 - Should Fix') |
| 61 | expect(checklist).toContain('P2 - Consider Optimizing') |
| 62 | } |
| 63 | }) |
| 64 | |
| 65 | it('keep each size skill focused on its own layout language', () => { |
| 66 | const skillNames = sizeLayoutSkills.map((skill) => skill.name) |
| 67 | |
| 68 | for (const skill of sizeLayoutSkills) { |
| 69 | const combined = [ |
| 70 | readProjectFile(`${skill.dir}/SKILL.md`), |
| 71 | readProjectFile(`${skill.dir}/references/catalog.md`), |
| 72 | readProjectFile(`${skill.dir}/references/checklist.md`) |
| 73 | ].join('\n') |
| 74 | |
| 75 | for (const otherSkillName of skillNames.filter((name) => name !== skill.name)) { |
| 76 | expect(combined).not.toContain(otherSkillName) |
| 77 | } |
| 78 | expect(combined).not.toContain('When not to use') |
| 79 | expect(combined).not.toContain('Do not use') |
| 80 | } |
| 81 | }) |
| 82 | |
| 83 | it('low-density vertical/poster skills cap content capacity and prescribe overload priority', () => { |
| 84 | // These canvases are social/card formats, not compressed slides. They MUST |
| 85 | // declare a hard capacity ceiling and an overload priority so the page agent |
| 86 | // compresses content instead of shrinking fonts or overflowing. |
| 87 | const lowDensitySkills = [ |
| 88 | 'vertical-9-16-layout-skill', |
| 89 | 'vertical-3-4-layout-skill', |
| 90 | 'red-layout-skill' |
| 91 | ] |
| 92 | |
| 93 | for (const name of lowDensitySkills) { |
| 94 | const source = readProjectFile(`resources/skills/${name}/SKILL.md`) |
| 95 | |
| 96 | // Hard capacity ceiling is declared. |
| 97 | expect(source).toContain('Capacity ceiling (hard)') |
| 98 | // Vertical fill rule forces flex-1 / justify-between so the page never |
| 99 | // leaves an accidental bottom gap (LLMs cannot pixel-estimate accurately; |
| 100 | // CSS auto-distribution is the reliable fix). |
| 101 | expect(source).toContain('Vertical fill (hard)') |
| 102 | expect(source).toContain('flex-1') |
| 103 | expect(source).toContain('no accidental top-stack or bottom gap') |
| 104 | // Overload priority explicitly forbids going below font floors or |
| 105 | // exceeding canvas height as a way to fit more content. |
| 106 | expect(source).toContain('resolve overload in this priority') |
| 107 | expect(source).toContain('Never resolve overload by going below the font floors') |
| 108 | // Recognises the format as a low-density carrier, not a compressed slide. |
| 109 | expect(source).toContain('not a compressed slide') |
| 110 | } |
| 111 | }) |
| 112 | }) |
| 113 |