diff --git a/tests/blogeditor/forms.test.ts b/tests/blogeditor/forms.test.ts new file mode 100644 index 0000000..f830a3a --- /dev/null +++ b/tests/blogeditor/forms.test.ts @@ -0,0 +1,116 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { mediaFileName, slugFileStem } from '../../assets/src/blogeditor/imageform' +import { isExternalUrl } from '../../assets/src/blogeditor/linkform' +import { button, chooseImage, click, fill, form, mount } from './helpers' + +// J3 link form and J4 image form (UI-SPEC J3, J4): the rel checkboxes +// follow the URL as it is typed, the generic-text warning comes and goes +// without blocking, the file-name slug rule's examples, and a decorative +// image clears and locks its alt. The first cases are in additions.test.ts. + +afterEach(() => { + document.body.replaceChildren() +}) + +const ON = JSON.stringify({ headingLevels: [2, 3, 4], genericAnchors: ['click here', 'czytaj więcej'] }) + +function openLink(text: string) { + const m = mount(`
${text}
`, { options: ON }) + m.editor.commands.setTextSelection({ from: 1, to: 1 + text.length }) + click(button(m.el, 'link')) + const linkForm = form(m.el, 'link')! + const box = (token: string) => linkForm.querySelectorPartner<\/a><\/p>$/)
+ })
+
+ it('writes no rel for an internal link even when a box was checked', () => {
+ const { el, box, url, linkForm } = openLink('Partner')
+ fill(url, 'https://partner.test')
+ box('sponsored').checked = true
+ fill(url, '/oferta')
+ click(linkForm.querySelector('[data-action="apply"]'))
+ expect(el.value).toBe('')
+ })
+})
+
+describe('J3 generic link text', () => {
+ it('warns for a listed text in any case or punctuation and stays quiet otherwise', () => {
+ const generic = openLink('Czytaj więcej…')
+ expect(generic.linkForm.querySelector See click here and Intro text
Only links
Intro
All link
', { options: ON })
+ const facts = factsFromEditor(editor)
+ expect(facts.headings.map((h) => [h.level, h.text, h.linkOnly])).toEqual([
+ [2, 'Intro', false],
+ [3, 'All link', true],
+ ])
+ expect(facts.headings.every((h) => h.pos >= 0)).toBe(true)
+ // A link inside a heading is a link text too.
+ expect(facts.links).toEqual(['All link', 'click here', 'more'])
+ })
+})
+
+describe('heading flags', () => {
+ const facts = factsFromSource('Top
Skip
Link
Deep
')
+
+ it('flags skipped levels from the page title H1, empty and link-only headings', () => {
+ const flags = headingFlags(facts, en, { allowsH1: true, source: true })
+ expect(flags[0]).toEqual([])
+ expect(flags[1]).toEqual(['Skipped level: H1 → H3'])
+ expect(flags[2]).toEqual([en.flagEmpty])
+ expect(flags[3]).toEqual([en.flagLinkOnly])
+ expect(flags[4]).toEqual(['Skipped level: H2 → H4'])
+ })
+
+ it('flags an H1 only in the Source tab while level 1 is not offered', () => {
+ expect(headingFlags(facts, en, { allowsH1: false, source: true })[0]).toEqual([en.flagH1])
+ expect(headingFlags(facts, en, { allowsH1: false, source: false })[0]).toEqual([])
+ expect(headingFlags(facts, en, { allowsH1: true, source: true })[0]).toEqual([])
+ expect(headingFlags({ headings: [], links: [] }, en, { allowsH1: false, source: true })).toEqual([])
+ })
+})
+
+describe('generic link texts', () => {
+ it('normalises and matches the generic list', () => {
+ expect(normalizeAnchor(' Kliknij TUTAJ!!! ')).toBe('kliknij tutaj')
+ expect(normalizeAnchor('»Więcej«')).toBe('więcej')
+ expect(isGenericAnchor('Click here.', ['click here'])).toBe(true)
+ expect(isGenericAnchor('Click here to read the study', ['click here'])).toBe(false)
+ expect(isGenericAnchor('!!!', [''])).toBe(false)
+ })
+})
+
+describe('outline timing', () => {
+ it('recomputes the Source tab facts only after the 300 ms debounce and never edits the value', () => {
+ vi.useFakeTimers()
+ expect(OUTLINE_DEBOUNCE_MS).toBe(300)
+ const { el, editor, changes } = mount('One
', { options: OFF })
+ const toggle = el.querySelectorOne
Two
Three
')
+ vi.advanceTimersByTime(OUTLINE_DEBOUNCE_MS - 50)
+ expect(toggle.textContent).toBe('Outline · 1 heading')
+ fill(el.querySelector('textarea'), 'One
Two
')
+ vi.advanceTimersByTime(OUTLINE_DEBOUNCE_MS - 50)
+ expect(toggle.textContent).toBe('Outline · 1 heading')
+ vi.advanceTimersByTime(50)
+ expect(toggle.textContent).toBe('Outline · 2 headings')
+ expect(changes).toHaveLength(3)
+ })
+})