From 3399b0d79d5916b87989762bb25dd213208fabca Mon Sep 17 00:00:00 2001 From: KarimAziev Date: Mon, 29 Apr 2024 12:56:31 +0300 Subject: [PATCH] test: add unit tests for editor handlers and utilities --- __tests__/src/handlers/ace.test.ts | 102 +++++++++++++++++ __tests__/src/handlers/codemirror5.test.ts | 112 +++++++++++++++++++ __tests__/src/handlers/codemirror6.test.ts | 114 +++++++++++++++++++ __tests__/src/handlers/monaco.test.ts | 115 ++++++++++++++++++++ __tests__/src/util/event-dispatcher.test.ts | 80 ++++++++++++++ 5 files changed, 523 insertions(+) create mode 100644 __tests__/src/handlers/ace.test.ts create mode 100644 __tests__/src/handlers/codemirror5.test.ts create mode 100644 __tests__/src/handlers/codemirror6.test.ts create mode 100644 __tests__/src/handlers/monaco.test.ts create mode 100644 __tests__/src/util/event-dispatcher.test.ts diff --git a/__tests__/src/handlers/ace.test.ts b/__tests__/src/handlers/ace.test.ts new file mode 100644 index 0000000..f4dcd9b --- /dev/null +++ b/__tests__/src/handlers/ace.test.ts @@ -0,0 +1,102 @@ +import AceHandler from '@/handlers/ace'; +import InjectorHandler from '@/handlers/injector'; + +let mockElem: HTMLTextAreaElement; +let mockParent: HTMLDivElement; + +const mockContentEventsBinder = { + bind: jest.fn(), +}; + +describe('AceHandler', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockElem = document.createElement('textarea'); + mockParent = document.createElement('div'); + mockParent.appendChild(mockElem); + document.body.appendChild(mockParent); + }); + + afterAll(() => { + document.body.innerHTML = ''; + }); + + describe('constructor', () => { + it('should correctly initialize with the given parameters', () => { + const handler = new AceHandler(mockElem, mockContentEventsBinder); + + expect(handler).toBeDefined(); + expect((handler as any).elem).toBe(mockElem); + }); + }); + + describe('setValue', () => { + it('should set value without triggering DOM event', () => { + const mockValue = 'test value'; + const options = { text: mockValue, triggerDOMEvent: false }; + const handler = new AceHandler(mockElem, mockContentEventsBinder); + handler.setValue = jest.fn(); + handler.setValue(mockValue, options); + + expect(handler.setValue).toHaveBeenCalledWith(mockValue, { + ...options, + triggerDOMEvent: false, + }); + }); + }); + + describe('getValue', () => { + it('should call super.getValue', async () => { + const handler = new AceHandler(mockElem, mockContentEventsBinder); + const superGetValueSpy = jest + .spyOn(InjectorHandler.prototype, 'getValue') + .mockImplementation(async () => ({ text: 'my text' })); + await handler.getValue(); + expect(superGetValueSpy).toHaveBeenCalled(); + superGetValueSpy.mockRestore(); + }); + + it('should post a "getValue" message and resolve with the value', async () => { + const handler = new AceHandler(mockElem, mockContentEventsBinder); + const promise = handler.getValue(); + handler.emit('value', { text: 'testValue' }); + await expect(promise).resolves.toEqual({ text: 'testValue' }); + }); + }); + + describe('static canHandle determines if the given element can be handled by AceHandler', () => { + it('should correctly identify if the element can be handled', () => { + const canHandle = AceHandler.canHandle(mockElem); + + expect(canHandle).toBe(false); + }); + it('should return false when the parent element itself is checked', () => { + const canHandle = AceHandler.canHandle(mockParent as any); + + expect(canHandle).toBe(false); + }); + it('should return true if the element meets all conditions', () => { + mockElem.classList.add('ace_text-input'); + + const canHandle = AceHandler.canHandle(mockElem); + + expect(canHandle).toBe(true); + }); + }); + + describe('static getHintArea', () => { + it("should return the closest visual element that matches the handler's selector", () => { + const result = AceHandler.getHintArea(mockElem); + + expect(result).toBe(mockParent); + }); + }); + + describe('static getName', () => { + it('should return "Ace" as the handler name', () => { + const name = AceHandler.getName(); + + expect(name).toBe('Ace'); + }); + }); +}); diff --git a/__tests__/src/handlers/codemirror5.test.ts b/__tests__/src/handlers/codemirror5.test.ts new file mode 100644 index 0000000..781850d --- /dev/null +++ b/__tests__/src/handlers/codemirror5.test.ts @@ -0,0 +1,112 @@ +import CodeMirror5Handler from '@/handlers/codemirror5'; +import InjectorHandler from '@/handlers/injector'; +import { VISUAL_ELEMENT_SELECTOR } from '@/handlers/config/const'; + +let mockElem: HTMLTextAreaElement; +let mockParent: HTMLDivElement; + +const mockContentEventsBinder = { + bind: jest.fn(), +}; + +describe('CodeMirror5Handler', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockElem = document.createElement('textarea'); + mockParent = document.createElement('div'); + mockParent.appendChild(mockElem); + document.body.appendChild(mockParent); + }); + + afterAll(() => { + document.body.innerHTML = ''; + }); + + describe('constructor', () => { + it('should correctly initialize with the given parameters', () => { + const handler = new CodeMirror5Handler(mockElem, mockContentEventsBinder); + + expect(handler).toBeDefined(); + expect((handler as any).elem).toBe(mockElem); + }); + }); + + describe('setValue', () => { + it('should set value without triggering DOM event', () => { + const mockValue = 'test value'; + const options = { text: mockValue, triggerDOMEvent: false }; + const handler = new CodeMirror5Handler(mockElem, mockContentEventsBinder); + handler.setValue = jest.fn(); + handler.setValue(mockValue, options); + + expect(handler.setValue).toHaveBeenCalledWith(mockValue, { + ...options, + triggerDOMEvent: false, + }); + }); + }); + + describe('getValue', () => { + it('should call super.getValue', async () => { + const handler = new CodeMirror5Handler(mockElem, mockContentEventsBinder); + const superGetValueSpy = jest + .spyOn(InjectorHandler.prototype, 'getValue') + .mockImplementation(async () => ({ text: 'my text' })); + await handler.getValue(); + expect(superGetValueSpy).toHaveBeenCalled(); + superGetValueSpy.mockRestore(); + }); + + it('should post a "getValue" message and resolve with the value', async () => { + const handler = new CodeMirror5Handler(mockElem, mockContentEventsBinder); + const promise = handler.getValue(); + handler.emit('value', { text: 'testValue' }); + await expect(promise).resolves.toEqual({ text: 'testValue' }); + }); + }); + + describe('static canHandle determines if the given element can be handled by CodeMirror5Handler', () => { + it('should correctly identify if the element can be handled', () => { + const canHandle = CodeMirror5Handler.canHandle(mockElem); + + expect(canHandle).toBe(false); + }); + it('should return true when the parent element itself is checked', () => { + mockParent.classList.add( + VISUAL_ELEMENT_SELECTOR.codeMirror5.replace('.', ''), + ); + const canHandle = CodeMirror5Handler.canHandle(mockParent as any); + + expect(canHandle).toBe(true); + }); + it('should return true if the element meets all conditions', () => { + mockParent.classList.add( + VISUAL_ELEMENT_SELECTOR.codeMirror5.replace('.', ''), + ); + + const canHandle = CodeMirror5Handler.canHandle(mockElem); + + expect(canHandle).toBe(true); + }); + }); + + describe('static getHintArea', () => { + it("should return the closest visual element that matches the handler's selector", () => { + mockParent.classList.add( + VISUAL_ELEMENT_SELECTOR.codeMirror5.replace('.', ''), + ); + + const result = CodeMirror5Handler.getHintArea(mockElem); + + expect(result).toBe(mockParent); + }); + }); + + describe('static getName', () => { + it('should return "codemirror5" as the handler name', () => { + const name = CodeMirror5Handler.getName(); + + expect(name).toBe('codemirror5'); + }); + }); +}); diff --git a/__tests__/src/handlers/codemirror6.test.ts b/__tests__/src/handlers/codemirror6.test.ts new file mode 100644 index 0000000..5b5a6a8 --- /dev/null +++ b/__tests__/src/handlers/codemirror6.test.ts @@ -0,0 +1,114 @@ +import CodeMirror6Handler from '@/handlers/codemirror6'; +import InjectorHandler from '@/handlers/injector'; +import { VISUAL_ELEMENT_SELECTOR } from '@/handlers/config/const'; + +let mockElem: HTMLTextAreaElement; +let mockParent: HTMLDivElement; + +const mockContentEventsBinder = { + bind: jest.fn(), +}; + +describe('CodeMirror6Handler', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockElem = document.createElement('textarea'); + mockParent = document.createElement('div'); + mockParent.appendChild(mockElem); + document.body.appendChild(mockParent); + }); + + afterAll(() => { + document.body.innerHTML = ''; + }); + + describe('constructor', () => { + it('should correctly initialize with the given parameters', () => { + const handler = new CodeMirror6Handler(mockElem, mockContentEventsBinder); + + expect(handler).toBeDefined(); + expect((handler as any).elem).toBe(mockElem); + }); + }); + + describe('setValue', () => { + it('should set value without triggering DOM event', () => { + const mockValue = 'test value'; + const options = { text: mockValue, triggerDOMEvent: false }; + const handler = new CodeMirror6Handler(mockElem, mockContentEventsBinder); + handler.setValue = jest.fn(); + handler.setValue(mockValue, options); + + expect(handler.setValue).toHaveBeenCalledWith(mockValue, { + ...options, + triggerDOMEvent: false, + }); + }); + }); + + describe('getValue', () => { + it('should call super.getValue', async () => { + const handler = new CodeMirror6Handler(mockElem, mockContentEventsBinder); + const superGetValueSpy = jest + .spyOn(InjectorHandler.prototype, 'getValue') + .mockImplementation(async () => ({ text: 'my text' })); + await handler.getValue(); + expect(superGetValueSpy).toHaveBeenCalled(); + superGetValueSpy.mockRestore(); + }); + + it('should post a "getValue" message and resolve with the value', async () => { + const handler = new CodeMirror6Handler(mockElem, mockContentEventsBinder); + const promise = handler.getValue(); + handler.emit('value', { text: 'testValue' }); + await expect(promise).resolves.toEqual({ text: 'testValue' }); + }); + }); + + describe('static canHandle determines if the given element can be handled by CodeMirror6Handler', () => { + it('should correctly identify if the element can be handled', () => { + const canHandle = CodeMirror6Handler.canHandle(mockElem); + + expect(canHandle).toBe(false); + }); + it('should return false when the parent element itself is checked', () => { + mockParent.classList.add( + VISUAL_ELEMENT_SELECTOR.cmEditor.replace('.', ''), + ); + const canHandle = CodeMirror6Handler.canHandle(mockParent as any); + + expect(canHandle).toBe(false); + }); + it('should return true if the element meets all conditions', () => { + mockParent.classList.add( + VISUAL_ELEMENT_SELECTOR.cmEditor.replace('.', ''), + ); + + mockElem.classList.add('cm-content'); + + const canHandle = CodeMirror6Handler.canHandle(mockElem); + + expect(canHandle).toBe(true); + }); + }); + + describe('static getHintArea', () => { + it("should return the closest visual element that matches the handler's selector", () => { + mockParent.classList.add( + VISUAL_ELEMENT_SELECTOR.cmEditor.replace('.', ''), + ); + + const result = CodeMirror6Handler.getHintArea(mockElem); + + expect(result).toBe(mockParent); + }); + }); + + describe('static getName', () => { + it('should return "codemirror6" as the handler name', () => { + const name = CodeMirror6Handler.getName(); + + expect(name).toBe('codemirror6'); + }); + }); +}); diff --git a/__tests__/src/handlers/monaco.test.ts b/__tests__/src/handlers/monaco.test.ts new file mode 100644 index 0000000..d3a78de --- /dev/null +++ b/__tests__/src/handlers/monaco.test.ts @@ -0,0 +1,115 @@ +import MonacoHandler from '@/handlers/monaco'; +import InjectorHandler from '@/handlers/injector'; +import { VISUAL_ELEMENT_SELECTOR } from '@/handlers/config/const'; + +let mockElem: HTMLTextAreaElement; +let mockParent: HTMLDivElement; + +const mockContentEventsBinder = { + bind: jest.fn(), +}; + +describe('MonacoHandler', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockElem = document.createElement('textarea'); + mockParent = document.createElement('div'); + mockParent.appendChild(mockElem); + document.body.appendChild(mockParent); + }); + + afterAll(() => { + document.body.innerHTML = ''; + }); + + describe('constructor', () => { + it('should correctly initialize with the given parameters', () => { + const handler = new MonacoHandler(mockElem, mockContentEventsBinder); + + expect(handler).toBeDefined(); + expect(handler.elem).toBe(mockElem); + }); + }); + + describe('setValue', () => { + it('should set value without triggering DOM event', () => { + const mockValue = 'test value'; + const options = { text: mockValue, triggerDOMEvent: false }; + const handler = new MonacoHandler(mockElem, mockContentEventsBinder); + handler.setValue = jest.fn(); + handler.setValue(mockValue, options); + + expect(handler.setValue).toHaveBeenCalledWith(mockValue, { + ...options, + triggerDOMEvent: false, + }); + }); + }); + + describe('getValue', () => { + it('should call super.getValue', async () => { + const handler = new MonacoHandler(mockElem, mockContentEventsBinder); + const superGetValueSpy = jest + .spyOn(InjectorHandler.prototype, 'getValue') + .mockImplementation(async () => ({ text: 'my text' })); + await handler.getValue(); + expect(superGetValueSpy).toHaveBeenCalled(); + superGetValueSpy.mockRestore(); + }); + + it('should post a "getValue" message and resolve with the value', async () => { + const handler = new MonacoHandler(mockElem, mockContentEventsBinder); + const promise = handler.getValue(); + handler.emit('value', { text: 'testValue' }); + await expect(promise).resolves.toEqual({ text: 'testValue' }); + }); + }); + + describe('static canHandle determines if the given element can be handled by MonacoHandler', () => { + it('should correctly identify if the element can be handled', () => { + const canHandle = MonacoHandler.canHandle(mockElem); + + expect(canHandle).toBe(false); + }); + it('should return false when the parent element itself is checked', () => { + mockParent.classList.add(VISUAL_ELEMENT_SELECTOR.monaco.replace('.', '')); + const canHandle = MonacoHandler.canHandle(mockParent as any); + + expect(canHandle).toBe(false); + }); + it('should return true if the element meets all conditions', () => { + mockParent.classList.add(VISUAL_ELEMENT_SELECTOR.monaco.replace('.', '')); + + const canHandle = MonacoHandler.canHandle(mockElem); + + expect(canHandle).toBe(true); + }); + + it('returns false if the textarea is not wrapped in an element with the `monaco-editor` class', () => { + mockParent.classList.remove( + VISUAL_ELEMENT_SELECTOR.monaco.replace('.', ''), + ); + const canHandle = MonacoHandler.canHandle(mockElem); + + expect(canHandle).toBe(false); + }); + }); + + describe('static getHintArea', () => { + it('should return the closest visual element that matches the monaco selector', () => { + mockParent.classList.add(VISUAL_ELEMENT_SELECTOR.monaco.replace('.', '')); + + const result = MonacoHandler.getHintArea(mockElem); + + expect(result).toBe(mockParent); + }); + }); + + describe('static getName', () => { + it('should return "monaco" as the handler name', () => { + const name = MonacoHandler.getName(); + + expect(name).toBe('monaco'); + }); + }); +}); diff --git a/__tests__/src/util/event-dispatcher.test.ts b/__tests__/src/util/event-dispatcher.test.ts new file mode 100644 index 0000000..033b5f3 --- /dev/null +++ b/__tests__/src/util/event-dispatcher.test.ts @@ -0,0 +1,80 @@ +import { CustomEventDispatcher } from '@/util/event-dispatcher'; + +describe('CustomEventDispatcher', () => { + let element: HTMLDivElement; + let dispatcher: CustomEventDispatcher; + + beforeEach(() => { + element = document.createElement('div'); + document.body.appendChild(element); + dispatcher = new CustomEventDispatcher(element); + }); + + afterEach(() => { + document.body.removeChild(element); + }); + + it('should dispatch a "change" event', () => { + const mockFn = jest.fn(); + element.addEventListener('change', mockFn); + dispatcher.change(); + expect(mockFn).toHaveBeenCalled(); + }); + + it('should dispatch a "input" event', () => { + const mockFn = jest.fn(); + element.addEventListener('input', mockFn); + dispatcher.input(); + expect(mockFn).toHaveBeenCalled(); + }); + + it('should dispatch a "keyup" event with correct properties', () => { + const mockFn = jest.fn(); + element.addEventListener('keyup', mockFn); + dispatcher.keyup({ key: 'Enter' }); + expect(mockFn).toHaveBeenCalled(); + const event = mockFn.mock.calls[0][0]; + expect(event.detail.atomicChromeSyntheticEvent).toBe(true); + }); + + it('should dispatch a "keypress" event with correct properties', () => { + const mockFn = jest.fn(); + element.addEventListener('keypress', mockFn); + dispatcher.keypress(); + expect(mockFn).toHaveBeenCalled(); + const event = mockFn.mock.calls[0][0]; + expect(event.detail.atomicChromeSyntheticEvent).toBe(true); + }); + + it('should dispatch a "keydown" event with correct properties', () => { + const mockFn = jest.fn(); + element.addEventListener('keydown', mockFn); + dispatcher.keydown(); + expect(mockFn).toHaveBeenCalled(); + const event = mockFn.mock.calls[0][0]; + expect(event.detail.atomicChromeSyntheticEvent).toBe(true); + }); + + it('should identify if an event is an Atomic Chrome Custom Event', () => { + const event = new CustomEvent('test', { + detail: { atomicChromeSyntheticEvent: true }, + }); + expect(CustomEventDispatcher.isAtomicChromeCustomEvent(event)).toBeTruthy(); + }); + + it('should dispatch a custom "click" event and simulate element interaction', () => { + const clickMockFn = jest.fn(); + const mousedownMockFn = jest.fn(); + const mouseupMockFn = jest.fn(); + + element.addEventListener('click', clickMockFn); + element.addEventListener('mousedown', mousedownMockFn); + element.addEventListener('mouseup', mouseupMockFn); + + dispatcher.click(); + + expect(mousedownMockFn).toHaveBeenCalled(); + expect(mouseupMockFn).toHaveBeenCalled(); + expect(clickMockFn).toHaveBeenCalled(); + }); +});