test: element tests for E8 types and currency, E9 states and queue, E10 list caps, the parent select
- E8: the page and profile type options, an invalid currency - E9: the checking line with aria-busy, noRules per type, one queued request, the danger line and its recovery - E10: both lists capped at 10 - the parent select's value parsing, empty default and disabled state
This commit is contained in:
91
tests/elements/graph.test.ts
Normal file
91
tests/elements/graph.test.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { labelsFor } from '../../assets/src/elements/labels'
|
||||
import { HostFailure, mountWidget, settle } from './host'
|
||||
|
||||
// E9 golem15-seo-graph (UI-SPEC C18): the checking line with aria-busy
|
||||
// while the request runs, the noRules line per type without Google rules,
|
||||
// at most one queued request, and the danger line that keeps the last
|
||||
// graph. The first render and the 800 ms re-check are in schema.test.ts.
|
||||
|
||||
beforeAll(async () => {
|
||||
await import('../../assets/src/elements/index')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
document.body.replaceChildren()
|
||||
})
|
||||
|
||||
const en = labelsFor('en')
|
||||
|
||||
const answer = {
|
||||
locale: 'en',
|
||||
url: 'https://example.test/x',
|
||||
graph: { '@context': 'https://schema.org', '@graph': [] },
|
||||
nodes: [
|
||||
{ id: 'https://example.test/x#webpage', type: 'WebPage', rules: false, findings: [] },
|
||||
{ id: 'https://example.test/x#service', type: 'Service', rules: false, findings: [] },
|
||||
{ id: 'https://example.test/x#website', type: 'WebSite', rules: true, findings: [{ level: 'required', property: 'name' }] },
|
||||
],
|
||||
}
|
||||
|
||||
describe('E9 golem15-seo-graph states', () => {
|
||||
it('shows the checking line and aria-busy while the request runs, neither after it', async () => {
|
||||
const host = mountWidget('golem15-seo-graph', { attrs: { 'ui-locale': 'en' }, answer: () => answer })
|
||||
await Promise.resolve()
|
||||
expect(host.node.getAttribute('aria-busy')).toBe('true')
|
||||
expect(host.node.querySelector('.seo-graph__checking')!.textContent).toBe(en.graphChecking)
|
||||
await settle()
|
||||
expect(host.node.getAttribute('aria-busy')).toBe('false')
|
||||
expect(host.node.querySelector('.seo-graph__checking')).toBeNull()
|
||||
})
|
||||
|
||||
it('writes one noRules line per node type without Google rules and the finding of the others', async () => {
|
||||
const host = mountWidget('golem15-seo-graph', { attrs: { 'ui-locale': 'en' }, answer: () => answer })
|
||||
await settle()
|
||||
const text = host.node.textContent ?? ''
|
||||
expect(text).toContain(en.noRules.replace(':type', 'WebPage'))
|
||||
expect(text).toContain(en.noRules.replace(':type', 'Service'))
|
||||
expect(text).not.toContain(en.noRules.replace(':type', 'WebSite'))
|
||||
expect(text).toContain('Missing required property “name” (WebSite)')
|
||||
expect(text).not.toContain(en.allGood)
|
||||
})
|
||||
|
||||
it('queues at most one request while one runs', async () => {
|
||||
const host = mountWidget('golem15-seo-graph', { attrs: { 'ui-locale': 'en' }, answer: () => answer })
|
||||
await Promise.resolve()
|
||||
const node = host.node as HTMLElement & { check(): void }
|
||||
node.check()
|
||||
node.check()
|
||||
node.check()
|
||||
await settle()
|
||||
expect(host.posted).toHaveLength(2)
|
||||
expect(host.node.getAttribute('aria-busy')).toBe('false')
|
||||
})
|
||||
|
||||
it('shows the danger line on the first failure and clears it on the next success', async () => {
|
||||
let fail = true
|
||||
const host = mountWidget('golem15-seo-graph', {
|
||||
attrs: { 'ui-locale': 'en' },
|
||||
answer: () => {
|
||||
if (fail) {
|
||||
throw new HostFailure('down')
|
||||
}
|
||||
return answer
|
||||
},
|
||||
})
|
||||
await settle()
|
||||
expect(host.node.querySelector('.seo-graph__error')!.textContent).toBe(en.graphFailed)
|
||||
expect(host.node.getAttribute('aria-busy')).toBe('false')
|
||||
fail = false
|
||||
host.node.querySelector<HTMLButtonElement>('.seo-graph__button')!.click()
|
||||
await settle()
|
||||
expect(host.node.querySelector('.seo-graph__error')).toBeNull()
|
||||
expect(host.node.textContent).toContain('WebSite')
|
||||
})
|
||||
|
||||
it('renders the Polish labels with ui-locale pl', async () => {
|
||||
const host = mountWidget('golem15-seo-graph', { attrs: { 'ui-locale': 'pl' }, answer: () => ({ ...answer, nodes: [answer.nodes[0]] }) })
|
||||
await settle()
|
||||
expect(host.node.textContent).toContain(labelsFor('pl').noRules.replace(':type', 'WebPage'))
|
||||
})
|
||||
})
|
||||
@@ -121,6 +121,22 @@ describe('E10 golem15-seo-links', () => {
|
||||
expect(host.node.querySelector('.seo-links__url')!.textContent).toBe('https://example.com/pages/shiitake')
|
||||
})
|
||||
|
||||
it('lists at most 10 pages in each list', async () => {
|
||||
const many: LinksData = {
|
||||
...crawled,
|
||||
orphan: false,
|
||||
out: Array.from({ length: 14 }, (_, i) => ({ phrase: `p${i}`, title: `Out ${i}`, url: `https://example.com/o/${i}` })),
|
||||
in: Array.from({ length: 12 }, (_, i) => ({ phrase: `q${i}`, title: `In ${i}`, adminUrl: `/backend/x/${i}` })),
|
||||
}
|
||||
const host = mountWidget('golem15-seo-links', { attrs: { 'ui-locale': 'en' }, answer: () => many })
|
||||
await settle()
|
||||
expect(host.node.querySelectorAll('.seo-links__list--out .seo-links__row')).toHaveLength(10)
|
||||
expect(host.node.querySelectorAll('.seo-links__list--in .seo-links__row')).toHaveLength(10)
|
||||
expect(host.node.textContent).toContain('Out 9')
|
||||
expect(host.node.textContent).not.toContain('Out 10')
|
||||
expect(host.node.querySelector('.seo-links__orphan')).toBeNull()
|
||||
})
|
||||
|
||||
it('asks again on the ML locale switch', async () => {
|
||||
const host = mountWidget('golem15-seo-links', { attrs: { 'ui-locale': 'en' }, answer: () => crawled })
|
||||
await settle()
|
||||
|
||||
66
tests/elements/parent.test.ts
Normal file
66
tests/elements/parent.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { labelsFor } from '../../assets/src/elements/labels'
|
||||
import { parseParent } from '../../assets/src/elements/parent'
|
||||
|
||||
// golem15-seo-parent (P33-23, D-86): the primary category select. The
|
||||
// first-render case is in schema.test.ts.
|
||||
|
||||
beforeAll(async () => {
|
||||
await import('../../assets/src/elements/index')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
document.body.replaceChildren()
|
||||
})
|
||||
|
||||
function mount(value: unknown, attrs: Record<string, string> = {}): HTMLElement & { value: unknown } {
|
||||
const node = document.createElement('golem15-seo-parent') as HTMLElement & { value: unknown }
|
||||
node.setAttribute('ui-locale', 'en')
|
||||
for (const [k, v] of Object.entries(attrs)) {
|
||||
node.setAttribute(k, v)
|
||||
}
|
||||
node.value = value
|
||||
document.body.append(node)
|
||||
return node
|
||||
}
|
||||
|
||||
describe('parseParent', () => {
|
||||
it('reads the loaded object, a plain stored choice and junk', () => {
|
||||
expect(parseParent('cat:1')).toEqual({ selected: 'cat:1', options: [] })
|
||||
expect(parseParent({ selected: 'cat:2', options: [{ value: 'cat:2', label: '' }, { value: 'cat:3', label: 'Three' }, { label: 'no value' }, null, 'x'] })).toEqual({
|
||||
selected: 'cat:2',
|
||||
options: [
|
||||
{ value: 'cat:2', label: 'cat:2' },
|
||||
{ value: 'cat:3', label: 'Three' },
|
||||
],
|
||||
})
|
||||
expect(parseParent({ selected: 5, options: 'nope' })).toEqual({ selected: '', options: [] })
|
||||
expect(parseParent(null)).toEqual({ selected: '', options: [] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('golem15-seo-parent', () => {
|
||||
it('starts on the empty default option when nothing is stored', () => {
|
||||
const node = mount({ selected: '', options: [{ value: 'cat:1', label: 'Recipes' }] })
|
||||
const select = node.querySelector('select')!
|
||||
expect(select.options[0]!.value).toBe('')
|
||||
expect(select.options[0]!.textContent).toBe(labelsFor('en').primaryDefault)
|
||||
expect(select.value).toBe('')
|
||||
expect(node.value).toBe('')
|
||||
})
|
||||
|
||||
it('keeps its options when the stored choice is set as a string', () => {
|
||||
const node = mount({ selected: '', options: [{ value: 'cat:1', label: 'Recipes' }, { value: 'cat:2', label: 'Mushrooms' }] })
|
||||
node.value = 'cat:2'
|
||||
expect(node.value).toBe('cat:2')
|
||||
expect(node.querySelector('select')!.value).toBe('cat:2')
|
||||
expect(node.querySelectorAll('option')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('disables the select with the disabled attribute and speaks Polish with ui-locale pl', () => {
|
||||
const node = mount({ selected: '', options: [] }, { disabled: '' })
|
||||
expect(node.querySelector('select')!.disabled).toBe(true)
|
||||
node.setAttribute('ui-locale', 'pl')
|
||||
expect(node.querySelector('option')!.textContent).toBe(labelsFor('pl').primaryDefault)
|
||||
})
|
||||
})
|
||||
@@ -89,6 +89,26 @@ describe('E8 golem15-seo-schema', () => {
|
||||
expect(node.querySelector('.seo-schema__status')!.textContent).toBe(labelsFor('en').jsonTooLarge)
|
||||
})
|
||||
|
||||
it('renders the page type options and a fixed ProfilePage for profiles', () => {
|
||||
const page = mountSchema('page', '')
|
||||
const en = labelsFor('en')
|
||||
expect([...page.querySelectorAll('option')].map((o) => o.textContent)).toEqual(['Default (WebPage)', en.tWebPage, en.tAboutPage, en.tContactPage, en.tService])
|
||||
document.body.replaceChildren()
|
||||
const profile = mountSchema('profile', '')
|
||||
expect(profile.querySelector('select')).toBeNull()
|
||||
expect(profile.querySelector('.seo-schema__fixed')!.textContent).toBe(en.tProfilePage)
|
||||
})
|
||||
|
||||
it('flags an invalid currency and keeps the Service fields out of a non-Service value', () => {
|
||||
const node = mountSchema('page', JSON.stringify({ type: 'Service', fields: { currency: 'PLN' }, custom: '' }))
|
||||
const currency = node.querySelectorAll<HTMLInputElement>('.seo-schema__grid input')[3]
|
||||
currency.value = 'ZLOTY'
|
||||
currency.dispatchEvent(new Event('input'))
|
||||
expect(node.textContent).toContain(labelsFor('en').currencyInvalid)
|
||||
expect(JSON.parse(node.value).fields.currency).toBe('PLN')
|
||||
expect(serializeValue(parseValue(JSON.stringify({ type: 'AboutPage', fields: {}, custom: '' })))).toBe(JSON.stringify({ type: 'AboutPage', fields: {}, custom: '' }))
|
||||
})
|
||||
|
||||
it('shows a single fixed type for collections', () => {
|
||||
const node = mountSchema('collection', '')
|
||||
expect(node.querySelector('select')).toBeNull()
|
||||
|
||||
Reference in New Issue
Block a user