Skip to content

Commit e1d6ed0

Browse files
authored
fix: add tests for geometry (#4728)
* fix: add tests for geometry * fix: ci * fix: 官网内容覆盖 * fix: resolve conversation
1 parent 750dcfc commit e1d6ed0

File tree

11 files changed

+1416
-1
lines changed

11 files changed

+1416
-1
lines changed

.github/workflows/gh-pages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,5 @@ jobs:
5656
with:
5757
github_token: ${{secrets.PERSONAL_ACCESS_TOKEN}}
5858
directory: site/dist
59-
branch: gh-pages
59+
branch: site
6060
force: true

__tests__/geometry/curve.spec.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { Curve, Point, Rectangle, Polyline, Line } from '../../src'
3+
4+
describe('Curve', () => {
5+
const start = new Point(0, 0)
6+
const cp1 = new Point(0, 10)
7+
const cp2 = new Point(10, 10)
8+
const end = new Point(10, 0)
9+
10+
const curve = new Curve(start, cp1, cp2, end)
11+
12+
it('should construct correctly', () => {
13+
expect(curve.start.equals(start)).toBe(true)
14+
expect(curve.end.equals(end)).toBe(true)
15+
expect(curve.controlPoint1.equals(cp1)).toBe(true)
16+
expect(curve.controlPoint2.equals(cp2)).toBe(true)
17+
})
18+
19+
it('bbox() should return a Rectangle containing the curve', () => {
20+
const box = curve.bbox()
21+
expect(box).toBeInstanceOf(Rectangle)
22+
expect(box.width).toBeGreaterThan(0)
23+
expect(box.height).toBeGreaterThan(0)
24+
})
25+
26+
it('endpointDistance() should compute straight-line distance', () => {
27+
expect(curve.endpointDistance()).toBeCloseTo(10, 5)
28+
})
29+
30+
it('getSkeletonPoints() works for t=0, t=1 and 0<t<1', () => {
31+
const s0 = curve.getSkeletonPoints(0)
32+
expect(s0.divider.equals(start)).toBe(true)
33+
34+
const s1 = curve.getSkeletonPoints(1)
35+
expect(s1.divider.equals(end)).toBe(true)
36+
37+
const mid = curve.getSkeletonPoints(0.5)
38+
expect(mid.divider).toBeInstanceOf(Point)
39+
})
40+
41+
it('divideAtT should split the curve correctly', () => {
42+
const [c1, c2] = curve.divideAtT(0.5)
43+
expect(c1).toBeInstanceOf(Curve)
44+
expect(c2).toBeInstanceOf(Curve)
45+
46+
const [c3, c4] = curve.divideAtT(0)
47+
expect(c3.start.equals(c3.end)).toBe(true)
48+
49+
const [c5, c6] = curve.divideAtT(1)
50+
expect(c6.start.equals(c6.end)).toBe(true)
51+
})
52+
53+
it('length() should be positive and lengthAtT <= total length', () => {
54+
const len = curve.length()
55+
expect(len).toBeGreaterThan(0)
56+
expect(curve.lengthAtT(0.5)).toBeLessThanOrEqual(len)
57+
})
58+
59+
it('pointAt and pointAtT should return points on curve', () => {
60+
expect(curve.pointAt(0).equals(start)).toBe(true)
61+
expect(curve.pointAt(1).equals(end)).toBe(true)
62+
expect(curve.pointAt(0.5)).toBeInstanceOf(Point)
63+
64+
expect(curve.pointAtT(0).equals(start)).toBe(true)
65+
expect(curve.pointAtT(1).equals(end)).toBe(true)
66+
})
67+
68+
it('tangentAt / tangentAtLength / tangentAtT should return Line or null', () => {
69+
const tan = curve.tangentAt(0.5)
70+
expect(tan).toBeInstanceOf(Line)
71+
72+
const tan2 = curve.tangentAtLength(curve.length() / 2)
73+
expect(tan2).toBeInstanceOf(Line)
74+
75+
const collapsed = new Curve(start, start, start, start)
76+
expect(collapsed.tangentAt(0.5)).toBeNull()
77+
expect(collapsed.tangentAtLength(1)).toBeNull()
78+
expect(collapsed.tangentAtT(0.5)).toBeNull()
79+
})
80+
81+
it('closestPoint / closestPointT / closestPointLength / closestPointNormalizedLength', () => {
82+
const p = new Point(5, 5)
83+
const cp = curve.closestPoint(p)
84+
expect(cp).toBeInstanceOf(Point)
85+
86+
const t = curve.closestPointT(p)
87+
expect(typeof t).toBe('number')
88+
89+
const l = curve.closestPointLength(p)
90+
expect(l).toBeGreaterThan(0)
91+
92+
const nl = curve.closestPointNormalizedLength(p)
93+
expect(nl).toBeGreaterThanOrEqual(0)
94+
expect(nl).toBeLessThanOrEqual(1)
95+
})
96+
97+
it('containsPoint should check polyline containment', () => {
98+
const p = new Point(5, 5)
99+
expect(curve.containsPoint(p)).toBeTypeOf('boolean')
100+
})
101+
102+
it('divideAt and divideAtLength should split properly', () => {
103+
const [a, b] = curve.divideAt(0.3)
104+
expect(a).toBeInstanceOf(Curve)
105+
expect(b).toBeInstanceOf(Curve)
106+
107+
const [c, d] = curve.divideAtLength(curve.length() / 2)
108+
expect(c).toBeInstanceOf(Curve)
109+
expect(d).toBeInstanceOf(Curve)
110+
})
111+
112+
it('toPoints / toPolyline return valid results', () => {
113+
const points = curve.toPoints()
114+
expect(points.length).toBeGreaterThan(2)
115+
const poly = curve.toPolyline()
116+
expect(poly).toBeInstanceOf(Polyline)
117+
})
118+
119+
it('scale / rotate / translate should mutate curve and return itself', () => {
120+
const c2 = curve.clone().scale(2, 2)
121+
expect(c2.start.x).toBeCloseTo(0)
122+
123+
const c3 = curve.clone().rotate(90, new Point(0, 0))
124+
expect(c3).toBeInstanceOf(Curve)
125+
126+
const c4 = curve.clone().translate(5, 5)
127+
expect(c4.start.x).toBeGreaterThan(0)
128+
129+
const c5 = curve.clone().translate(new Point(1, 1))
130+
expect(c5.start.x).toBeGreaterThan(0)
131+
})
132+
133+
it('equals, clone, toJSON, serialize', () => {
134+
const c2 = curve.clone()
135+
expect(curve.equals(c2)).toBe(true)
136+
expect(curve.toJSON()).toHaveProperty('start')
137+
expect(typeof curve.serialize()).toBe('string')
138+
})
139+
140+
it('Curve.isCurve works correctly', () => {
141+
expect(Curve.isCurve(curve)).toBe(true)
142+
expect(Curve.isCurve(null)).toBe(false)
143+
})
144+
145+
it('Curve.throughPoints should generate curves', () => {
146+
const points = [new Point(0, 0), new Point(5, 5), new Point(10, 0)]
147+
const curves = Curve.throughPoints(points)
148+
expect(Array.isArray(curves)).toBe(true)
149+
expect(curves[0]).toBeInstanceOf(Curve)
150+
})
151+
152+
it('Curve.throughPoints should throw on invalid input', () => {
153+
expect(() => Curve.throughPoints([])).toThrowError()
154+
expect(() => Curve.throughPoints(null as any)).toThrowError()
155+
})
156+
})
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { Line } from '@'
3+
import { Point } from '@/geometry'
4+
import { Close } from '@/geometry/path/close'
5+
import { MoveTo } from '@/geometry/path/moveto'
6+
import { LineTo } from '@/geometry/path/lineto'
7+
8+
describe('Close', () => {
9+
const move = new MoveTo(0, 0)
10+
const lineTo = new LineTo(10, 0)
11+
const close = new Close()
12+
13+
// 模拟 path 结构
14+
close.previousSegment = lineTo
15+
close.subpathStartSegment = move
16+
17+
it('should have type Z', () => {
18+
expect(close.type).toBe('Z')
19+
expect(close.serialize()).toBe('Z')
20+
})
21+
22+
it('should throw if subpathStartSegment missing', () => {
23+
const c = new Close()
24+
c.previousSegment = lineTo
25+
expect(() => c.end).toThrow(/Missing subpath start segment/)
26+
})
27+
28+
it('should return correct line and end', () => {
29+
expect(close.end.equals(move.end)).toBe(true)
30+
expect(close.line).toBeDefined()
31+
expect(close.length()).toBeGreaterThan(0)
32+
})
33+
34+
it('bbox/point/tangent methods should work', () => {
35+
expect(close.bbox()).toBeDefined()
36+
expect(close.pointAt(0)).toBeInstanceOf(Point)
37+
expect(close.pointAtLength(5)).toBeInstanceOf(Point)
38+
expect(close.tangentAt(0)).toBeInstanceOf(Line)
39+
expect(close.tangentAtLength(5)).toBeInstanceOf(Line)
40+
})
41+
42+
it('should divide correctly', () => {
43+
const [seg1, seg2] = close.divideAt(0.5)
44+
expect(seg1).toBeInstanceOf(LineTo) // 前半段转成 LineTo
45+
expect(seg2).toBeInstanceOf(LineTo)
46+
47+
const [s1, s2] = close.divideAtLength(close.length() / 2)
48+
expect(s1).toBeInstanceOf(LineTo)
49+
expect(s2).toBeInstanceOf(LineTo)
50+
})
51+
52+
it('isDifferentiable should return correct values', () => {
53+
expect(close.isDifferentiable()).toBe(true)
54+
const c = new Close()
55+
expect(c.isDifferentiable()).toBe(false)
56+
})
57+
58+
it('scale/rotate/translate should return itself', () => {
59+
expect(close.scale()).toBe(close)
60+
expect(close.rotate()).toBe(close)
61+
expect(close.translate()).toBe(close)
62+
})
63+
64+
it('equals should work', () => {
65+
const another = new Close()
66+
another.previousSegment = lineTo
67+
another.subpathStartSegment = move
68+
expect(close.equals(another)).toBe(true)
69+
70+
const different = new Close()
71+
different.previousSegment = new LineTo(5, 5)
72+
different.subpathStartSegment = move
73+
expect(close.equals(different)).toBe(false)
74+
})
75+
76+
it('toJSON should return correct object', () => {
77+
const json = close.toJSON()
78+
expect(json).toHaveProperty('type', 'Z')
79+
expect(json.start).toBeDefined()
80+
expect(json.end).toBeDefined()
81+
})
82+
83+
it('clone should return equal Close', () => {
84+
const cloned = close.clone()
85+
expect(cloned).toBeInstanceOf(Close)
86+
// clone 还没有 previousSegment,需要手动赋值才可比较
87+
cloned.previousSegment = lineTo
88+
cloned.subpathStartSegment = move
89+
expect(close.equals(cloned)).toBe(true)
90+
})
91+
})

0 commit comments

Comments
 (0)