Completed crosshair, slider, and scroll hooks.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useCrosshair } from "../crosshair";
|
||||
import type { CartesianSpace } from "../../types";
|
||||
|
||||
// Test Fixtures
|
||||
|
||||
function TestSquare() {
|
||||
const [origin, setOrigin] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
|
||||
const { crosshairRef, isDragging } = useCrosshair({
|
||||
origin,
|
||||
dimensions,
|
||||
setXPosition,
|
||||
setYPosition,
|
||||
});
|
||||
|
||||
const boundaryRef = useRef<HTMLDivElement>(null);
|
||||
const xCrosshairRef = useRef<HTMLSpanElement>(null);
|
||||
const yCrosshairRef = useRef<HTMLSpanElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const element = boundaryRef.current;
|
||||
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
setOrigin({ x: rect.left, y: rect.top });
|
||||
setDimensions({ x: rect.width, y: rect.height });
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={crosshairRef}
|
||||
data-cy="crosshair-container"
|
||||
style={{
|
||||
position: "relative",
|
||||
width: 250,
|
||||
height: 250,
|
||||
margin: 50,
|
||||
border: "3px solid black",
|
||||
cursor: "crosshair",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={boundaryRef}
|
||||
data-cy="boundary"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 12,
|
||||
bottom: 12,
|
||||
right: 12,
|
||||
left: 12,
|
||||
background: "rgb(200, 200, 200)",
|
||||
}}
|
||||
></div>
|
||||
<span
|
||||
ref={yCrosshairRef}
|
||||
data-cy="y-crosshair"
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: 250,
|
||||
height: 25,
|
||||
top: yPosition,
|
||||
left: 0,
|
||||
background: "rgba(255, 0, 0, 0.5)",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
ref={xCrosshairRef}
|
||||
data-cy="x-crosshair"
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: 25,
|
||||
height: 250,
|
||||
top: 0,
|
||||
left: xPosition,
|
||||
background: "rgba(0, 255, 0, 0.5)",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
X Position: <span data-cy="x-position-display">{xPosition}</span>px
|
||||
<br />Y Position: <span data-cy="y-position-display">{yPosition}</span>
|
||||
px
|
||||
</p>
|
||||
<p>
|
||||
Is Dragging:{" "}
|
||||
<span data-cy="is-dragging-display">
|
||||
{isDragging ? "True" : "False"}
|
||||
</span>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
const assertPosition = (
|
||||
expectedXPosition: number,
|
||||
expectedYPosition: number,
|
||||
) => {
|
||||
cy.dataCy("x-position-display").should("contain", expectedXPosition);
|
||||
cy.dataCy("y-position-display").should("contain", expectedYPosition);
|
||||
|
||||
cy.dataCy("x-crosshair").should("have.css", "left", `${expectedXPosition}px`);
|
||||
cy.dataCy("y-crosshair").should("have.css", "top", `${expectedYPosition}px`);
|
||||
};
|
||||
|
||||
const triggerMouseEvent = (eventType: string, x: number, y: number) => {
|
||||
cy.dataCy("crosshair-container").trigger(eventType, {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
eventConstructor: "MouseEvent",
|
||||
});
|
||||
};
|
||||
|
||||
const triggerTouchEvent = (eventType: string, x: number, y: number) => {
|
||||
cy.dataCy("crosshair-container").trigger(eventType, {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
touches: [{ clientX: x, clientY: y }],
|
||||
eventConstructor: "TouchEvent",
|
||||
});
|
||||
};
|
||||
|
||||
describe("crosshair tests", () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<TestSquare />);
|
||||
});
|
||||
|
||||
it("moves the crosshairs with mouse events", () => {
|
||||
assertPosition(0, 0);
|
||||
|
||||
triggerMouseEvent("mousedown", 70, 60);
|
||||
assertPosition(0, 0);
|
||||
|
||||
triggerMouseEvent("mousemove", 180, 180);
|
||||
assertPosition(107, 115);
|
||||
|
||||
triggerMouseEvent("mousemove", 500, 500);
|
||||
assertPosition(225, 225);
|
||||
|
||||
triggerMouseEvent("mousemove", 0, 250);
|
||||
assertPosition(0, 185);
|
||||
|
||||
triggerMouseEvent("mouseup", 0, 250);
|
||||
assertPosition(0, 185);
|
||||
});
|
||||
|
||||
it("moves the crosshairs with touch events", () => {
|
||||
assertPosition(0, 0);
|
||||
|
||||
triggerTouchEvent("touchstart", 70, 60);
|
||||
assertPosition(0, 0);
|
||||
|
||||
triggerTouchEvent("touchmove", 180, 180);
|
||||
assertPosition(107, 115);
|
||||
|
||||
triggerTouchEvent("touchmove", 500, 500);
|
||||
assertPosition(225, 225);
|
||||
|
||||
triggerTouchEvent("touchmove", 0, 250);
|
||||
assertPosition(0, 185);
|
||||
|
||||
triggerTouchEvent("touchend", 0, 250);
|
||||
assertPosition(0, 185);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,18 +4,17 @@ import type { CartesianSpace } from "../../types";
|
||||
|
||||
// Test Fixtures
|
||||
|
||||
function printMeasurements(name: string, rect: DOMRect | undefined) {
|
||||
console.log(
|
||||
`${name} Measurements: (${rect?.left}, ${rect?.top}), (${rect?.width}, ${rect?.height})`,
|
||||
);
|
||||
}
|
||||
|
||||
function TestSlider() {
|
||||
function TestSlider({
|
||||
direction = Direction.HORIZONTAL,
|
||||
}: {
|
||||
direction?: Direction;
|
||||
}) {
|
||||
const [origin, setOrigin] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
|
||||
const [position, setPosition] = useState(0);
|
||||
const [sliderValue, setSliderValue] = useState(0);
|
||||
const { sliderRef, isDragging } = useSlider({
|
||||
direction: Direction.HORIZONTAL,
|
||||
direction,
|
||||
origin,
|
||||
dimensions,
|
||||
setPosition,
|
||||
@@ -32,10 +31,23 @@ function TestSlider() {
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
setOrigin({ x: rect.left, y: rect.top });
|
||||
setDimensions({ x: rect.width, y: rect!.height });
|
||||
setDimensions({ x: rect.width, y: rect.height });
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const maxValue =
|
||||
direction == Direction.HORIZONTAL ? dimensions.x : dimensions.y;
|
||||
if (maxValue > 0) {
|
||||
const percentage = parseFloat(((position / maxValue) * 100).toFixed(3));
|
||||
setSliderValue(percentage);
|
||||
} else {
|
||||
setSliderValue(0);
|
||||
}
|
||||
}, [dimensions, direction, position]);
|
||||
|
||||
const isHorizontal = direction === Direction.HORIZONTAL;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -43,8 +55,8 @@ function TestSlider() {
|
||||
ref={sliderRef}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: 375,
|
||||
height: 50,
|
||||
width: isHorizontal ? 300 : 50,
|
||||
height: isHorizontal ? 50 : 300,
|
||||
border: "3px solid black",
|
||||
margin: 50,
|
||||
cursor: "pointer",
|
||||
@@ -55,10 +67,10 @@ function TestSlider() {
|
||||
ref={railRef}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 25,
|
||||
right: 25,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: isHorizontal ? 25 : 0,
|
||||
right: isHorizontal ? 25 : 0,
|
||||
top: isHorizontal ? 0 : 25,
|
||||
bottom: isHorizontal ? 0 : 25,
|
||||
background: "rgb(200,200,200)",
|
||||
}}
|
||||
/>
|
||||
@@ -67,8 +79,9 @@ function TestSlider() {
|
||||
ref={handleRef}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: position,
|
||||
top: 0,
|
||||
...(isHorizontal
|
||||
? { left: position, top: 0 }
|
||||
: { left: 0, top: position }),
|
||||
width: 50,
|
||||
height: 50,
|
||||
background: "rgba(255,0,0,0.5)",
|
||||
@@ -78,23 +91,18 @@ function TestSlider() {
|
||||
</div>
|
||||
<p>
|
||||
Position: <span data-cy="position-display">{position}</span>px
|
||||
<br /> Value: <span data-cy="value-display">{sliderValue}</span>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
describe("Slider Hook Tests", () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<TestSlider />);
|
||||
});
|
||||
|
||||
function createTestUtils(isHorizontal = true) {
|
||||
const assertPosition = (expectedPosition: number) => {
|
||||
cy.dataCy("position-display").should("contain", expectedPosition);
|
||||
cy.dataCy("slider-handle").should(
|
||||
"have.css",
|
||||
"left",
|
||||
isHorizontal ? "left" : "top",
|
||||
`${expectedPosition}px`,
|
||||
);
|
||||
};
|
||||
@@ -107,7 +115,49 @@ describe("Slider Hook Tests", () => {
|
||||
});
|
||||
};
|
||||
|
||||
it("moves the slider with mouse drag.", () => {
|
||||
const triggerTouchEvent = (eventType: string, x: number, y: number) => {
|
||||
cy.dataCy("slider-container").trigger(eventType, {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
touches: [{ clientX: x, clientY: y }],
|
||||
eventConstructor: "TouchEvent",
|
||||
});
|
||||
};
|
||||
|
||||
const triggerWheelEvent = (deltaY: number) => {
|
||||
cy.dataCy("slider-container").trigger("wheel", {
|
||||
deltaY,
|
||||
eventConstructor: "WheelEvent",
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
assertPosition,
|
||||
triggerMouseEvent,
|
||||
triggerTouchEvent,
|
||||
triggerWheelEvent,
|
||||
};
|
||||
}
|
||||
|
||||
const isTouchSupported = () => {
|
||||
return typeof TouchEvent !== "undefined";
|
||||
};
|
||||
|
||||
// Tests
|
||||
|
||||
describe("horizontal slider hook tests", () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<TestSlider direction={Direction.HORIZONTAL} />);
|
||||
});
|
||||
|
||||
const {
|
||||
assertPosition,
|
||||
triggerMouseEvent,
|
||||
triggerTouchEvent,
|
||||
triggerWheelEvent,
|
||||
} = createTestUtils(true);
|
||||
|
||||
it("moves the slider with mouse events.", () => {
|
||||
assertPosition(0);
|
||||
|
||||
triggerMouseEvent("mousedown", 86, 53);
|
||||
@@ -117,7 +167,7 @@ describe("Slider Hook Tests", () => {
|
||||
assertPosition(64);
|
||||
|
||||
triggerMouseEvent("mousemove", 500, 500);
|
||||
assertPosition(325);
|
||||
assertPosition(250);
|
||||
|
||||
triggerMouseEvent("mousemove", 250, 250);
|
||||
assertPosition(164);
|
||||
@@ -125,4 +175,132 @@ describe("Slider Hook Tests", () => {
|
||||
triggerMouseEvent("mouseup", 250, 250);
|
||||
assertPosition(164);
|
||||
});
|
||||
|
||||
if (isTouchSupported()) {
|
||||
it("moves the slider with touch events.", () => {
|
||||
assertPosition(0);
|
||||
|
||||
triggerTouchEvent("touchstart", 86, 53);
|
||||
assertPosition(0);
|
||||
|
||||
triggerTouchEvent("touchmove", 150, 150);
|
||||
assertPosition(64);
|
||||
|
||||
triggerTouchEvent("touchmove", 500, 500);
|
||||
assertPosition(250);
|
||||
|
||||
triggerTouchEvent("touchmove", 250, 250);
|
||||
assertPosition(164);
|
||||
|
||||
triggerTouchEvent("touchend", 250, 250);
|
||||
assertPosition(164);
|
||||
});
|
||||
} else {
|
||||
console.log("Skipping Unsupported Touch Event Tests");
|
||||
}
|
||||
|
||||
it("moves the slider with mouse wheel scrolling", () => {
|
||||
assertPosition(0);
|
||||
|
||||
triggerWheelEvent(100);
|
||||
assertPosition(1);
|
||||
|
||||
triggerWheelEvent(100);
|
||||
assertPosition(2);
|
||||
|
||||
triggerWheelEvent(-100);
|
||||
assertPosition(1);
|
||||
|
||||
// Many smaller scrolls, to simulate touchpads
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
assertPosition(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("vertical slider hook tests", () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<TestSlider direction={Direction.VERTICAL} />);
|
||||
});
|
||||
|
||||
const {
|
||||
assertPosition,
|
||||
triggerMouseEvent,
|
||||
triggerTouchEvent,
|
||||
triggerWheelEvent,
|
||||
} = createTestUtils(false);
|
||||
|
||||
it("moves the slider with mouse drag.", () => {
|
||||
assertPosition(0);
|
||||
|
||||
triggerMouseEvent("mousedown", 86, 53);
|
||||
assertPosition(0);
|
||||
|
||||
triggerMouseEvent("mousemove", 150, 150);
|
||||
assertPosition(72);
|
||||
|
||||
triggerMouseEvent("mousemove", 500, 500);
|
||||
assertPosition(250);
|
||||
|
||||
triggerMouseEvent("mousemove", 250, 250);
|
||||
assertPosition(172);
|
||||
|
||||
triggerMouseEvent("mouseup", 250, 250);
|
||||
assertPosition(172);
|
||||
});
|
||||
|
||||
if (isTouchSupported()) {
|
||||
it("moves the slider with touch events.", () => {
|
||||
assertPosition(0);
|
||||
|
||||
triggerTouchEvent("touchstart", 86, 53);
|
||||
assertPosition(0);
|
||||
|
||||
triggerTouchEvent("touchmove", 150, 150);
|
||||
assertPosition(72);
|
||||
|
||||
triggerTouchEvent("touchmove", 500, 500);
|
||||
assertPosition(250);
|
||||
|
||||
triggerTouchEvent("touchmove", 250, 250);
|
||||
assertPosition(172);
|
||||
|
||||
triggerTouchEvent("touchend", 250, 250);
|
||||
assertPosition(172);
|
||||
});
|
||||
} else {
|
||||
console.log("Skipping Unsupported Touch Event Tests");
|
||||
}
|
||||
|
||||
it("moves the slider with mouse wheel scrolling", () => {
|
||||
assertPosition(0);
|
||||
|
||||
triggerWheelEvent(100);
|
||||
assertPosition(1);
|
||||
|
||||
triggerWheelEvent(100);
|
||||
assertPosition(2);
|
||||
|
||||
triggerWheelEvent(-100);
|
||||
assertPosition(1);
|
||||
|
||||
// Many smaller scrolls, to simulate touchpads
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
triggerWheelEvent(20);
|
||||
assertPosition(4);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user