Skip to content

Commit 52f7ae0

Browse files
committed
feat(signal): add specification test code
1 parent 61afda0 commit 52f7ae0

File tree

10 files changed

+319
-12
lines changed

10 files changed

+319
-12
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
"postinstall": "[ -n \"$SKIP_POSTINSTALL\" ] || (npx nx build @gesturejs/builder && SKIP_POSTINSTALL=1 pnpm rebuild)"
55
},
66
"devDependencies": {
7+
"@vitest/coverage-v8": "^2.0.0",
8+
"jsdom": "^25.0.0",
79
"nx": "^22.2",
810
"typescript": "^5.9",
9-
"vite": "^7.3"
11+
"vite": "^7.3",
12+
"vitest": "^2.0.0"
1013
}
1114
}

packages/signal/package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
"import": "./dist/core/index.js",
2020
"require": "./dist/core/index.cjs"
2121
},
22-
"./pointer-signal": {
23-
"types": "./dist/pointer-signal/index.d.ts",
24-
"import": "./dist/pointer-signal/index.js",
25-
"require": "./dist/pointer-signal/index.cjs"
22+
"./single-pointer": {
23+
"types": "./dist/single-pointer/index.d.ts",
24+
"import": "./dist/single-pointer/index.js",
25+
"require": "./dist/single-pointer/index.cjs"
2626
}
2727
},
2828
"files": [
@@ -38,6 +38,10 @@
3838
"@gesturejs/stream": "^0.1.0"
3939
},
4040
"devDependencies": {
41-
"@gesturejs/builder": "workspace:*"
41+
"@gesturejs/builder": "workspace:*",
42+
"@vitest/coverage-v8": "*",
43+
"jsdom": "*",
44+
"vite": "*",
45+
"vitest": "*"
4246
}
4347
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { describe, it, expect, vi } from "vitest";
2+
import { createObjectPool } from "./pool.js";
3+
4+
describe("createObjectPool", () => {
5+
const factory = () => ({ value: 0 });
6+
const reset = (obj: { value: number }) => {
7+
obj.value = 0;
8+
};
9+
10+
it("should pre-allocate objects on creation", () => {
11+
const pool = createObjectPool(factory, reset, 5);
12+
13+
expect(pool.size).toBe(5);
14+
expect(pool.active).toBe(0);
15+
});
16+
17+
it("should return object from pool on acquire", () => {
18+
const pool = createObjectPool(factory, reset, 3);
19+
20+
const obj = pool.acquire();
21+
22+
expect(obj).toEqual({ value: 0 });
23+
expect(pool.size).toBe(2);
24+
expect(pool.active).toBe(1);
25+
});
26+
27+
it("should create new object when pool is empty", () => {
28+
const factorySpy = vi.fn(factory);
29+
const pool = createObjectPool(factorySpy, reset, 1);
30+
31+
pool.acquire();
32+
pool.acquire();
33+
34+
expect(factorySpy).toHaveBeenCalledTimes(2);
35+
});
36+
37+
it("should reset and return object to pool on release", () => {
38+
const pool = createObjectPool(factory, reset, 1);
39+
40+
const obj = pool.acquire();
41+
obj.value = 42;
42+
pool.release(obj);
43+
44+
expect(obj.value).toBe(0);
45+
expect(pool.size).toBe(1);
46+
expect(pool.active).toBe(0);
47+
});
48+
49+
it("should not exceed maxSize on release", () => {
50+
const pool = createObjectPool(factory, reset, 0, 2);
51+
52+
const obj1 = pool.acquire();
53+
const obj2 = pool.acquire();
54+
const obj3 = pool.acquire();
55+
56+
pool.release(obj1);
57+
pool.release(obj2);
58+
pool.release(obj3);
59+
60+
expect(pool.size).toBe(2);
61+
});
62+
63+
it("should clear all pooled objects", () => {
64+
const pool = createObjectPool(factory, reset, 5);
65+
66+
pool.acquire();
67+
pool.clear();
68+
69+
expect(pool.size).toBe(0);
70+
expect(pool.active).toBe(0);
71+
});
72+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, it, expect } from "vitest";
2+
import { createSignal } from "./signal.js";
3+
4+
describe("createSignal", () => {
5+
it("should create signal with type and deviceId", () => {
6+
const signal = createSignal("pointer", "mouse-1");
7+
8+
expect(signal.type).toBe("pointer");
9+
expect(signal.deviceId).toBe("mouse-1");
10+
expect(typeof signal.timestamp).toBe("number");
11+
});
12+
13+
it("should use performance.now() for timestamp", () => {
14+
const before = performance.now();
15+
const signal = createSignal("test", "device-1");
16+
const after = performance.now();
17+
18+
expect(signal.timestamp).toBeGreaterThanOrEqual(before);
19+
expect(signal.timestamp).toBeLessThanOrEqual(after);
20+
});
21+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from "vitest";
2+
import {
3+
createDefaultSinglePointer,
4+
resetSinglePointer,
5+
isSinglePointer,
6+
} from "./signal.js";
7+
import type { Signal } from "../core/signal.js";
8+
9+
describe("createDefaultSinglePointer", () => {
10+
it("should create pointer with default values", () => {
11+
const pointer = createDefaultSinglePointer();
12+
13+
expect(pointer).toEqual({
14+
type: "pointer",
15+
timestamp: 0,
16+
deviceId: "",
17+
phase: "move",
18+
x: 0,
19+
y: 0,
20+
pointerType: "unknown",
21+
button: "none",
22+
pressure: 0.5,
23+
});
24+
});
25+
});
26+
27+
describe("resetSinglePointer", () => {
28+
it("should reset pointer to default values", () => {
29+
const pointer = createDefaultSinglePointer();
30+
pointer.timestamp = 100;
31+
pointer.deviceId = "mouse-1";
32+
pointer.phase = "start";
33+
pointer.x = 150;
34+
pointer.y = 200;
35+
pointer.pointerType = "mouse";
36+
pointer.button = "primary";
37+
pointer.pressure = 1.0;
38+
39+
resetSinglePointer(pointer);
40+
41+
expect(pointer.timestamp).toBe(0);
42+
expect(pointer.deviceId).toBe("");
43+
expect(pointer.phase).toBe("move");
44+
expect(pointer.x).toBe(0);
45+
expect(pointer.y).toBe(0);
46+
expect(pointer.pointerType).toBe("unknown");
47+
expect(pointer.button).toBe("none");
48+
expect(pointer.pressure).toBe(0.5);
49+
});
50+
});
51+
52+
describe("isSinglePointer", () => {
53+
it("should return true for pointer type signal", () => {
54+
const signal: Signal = { type: "pointer", timestamp: 0, deviceId: "" };
55+
56+
expect(isSinglePointer(signal)).toBe(true);
57+
});
58+
59+
it("should return false for non-pointer signal", () => {
60+
const signal: Signal = { type: "gesture", timestamp: 0, deviceId: "" };
61+
62+
expect(isSinglePointer(signal)).toBe(false);
63+
});
64+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, it, expect } from "vitest";
2+
import { toPointerButton } from "./types.js";
3+
4+
describe("toPointerButton", () => {
5+
it("should map standard mouse buttons", () => {
6+
expect(toPointerButton(-1)).toBe("none");
7+
expect(toPointerButton(0)).toBe("primary");
8+
expect(toPointerButton(1)).toBe("auxiliary");
9+
expect(toPointerButton(2)).toBe("secondary");
10+
expect(toPointerButton(3)).toBe("back");
11+
expect(toPointerButton(4)).toBe("forward");
12+
});
13+
14+
it("should return 'none' for unknown button numbers", () => {
15+
expect(toPointerButton(5)).toBe("none");
16+
expect(toPointerButton(99)).toBe("none");
17+
});
18+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, it, expect } from "vitest";
2+
import {
3+
eventTypeToPhase,
4+
normalizePointerType,
5+
getButton,
6+
getDeviceId,
7+
} from "./utils.js";
8+
9+
describe("eventTypeToPhase", () => {
10+
it("should map start events to 'start'", () => {
11+
expect(eventTypeToPhase("pointerdown")).toBe("start");
12+
expect(eventTypeToPhase("mousedown")).toBe("start");
13+
expect(eventTypeToPhase("touchstart")).toBe("start");
14+
});
15+
16+
it("should map move events to 'move'", () => {
17+
expect(eventTypeToPhase("pointermove")).toBe("move");
18+
expect(eventTypeToPhase("mousemove")).toBe("move");
19+
expect(eventTypeToPhase("touchmove")).toBe("move");
20+
});
21+
22+
it("should map end events to 'end'", () => {
23+
expect(eventTypeToPhase("pointerup")).toBe("end");
24+
expect(eventTypeToPhase("mouseup")).toBe("end");
25+
expect(eventTypeToPhase("touchend")).toBe("end");
26+
});
27+
28+
it("should map cancel events to 'cancel'", () => {
29+
expect(eventTypeToPhase("pointercancel")).toBe("cancel");
30+
expect(eventTypeToPhase("touchcancel")).toBe("cancel");
31+
});
32+
33+
it("should default to 'move' for unknown events", () => {
34+
expect(eventTypeToPhase("unknown")).toBe("move");
35+
});
36+
});
37+
38+
describe("normalizePointerType", () => {
39+
it("should normalize known pointer types", () => {
40+
expect(normalizePointerType("mouse")).toBe("mouse");
41+
expect(normalizePointerType("touch")).toBe("touch");
42+
expect(normalizePointerType("pen")).toBe("pen");
43+
});
44+
45+
it("should return 'unknown' for unrecognized types", () => {
46+
expect(normalizePointerType("stylus")).toBe("unknown");
47+
expect(normalizePointerType("")).toBe("unknown");
48+
});
49+
});
50+
51+
describe("getButton", () => {
52+
it("should return 'none' for move events", () => {
53+
const event = { type: "pointermove", button: 0 } as PointerEvent;
54+
55+
expect(getButton(event)).toBe("none");
56+
});
57+
58+
it("should return button type for non-move events", () => {
59+
expect(getButton({ type: "pointerdown", button: 0 } as PointerEvent)).toBe(
60+
"primary"
61+
);
62+
expect(getButton({ type: "pointerdown", button: 2 } as PointerEvent)).toBe(
63+
"secondary"
64+
);
65+
});
66+
});
67+
68+
describe("getDeviceId", () => {
69+
it("should return pointer-based id for PointerEvent", () => {
70+
const event = {
71+
pointerType: "mouse",
72+
pointerId: 1,
73+
} as PointerEvent;
74+
75+
expect(getDeviceId(event)).toBe("mouse-1");
76+
});
77+
78+
it("should return 'touch-device' for TouchEvent", () => {
79+
const event = { touches: [] } as unknown as TouchEvent;
80+
81+
expect(getDeviceId(event)).toBe("touch-device");
82+
});
83+
84+
it("should return 'mouse-device' for MouseEvent", () => {
85+
const event = {} as MouseEvent;
86+
87+
expect(getDeviceId(event)).toBe("mouse-device");
88+
});
89+
});

packages/signal/vitest.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
globals: true,
6+
environment: "jsdom",
7+
include: ["src/**/*.spec.ts"],
8+
coverage: {
9+
provider: "v8",
10+
reporter: ["text", "html"],
11+
include: ["src/**/*.ts"],
12+
exclude: ["src/**/*.spec.ts"],
13+
},
14+
},
15+
});

packages/stream/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@
5353
},
5454
"devDependencies": {
5555
"@gesturejs/builder": "workspace:*",
56+
"@vitest/coverage-v8": "*",
57+
"jsdom": "*",
5658
"vite": "*",
57-
"vitest": "^2.0.0",
58-
"jsdom": "^25.0.0",
59-
"@vitest/coverage-v8": "^2.0.0"
59+
"vitest": "*"
6060
}
6161
}

pnpm-lock.yaml

Lines changed: 24 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)