372 lines
12 KiB
TypeScript
372 lines
12 KiB
TypeScript
|
import {
|
||
|
stateReducer,
|
||
|
SelectionMode,
|
||
|
EmptySelectionAction,
|
||
|
HoverState,
|
||
|
} from './HudCanvasState';
|
||
|
|
||
|
const initialState = {
|
||
|
width: 5000,
|
||
|
emptySelectionAction: EmptySelectionAction.SelectNothing,
|
||
|
hoverX: 0,
|
||
|
selection: { start: 0, end: 0 },
|
||
|
origSelection: { start: 0, end: 0 },
|
||
|
mousedownX: 0,
|
||
|
mode: SelectionMode.Normal,
|
||
|
prevMode: SelectionMode.Normal,
|
||
|
cursorClass: 'cursor-auto',
|
||
|
hoverState: HoverState.Normal,
|
||
|
shouldPublish: false,
|
||
|
};
|
||
|
|
||
|
describe('stateReducer', () => {
|
||
|
describe('setselection', () => {
|
||
|
it('sets the selection', () => {
|
||
|
const state = stateReducer(
|
||
|
{ ...initialState },
|
||
|
{ type: 'setselection', x: 0, selection: { start: 100, end: 200 } }
|
||
|
);
|
||
|
expect(state.selection).toEqual({ start: 100, end: 200 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('mousedown', () => {
|
||
|
describe('when hovering over the selection start', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{ ...initialState, selection: { start: 1000, end: 2000 } },
|
||
|
{ type: 'mousedown', x: 995 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.ResizingStart);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 2000 });
|
||
|
expect(state.mousedownX).toEqual(995);
|
||
|
});
|
||
|
});
|
||
|
describe('when hovering over the selection end', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{ ...initialState, selection: { start: 1000, end: 2000 } },
|
||
|
{ type: 'mousedown', x: 2003 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.ResizingEnd);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 2000 });
|
||
|
expect(state.mousedownX).toEqual(2003);
|
||
|
});
|
||
|
});
|
||
|
describe('when hovering over the selection', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{ ...initialState, selection: { start: 1000, end: 2000 } },
|
||
|
{ type: 'mousedown', x: 1500 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Dragging);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 2000 });
|
||
|
expect(state.mousedownX).toEqual(1500);
|
||
|
});
|
||
|
});
|
||
|
describe('when not hovering over the selection', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{ ...initialState, selection: { start: 1000, end: 2000 } },
|
||
|
{ type: 'mousedown', x: 3000 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Selecting);
|
||
|
expect(state.selection).toEqual({ start: 3000, end: 3000 });
|
||
|
expect(state.mousedownX).toEqual(3000);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('mouseup', () => {
|
||
|
describe('when re-entering normal mode', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 2000 },
|
||
|
mode: SelectionMode.Selecting,
|
||
|
mousedownX: 1200,
|
||
|
},
|
||
|
{ type: 'mouseup', x: 0 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 2000 });
|
||
|
expect(state.mousedownX).toEqual(1200);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('nothing is selected and emptySelectionAction is SelectNothing', () => {
|
||
|
it('clears the selection at the mouse x coord', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 1000 },
|
||
|
mode: SelectionMode.Selecting,
|
||
|
emptySelectionAction: EmptySelectionAction.SelectNothing,
|
||
|
},
|
||
|
{ type: 'mouseup', x: 500 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 500, end: 500 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('nothing is selected and emptySelectionAction is SelectPrevious', () => {
|
||
|
it('reverts to the previous selection', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 2000 },
|
||
|
mode: SelectionMode.Selecting,
|
||
|
emptySelectionAction: EmptySelectionAction.SelectNothing,
|
||
|
},
|
||
|
{ type: 'mouseup', x: 0 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 2000 });
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('mouseup', () => {
|
||
|
it('sets the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 2000, end: 3000 },
|
||
|
mode: SelectionMode.Dragging,
|
||
|
mousedownX: 475,
|
||
|
},
|
||
|
{ type: 'mouseleave', x: 500 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Dragging);
|
||
|
expect(state.selection).toEqual({ start: 2000, end: 3000 });
|
||
|
expect(state.mousedownX).toEqual(475);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('mousemove', () => {
|
||
|
describe('in normal mode', () => {
|
||
|
describe('when hovering over the selection start', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 3000 },
|
||
|
mode: SelectionMode.Normal,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 997 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 3000 });
|
||
|
expect(state.hoverState).toEqual(HoverState.OverSelectionStart);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('when hovering over the selection end', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 3000 },
|
||
|
mode: SelectionMode.Normal,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 3009 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 3000 });
|
||
|
expect(state.hoverState).toEqual(HoverState.OverSelectionEnd);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('when hovering over the selection', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 3000 },
|
||
|
mode: SelectionMode.Normal,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 1200 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 3000 });
|
||
|
expect(state.hoverState).toEqual(HoverState.OverSelection);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('when hovering elsewhere', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 3000 },
|
||
|
mode: SelectionMode.Normal,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 10 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Normal);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 3000 });
|
||
|
expect(state.hoverState).toEqual(HoverState.Normal);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('in selecting mode', () => {
|
||
|
describe('a normal selection', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 2000, end: 3000 },
|
||
|
mode: SelectionMode.Selecting,
|
||
|
mousedownX: 2000,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 3005 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Selecting);
|
||
|
expect(state.selection).toEqual({ start: 2000, end: 3005 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('when crossing over', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 2000, end: 2002 },
|
||
|
mode: SelectionMode.Selecting,
|
||
|
mousedownX: 2000,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 1995 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Selecting);
|
||
|
expect(state.selection).toEqual({ start: 1995, end: 2000 });
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('in dragging mode', () => {
|
||
|
describe('in the middle of the canvas', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 1500 },
|
||
|
origSelection: { start: 1000, end: 1500 },
|
||
|
mode: SelectionMode.Dragging,
|
||
|
mousedownX: 1200,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 1220 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Dragging);
|
||
|
expect(state.selection).toEqual({ start: 1020, end: 1520 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('at the start of the canvas', () => {
|
||
|
it('constrains the movement and updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 10, end: 210 },
|
||
|
origSelection: { start: 10, end: 210 },
|
||
|
mode: SelectionMode.Dragging,
|
||
|
mousedownX: 50,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 30 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Dragging);
|
||
|
expect(state.selection).toEqual({ start: 0, end: 200 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('at the end of the canvas', () => {
|
||
|
it('constrains the movement and updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
width: 3000,
|
||
|
selection: { start: 2800, end: 2900 },
|
||
|
origSelection: { start: 2800, end: 2900 },
|
||
|
mode: SelectionMode.Dragging,
|
||
|
mousedownX: 1200,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 1350 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.Dragging);
|
||
|
expect(state.selection).toEqual({ start: 2900, end: 3000 });
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('in resizing start mode', () => {
|
||
|
describe('a normal resize', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 2000, end: 3000 },
|
||
|
origSelection: { start: 2000, end: 3000 },
|
||
|
mode: SelectionMode.ResizingStart,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 2020 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.ResizingStart);
|
||
|
expect(state.selection).toEqual({ start: 2020, end: 3000 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('when crossing over', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 2000, end: 2002 },
|
||
|
origSelection: { start: 2000, end: 2002 },
|
||
|
mode: SelectionMode.ResizingStart,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 2010 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.ResizingStart);
|
||
|
expect(state.selection).toEqual({ start: 2002, end: 2010 });
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('in resizing end mode', () => {
|
||
|
describe('a normal resize', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 1000, end: 2000 },
|
||
|
origSelection: { start: 1000, end: 2000 },
|
||
|
mode: SelectionMode.ResizingEnd,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 2007 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.ResizingEnd);
|
||
|
expect(state.selection).toEqual({ start: 1000, end: 2007 });
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('when crossing over', () => {
|
||
|
it('updates the state', () => {
|
||
|
const state = stateReducer(
|
||
|
{
|
||
|
...initialState,
|
||
|
selection: { start: 2000, end: 2002 },
|
||
|
origSelection: { start: 2000, end: 2002 },
|
||
|
mode: SelectionMode.ResizingEnd,
|
||
|
},
|
||
|
{ type: 'mousemove', x: 1995 }
|
||
|
);
|
||
|
expect(state.mode).toEqual(SelectionMode.ResizingEnd);
|
||
|
expect(state.selection).toEqual({ start: 1995, end: 2000 });
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|