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 to edit', () => { const editor = new lib.Editor({}); editor.run(); editor.wysiwyg(); editor.edit(); expect(editor.getState()).toBe('edit'); }); 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.edit(); editor.view(); expect(modes).toEqual(['view', 'wysiwyg', 'edit', 'view']); }); it('sourceMode disabled blocks edit', () => { resetDOM(); const editor = new lib.Editor({ currentTheme: 'no-source', themes: [{ name: 'no-source', features: { sourceMode: false }, }], }); editor.run(); editor.wysiwyg(); editor.edit(); expect(editor.getState()).toBe('wysiwyg'); }); }); 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(); expect(lib.defaultTheme.features.sourceMode).toBe(true); }); }); 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', () => { beforeEach(() => resetDOM()); it('converts strong', () => { const editor = new lib.Editor({}); editor.run(); expect(editor.htmlToMarkdown('bold')).toBe('**bold**'); }); it('converts em', () => { const editor = new lib.Editor({}); editor.run(); expect(editor.htmlToMarkdown('italic')).toBe('*italic*'); }); });