import { ribbit, resetDOM } from './setup'; const r = ribbit(); describe('ToolbarManager', () => { beforeEach(() => resetDOM('**bold** text')); describe('button registration', () => { it('registers tag buttons', () => { const editor = new r.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('bold')).toBeDefined(); expect(editor.toolbar.buttons.get('italic')).toBeDefined(); expect(editor.toolbar.buttons.get('code')).toBeDefined(); }); it('registers editor actions', () => { const editor = new r.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('save')).toBeDefined(); expect(editor.toolbar.buttons.get('toggle')).toBeDefined(); expect(editor.toolbar.buttons.get('markdown')).toBeDefined(); }); it('registers macro buttons', () => { const editor = new r.Editor({ macros: [{ name: 'user', toHTML: () => 'u' }], }); editor.run(); expect(editor.toolbar.buttons.get('macro:user')).toBeDefined(); }); it('skips macros with button: false', () => { const editor = new r.Editor({ macros: [{ name: 'hidden', toHTML: () => '', button: false }], }); editor.run(); expect(editor.toolbar.buttons.get('macro:hidden')).toBeUndefined(); }); it('skips tags without button', () => { const editor = new r.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('paragraph')).toBeUndefined(); }); }); describe('button properties', () => { it('bold has correct label and shortcut', () => { const editor = new r.Editor({}); editor.run(); const bold = editor.toolbar.buttons.get('bold')!; expect(bold.label).toBe('Bold'); expect(bold.shortcut).toBe('Ctrl+B'); }); it('bold action is wrap', () => { const editor = new r.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('bold')!.action).toBe('wrap'); }); it('save action is custom', () => { const editor = new r.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('save')!.action).toBe('custom'); }); it('table has template', () => { const editor = new r.Editor({}); editor.run(); const table = editor.toolbar.buttons.get('table')!; expect(table.template).toContain('Header'); expect(table.replaceSelection).toBe(false); }); it('macro button has insert action', () => { const editor = new r.Editor({ macros: [{ name: 'toc', toHTML: () => '' }], }); editor.run(); const btn = editor.toolbar.buttons.get('macro:toc')!; expect(btn.action).toBe('insert'); expect(btn.template).toBe('@toc'); }); }); describe('button.hide() and button.show()', () => { it('hide sets visible false', () => { const editor = new r.Editor({}); editor.run(); const bold = editor.toolbar.buttons.get('bold')!; expect(bold.visible).toBe(true); bold.hide(); expect(bold.visible).toBe(false); }); it('show restores visible', () => { const editor = new r.Editor({}); editor.run(); const bold = editor.toolbar.buttons.get('bold')!; bold.hide(); bold.show(); expect(bold.visible).toBe(true); }); }); describe('render()', () => { it('returns an HTMLElement', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); expect(el.tagName).toBe('NAV'); expect(el.className).toBe('ribbit-toolbar'); }); it('contains buttons', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); expect(el.querySelector('.ribbit-btn-bold')).not.toBeNull(); expect(el.querySelector('.ribbit-btn-save')).not.toBeNull(); }); it('buttons have aria-label', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); const bold = el.querySelector('.ribbit-btn-bold'); expect(bold?.getAttribute('aria-label')).toBe('Bold'); }); it('buttons have title with shortcut', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); const bold = el.querySelector('.ribbit-btn-bold'); expect(bold?.getAttribute('title')).toBe('Bold (Ctrl+B)'); }); it('renders spacers', () => { const editor = new r.Editor({ autoToolbar: false, toolbar: ['bold', '', 'save'], }); editor.run(); const el = editor.toolbar.render(); expect(el.querySelector('.spacer')).not.toBeNull(); }); it('renders dropdown groups', () => { const editor = new r.Editor({ autoToolbar: false, toolbar: [{ group: 'Test', items: ['bold', 'italic'] }], }); editor.run(); const el = editor.toolbar.render(); expect(el.querySelector('.ribbit-dropdown')).not.toBeNull(); }); }); describe('auto-render', () => { it('inserts toolbar before editor by default', () => { resetDOM(); const editor = new r.Editor({}); editor.run(); const toolbar = editor.element.previousElementSibling; expect(toolbar?.className).toBe('ribbit-toolbar'); }); it('does not insert when autoToolbar is false', () => { resetDOM(); const editor = new r.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.element.previousElementSibling; expect(toolbar?.className || '').not.toBe('ribbit-toolbar'); }); }); describe('custom layout', () => { it('respects custom toolbar order', () => { const editor = new r.Editor({ autoToolbar: false, toolbar: ['save', 'bold'], }); editor.run(); const el = editor.toolbar.render(); const buttons = el.querySelectorAll('button'); expect(buttons[0]?.className).toBe('ribbit-btn-save'); expect(buttons[1]?.className).toBe('ribbit-btn-bold'); }); it('auto-generates layout when not specified', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); expect(el.querySelectorAll('button').length).toBeGreaterThan(3); }); }); describe('enable/disable', () => { it('disable adds disabled class', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); editor.toolbar.disable(); const bold = el.querySelector('.ribbit-btn-bold'); expect(bold?.classList.contains('disabled')).toBe(true); }); it('enable removes disabled class', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); const el = editor.toolbar.render(); editor.toolbar.disable(); editor.toolbar.enable(); const bold = el.querySelector('.ribbit-btn-bold'); expect(bold?.classList.contains('disabled')).toBe(false); }); }); describe('updateActiveState', () => { it('sets active class on matching buttons', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); editor.toolbar.render(); editor.toolbar.updateActiveState(['bold']); expect(editor.toolbar.buttons.get('bold')!.element?.classList.contains('active')).toBe(true); expect(editor.toolbar.buttons.get('italic')!.element?.classList.contains('active')).toBe(false); }); it('clears active when not in list', () => { const editor = new r.Editor({ autoToolbar: false }); editor.run(); editor.toolbar.render(); editor.toolbar.updateActiveState(['bold']); editor.toolbar.updateActiveState([]); expect(editor.toolbar.buttons.get('bold')!.element?.classList.contains('active')).toBe(false); }); }); describe('save button', () => { it('triggers editor.save()', () => { resetDOM(); let saved = false; const editor = new r.Editor({ autoToolbar: false, on: { save: () => { saved = true; } }, }); editor.run(); editor.toolbar.render(); editor.toolbar.buttons.get('save')!.click(); expect(saved).toBe(true); }); }); describe('toggle button', () => { it('switches from view to wysiwyg', () => { resetDOM(); const editor = new r.Editor({ autoToolbar: false }); editor.run(); editor.toolbar.render(); expect(editor.getState()).toBe('view'); editor.toolbar.buttons.get('toggle')!.click(); expect(editor.getState()).toBe('wysiwyg'); }); it('switches from wysiwyg to view', () => { resetDOM(); const editor = new r.Editor({ autoToolbar: false }); editor.run(); editor.wysiwyg(); editor.toolbar.render(); editor.toolbar.buttons.get('toggle')!.click(); expect(editor.getState()).toBe('view'); }); }); });