2026-04-28 21:39:13 -07:00
|
|
|
import { ribbit, resetDOM } from './setup';
|
|
|
|
|
|
2026-04-29 15:48:36 -07:00
|
|
|
const lib = ribbit();
|
2026-04-28 21:39:13 -07:00
|
|
|
|
|
|
|
|
describe('RibbitEmitter', () => {
|
|
|
|
|
beforeEach(() => resetDOM());
|
|
|
|
|
|
|
|
|
|
it('fires save event', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
let received: any = null;
|
2026-04-29 15:48:36 -07:00
|
|
|
editor.on('save', (payload: any) => {
|
|
|
|
|
received = payload;
|
|
|
|
|
});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.save();
|
|
|
|
|
expect(received).toHaveProperty('markdown');
|
|
|
|
|
expect(received).toHaveProperty('html');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('off removes handler', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
let count = 0;
|
2026-04-29 15:48:36 -07:00
|
|
|
const handler = () => {
|
|
|
|
|
count++;
|
|
|
|
|
};
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.on('save', handler);
|
|
|
|
|
editor.save();
|
|
|
|
|
editor.off('save', handler);
|
|
|
|
|
editor.save();
|
|
|
|
|
expect(count).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('multiple listeners', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
let count = 0;
|
2026-04-29 15:48:36 -07:00
|
|
|
editor.on('save', () => {
|
|
|
|
|
count++;
|
|
|
|
|
});
|
|
|
|
|
editor.on('save', () => {
|
|
|
|
|
count++;
|
|
|
|
|
});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.save();
|
|
|
|
|
expect(count).toBe(2);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Ribbit viewer', () => {
|
|
|
|
|
beforeEach(() => resetDOM('**bold**'));
|
|
|
|
|
|
|
|
|
|
it('starts with null state', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const viewer = new lib.Viewer({});
|
2026-04-28 21:39:13 -07:00
|
|
|
expect(viewer.getState()).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('run sets view state', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const viewer = new lib.Viewer({});
|
2026-04-28 21:39:13 -07:00
|
|
|
viewer.run();
|
|
|
|
|
expect(viewer.getState()).toBe('view');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders html', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const viewer = new lib.Viewer({});
|
2026-04-28 21:39:13 -07:00
|
|
|
viewer.run();
|
|
|
|
|
expect(viewer.element.innerHTML).toContain('<strong>bold</strong>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('getMarkdown returns source', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const viewer = new lib.Viewer({});
|
2026-04-28 21:39:13 -07:00
|
|
|
expect(viewer.getMarkdown()).toBe('**bold**');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Ribbit events', () => {
|
|
|
|
|
it('ready fires on run', () => {
|
|
|
|
|
resetDOM('hello');
|
|
|
|
|
let payload: any = null;
|
2026-04-29 15:48:36 -07:00
|
|
|
const viewer = new lib.Viewer({
|
|
|
|
|
on: {
|
|
|
|
|
ready: (eventPayload: any) => {
|
|
|
|
|
payload = eventPayload;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-04-28 21:39:13 -07:00
|
|
|
viewer.run();
|
|
|
|
|
expect(payload).toHaveProperty('markdown');
|
|
|
|
|
expect(payload).toHaveProperty('mode', 'view');
|
|
|
|
|
expect(payload.theme.name).toBe('ribbit-default');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('RibbitEditor modes', () => {
|
|
|
|
|
beforeEach(() => resetDOM('**bold**'));
|
|
|
|
|
|
|
|
|
|
it('starts in view', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
expect(editor.getState()).toBe('view');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('switches to wysiwyg', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.wysiwyg();
|
|
|
|
|
expect(editor.getState()).toBe('wysiwyg');
|
|
|
|
|
expect(editor.element.contentEditable).toBe('true');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('switches to edit', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.wysiwyg();
|
|
|
|
|
editor.edit();
|
|
|
|
|
expect(editor.getState()).toBe('edit');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('switches back to view', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.wysiwyg();
|
|
|
|
|
editor.view();
|
|
|
|
|
expect(editor.getState()).toBe('view');
|
|
|
|
|
expect(editor.element.contentEditable).toBe('false');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('fires modeChange events', () => {
|
|
|
|
|
const modes: string[] = [];
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({
|
|
|
|
|
on: {
|
|
|
|
|
modeChange: ({ current }: any) => {
|
|
|
|
|
modes.push(current);
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
editor.run();
|
|
|
|
|
editor.wysiwyg();
|
|
|
|
|
editor.edit();
|
|
|
|
|
editor.view();
|
|
|
|
|
expect(modes).toEqual(['view', 'wysiwyg', 'edit', 'view']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sourceMode disabled blocks edit', () => {
|
|
|
|
|
resetDOM();
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({
|
2026-04-28 21:39:13 -07:00
|
|
|
currentTheme: 'no-source',
|
2026-04-29 15:48:36 -07:00
|
|
|
themes: [{
|
|
|
|
|
name: 'no-source',
|
|
|
|
|
features: { sourceMode: false },
|
|
|
|
|
}],
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
editor.run();
|
|
|
|
|
editor.wysiwyg();
|
|
|
|
|
editor.edit();
|
|
|
|
|
expect(editor.getState()).toBe('wysiwyg');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('ThemeManager', () => {
|
|
|
|
|
beforeEach(() => resetDOM());
|
|
|
|
|
|
|
|
|
|
it('lists registered themes', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({ themes: [{ name: 'dark' }] });
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
expect(editor.themes.list()).toContain('ribbit-default');
|
|
|
|
|
expect(editor.themes.list()).toContain('dark');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('set switches theme', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({ themes: [{ name: 'dark' }] });
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.themes.set('dark');
|
|
|
|
|
expect(editor.themes.current().name).toBe('dark');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('disable hides from list', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({ themes: [{ name: 'dark' }] });
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.themes.disable('dark');
|
|
|
|
|
expect(editor.themes.list()).not.toContain('dark');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('enable restores to list', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({ themes: [{ name: 'dark' }] });
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.themes.disable('dark');
|
|
|
|
|
editor.themes.enable('dark');
|
|
|
|
|
expect(editor.themes.list()).toContain('dark');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('set disabled throws', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({ themes: [{ name: 'dark' }] });
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
editor.themes.disable('dark');
|
|
|
|
|
expect(() => editor.themes.set('dark')).toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('set unknown throws', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
expect(() => editor.themes.set('nonexistent')).toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('remove active throws', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
expect(() => editor.themes.remove(editor.themes.current().name)).toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('fires themeChange', () => {
|
|
|
|
|
let payload: any = null;
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({
|
2026-04-28 21:39:13 -07:00
|
|
|
themes: [{ name: 'dark' }],
|
2026-04-29 15:48:36 -07:00
|
|
|
on: {
|
|
|
|
|
themeChange: (eventPayload: any) => {
|
|
|
|
|
payload = eventPayload;
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
editor.run();
|
|
|
|
|
editor.themes.set('dark');
|
|
|
|
|
expect(payload.current.name).toBe('dark');
|
|
|
|
|
expect(payload.previous.name).toBe('ribbit-default');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
describe('defaultTheme', () => {
|
|
|
|
|
it('has correct shape', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
expect(lib.defaultTheme.name).toBe('ribbit-default');
|
|
|
|
|
expect(lib.defaultTheme.tags).toBeDefined();
|
|
|
|
|
expect(lib.defaultTheme.features.sourceMode).toBe(true);
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Utility functions', () => {
|
|
|
|
|
it('encodeHtmlEntities', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
expect(lib.encodeHtmlEntities('<')).toBe('<');
|
|
|
|
|
expect(lib.encodeHtmlEntities('>')).toBe('>');
|
|
|
|
|
expect(lib.encodeHtmlEntities('&')).toBe('&');
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('decodeHtmlEntities', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
expect(lib.decodeHtmlEntities('<')).toBe('<');
|
|
|
|
|
expect(lib.decodeHtmlEntities('&')).toBe('&');
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('camelCase', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
expect(lib.camelCase('hello').join('')).toBe('Hello');
|
|
|
|
|
expect(lib.camelCase('hello world').join(' ')).toBe('Hello World');
|
2026-04-28 21:39:13 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Editor htmlToMarkdown', () => {
|
|
|
|
|
beforeEach(() => resetDOM());
|
|
|
|
|
|
|
|
|
|
it('converts strong', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
expect(editor.htmlToMarkdown('<strong>bold</strong>')).toBe('**bold**');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('converts em', () => {
|
2026-04-29 15:48:36 -07:00
|
|
|
const editor = new lib.Editor({});
|
2026-04-28 21:39:13 -07:00
|
|
|
editor.run();
|
|
|
|
|
expect(editor.htmlToMarkdown('<em>italic</em>')).toBe('*italic*');
|
|
|
|
|
});
|
|
|
|
|
});
|