-
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.
Merge pull request #166 from OneBusAway/coverage
Coverage
- Loading branch information
Showing
4 changed files
with
365 additions
and
3 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
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
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(); | ||
}); | ||
}); |
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,99 @@ | ||
import { describe, it, expect } from 'vitest'; | ||
import { toDirection, calculateMidpoint } from '$lib/mathUtils'; | ||
|
||
describe('toDirection', () => { | ||
it('converts east orientation (0°) to 90° direction', () => { | ||
expect(toDirection(0)).toBe(90); | ||
}); | ||
|
||
it('converts north orientation (90°) to 0° direction', () => { | ||
expect(toDirection(90)).toBe(0); | ||
}); | ||
|
||
it('converts west orientation (180°) to 270° direction', () => { | ||
expect(toDirection(180)).toBe(270); | ||
}); | ||
|
||
it('converts south orientation (270°) to 180° direction', () => { | ||
expect(toDirection(270)).toBe(180); | ||
}); | ||
|
||
it('handles negative orientations', () => { | ||
expect(toDirection(-90)).toBe(180); // -90° orientation should be 180° direction | ||
expect(toDirection(-180)).toBe(270); // -180° orientation should be 270° direction | ||
expect(toDirection(-270)).toBe(0); // -270° orientation should be 0° direction | ||
}); | ||
|
||
it('handles orientations > 360°', () => { | ||
expect(toDirection(450)).toBe(0); // 450° orientation (90° + 360°) should be 0° direction | ||
expect(toDirection(720)).toBe(90); // 720° orientation (0° + 2*360°) should be 90° direction | ||
}); | ||
|
||
it('converts arbitrary angles correctly', () => { | ||
expect(toDirection(45)).toBe(45); // 45° orientation to 45° direction | ||
expect(toDirection(135)).toBe(315); // 135° orientation to 315° direction | ||
expect(toDirection(225)).toBe(225); // 225° orientation to 225° direction | ||
expect(toDirection(315)).toBe(135); // 315° orientation to 135° direction | ||
}); | ||
}); | ||
|
||
describe('calculateMidpoint', () => { | ||
it('calculates midpoint for two stops', () => { | ||
const stops = [ | ||
{ lat: 47.6062, lon: -122.3321 }, | ||
{ lat: 47.6092, lon: -122.3331 } | ||
]; | ||
|
||
const result = calculateMidpoint(stops); | ||
|
||
expect(result.lat).toBeCloseTo(47.6077); | ||
expect(result.lng).toBeCloseTo(-122.3326); | ||
}); | ||
|
||
it('calculates midpoint for multiple stops', () => { | ||
const stops = [ | ||
{ lat: 47.6062, lon: -122.3321 }, | ||
{ lat: 47.6092, lon: -122.3331 }, | ||
{ lat: 47.6082, lon: -122.3341 } | ||
]; | ||
|
||
const result = calculateMidpoint(stops); | ||
|
||
expect(result.lat).toBeCloseTo(47.6079); | ||
expect(result.lng).toBeCloseTo(-122.3331); | ||
}); | ||
|
||
it('returns same point for single stop', () => { | ||
const stops = [{ lat: 47.6062, lon: -122.3321 }]; | ||
|
||
const result = calculateMidpoint(stops); | ||
|
||
expect(result.lat).toBe(47.6062); | ||
expect(result.lng).toBe(-122.3321); | ||
}); | ||
|
||
it('handles positive and negative coordinates', () => { | ||
const stops = [ | ||
{ lat: -33.8688, lon: 151.2093 }, // Sydney | ||
{ lat: 40.7128, lon: -74.006 } // New York | ||
]; | ||
|
||
const result = calculateMidpoint(stops); | ||
|
||
expect(result.lat).toBeCloseTo(3.422); | ||
expect(result.lng).toBeCloseTo(38.6017); | ||
}); | ||
|
||
it('handles coordinates around the same area', () => { | ||
const stops = [ | ||
{ lat: 47.6062, lon: -122.3321 }, | ||
{ lat: 47.6065, lon: -122.3324 }, | ||
{ lat: 47.6068, lon: -122.3327 } | ||
]; | ||
|
||
const result = calculateMidpoint(stops); | ||
|
||
expect(result.lat).toBeCloseTo(47.6065); | ||
expect(result.lng).toBeCloseTo(-122.3324); | ||
}); | ||
}); |