-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2116195
commit 3810463
Showing
1 changed file
with
158 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; | ||
import { keybinding } from '$lib/keybinding'; | ||
|
||
describe('keybinding action', () => { | ||
let node; | ||
let mockWindow; | ||
let currentHandler; | ||
|
||
beforeEach(() => { | ||
node = { | ||
click: vi.fn() | ||
}; | ||
|
||
mockWindow = { | ||
addEventListener: vi.fn((event, handler) => { | ||
if (event === 'keydown') { | ||
currentHandler = handler; | ||
} | ||
}), | ||
removeEventListener: vi.fn((event, handler) => { | ||
if (event === 'keydown' && handler === currentHandler) { | ||
currentHandler = null; | ||
} | ||
}) | ||
}; | ||
|
||
// Set up mock window | ||
const originalWindow = global.window; | ||
global.window = mockWindow; | ||
|
||
beforeEach.cleanup = () => { | ||
global.window = originalWindow; | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
beforeEach.cleanup?.(); | ||
vi.clearAllMocks(); | ||
currentHandler = null; | ||
}); | ||
|
||
const triggerKeydown = (keyParams) => { | ||
const event = { | ||
code: keyParams.code || '', | ||
altKey: keyParams.alt || false, | ||
shiftKey: keyParams.shift || false, | ||
ctrlKey: keyParams.control || false, | ||
metaKey: keyParams.control || false, | ||
preventDefault: vi.fn() | ||
}; | ||
|
||
if (currentHandler) { | ||
currentHandler(event); | ||
} | ||
return event; | ||
}; | ||
|
||
it('adds keydown event listener on initialization', () => { | ||
keybinding(node, { code: 'KeyA' }); | ||
expect(mockWindow.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); | ||
}); | ||
|
||
it('removes existing listener before adding new one on update', () => { | ||
let params = { code: 'KeyA' }; | ||
const action = keybinding(node, params); | ||
|
||
// Clear the initial setup calls | ||
mockWindow.addEventListener.mockClear(); | ||
mockWindow.removeEventListener.mockClear(); | ||
|
||
// Update the params object directly | ||
params.code = 'KeyB'; | ||
action.update(); | ||
|
||
expect(mockWindow.removeEventListener).toHaveBeenCalledTimes(1); | ||
expect(mockWindow.addEventListener).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('removes listener on destroy', () => { | ||
const action = keybinding(node, { code: 'KeyA' }); | ||
action.destroy(); | ||
|
||
expect(mockWindow.removeEventListener).toHaveBeenCalled(); | ||
}); | ||
|
||
it('only responds to matching key code', () => { | ||
keybinding(node, { code: 'KeyA' }); | ||
|
||
triggerKeydown({ code: 'KeyB' }); | ||
expect(node.click).not.toHaveBeenCalled(); | ||
|
||
triggerKeydown({ code: 'KeyA' }); | ||
expect(node.click).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('responds to updated key code', () => { | ||
// Create params object that we'll modify | ||
let params = { code: 'KeyA' }; | ||
const action = keybinding(node, params); | ||
|
||
// Verify initial binding works | ||
triggerKeydown({ code: 'KeyA' }); | ||
expect(node.click).toHaveBeenCalledTimes(1); | ||
|
||
// Update params and trigger update | ||
node.click.mockClear(); | ||
params.code = 'KeyB'; | ||
action.update(); | ||
|
||
// Verify new binding works | ||
triggerKeydown({ code: 'KeyB' }); | ||
expect(node.click).toHaveBeenCalledTimes(1); | ||
|
||
// Verify old binding doesn't work | ||
node.click.mockClear(); | ||
triggerKeydown({ code: 'KeyA' }); | ||
expect(node.click).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('handles modifier keys correctly', () => { | ||
keybinding(node, { | ||
code: 'KeyA', | ||
alt: true, | ||
shift: true, | ||
control: true | ||
}); | ||
|
||
triggerKeydown({ code: 'KeyA' }); | ||
expect(node.click).not.toHaveBeenCalled(); | ||
|
||
triggerKeydown({ code: 'KeyA', alt: true }); | ||
expect(node.click).not.toHaveBeenCalled(); | ||
|
||
triggerKeydown({ code: 'KeyA', alt: true, shift: true, control: true }); | ||
expect(node.click).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('handles meta key as control', () => { | ||
keybinding(node, { | ||
code: 'KeyA', | ||
control: true | ||
}); | ||
|
||
const event = triggerKeydown({ code: 'KeyA', control: true }); | ||
expect(node.click).toHaveBeenCalledTimes(1); | ||
expect(event.preventDefault).toHaveBeenCalled(); | ||
}); | ||
|
||
it('triggers callback instead of click when provided', () => { | ||
const callback = vi.fn(); | ||
keybinding(node, { code: 'KeyA', callback }); | ||
|
||
triggerKeydown({ code: 'KeyA' }); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
expect(node.click).not.toHaveBeenCalled(); | ||
}); | ||
}); |