vitest + happy-dom tests of the UI-SPEC backstops: E1 status thresholds (29/30 characters, 580/581 px; 119/120 and 920/921 for descriptions) with a mocked measureText and the counter's wrap contract; the E2 grammar over every directive combination, the emitted values and the stacking contract with unclipped en and pl labels; the E3 effective title, word-boundary truncation and keyphrase stem matching; each E4 lint rule and a clean file; E6 apply gating. happy-dom has no layout engine, so the narrow-width rows check the stylesheet's layout contract.
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
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)
|
|
})
|
|
})
|