/* eslint-env mocha */
/* eslint-disable quotes */
mocha.setup({
ui: 'bdd'
})
describe('unit tests', () => {
const assert = chai.assert
const add = html => {
const tpl = document.createElement('template')
tpl.innerHTML = html
return document.body.appendChild(tpl.content.firstElementChild)
}
const sleep = t => new Promise(resolve => setTimeout(resolve, t))
const tick = () => new Promise(resolve => requestAnimationFrame(resolve))
describe('constructor()', () => {
it('should not load marked if marked already loaded', async () => {
window.marked = true
const fixture = add(``)
await fixture.waitForReady()
const nodes = document.head.querySelectorAll('script')
for (let a = 0; a < nodes.length; a++) {
assert(!nodes[a].src.endsWith('marked.min.js'))
}
fixture.remove()
})
it('should not load prism if prism already loaded', async () => {
window.marked = false
let nodes = document.head.querySelectorAll('script')
for (let a = 0; a < nodes.length; a++) {
if (nodes[a].src.includes('prism')) {
nodes[a].remove()
}
}
const f = add(``)
await f.loadScript(f.config.markedUrl)
await f.waitForReady()
nodes = document.head.querySelectorAll('script')
for (let a = 0; a < nodes.length; a++) {
assert(!nodes[a].src.includes('prism'))
}
f.remove()
})
it('should merge ZeroMdConfig opts into config', async () => {
const f = add(``)
await f.waitForReady()
assert(f.config.foo === 'bar')
f.remove()
})
})
describe('getters and setters', () => {
let f
before(() => { f = add(``) })
after(() => f.remove())
it('src reflects', () => {
assert(f.src === 'dummy.md')
f.src = 'dummy2.md'
assert(f.getAttribute('src') === 'dummy2.md')
})
it('boolean equates to true in class prop', () => {
assert(f.manualRender === true)
})
it('boolean reflects', () => {
f.manualRender = false
assert(!f.hasAttribute('manual-render'))
})
})
describe('buildStyles()', () => {
let f
afterEach(() => f.remove())
it('uses default styles if no template declared', () => {
f = add(``)
const s = f.makeNode(f.buildStyles()).outerHTML
assert(s.includes('/github-markdown.min.css'))
})
it('uses template styles', () => {
f = add(``)
const s = f.makeNode(f.buildStyles()).outerHTML
assert(!s.includes('/github-markdown.min.css'))
assert(s.includes('example.css'))
})
it('prepends correctly', () => {
f = add(``)
const s = f.makeNode(f.buildStyles()).outerHTML
assert(s.indexOf('p{color:red;}') < s.indexOf('markdown.min'))
})
it('appends correctly', () => {
f = add(``)
const s = f.makeNode(f.buildStyles()).outerHTML
assert(s.indexOf('p{color:red;}') > s.indexOf('markdown.min'))
})
it('allows passing an empty template to override default template', () => {
f = add(``)
const s = f.makeNode(f.buildStyles())
assert(s.querySelectorAll('link').length === 0)
})
})
describe('buildMd()', () => {
let f
beforeEach(() => { f = add(``) })
afterEach(() => f.remove())
it('converts src to md', async () => {
f.src = 'fixture.md'
await f.render()
assert(f.shadowRoot.querySelector('.markdown-body>h1').innerHTML === 'markdown-fixture')
})
it('falls back to script when src is falsy', async () => {
const el = document.createElement('script')
el.setAttribute('type', 'text/markdown')
el.text = `# fallback`
f.appendChild(el)
await f.render()
assert(f.shadowRoot.querySelector('.markdown-body>h1').innerHTML === 'fallback')
})
it('highlights java code too', async () => {
f.src = 'fixture.md'
await f.render()
await sleep(200) // freaking ugly but blame prism
const el = f.shadowRoot.querySelector('.markdown-body pre>code.language-java :first-child')
assert(el.classList.contains('token'))
})
it('language-detects unhinted code blocks', async () => {
f.src = 'fixture.md'
await f.render()
const nodes = [...f.shadowRoot.querySelectorAll('p')].filter(i => i.textContent === 'Unhinted:')
assert(nodes[0].nextElementSibling.className.includes('language-'))
})
it('dedents when script data-dedent set', async () => {
const el = document.createElement('script')
el.setAttribute('type', 'text/markdown')
el.setAttribute('data-dedent', '')
el.text = `
# fallback`
f.appendChild(el)
await f.render()
assert(f.shadowRoot.querySelector('.markdown-body>h1').innerHTML === 'fallback')
})
it('resolves md base urls relative to src', async () => {
f.src = 'test1/fixture.md'
await f.render()
const a = document.createElement('a')
a.href = f.shadowRoot.querySelector('img').src
assert(a.pathname === '/test1/cat.jpg')
})
})
describe('stampBody()', () => {
let f
beforeEach(() => { f = add(``) })
afterEach(() => f.remove())
it('stamps html body into shadow dom', () => {
f.stampBody('
hello
')
assert(f.shadowRoot.querySelector('.test').innerHTML === 'hello')
})
it('stamps html body into light dom if no-shadow set', () => {
f.remove()
f = add(``)
f.stampBody('
hello
')
assert(f.querySelector('.test').innerHTML === 'hello')
})
})
describe('stampStyles()', () => {
let f
beforeEach(() => { f = add(``) })
afterEach(() => f.remove())
it('stamps html styles and wait for stylesheet links to resolve', async () => {
const html = ''
let loaded = false
f.shadowRoot.addEventListener('load', () => {
loaded = true
}, {
once: true,
capture: true
})
await f.stampStyles(html)
assert(loaded)
})
it('still stamps html styles if a link errors', async () => {
const html = ''
await f.stampStyles(html)
assert(f.shadowRoot.querySelector('link[href="fixture.css"]'))
})
})
describe('render()', () => {
let f
afterEach(() => f.remove())
it('auto re-renders when src change', done => {
f = add(``)
f.addEventListener('zero-md-rendered', () => {
if (f.src === 'fixture.md') {
assert(f.shadowRoot.querySelector('h1').innerHTML === 'markdown-fixture')
f.src = 'test1/fixture.md'
} else if (f.src === 'test1/fixture.md') {
assert(f.shadowRoot.querySelector('h1').innerHTML === 'relative-link-test')
done()
}
})
})
it('prevents FOUC by ensuring styles are stamped and resolved first, before stamping md', async () => {
f = add(``)
const job = f.render()
await tick()
assert(f.shadowRoot.querySelector('link'))
assert(!f.shadowRoot.querySelector('h1'))
await job
assert(f.shadowRoot.querySelector('h1'))
})
it('renders markdown-body with optional classes', async () => {
f = add(``)
await f.render({ classes: 'test-class' })
assert(f.shadowRoot.querySelector('.markdown-body').classList.contains('test-class'))
await f.render({ classes: ['test2', 'test3'] })
assert(f.shadowRoot.querySelector('.markdown-body').classList.contains('test3'))
})
it('renders partially if body changes but styles do not', async () => {
f = add(``)
await f.render()
let detail = {}
f.addEventListener('zero-md-rendered', e => {
detail = e.detail
})
f.querySelector('script').innerText = '# test2'
await f.render()
await tick()
assert(detail.stamped && detail.stamped.body === true)
assert(detail.stamped && !detail.stamped.styles)
const h1 = f.shadowRoot.querySelector('h1')
assert(window.getComputedStyle(h1).getPropertyValue('color') === 'rgb(255, 0, 0)')
})
it('renders partially if styles change but body does not', async () => {
f = add(``)
await f.render()
let detail = {}
f.addEventListener('zero-md-rendered', e => {
detail = e.detail
})
const tpl = f.querySelector('template')
tpl.content.firstElementChild.innerText = 'h1{color:blue}'
await f.render()
await tick()
assert(detail.stamped && detail.stamped.styles === true)
assert(detail.stamped && !detail.stamped.body)
const h1 = f.shadowRoot.querySelector('h1')
assert(window.getComputedStyle(h1).getPropertyValue('color') === 'rgb(0, 0, 255)')
})
})
describe('hash-link scrolls', () => {
let f
afterEach(() => {
location.hash = ''
f.remove()
})
it('scrolls to element if location.hash set on first render', async () => {
location.hash = 'tamen-et-veri'
f = add(`
`)
await sleep(500)
assert(f.scrollTop > 0)
})
it('hijacks same-doc hash links and scrolls id into view', async () => {
f = add(`