import { ribbit, resetDOM } from './setup'; const lib = ribbit(); describe('RibbitEmitter', () => { beforeEach(() => resetDOM()); it('fires save event', () => { const editor = new lib.Editor({}); editor.run(); let received: any = null; editor.on('save', (payload: any) => { received = payload; }); editor.save(); expect(received).toHaveProperty('markdown'); expect(received).toHaveProperty('html'); }); it('off removes handler', () => { const editor = new lib.Editor({}); editor.run(); let count = 0; const handler = () => { count++; }; editor.on('save', handler); editor.save(); editor.off('save', handler); editor.save(); expect(count).toBe(1); }); it('multiple listeners', () => { const editor = new lib.Editor({}); editor.run(); let count = 0; editor.on('save', () => { count++; }); editor.on('save', () => { count++; }); editor.save(); expect(count).toBe(2); }); }); describe('Ribbit viewer', () => { beforeEach(() => resetDOM('**bold**')); it('starts with null state', () => { const viewer = new lib.Viewer({}); expect(viewer.getState()).toBeNull(); }); it('run sets view state', () => { const viewer = new lib.Viewer({}); viewer.run(); expect(viewer.getState()).toBe('view'); }); it('renders html', () => { const viewer = new lib.Viewer({}); viewer.run(); expect(viewer.element.innerHTML).toContain('bold'); }); it('getMarkdown returns source', () => { const viewer = new lib.Viewer({}); expect(viewer.getMarkdown()).toBe('**bold**'); }); }); describe('Ribbit events', () => { it('ready fires on run', () => { resetDOM('hello'); let payload: any = null; const viewer = new lib.Viewer({ on: { ready: (eventPayload: any) => { payload = eventPayload; }, }, }); 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', () => { const editor = new lib.Editor({}); editor.run(); expect(editor.getState()).toBe('view'); }); it('switches to wysiwyg', () => { const editor = new lib.Editor({}); editor.run(); editor.wysiwyg(); expect(editor.getState()).toBe('wysiwyg'); expect(editor.element.contentEditable).toBe('true'); }); it('switches back to view', () => { const editor = new lib.Editor({}); editor.run(); editor.wysiwyg(); editor.view(); expect(editor.getState()).toBe('view'); expect(editor.element.contentEditable).toBe('false'); }); it('fires modeChange events', () => { const modes: string[] = []; const editor = new lib.Editor({ on: { modeChange: ({ current }: any) => { modes.push(current); }, }, }); editor.run(); editor.wysiwyg(); editor.view(); expect(modes).toEqual(['view', 'wysiwyg', 'edit', 'view']); }); }); describe('ThemeManager', () => { beforeEach(() => resetDOM()); it('lists registered themes', () => { const editor = new lib.Editor({ themes: [{ name: 'dark' }] }); editor.run(); expect(editor.themes.list()).toContain('ribbit-default'); expect(editor.themes.list()).toContain('dark'); }); it('set switches theme', () => { const editor = new lib.Editor({ themes: [{ name: 'dark' }] }); editor.run(); editor.themes.set('dark'); expect(editor.themes.current().name).toBe('dark'); }); it('disable hides from list', () => { const editor = new lib.Editor({ themes: [{ name: 'dark' }] }); editor.run(); editor.themes.disable('dark'); expect(editor.themes.list()).not.toContain('dark'); }); it('enable restores to list', () => { const editor = new lib.Editor({ themes: [{ name: 'dark' }] }); editor.run(); editor.themes.disable('dark'); editor.themes.enable('dark'); expect(editor.themes.list()).toContain('dark'); }); it('set disabled throws', () => { const editor = new lib.Editor({ themes: [{ name: 'dark' }] }); editor.run(); editor.themes.disable('dark'); expect(() => editor.themes.set('dark')).toThrow(); }); it('set unknown throws', () => { const editor = new lib.Editor({}); editor.run(); expect(() => editor.themes.set('nonexistent')).toThrow(); }); it('remove active throws', () => { const editor = new lib.Editor({}); editor.run(); expect(() => editor.themes.remove(editor.themes.current().name)).toThrow(); }); it('fires themeChange', () => { let payload: any = null; const editor = new lib.Editor({ themes: [{ name: 'dark' }], on: { themeChange: (eventPayload: any) => { payload = eventPayload; }, }, }); 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', () => { expect(lib.defaultTheme.name).toBe('ribbit-default'); expect(lib.defaultTheme.tags).toBeDefined(); }); }); describe('Utility functions', () => { it('encodeHtmlEntities', () => { expect(lib.encodeHtmlEntities('<')).toBe('<'); expect(lib.encodeHtmlEntities('>')).toBe('>'); expect(lib.encodeHtmlEntities('&')).toBe('&'); }); it('decodeHtmlEntities', () => { expect(lib.decodeHtmlEntities('<')).toBe('<'); expect(lib.decodeHtmlEntities('&')).toBe('&'); }); it('camelCase', () => { expect(lib.camelCase('hello').join('')).toBe('Hello'); expect(lib.camelCase('hello world').join(' ')).toBe('Hello World'); }); }); describe('Editor htmlToMarkdown', () => { it('returns markdown in view state', () => { resetDOM('**bold**'); const editor = new lib.Editor({}); console.log(editor.getMarkdown()); editor.run(); console.log(editor.getMarkdown()); expect(editor.getMarkdown()).toBe('**bold**'); }); it('returns markdown in wysiwyg state', () => { resetDOM('**bold**'); const editor = new lib.Editor({}); editor.run(); editor.wysiwyg(); expect(editor.getMarkdown()).toBe('**bold**'); }); it('round-trips inline formatting', () => { resetDOM('hello **world** and *italic*'); const editor = new lib.Editor({}); editor.run(); editor.wysiwyg(); expect(editor.getMarkdown()).toBe('hello **world** and *italic*'); }); });