import { ribbit, resetDOM } from './setup'; const lib = ribbit(); describe('ToolbarManager', () => { beforeEach(() => resetDOM('**bold** text')); describe('button registration', () => { it('registers tag buttons', () => { const editor = new lib.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 lib.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 lib.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 lib.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 lib.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('paragraph')).toBeUndefined(); }); }); describe('button properties', () => { it('bold has correct label and shortcut', () => { const editor = new lib.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 lib.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('bold')!.action).toBe('wrap'); }); it('save action is custom', () => { const editor = new lib.Editor({}); editor.run(); expect(editor.toolbar.buttons.get('save')!.action).toBe('custom'); }); it('table has template', () => { const editor = new lib.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 lib.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 lib.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 lib.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 lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); expect(toolbar.tagName).toBe('NAV'); expect(toolbar.className).toBe('ribbit-toolbar'); }); it('contains buttons', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); expect(toolbar.querySelector('.ribbit-btn-bold')).not.toBeNull(); expect(toolbar.querySelector('.ribbit-btn-save')).not.toBeNull(); }); it('buttons have aria-label', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); const bold = toolbar.querySelector('.ribbit-btn-bold'); expect(bold?.getAttribute('aria-label')).toBe('Bold'); }); it('buttons have title with shortcut', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); const bold = toolbar.querySelector('.ribbit-btn-bold'); expect(bold?.getAttribute('title')).toBe('Bold (Ctrl+B)'); }); it('renders spacers', () => { const editor = new lib.Editor({ autoToolbar: false, toolbar: ['bold', '', 'save'], }); editor.run(); const toolbar = editor.toolbar.render(); expect(toolbar.querySelector('.spacer')).not.toBeNull(); }); it('renders dropdown groups', () => { const editor = new lib.Editor({ autoToolbar: false, toolbar: [{ group: 'Test', items: ['bold', 'italic'], }], }); editor.run(); const toolbar = editor.toolbar.render(); expect(toolbar.querySelector('.ribbit-dropdown')).not.toBeNull(); }); }); describe('auto-render', () => { it('inserts toolbar before editor by default', () => { resetDOM(); const editor = new lib.Editor({}); editor.run(); const toolbarElement = editor.element.previousElementSibling; expect(toolbarElement?.className).toBe('ribbit-toolbar'); }); it('does not insert when autoToolbar is false', () => { resetDOM(); const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const toolbarElement = editor.element.previousElementSibling; expect(toolbarElement?.className || '').not.toBe('ribbit-toolbar'); }); }); describe('custom layout', () => { it('respects custom toolbar order', () => { const editor = new lib.Editor({ autoToolbar: false, toolbar: ['save', 'bold'], }); editor.run(); const toolbar = editor.toolbar.render(); const buttons = toolbar.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 lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); expect(toolbar.querySelectorAll('button').length).toBeGreaterThan(3); }); }); describe('enable/disable', () => { it('disable adds disabled class', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); editor.toolbar.disable(); const bold = toolbar.querySelector('.ribbit-btn-bold'); expect(bold?.classList.contains('disabled')).toBe(true); }); it('enable removes disabled class', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const toolbar = editor.toolbar.render(); editor.toolbar.disable(); editor.toolbar.enable(); const bold = toolbar.querySelector('.ribbit-btn-bold'); expect(bold?.classList.contains('disabled')).toBe(false); }); }); describe('updateActiveState', () => { it('sets active class on matching buttons', () => { const editor = new lib.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 lib.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('heading and list buttons', () => { it('registers h1-h6', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); for (let level = 1; level <= 6; level++) { const btn = editor.toolbar.buttons.get(`h${level}`); expect(btn).toBeDefined(); expect(btn!.label).toBe(`H${level}`); expect(btn!.shortcut).toBe(`Ctrl+${level}`); expect(btn!.action).toBe('prefix'); } }); it('registers ul and ol', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); expect(editor.toolbar.buttons.get('ul')!.shortcut).toBe('Ctrl+Shift+8'); expect(editor.toolbar.buttons.get('ol')!.shortcut).toBe('Ctrl+Shift+7'); }); }); describe('keyboard shortcuts', () => { it('all formatting buttons have shortcuts', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); const expected = ['bold', 'italic', 'code', 'link', 'save']; for (const id of expected) { expect(editor.toolbar.buttons.get(id)!.shortcut).toBeDefined(); } }); it('block buttons have shortcuts', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); expect(editor.toolbar.buttons.get('fencedCode')!.shortcut).toBe('Ctrl+Shift+E'); expect(editor.toolbar.buttons.get('blockquote')!.shortcut).toBe('Ctrl+Shift+.'); expect(editor.toolbar.buttons.get('table')!.shortcut).toBe('Ctrl+Shift+T'); expect(editor.toolbar.buttons.get('hr')!.shortcut).toBe('Ctrl+Shift+-'); }); it('editor actions have shortcuts', () => { const editor = new lib.Editor({ autoToolbar: false }); editor.run(); expect(editor.toolbar.buttons.get('toggle')!.shortcut).toBe('Ctrl+Shift+V'); expect(editor.toolbar.buttons.get('markdown')!.shortcut).toBe('Ctrl+/'); }); }); describe('save button', () => { it('triggers editor.save()', () => { resetDOM(); let saved = false; const editor = new lib.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 lib.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 lib.Editor({ autoToolbar: false }); editor.run(); editor.wysiwyg(); editor.toolbar.render(); editor.toolbar.buttons.get('toggle')!.click(); expect(editor.getState()).toBe('view'); }); }); });