import { describe, expect, it } from 'vitest'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { assertManifest, assertIndexDiscoveryMeta, assertPluginsDiscoveryMeta, analyzeSitemap, assertPrerenderedPluginRoutes, assertPrerenderedSkillRoutes, assertIndexSocialMeta, assertLlms, assertRobots, assertSitemap, extractSitemapLocations, } from './verify-seo-assets.js'; describe('seo assets verification helpers', () => { it('extracts sitemap location values in declaration order', () => { const xml = ` https://example.com/ https://example.com/skill/agent-a `; const locs = extractSitemapLocations(xml); expect(locs).toEqual([ 'https://example.com/', 'https://example.com/skill/agent-a', ]); }); it('validates a canonical sitemap with base path and enough top skills', () => { const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/plugins https://owner.github.io/repo/skill/agent-a https://owner.github.io/repo/skill/agent-b `; expect(() => assertSitemap(xml, { minSkillUrls: 2 })).not.toThrow(); }); it('throws when sitemap has duplicated URLs', () => { const xml = ` https://example.com/ https://example.com/ `; expect(() => assertSitemap(xml)).toThrow('duplicated'); }); it('requires robots directives', () => { const robots = ` User-agent: * Allow: / User-agent: GPTBot Allow: / User-agent: OAI-SearchBot Allow: / User-agent: ClaudeBot Allow: / User-agent: PerplexityBot Allow: / Sitemap: https://example.com/sitemap.xml `; expect(() => assertRobots(robots)).not.toThrow(); }); it('requires llms.txt discovery signals', () => { const llms = ` # Antigravity Awesome Skills 1,678+ agentic skills with specialized plugins for Claude Code and Codex CLI. https://github.com/sickn33/antigravity-awesome-skills Canonical source of truth: the GitHub repository is the primary project URL. `; expect(() => assertLlms(llms)).not.toThrow(); }); it('requires social image tags in rendered index html', () => { const html = ` `; expect(() => assertIndexSocialMeta(html)).not.toThrow(); }); it('requires current discovery copy in rendered index html', () => { const html = ` Antigravity Awesome Skills | 1,678+ AI coding skills and plugins `; expect(() => assertIndexDiscoveryMeta(html)).not.toThrow(); }); it('requires plugin landing discovery copy in rendered plugin html', () => { const html = ` AAS Specialized Plugins | 15 AI coding workflow packs `; expect(() => assertPluginsDiscoveryMeta(html)).not.toThrow(); }); it('validates prerendered skill route files when present', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-')); const distDir = path.join(tmpDir, 'dist'); const routeFile = path.join(distDir, 'skill', 'agent-a', 'index.html'); fs.mkdirSync(path.dirname(routeFile), { recursive: true }); fs.writeFileSync(routeFile, ''); const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/skill/agent-a `; const report = analyzeSitemap(xml); expect(() => assertPrerenderedSkillRoutes(report.skillUrls, distDir, report.normalizedRootPath)).not.toThrow(); }); it('validates prerendered plugin route files when present', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-')); const distDir = path.join(tmpDir, 'dist'); const routeFile = path.join(distDir, 'plugins', 'index.html'); fs.mkdirSync(path.dirname(routeFile), { recursive: true }); fs.writeFileSync( routeFile, 'AAS Specialized Plugins | 15 AI coding workflow packs', ); const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/plugins `; const report = analyzeSitemap(xml, { minSkillUrls: 0 }); expect(() => assertPrerenderedPluginRoutes(report.pluginUrls, distDir, report.normalizedRootPath)).not.toThrow(); }); it('throws when a prerendered skill file is missing', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-')); const distDir = path.join(tmpDir, 'dist'); const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/skill/agent-a `; const report = analyzeSitemap(xml); expect(() => assertPrerenderedSkillRoutes(report.skillUrls, distDir, report.normalizedRootPath)).toThrow( 'Missing prerendered page for skill route', ); }); it('rejects missing social image tags', () => { const html = ` `; expect(() => assertIndexSocialMeta(html)).toThrow('twitter:image'); }); it('requires manifest identity and theme fields', () => { const manifest = JSON.stringify( { name: 'Antigravity', short_name: 'AG', theme_color: '#112233', description: 'desc', icons: [{ src: 'icon.svg' }], }, null, 2, ); expect(() => assertManifest(manifest)).not.toThrow(); }); });