import { afterEach, beforeAll, describe, expect, it } from 'vitest' import { byteSize, findingText, lintRobots, MAX_FINDINGS, MAX_ROBOTS_BYTES } from '../../assets/src/elements/robotstxt' import { labelsFor } from '../../assets/src/elements/labels' // E4 golem15-seo-robotstxt (UI-SPEC C10): each lint rule fires on its // sample line, none fires on a clean file, findings never block. beforeAll(async () => { await import('../../assets/src/elements/index') }) afterEach(() => { document.body.replaceChildren() }) const clean = [ '# Rules for every crawler', 'User-agent: *', 'Disallow: /admin', 'Allow: /', 'Disallow: *.pdf$', 'Disallow:', '', 'User-agent: Googlebot # inline comment', 'Allow: /public', 'Host: example.test', 'Clean-param: utm_source', ].join('\n') describe('E4 lint', () => { it('a clean file has no finding', () => { expect(lintRobots(clean)).toEqual([]) expect(lintRobots(clean.replace(/\n/g, '\r\n'))).toEqual([]) expect(lintRobots('')).toEqual([]) }) it.each([ ['noColon', 'User-agent: *\nDisallow /private', { kind: 'noColon', line: 2 }], ['noGroup', 'Disallow: /private\nUser-agent: *', { kind: 'noGroup', line: 1 }], ['badPath', 'User-agent: *\nDisallow: private', { kind: 'badPath', line: 2 }], ['ownSitemap', 'User-agent: *\nSitemap: https://example.test/sitemap.xml', { kind: 'ownSitemap', line: 2 }], ['crawlDelay', 'User-agent: *\nCrawl-delay: 10', { kind: 'crawlDelay', line: 2 }], ['unknownSuggest', 'User-agent: *\nDissallow: /x', { kind: 'unknownSuggest', line: 2, word: 'Dissallow', suggestion: 'Disallow' }], ['unknown', 'User-agent: *\nNoindex-everything: /x', { kind: 'unknown', line: 2, word: 'Noindex-everything' }], ])('%s fires on its sample line', (kind, file, want) => { const findings = lintRobots(file) expect(findings).toEqual([want]) for (const locale of ['en', 'pl']) { const text = findingText(labelsFor(locale), findings[0]) expect(text).toContain(String(want.line)) expect(text).not.toMatch(/:(line|word|suggestion)\b/) } expect(kind).toBe(want.kind) }) it('a misspelt agent line suggests User-agent', () => { expect(lintRobots('User-agnet: *')).toEqual([{ kind: 'unknownSuggest', line: 1, word: 'User-agnet', suggestion: 'User-agent' }]) }) it('the element lists at most ten findings and then "and more"', () => { const node = document.createElement('golem15-seo-robotstxt') as HTMLElement & { value: string } node.setAttribute('ui-locale', 'en') document.body.append(node) node.value = Array.from({ length: MAX_FINDINGS + 3 }, () => 'Crawl-delay: 5').join('\n') const panel = node.querySelector('.seo-robotstxt__lint') as HTMLElement expect(panel.textContent).toContain(labelsFor('en').moreFindings.replace(':count', '3')) node.value = clean expect(panel.textContent).not.toContain('Crawl-delay') }) it('findings never block: the element still emits every edit', () => { const node = document.createElement('golem15-seo-robotstxt') as HTMLElement & { value: string } document.body.append(node) const seen: string[] = [] node.addEventListener('summer-change', (ev) => seen.push((ev as CustomEvent).detail.value)) const area = node.querySelector('textarea') as HTMLTextAreaElement area.value = 'Disallow /broken' area.dispatchEvent(new Event('input')) expect(seen).toEqual(['Disallow /broken']) }) it('the size meter counts UTF-8 bytes against 500 KB', () => { expect(byteSize('abc')).toBe(3) expect(byteSize('żółw')).toBe(7) const node = document.createElement('golem15-seo-robotstxt') as HTMLElement & { value: string } document.body.append(node) node.value = 'x'.repeat(MAX_ROBOTS_BYTES) expect(node.classList.contains('seo-robotstxt--over')).toBe(false) node.value = 'x'.repeat(MAX_ROBOTS_BYTES + 1) expect(node.classList.contains('seo-robotstxt--over')).toBe(true) }) })