import { expect, fixture, html, oneEvent } from '@open-wc/testing'; import './nile-markdown'; import NileMarkdown from './nile-markdown'; describe('NileMarkdown', () => { // === RENDERING === it('1. should render without errors', async () => { const el = await fixture( html`` ); expect(el).to.exist; }); it('2. should have a shadow root', async () => { const el = await fixture( html`` ); expect(el.shadowRoot).to.not.be.null; }); it('3. should have base part', async () => { const el = await fixture( html`` ); const base = el.shadowRoot!.querySelector('[part~="base"]'); expect(base).to.exist; }); it('4. should be instance of NileMarkdown', async () => { const el = await fixture( html`` ); expect(el).to.be.instanceOf(NileMarkdown); }); it('5. should have correct tag name', async () => { const el = await fixture( html`` ); expect(el.tagName.toLowerCase()).to.equal('nile-markdown'); }); // === DEFAULT PROPERTIES === it('6. should have value default to empty string', async () => { const el = await fixture( html`` ); expect(el.value).to.equal(''); }); it('7. should have tabSize default to 4', async () => { const el = await fixture( html`` ); expect(el.tabSize).to.equal(4); }); // === VALUE PROPERTY === it('8. should render markdown from value property', async () => { const el = await fixture( html`` ); await el.updateComplete; const h1 = el.shadowRoot!.querySelector('h1'); expect(h1).to.exist; expect(h1!.textContent).to.equal('Heading'); }); it('9. should render bold text', async () => { const el = await fixture( html`` ); await el.updateComplete; const strong = el.shadowRoot!.querySelector('strong'); expect(strong).to.exist; expect(strong!.textContent).to.equal('bold'); }); it('10. should render links', async () => { const el = await fixture( html`` ); await el.updateComplete; const a = el.shadowRoot!.querySelector('a'); expect(a).to.exist; expect(a!.getAttribute('href')).to.equal('https://example.com'); }); it('11. should render lists', async () => { const el = await fixture( html`` ); el.value = '- one\n- two\n- three'; await el.updateComplete; const items = el.shadowRoot!.querySelectorAll('li'); expect(items.length).to.equal(3); }); it('12. should render code blocks', async () => { const el = await fixture( html`` ); el.value = '```\nconst a = 1;\n```'; await el.updateComplete; const pre = el.shadowRoot!.querySelector('pre code'); expect(pre).to.exist; }); it('13. should render GFM tables', async () => { const el = await fixture( html`` ); el.value = '| a | b |\n| - | - |\n| 1 | 2 |'; await el.updateComplete; const table = el.shadowRoot!.querySelector('table'); expect(table).to.exist; }); it('14. should render blockquotes', async () => { const el = await fixture( html`` ); el.value = '> quoted'; await el.updateComplete; const quote = el.shadowRoot!.querySelector('blockquote'); expect(quote).to.exist; }); // === SCRIPT CHILD === it('15. should render markdown from a script child', async () => { const el = await fixture(html` `); await el.updateComplete; const h1 = el.shadowRoot!.querySelector('h1'); expect(h1).to.exist; expect(h1!.textContent).to.equal('From Script'); }); it('16. should normalize indented script content', async () => { const el = await fixture(html` `); await el.updateComplete; // Indented content must not be treated as a code block expect(el.shadowRoot!.querySelector('pre')).to.not.exist; expect(el.shadowRoot!.querySelector('h1')).to.exist; expect(el.shadowRoot!.querySelector('p')).to.exist; }); it('17. value should take precedence over script child', async () => { const el = await fixture(html` `); await el.updateComplete; const h1 = el.shadowRoot!.querySelector('h1'); expect(h1!.textContent).to.equal('From Value'); }); // === DYNAMIC UPDATES === it('18. should re-render when value changes', async () => { const el = await fixture( html`` ); await el.updateComplete; el.value = '# Two'; await el.updateComplete; await el.updateComplete; const h1 = el.shadowRoot!.querySelector('h1'); expect(h1!.textContent).to.equal('Two'); }); it('19. should re-render when script content changes', async () => { const el = await fixture(html` `); await el.updateComplete; const script = el.querySelector('script')!; script.textContent = '# After'; // MutationObserver fires async await new Promise(resolve => setTimeout(resolve, 0)); await el.updateComplete; const h1 = el.shadowRoot!.querySelector('h1'); expect(h1!.textContent).to.equal('After'); }); it('20. renderMarkdown() should re-parse manually', async () => { const el = await fixture( html`` ); await el.updateComplete; el.renderMarkdown(); await el.updateComplete; expect(el.shadowRoot!.querySelector('h1')!.textContent).to.equal('Manual'); }); // === EVENTS === it('21. should emit nile-markdown-rendered after rendering', async () => { const el = document.createElement('nile-markdown') as NileMarkdown; const listener = oneEvent(el, 'nile-markdown-rendered'); el.value = '# Event'; document.body.appendChild(el); const event = await listener; expect(event).to.exist; el.remove(); }); // === STATIC API === it('22. getMarked() should return a shared instance', () => { const a = NileMarkdown.getMarked(); const b = NileMarkdown.getMarked(); expect(a).to.equal(b); }); it('23. updateAll() should not throw', async () => { await fixture( html`` ); expect(() => NileMarkdown.updateAll()).to.not.throw(); }); // === EDGE CASES === it('24. should handle empty content', async () => { const el = await fixture( html`` ); const base = el.shadowRoot!.querySelector('[part~="base"]'); expect(base!.textContent!.trim()).to.equal(''); }); it('25. should have static styles', () => { expect(NileMarkdown.styles).to.exist; }); });