UNPKG

spin-wheel

Version:

An easy to use, themeable component for randomising choices and prizes.

343 lines (253 loc) 8.29 kB
import {jest, test, expect, beforeEach, afterEach} from '@jest/globals'; import {Defaults} from '../src/constants.js'; import {createWheel, createContainer} from '../scripts/test.js'; import {getInstanceProperties} from '../scripts/util.js'; import {Wheel} from '../src/wheel.js'; beforeEach(() => { jest.useFakeTimers(); let count = 0; jest.spyOn(window, 'requestAnimationFrame') .mockImplementation(callback => { setTimeout(() => callback(count*100), 100); // Mocked frame will be called exactly every .1 seconds. return ++count; // Return frame id; }); }); afterEach(() => { window.requestAnimationFrame.mockRestore(); jest.runOnlyPendingTimers(); jest.useRealTimers(); }); test('Mocked requestAnimationFrame works', async () => { let time; const f = ((n = 0) => { time = n; window.requestAnimationFrame(f); }); f(); expect(time).toBe(0); jest.advanceTimersByTime(100); expect(time).toBe(100); jest.advanceTimersByTime(200); expect(time).toBe(300); jest.advanceTimersByTime(50); expect(time).toBe(300); // Should not advance because the mocked frame only renders every 100ms. }); test('Initial state is correct', () => { const wheel = createWheel(); expect(wheel).toMatchSnapshot(); }); test('Wheel can be initialised with props', () => { createWheel(null); createWheel({}); }); test('Should throw when initialised without container param', () => { expect(() => { return new Wheel(); }).toThrow('container must be an instance of Element'); }); test('A default value exists for each property', () => { const wheel = createWheel(); const setters = getInstanceProperties(wheel).setters; for (const i of setters) { expect(Defaults.wheel[i]).not.toBe(undefined); } }); test('Each property is given a default value when instantiated', () => { const wheel = createWheel(); const setters = getInstanceProperties(wheel).setters; for (const i of setters) { expect(wheel[i]).toEqual(Defaults.wheel[i]); } }); test('Method "getItemAngles" works', () => { const wheel = createWheel({ numberOfItems: 4, rotation: 90, // Rotation should not affect start/end angles }); expect(wheel.items[0].getStartAngle()).toBe(0); // First start angle should be 0. expect(wheel.items[0].getEndAngle()).toBe(90); expect(wheel.items[1].getStartAngle()).toBe(90); expect(wheel.items[1].getEndAngle()).toBe(180); expect(wheel.items[3].getEndAngle()).toBe(360); // Last end angle should be 360. }); test('Method "getItemAngles" works when weighted', () => { const wheel = createWheel({ items: [ {weight: 2}, {weight: 1}, {weight: 1}, ], }); const angles = wheel.getItemAngles(); expect(angles[0].start).toBe(0); expect(angles[0].end).toBe(180); expect(angles[1].start).toBe(180); expect(angles[1].end).toBe(270); }); test('Method "spin" works', async () => { const wheel = createWheel({ rotationResistance: -10, }); // Spin the wheel at 10 degrees/s: wheel.spin(10); expect(wheel.rotationSpeed).toBe(10); // 0.5 seconds elapsed. jest.advanceTimersByTime(500); expect(wheel.rotationSpeed).toBe(5); // 1 second elapsed. jest.advanceTimersByTime(500); expect(wheel.rotationSpeed).toBe(0); expect(wheel.rotation).toBe(5.5); // Wheel should finally rest at 5.5 degrees due to rotationResistance. }); test('Method "spinTo" works', async () => { const wheel = createWheel(); wheel.spinTo(360, 0); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(360); wheel.spinTo(-360, 0); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(-360); }); test('Method "spinToItem" works', async () => { // Note: this is a simple integration test. // The full logic is tested elsewhere for `calcWheelRotationForTargetAngle`. const wheel = createWheel({ numberOfItems: 4, }); // Clockwise: let direction = 1; let itemIndex = 0; const numberOfRevolutions = 0; wheel.spinToItem(itemIndex, 0, true, numberOfRevolutions, direction, null); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(360 - 45); // Anti-clockwise: direction = -1; itemIndex = 0; wheel.rotation = 0; wheel.spinToItem(itemIndex, 0, true, numberOfRevolutions, direction, null); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(0 - 45); // Anti-clockwise, but rotation is just past target, so will have to spin almost 360 degrees again: direction = -1; itemIndex = 0; wheel.rotation = -46; wheel.spinToItem(itemIndex, 0, true, numberOfRevolutions, direction, null); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(0 - 360 - 45); // Clockwise, pointer angle is below 180: direction = 1; itemIndex = 0; wheel.pointerAngle = 90; wheel.rotation = 0; wheel.spinToItem(itemIndex, 0, true, numberOfRevolutions, direction, null); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(45); // Clockwise, pointer angle is above 180: direction = 1; itemIndex = 0; wheel.pointerAngle = 270; wheel.rotation = 0; wheel.spinToItem(itemIndex, 0, true, numberOfRevolutions, direction, null); jest.advanceTimersByTime(100); expect(wheel.rotation).toBe(270 - 45); // TODO: test number of revolutions }); test('Method "getRotationSpeedPlusDrag" works (resistance is applied to wheel correctly)', () => { const wheel = createWheel({ rotationResistance: -1, }); wheel.spin(2); // No time has elapsed (since the last frame), rotation speed should be unchanged: let delta = 0; expect(wheel.getRotationSpeedPlusDrag(delta)).toBe(2); // 1 millisecond has passed: delta = 1; expect(wheel.getRotationSpeedPlusDrag(delta)).toBe(1.999); delta = 100; expect(wheel.getRotationSpeedPlusDrag(delta)).toBe(1.900); delta = 2000; expect(wheel.getRotationSpeedPlusDrag(delta)).toBe(0); // Ensure rotation speed does not go past 0: delta = 2001; expect(wheel.getRotationSpeedPlusDrag(delta)).toBe(0); // Clockwise wheel.spin(-2); expect(wheel.getRotationSpeedPlusDrag(delta)).toBe(0); // Anti-clockwise }); test('Property "rotationSpeedMax" works', () => { const wheel = createWheel({ rotationSpeedMax: 1, }); wheel.spin(2); expect(wheel.rotationSpeed).toBe(1); wheel.spin(-2); expect(wheel.rotationSpeed).toBe(-1); }); test('Method "stop" works', () => { const wheel = createWheel({ numberOfItems: 2, rotationResistance: 0, }); const currentRotation = wheel.rotation; // Use Spin: wheel.spin(1); wheel.stop(); expect(wheel.rotationSpeed).toBe(0); jest.advanceTimersByTime(1000); expect(wheel.rotation).toBe(currentRotation); // Use SpinTo: wheel.spinTo(180, 1); wheel.stop(); jest.advanceTimersByTime(1000); expect(wheel.rotation).toBe(currentRotation); }); test('Wheel can be removed from the DOM and added back again', () => { const wheel = createWheel({ numberOfItems: 2, }); wheel.remove(); wheel.add(createContainer()); }); test('Event "currentIndexChange" is raised', async () => { const wheel = createWheel({ numberOfItems: 2, rotationSpeedMax: 360, rotationResistance: 0, onCurrentIndexChange: jest.fn(), }); wheel.spin(360); jest.advanceTimersByTime(600); // Advance to just after 180 degrees (because the angle check is exclusive of the end angle). expect(wheel.onCurrentIndexChange).toHaveBeenCalledTimes(1); expect(wheel.onCurrentIndexChange).toHaveBeenCalledWith({ type: 'currentIndexChange', currentIndex: 0, }); }); test('Event "rest" is raised', async () => { const wheel = createWheel({ numberOfItems: 2, rotationResistance: -10, onRest: jest.fn(), }); wheel.spin(10); jest.advanceTimersByTime(1000); expect(wheel.onRest).toHaveBeenCalledTimes(1); expect(wheel.onRest).toHaveBeenCalledWith({ type: 'rest', currentIndex: 1, rotation: 5.5, }); }); test('Event "spin" is raised', async () => { const wheel = createWheel({ onSpin: jest.fn(), }); wheel.spin(10); expect(wheel.onSpin).toHaveBeenCalledTimes(1); expect(wheel.onSpin).toHaveBeenCalledWith({ type: 'spin', method: 'spin', rotationResistance: Defaults.wheel.rotationResistance, rotationSpeed: 10, }); });