Appearance
Testing Plugins
Generative DOM uses Vitest with a jsdom environment for testing. This guide explains how to test plugins effectively.
Test Setup
Create a test file for your plugin:
ts
import { describe, it, expect, beforeEach } from 'vitest';
import { GenerativeDom } from '@generative-dom/core';
import { markdownBase } from '@generative-dom/plugins';
import { myPlugin } from './my-plugin';
describe('myPlugin', () => {
let container: HTMLElement;
let md: GenerativeDom;
beforeEach(() => {
container = document.createElement('div');
md = new GenerativeDom({
container,
plugins: [markdownBase(), myPlugin()],
});
});
});Testing Block Matching
Test that your plugin correctly matches its syntax:
ts
it('matches callout syntax', () => {
md.push('!!! note\nThis is a callout.\n!!!\n');
md.flush();
const callout = container.querySelector('.callout');
expect(callout).not.toBeNull();
expect(callout?.querySelector('strong')?.textContent).toBe('note');
expect(callout?.querySelector('p')?.textContent).toBe('This is a callout.');
});Testing Inline Matching
ts
it('matches highlight syntax', () => {
md.push('This is ==highlighted== text.');
md.flush();
const mark = container.querySelector('mark');
expect(mark).not.toBeNull();
expect(mark?.textContent).toBe('highlighted');
});Testing Streaming
Test that your plugin handles chunks correctly:
ts
it('handles stream splits', () => {
// Split the input at every possible position
const input = '!!! note\nCallout content.\n!!!\n';
for (let i = 1; i < input.length; i++) {
container.innerHTML = '';
md.reset();
md.push(input.slice(0, i));
md.push(input.slice(i));
md.flush();
const callout = container.querySelector('.callout');
expect(callout).not.toBeNull();
}
});
it('handles single-character streaming', () => {
const input = '!!! note\nCallout content.\n!!!\n';
for (const char of input) {
md.push(char);
}
md.flush();
const callout = container.querySelector('.callout');
expect(callout).not.toBeNull();
});Testing Edge Cases
ts
it('ignores incomplete syntax', () => {
md.push('!!! note\nNo closing fence');
md.flush();
// Should not render as callout -- syntax is incomplete
const callout = container.querySelector('.callout');
expect(callout).toBeNull();
});
it('handles empty content', () => {
md.push('!!! note\n\n!!!\n');
md.flush();
const callout = container.querySelector('.callout');
expect(callout).not.toBeNull();
});Testing Events
If your plugin emits events, test them with a spy:
ts
it('emits events', () => {
const handler = vi.fn();
md.on('my-plugin:custom-event', handler);
md.push('<my-syntax />');
md.flush();
expect(handler).toHaveBeenCalledOnce();
expect(handler).toHaveBeenCalledWith({ key: 'value' });
});Testing Security
Verify that your plugin does not introduce XSS vectors:
ts
it('does not allow script injection', () => {
md.push('!!! <script>alert("xss")</script>\nContent\n!!!\n');
md.flush();
expect(container.querySelector('script')).toBeNull();
// Verify the text appears as text, not as an element
expect(container.textContent).toContain('<script>');
});
it('does not use innerHTML', () => {
const spy = vi.spyOn(HTMLElement.prototype, 'innerHTML', 'set');
md.push('!!! note\nContent\n!!!\n');
md.flush();
expect(spy).not.toHaveBeenCalled();
spy.mockRestore();
});Testing Cleanup
ts
it('cleans up on reset', () => {
md.push('!!! note\nContent\n!!!\n');
md.flush();
expect(container.querySelector('.callout')).not.toBeNull();
md.reset();
expect(container.querySelector('.callout')).toBeNull();
});
it('cleans up on destroy', () => {
md.push('!!! note\nContent\n!!!\n');
md.flush();
md.destroy();
expect(container.innerHTML).toBe('');
});Using StreamSimulator
The mocks package provides a StreamSimulator for testing with different chunk strategies:
ts
import { StreamSimulator } from '@generative-dom/mocks';
it('works with random chunk sizes', async () => {
const sim = new StreamSimulator(md, {
chunkStrategy: 'random',
minChunkSize: 1,
maxChunkSize: 5,
delayMs: 0,
});
await sim.stream('!!! note\nCallout content.\n!!!\n');
md.flush();
expect(container.querySelector('.callout')).not.toBeNull();
});Running Tests
bash
# Run all tests
pnpm test
# Run tests for a specific plugin
pnpm test -- --filter my-plugin
# Run in watch mode
pnpm test -- --watch