import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { TableDomSelector, TableSelection } from '../../modules'; import { TableUp } from '../../table-up'; import { createQuillWithTableModule } from './utils'; beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); describe('TableUp - contenteditable change', () => { describe('when contenteditable becomes false', () => { it('should destroy all table modules', async () => { const quill = createQuillWithTableModule(`


`, { modules: [{ module: TableSelection }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; const module = tableUp.getModule(TableSelection.moduleName); expect(module).toBeDefined(); // Disable editor quill.enable(false); // Wait for MutationObserver callback await vi.runAllTimersAsync(); // Verify modules are cleared expect(tableUp.modules).not.toBeNullable(); expect(Object.keys(tableUp.modules).length).toEqual(0); }); it('should handle modules without destroy method gracefully', async () => { // Create a module without destroy method class MockModuleWithoutDestroy extends TableDomSelector { static moduleName = 'mock-module-without-destroy'; } const quill = createQuillWithTableModule(`


`, { modules: [{ module: MockModuleWithoutDestroy }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; // Should not throw error await expect(() => quill.enable(false)).not.toThrow(); // Modules should be cleared expect(tableUp.modules).not.toBeNullable(); expect(Object.keys(tableUp.modules).length).toEqual(0); }); }); describe('when contenteditable becomes true', () => { it('should create new module instances', async () => { const quill = createQuillWithTableModule(`


`, { modules: [{ module: TableSelection }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; const oldModule = tableUp.getModule(TableSelection.moduleName); expect(oldModule).toBeDefined(); // Disable editor quill.enable(false); await vi.runAllTimersAsync(); // Re-enable editor quill.enable(true); await vi.runAllTimersAsync(); // Get new module instance const newModule = tableUp.getModule(TableSelection.moduleName); // Verify new instance is created expect(newModule).toBeDefined(); expect(newModule).not.toBe(oldModule); }); it('should work with multiple enable/disable cycles', async () => { const quill = createQuillWithTableModule(`


`, { modules: [{ module: TableSelection }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; // Multiple toggle cycles quill.enable(false); await vi.runAllTimersAsync(); quill.enable(true); await vi.runAllTimersAsync(); quill.enable(false); await vi.runAllTimersAsync(); quill.enable(true); await vi.runAllTimersAsync(); // Verify modules still work const module = tableUp.getModule(TableSelection.moduleName); expect(module).toBeDefined(); expect(module).toBeInstanceOf(TableSelection); }); }); describe('error handling', () => { it('should continue working if module destroy throws error', async () => { // Create a module that throws error on destroy class BadModule extends TableDomSelector { static moduleName = 'bad-module'; destroy() { throw new Error('Destroy failed'); } } const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const quill = createQuillWithTableModule(`


`, { modules: [{ module: BadModule }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; // Should not throw error await expect(() => quill.enable(false)).not.toThrow(); // Should output warning expect(consoleWarnSpy).toHaveBeenCalledWith( expect.stringContaining('Failed to destroy module'), expect.any(Error), ); consoleWarnSpy.mockRestore(); // Modules should be cleared expect(tableUp.modules).toEqual({}); }); it('should handle partial destroy failures', async () => { class BadModule extends TableDomSelector { static moduleName = 'bad-module'; destroy() { throw new Error('Bad module destroy failed'); } } class GoodModule extends TableDomSelector { static moduleName = 'good-module'; destroy() { // Normal destroy } } const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const quill = createQuillWithTableModule(`


`, { modules: [ { module: BadModule }, { module: GoodModule }, ], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; quill.enable(false); await vi.runAllTimersAsync(); // Should have warning expect(consoleWarnSpy).toHaveBeenCalled(); consoleWarnSpy.mockRestore(); // All modules should be cleared expect(tableUp.modules).toEqual({}); }); }); describe('MutationObserver behavior', () => { it('should only respond to contenteditable attribute changes', async () => { const quill = createQuillWithTableModule(`


`, { modules: [{ module: TableSelection }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; const initialModuleCount = Object.keys(tableUp.modules).length; // Change other attributes, should not trigger destroy quill.root.setAttribute('data-test', 'value'); await vi.runAllTimersAsync(); expect(Object.keys(tableUp.modules).length).toBe(initialModuleCount); // Change contenteditable should trigger quill.enable(false); await vi.runAllTimersAsync(); expect(tableUp.modules).toEqual({}); }); it('should handle direct contenteditable attribute changes', async () => { const quill = createQuillWithTableModule(`


`, { modules: [{ module: TableSelection }], }); const tableUp = quill.getModule(TableUp.moduleName) as TableUp; // Directly set contenteditable attribute (instead of using enable method) quill.root.setAttribute('contenteditable', 'false'); await vi.runAllTimersAsync(); expect(Object.keys(tableUp.modules).length).toEqual(0); }); }); });