mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 14:08:38 +00:00
feat: Allow ctrl/cmd + click to open URLs present in codemirror editors (#5160)
* Allow ctrl/cmd + click to open URLs * fix for when user does cmd+tab, then comes back without it --------- Co-authored-by: Sid <siddharth@usebruno.com>
This commit is contained in:
@@ -0,0 +1,652 @@
|
||||
import makeLinkAwareCodeMirror from './makeLinkAwareCodeMirror';
|
||||
import LinkifyIt from 'linkify-it';
|
||||
import { isMacOS } from 'utils/common/platform';
|
||||
const CodeMirror = require('codemirror');
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('codemirror', () => {
|
||||
const mockEditor = {
|
||||
getDoc: jest.fn(),
|
||||
getAllMarks: jest.fn(),
|
||||
markText: jest.fn(),
|
||||
posFromIndex: jest.fn(),
|
||||
getWrapperElement: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
_destroyLinkAware: undefined
|
||||
};
|
||||
|
||||
const CodeMirror = jest.fn(() => mockEditor);
|
||||
return CodeMirror;
|
||||
});
|
||||
|
||||
// Mock linkify-it
|
||||
jest.mock('linkify-it', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
match: jest.fn()
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('utils/common/platform', () => ({
|
||||
isMacOS: jest.fn()
|
||||
}));
|
||||
// Mock requestAnimationFrame
|
||||
global.requestAnimationFrame = jest.fn((cb) => cb());
|
||||
|
||||
// Mock window.ipcRenderer
|
||||
global.window = {
|
||||
...global.window,
|
||||
ipcRenderer: {
|
||||
openExternal: jest.fn()
|
||||
},
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn()
|
||||
};
|
||||
|
||||
describe('makeLinkAwareCodeMirror', () => {
|
||||
let mockHost;
|
||||
let mockEditor;
|
||||
let mockDoc;
|
||||
let mockWrapperElement;
|
||||
let mockLinkify;
|
||||
let mockMark;
|
||||
let originalTimeout;
|
||||
let mockSetTimeout;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.useFakeTimers();
|
||||
|
||||
// Create a Jest mock for setTimeout
|
||||
mockSetTimeout = jest.spyOn(global, 'setTimeout');
|
||||
|
||||
// Store original timeout and mock requestAnimationFrame
|
||||
originalTimeout = global.setTimeout;
|
||||
global.requestAnimationFrame = jest.fn((cb) => cb());
|
||||
|
||||
// Setup DOM mocks
|
||||
mockHost = document.createElement('div');
|
||||
mockWrapperElement = {
|
||||
classList: {
|
||||
add: jest.fn(),
|
||||
remove: jest.fn()
|
||||
},
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn()
|
||||
};
|
||||
|
||||
mockMark = {
|
||||
clear: jest.fn()
|
||||
};
|
||||
|
||||
mockDoc = {
|
||||
getValue: jest.fn().mockReturnValue('Check out https://example.com and http://test.org')
|
||||
};
|
||||
|
||||
mockEditor = {
|
||||
getDoc: jest.fn().mockReturnValue(mockDoc),
|
||||
getAllMarks: jest.fn().mockReturnValue([mockMark]),
|
||||
markText: jest.fn(),
|
||||
posFromIndex: jest.fn().mockImplementation((index) => ({ line: 0, ch: index })),
|
||||
getWrapperElement: jest.fn().mockReturnValue(mockWrapperElement),
|
||||
on: jest.fn(),
|
||||
off: jest.fn()
|
||||
};
|
||||
|
||||
mockLinkify = {
|
||||
match: jest.fn().mockReturnValue([
|
||||
{ index: 10, lastIndex: 28, url: 'https://example.com' },
|
||||
{ index: 33, lastIndex: 48, url: 'http://test.org' }
|
||||
])
|
||||
};
|
||||
|
||||
// Setup mocks
|
||||
CodeMirror.mockReturnValue(mockEditor);
|
||||
|
||||
LinkifyIt.mockImplementation(() => mockLinkify);
|
||||
|
||||
// Mock window and ipcRenderer
|
||||
global.window = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
ipcRenderer: {
|
||||
openExternal: jest.fn()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete global.window;
|
||||
delete global.requestAnimationFrame;
|
||||
global.setTimeout = originalTimeout;
|
||||
mockSetTimeout.mockRestore();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('editor creation and configuration', () => {
|
||||
it('should create a CodeMirror editor with default options', () => {
|
||||
const result = makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
expect(CodeMirror).toHaveBeenCalledWith(
|
||||
mockHost,
|
||||
expect.objectContaining({
|
||||
configureMouse: expect.any(Function)
|
||||
})
|
||||
);
|
||||
expect(result).toBe(mockEditor);
|
||||
});
|
||||
|
||||
it('should merge custom options with default configuration', () => {
|
||||
const customOptions = { lineNumbers: true, theme: 'dark' };
|
||||
|
||||
makeLinkAwareCodeMirror(mockHost, customOptions);
|
||||
|
||||
expect(CodeMirror).toHaveBeenCalledWith(
|
||||
mockHost,
|
||||
expect.objectContaining({
|
||||
lineNumbers: true,
|
||||
theme: 'dark',
|
||||
configureMouse: expect.any(Function)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should return early if editor creation fails', () => {
|
||||
CodeMirror.mockReturnValue(null);
|
||||
|
||||
const result = makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should add _destroyLinkAware method to editor', () => {
|
||||
const result = makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
expect(result._destroyLinkAware).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
|
||||
describe('platform-specific key detection', () => {
|
||||
it('should detect Cmd key on macOS', () => {
|
||||
isMacOS.mockReturnValue(true);
|
||||
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const configureMouse = CodeMirror.mock.calls[0][1].configureMouse;
|
||||
const mockEvent = { metaKey: true, ctrlKey: false, target: { classList: { contains: () => true } } };
|
||||
|
||||
const result = configureMouse(null, null, mockEvent);
|
||||
|
||||
expect(result).toEqual({ addNew: false });
|
||||
});
|
||||
|
||||
it('should detect Ctrl key on non-macOS', () => {
|
||||
isMacOS.mockReturnValue(false);
|
||||
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const configureMouse = CodeMirror.mock.calls[0][1].configureMouse;
|
||||
const mockEvent = { metaKey: false, ctrlKey: true, target: { classList: { contains: () => true } } };
|
||||
|
||||
const result = configureMouse(null, null, mockEvent);
|
||||
|
||||
expect(result).toEqual({ addNew: false });
|
||||
});
|
||||
|
||||
it('should return empty object when modifier key is not pressed', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const configureMouse = CodeMirror.mock.calls[0][1].configureMouse;
|
||||
const mockEvent = { metaKey: false, ctrlKey: false, target: { classList: { contains: () => true } } };
|
||||
|
||||
const result = configureMouse(null, null, mockEvent);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return empty object when target is not a link', () => {
|
||||
isMacOS.mockReturnValue(true);
|
||||
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const configureMouse = CodeMirror.mock.calls[0][1].configureMouse;
|
||||
const mockEvent = { metaKey: true, target: { classList: { contains: () => false } } };
|
||||
|
||||
const result = configureMouse(null, null, mockEvent);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CSS class management', () => {
|
||||
it('should add cmd-ctrl-pressed class when modifier key is pressed', () => {
|
||||
isMacOS.mockReturnValue(true);
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const keydownHandler = global.window.addEventListener.mock.calls.find((call) => call[0] === 'keydown')[1];
|
||||
const mockEvent = { metaKey: true };
|
||||
|
||||
keydownHandler(mockEvent);
|
||||
|
||||
expect(mockWrapperElement.classList.add).toHaveBeenCalledWith('cmd-ctrl-pressed');
|
||||
});
|
||||
|
||||
it('should remove cmd-ctrl-pressed class when modifier key is released', () => {
|
||||
isMacOS.mockReturnValue(false);
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const keyupHandler = global.window.addEventListener.mock.calls.find((call) => call[0] === 'keyup')[1];
|
||||
const mockEvent = { ctrlKey: false };
|
||||
|
||||
keyupHandler(mockEvent);
|
||||
|
||||
expect(mockWrapperElement.classList.remove).toHaveBeenCalledWith('cmd-ctrl-pressed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('click handling', () => {
|
||||
it('should open external URL when Cmd+clicking on a link', () => {
|
||||
isMacOS.mockReturnValue(true);
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const clickHandler = mockWrapperElement.addEventListener.mock.calls.find((call) => call[0] === 'click')[1];
|
||||
const mockEvent = {
|
||||
metaKey: true,
|
||||
target: {
|
||||
classList: { contains: (className) => className === 'CodeMirror-link' },
|
||||
getAttribute: () => 'https://example.com'
|
||||
},
|
||||
preventDefault: jest.fn(),
|
||||
stopPropagation: jest.fn()
|
||||
};
|
||||
|
||||
clickHandler(mockEvent);
|
||||
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
||||
expect(global.window.ipcRenderer.openExternal).toHaveBeenCalledWith('https://example.com');
|
||||
});
|
||||
|
||||
it('should not open URL when clicking without modifier key', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const clickHandler = mockWrapperElement.addEventListener.mock.calls.find((call) => call[0] === 'click')[1];
|
||||
const mockEvent = {
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
target: {
|
||||
classList: { contains: (className) => className === 'CodeMirror-link' },
|
||||
getAttribute: () => 'https://example.com'
|
||||
}
|
||||
};
|
||||
|
||||
clickHandler(mockEvent);
|
||||
|
||||
expect(global.window.ipcRenderer.openExternal).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not open URL when clicking on non-link element', () => {
|
||||
isMacOS.mockReturnValue(true);
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const clickHandler = mockWrapperElement.addEventListener.mock.calls.find((call) => call[0] === 'click')[1];
|
||||
const mockEvent = {
|
||||
metaKey: true,
|
||||
target: {
|
||||
classList: { contains: () => false }
|
||||
}
|
||||
};
|
||||
|
||||
clickHandler(mockEvent);
|
||||
|
||||
expect(global.window.ipcRenderer.openExternal).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not open URL when data-url attribute is missing', () => {
|
||||
isMacOS.mockReturnValue(true);
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const clickHandler = mockWrapperElement.addEventListener.mock.calls.find((call) => call[0] === 'click')[1];
|
||||
const mockEvent = {
|
||||
metaKey: true,
|
||||
target: {
|
||||
classList: { contains: (className) => className === 'CodeMirror-link' },
|
||||
getAttribute: () => null
|
||||
},
|
||||
preventDefault: jest.fn(),
|
||||
stopPropagation: jest.fn()
|
||||
};
|
||||
|
||||
clickHandler(mockEvent);
|
||||
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
||||
expect(global.window.ipcRenderer.openExternal).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// Test debouncing behavior
|
||||
describe('debouncing', () => {
|
||||
it('should debounce URL marking on content changes', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
// Clear the calls from initial setup
|
||||
mockEditor.getAllMarks.mockClear();
|
||||
|
||||
// Simulate multiple rapid content changes
|
||||
const changeHandler = mockEditor.on.mock.calls.find((call) => call[0] === 'changes')[1];
|
||||
changeHandler();
|
||||
changeHandler();
|
||||
changeHandler();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(3);
|
||||
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 150);
|
||||
|
||||
// Fast-forward timers
|
||||
jest.runAllTimers();
|
||||
|
||||
// Should only mark URLs once
|
||||
expect(requestAnimationFrame).toHaveBeenCalledTimes(1);
|
||||
expect(mockEditor.getAllMarks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should apply link tooltips when marking URLs', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
expect(mockEditor.markText).toHaveBeenCalledWith(
|
||||
{ line: 0, ch: 10 },
|
||||
{ line: 0, ch: 28 },
|
||||
{
|
||||
className: 'CodeMirror-link',
|
||||
attributes: {
|
||||
'data-url': 'https://example.com',
|
||||
title: 'Hold Cmd and click to open link'
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Test animation frame handling
|
||||
describe('animation frame handling', () => {
|
||||
it('should use requestAnimationFrame for URL marking', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const changeHandler = mockEditor.on.mock.calls.find((call) => call[0] === 'changes')[1];
|
||||
changeHandler();
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(requestAnimationFrame).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hover behavior', () => {
|
||||
it('should add hover class on mouseover for link elements', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const mouseoverHandler = mockWrapperElement.addEventListener.mock.calls.find(
|
||||
(call) => call[0] === 'mouseover'
|
||||
)[1];
|
||||
|
||||
const mockTarget = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn(),
|
||||
remove: jest.fn()
|
||||
},
|
||||
previousElementSibling: {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn(),
|
||||
remove: jest.fn()
|
||||
},
|
||||
previousElementSibling: null
|
||||
},
|
||||
nextElementSibling: {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn(),
|
||||
remove: jest.fn()
|
||||
},
|
||||
nextElementSibling: null
|
||||
}
|
||||
};
|
||||
|
||||
const mockEvent = { target: mockTarget };
|
||||
mouseoverHandler(mockEvent);
|
||||
|
||||
expect(mockTarget.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockTarget.previousElementSibling.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockTarget.nextElementSibling.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
});
|
||||
|
||||
it('should not add hover class for non-link elements', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const mouseoverHandler = mockWrapperElement.addEventListener.mock.calls.find(
|
||||
(call) => call[0] === 'mouseover'
|
||||
)[1];
|
||||
|
||||
const mockTarget = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(false),
|
||||
add: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
const mockEvent = { target: mockTarget };
|
||||
mouseoverHandler(mockEvent);
|
||||
|
||||
expect(mockTarget.classList.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove hover class on mouseout', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const mouseoutHandler = mockWrapperElement.addEventListener.mock.calls.find((call) => call[0] === 'mouseout')[1];
|
||||
|
||||
const mockTarget = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
remove: jest.fn()
|
||||
},
|
||||
previousElementSibling: {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
remove: jest.fn()
|
||||
},
|
||||
previousElementSibling: null
|
||||
},
|
||||
nextElementSibling: {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
remove: jest.fn()
|
||||
},
|
||||
nextElementSibling: null
|
||||
}
|
||||
};
|
||||
|
||||
const mockEvent = { target: mockTarget };
|
||||
mouseoutHandler(mockEvent);
|
||||
|
||||
expect(mockTarget.classList.remove).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockTarget.previousElementSibling.classList.remove).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockTarget.nextElementSibling.classList.remove).toHaveBeenCalledWith('hovered-link');
|
||||
});
|
||||
|
||||
it('should handle multi-span links correctly on hover', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const mouseoverHandler = mockWrapperElement.addEventListener.mock.calls.find(
|
||||
(call) => call[0] === 'mouseover'
|
||||
)[1];
|
||||
|
||||
// Create a mock with a chain of link spans
|
||||
const mockNestedPrev = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
previousElementSibling: null
|
||||
};
|
||||
|
||||
const mockPrev = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
previousElementSibling: mockNestedPrev
|
||||
};
|
||||
|
||||
const mockNestedNext = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
nextElementSibling: null
|
||||
};
|
||||
|
||||
const mockNext = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
nextElementSibling: mockNestedNext
|
||||
};
|
||||
|
||||
const mockTarget = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
previousElementSibling: mockPrev,
|
||||
nextElementSibling: mockNext
|
||||
};
|
||||
|
||||
const mockEvent = { target: mockTarget };
|
||||
mouseoverHandler(mockEvent);
|
||||
|
||||
expect(mockTarget.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockPrev.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockNestedPrev.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockNext.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockNestedNext.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
});
|
||||
});
|
||||
|
||||
// Test memory cleanup
|
||||
describe('memory cleanup', () => {
|
||||
it('should properly clean up all event listeners and marks', () => {
|
||||
const editor = makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
editor._destroyLinkAware();
|
||||
|
||||
expect(mockEditor.off).toHaveBeenCalled();
|
||||
expect(global.window.removeEventListener).toHaveBeenCalledTimes(2);
|
||||
expect(mockWrapperElement.removeEventListener).toHaveBeenCalledTimes(3); // click, mouseover, mouseout
|
||||
expect(mockWrapperElement.removeEventListener).toHaveBeenCalledWith('click', expect.any(Function));
|
||||
expect(mockWrapperElement.removeEventListener).toHaveBeenCalledWith('mouseover', expect.any(Function));
|
||||
expect(mockWrapperElement.removeEventListener).toHaveBeenCalledWith('mouseout', expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle missing target in mouse event', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const configureMouse = CodeMirror.mock.calls[0][1].configureMouse;
|
||||
const mockEvent = { metaKey: true, target: null };
|
||||
|
||||
const result = configureMouse(null, null, mockEvent);
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle missing ipcRenderer', () => {
|
||||
delete global.window.ipcRenderer;
|
||||
isMacOS.mockReturnValue(true);
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const clickHandler = mockWrapperElement.addEventListener.mock.calls.find((call) => call[0] === 'click')[1];
|
||||
const mockEvent = {
|
||||
metaKey: true,
|
||||
target: {
|
||||
classList: { contains: (className) => className === 'CodeMirror-link' },
|
||||
getAttribute: () => 'https://example.com'
|
||||
},
|
||||
preventDefault: jest.fn(),
|
||||
stopPropagation: jest.fn()
|
||||
};
|
||||
|
||||
expect(() => clickHandler(mockEvent)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle LinkifyIt returning null matches', () => {
|
||||
mockLinkify.match.mockReturnValue(null);
|
||||
|
||||
expect(() => makeLinkAwareCodeMirror(mockHost)).not.toThrow();
|
||||
expect(mockEditor.markText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle null siblings in mouseover events', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const mouseoverHandler = mockWrapperElement.addEventListener.mock.calls.find(
|
||||
(call) => call[0] === 'mouseover'
|
||||
)[1];
|
||||
|
||||
const mockTarget = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
previousElementSibling: null,
|
||||
nextElementSibling: null
|
||||
};
|
||||
|
||||
const mockEvent = { target: mockTarget };
|
||||
|
||||
expect(() => mouseoverHandler(mockEvent)).not.toThrow();
|
||||
expect(mockTarget.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
});
|
||||
|
||||
it('should handle non-link siblings in mouseover events', () => {
|
||||
makeLinkAwareCodeMirror(mockHost);
|
||||
|
||||
const mouseoverHandler = mockWrapperElement.addEventListener.mock.calls.find(
|
||||
(call) => call[0] === 'mouseover'
|
||||
)[1];
|
||||
|
||||
const mockPrev = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(false),
|
||||
add: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
const mockNext = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(false),
|
||||
add: jest.fn()
|
||||
}
|
||||
};
|
||||
|
||||
const mockTarget = {
|
||||
classList: {
|
||||
contains: jest.fn().mockReturnValue(true),
|
||||
add: jest.fn()
|
||||
},
|
||||
previousElementSibling: mockPrev,
|
||||
nextElementSibling: mockNext
|
||||
};
|
||||
|
||||
const mockEvent = { target: mockTarget };
|
||||
mouseoverHandler(mockEvent);
|
||||
|
||||
expect(mockTarget.classList.add).toHaveBeenCalledWith('hovered-link');
|
||||
expect(mockPrev.classList.add).not.toHaveBeenCalled();
|
||||
expect(mockNext.classList.add).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user