@@ -94,3 +94,236 @@ describe('MeasurementEntity text orientation in draw() method', () => {
9494 expect ( mockDrawController . drawText ) . not . toHaveBeenCalled ( ) ;
9595 } ) ;
9696} ) ;
97+
98+ describe ( 'MeasurementEntity.distanceTo' , ( ) => {
99+ const layerId = 'test-layer' ;
100+
101+ // Re-define createMeasurement for this test suite
102+ // (Alternatively, this could be hoisted to a common scope in a real-world scenario)
103+ const createMeasurement = (
104+ start = new Point ( 0 , 0 ) ,
105+ end = new Point ( 100 , 0 ) ,
106+ offset = new Point ( 50 , 50 ) // Offset above the line (positive y for horizontal line)
107+ ) => new MeasurementEntity ( layerId , start , end , offset ) ;
108+
109+ it ( 'should return correct distance for a point closest to the main horizontal segment' , ( ) => {
110+ // Measurement: start(0,0), end(100,0), offset(0,20) -> main segment from (0,20) to (100,20)
111+ const measurement = createMeasurement ( new Point ( 0 , 0 ) , new Point ( 100 , 0 ) , new Point ( 0 , 20 ) ) ;
112+ const testPoint = new Point ( 50 , 30 ) ; // 10 units "above" the main segment's Y-coordinate
113+
114+ const distanceInfo = measurement . distanceTo ( testPoint ) ;
115+ expect ( distanceInfo ) . not . toBeNull ( ) ;
116+ expect ( distanceInfo ! [ 0 ] ) . toBeCloseTo ( 10 ) ;
117+ expect ( distanceInfo ! [ 1 ] . ps . equalTo ( testPoint ) ) . toBe ( true ) ; // Segment starts at testPoint
118+ expect ( distanceInfo ! [ 1 ] . pe . equalTo ( new Point ( 50 , 20 ) ) ) . toBe ( true ) ; // Segment ends at closest point on main segment
119+ } ) ;
120+
121+ it ( 'should return correct distance for a point closest to an endpoint of the main horizontal segment' , ( ) => {
122+ // Main segment from (0,20) to (100,20)
123+ const measurement = createMeasurement ( new Point ( 0 , 0 ) , new Point ( 100 , 0 ) , new Point ( 0 , 20 ) ) ;
124+ const testPoint = new Point ( 110 , 30 ) ; // Diagonally off the endPoint (100,20)
125+ // dx = 10, dy = 10. Distance = sqrt(10^2 + 10^2) = sqrt(200)
126+
127+ const distanceInfo = measurement . distanceTo ( testPoint ) ;
128+ expect ( distanceInfo ) . not . toBeNull ( ) ;
129+ expect ( distanceInfo ! [ 0 ] ) . toBeCloseTo ( Math . sqrt ( 200 ) ) ;
130+ expect ( distanceInfo ! [ 1 ] . ps . equalTo ( testPoint ) ) . toBe ( true ) ;
131+ expect ( distanceInfo ! [ 1 ] . pe . equalTo ( new Point ( 100 , 20 ) ) ) . toBe ( true ) ; // Closest to offsetEndPoint
132+ } ) ;
133+
134+ it ( 'should return correct distance for a point closest to one of the vertical extension lines' , ( ) => {
135+ // Measurement: start(0,0), end(100,0), offset(0,20)
136+ // Main segment: (0,20) to (100,20)
137+ // getDrawPoints will calculate extension lines. Let's use default MEASUREMENT_ORIGIN_MARGIN and MEASUREMENT_EXTENSION_LENGTH.
138+ // For simplicity, let's assume MEASUREMENT_ORIGIN_MARGIN = 2, MEASUREMENT_EXTENSION_LENGTH = 10 for calculation.
139+ // Actual values from App.consts are: MEASUREMENT_ORIGIN_MARGIN = 2, MEASUREMENT_EXTENSION_LENGTH = 8
140+ // So, first extension line for startPoint(0,0) with offset (0,20) (vectorPerpendicularFromLineTowardsOffsetPoint is (0,20))
141+ // offsetStartPointMargin: (0,0) + (0,1).normalize() * 2 = (0,2)
142+ // offsetStartPoint: (0,0) + (0,20) = (0,20)
143+ // offsetStartPointExtend: (0,20) + (0,1).normalize() * 8 = (0,28)
144+ // So, first extension line is from (0,2) to (0,28).
145+ // Note: The above calculation of offsetStartPointExtend is relative to offsetStartPoint, not startPoint
146+ // The actual calculation in getDrawPoints for offsetStartPointExtend is:
147+ // offsetStartPoint.clone().translate(vectorPerpendicularFromLineTowardsOffsetPointUnit.multiply(MEASUREMENT_EXTENSION_LENGTH))
148+ // vectorPerpendicularFromLineTowardsOffsetPointUnit for offset (0,20) is (0,1) (assuming start/end on x-axis)
149+ // So, offsetStartPointExtend = (0,20) + (0,1)*8 = (0,28)
150+ // And offsetStartPointMargin = (0,0) + (0,1)*2 = (0,2)
151+
152+ const measurement = createMeasurement ( new Point ( 0 , 0 ) , new Point ( 100 , 0 ) , new Point ( 0 , 20 ) ) ;
153+ // Test point closest to the first extension line (0,2) to (0,28)
154+ const testPoint = new Point ( 5 , 15 ) ; // 5 units away horizontally from the extension line
155+
156+ const distanceInfo = measurement . distanceTo ( testPoint ) ;
157+ expect ( distanceInfo ) . not . toBeNull ( ) ;
158+ expect ( distanceInfo ! [ 0 ] ) . toBeCloseTo ( 5 ) ;
159+ expect ( distanceInfo ! [ 1 ] . ps . equalTo ( testPoint ) ) . toBe ( true ) ;
160+ expect ( distanceInfo ! [ 1 ] . pe . equalTo ( new Point ( 0 , 15 ) ) ) . toBe ( true ) ; // Closest point on segment (0,2)-(0,28)
161+ } ) ;
162+
163+ it ( 'should return correct distance for a point collinear with main segment but outside' , ( ) => {
164+ // Main segment from (0,20) to (100,20)
165+ const measurement = createMeasurement ( new Point ( 0 , 0 ) , new Point ( 100 , 0 ) , new Point ( 0 , 20 ) ) ;
166+ const testPoint = new Point ( 120 , 20 ) ; // Collinear, 20 units away from offsetEndPoint (100,20)
167+
168+ const distanceInfo = measurement . distanceTo ( testPoint ) ;
169+ expect ( distanceInfo ) . not . toBeNull ( ) ;
170+ expect ( distanceInfo ! [ 0 ] ) . toBeCloseTo ( 20 ) ;
171+ expect ( distanceInfo ! [ 1 ] . ps . equalTo ( testPoint ) ) . toBe ( true ) ;
172+ expect ( distanceInfo ! [ 1 ] . pe . equalTo ( new Point ( 100 , 20 ) ) ) . toBe ( true ) ;
173+ } ) ;
174+
175+ it ( 'should return null for a zero-length measurement' , ( ) => {
176+ // startPoint and endPoint are the same
177+ const measurement = createMeasurement ( new Point ( 0 , 0 ) , new Point ( 0 , 0 ) , new Point ( 0 , 20 ) ) ;
178+ const testPoint = new Point ( 50 , 30 ) ;
179+ const distanceInfo = measurement . distanceTo ( testPoint ) ;
180+ expect ( distanceInfo ) . toBeNull ( ) ;
181+ } ) ;
182+
183+ it ( 'should correctly calculate distance to a point closer to the second extension line' , ( ) => {
184+ // Measurement: start(0,0), end(100,0), offset(0,20)
185+ // Second extension line for endPoint(100,0) with offset (0,20)
186+ // offsetEndPointMargin: (100,0) + (0,1)*2 = (100,2)
187+ // offsetEndPointExtend: (100,20) + (0,1)*8 = (100,28)
188+ // Second extension line is from (100,2) to (100,28)
189+ const measurement = createMeasurement ( new Point ( 0 , 0 ) , new Point ( 100 , 0 ) , new Point ( 0 , 20 ) ) ;
190+ const testPoint = new Point ( 95 , 15 ) ; // 5 units away horizontally from the second extension line
191+
192+ const distanceInfo = measurement . distanceTo ( testPoint ) ;
193+ expect ( distanceInfo ) . not . toBeNull ( ) ;
194+ expect ( distanceInfo ! [ 0 ] ) . toBeCloseTo ( 5 ) ;
195+ expect ( distanceInfo ! [ 1 ] . ps . equalTo ( testPoint ) ) . toBe ( true ) ;
196+ expect ( distanceInfo ! [ 1 ] . pe . equalTo ( new Point ( 100 , 15 ) ) ) . toBe ( true ) ; // Closest point on segment (100,2)-(100,28)
197+ } ) ;
198+ } ) ;
199+
200+ describe ( 'MeasurementEntity.containsPointOnShape' , ( ) => {
201+ const layerId = 'test-layer' ;
202+
203+ const createMeasurement = (
204+ start = new Point ( 0 , 0 ) ,
205+ end = new Point ( 100 , 0 ) ,
206+ offset = new Point ( 50 , 50 ) // Offset above the line (positive y)
207+ ) => new MeasurementEntity ( layerId , start , end , offset ) ;
208+
209+ it ( 'should return true for a point on the main measurement line' , ( ) => {
210+ const measurement = createMeasurement ( ) ;
211+ const drawPoints = ( measurement as any ) . getDrawPoints ( ) ;
212+
213+ const pointOnMainLineMid = new Point (
214+ ( drawPoints . offsetStartPoint . x + drawPoints . offsetEndPoint . x ) / 2 ,
215+ drawPoints . offsetStartPoint . y
216+ ) ;
217+ expect ( measurement . containsPointOnShape ( pointOnMainLineMid ) ) . toBe ( true ) ;
218+ expect ( measurement . containsPointOnShape ( drawPoints . offsetStartPoint . clone ( ) ) ) . toBe ( true ) ; // Use clone for safety
219+ expect ( measurement . containsPointOnShape ( drawPoints . offsetEndPoint . clone ( ) ) ) . toBe ( true ) ;
220+ } ) ;
221+
222+ it ( 'should return true for a point on the first extension line' , ( ) => {
223+ const measurement = createMeasurement ( ) ;
224+ const drawPoints = ( measurement as any ) . getDrawPoints ( ) ;
225+
226+ const pointOnExtLine1Mid = new Point (
227+ drawPoints . offsetStartPointMargin . x ,
228+ ( drawPoints . offsetStartPointMargin . y + drawPoints . offsetStartPointExtend . y ) / 2
229+ ) ;
230+ expect ( measurement . containsPointOnShape ( pointOnExtLine1Mid ) ) . toBe ( true ) ;
231+ expect ( measurement . containsPointOnShape ( drawPoints . offsetStartPointMargin . clone ( ) ) ) . toBe ( true ) ;
232+ expect ( measurement . containsPointOnShape ( drawPoints . offsetStartPointExtend . clone ( ) ) ) . toBe ( true ) ;
233+ } ) ;
234+
235+ it ( 'should return true for a point on the second extension line' , ( ) => {
236+ const measurement = createMeasurement ( ) ;
237+ const drawPoints = ( measurement as any ) . getDrawPoints ( ) ;
238+
239+ const pointOnExtLine2Mid = new Point (
240+ drawPoints . offsetEndPointMargin . x ,
241+ ( drawPoints . offsetEndPointMargin . y + drawPoints . offsetEndPointExtend . y ) / 2
242+ ) ;
243+ expect ( measurement . containsPointOnShape ( pointOnExtLine2Mid ) ) . toBe ( true ) ;
244+ expect ( measurement . containsPointOnShape ( drawPoints . offsetEndPointMargin . clone ( ) ) ) . toBe ( true ) ;
245+ expect ( measurement . containsPointOnShape ( drawPoints . offsetEndPointExtend . clone ( ) ) ) . toBe ( true ) ;
246+ } ) ;
247+
248+ it ( 'should return false for a point not on any line' , ( ) => {
249+ const measurement = createMeasurement ( ) ;
250+ const testPoint = new Point ( 500 , 500 ) ; // Clearly off lines
251+ expect ( measurement . containsPointOnShape ( testPoint ) ) . toBe ( false ) ;
252+ } ) ;
253+
254+ it ( 'should return false for a point very close to a line but not on it' , ( ) => {
255+ const measurement = createMeasurement ( ) ;
256+ const drawPoints = ( measurement as any ) . getDrawPoints ( ) ;
257+ // Point 0.1 units above the middle of the main line
258+ const pointNearMainLine = new Point (
259+ ( drawPoints . offsetStartPoint . x + drawPoints . offsetEndPoint . x ) / 2 ,
260+ drawPoints . offsetStartPoint . y + 0.1
261+ ) ;
262+ expect ( measurement . containsPointOnShape ( pointNearMainLine ) ) . toBe ( false ) ;
263+
264+ // Point 0.1 units to the side of the first extension line
265+ const pointNearExtLine1 = new Point (
266+ drawPoints . offsetStartPointMargin . x + 0.1 ,
267+ ( drawPoints . offsetStartPointMargin . y + drawPoints . offsetStartPointExtend . y ) / 2
268+ ) ;
269+ expect ( measurement . containsPointOnShape ( pointNearExtLine1 ) ) . toBe ( false ) ;
270+ } ) ;
271+
272+ it ( 'should return false if getDrawPoints returns null (e.g. zero-length measurement)' , ( ) => {
273+ const startPoint = new Point ( 10 , 10 ) ;
274+ const offsetPoint = new Point ( 60 , 50 ) ;
275+ // EndPoint is the same as startPoint
276+ const measurement = new MeasurementEntity ( layerId , startPoint , startPoint . clone ( ) , offsetPoint ) ;
277+ const testPoint = new Point ( 10 , 10 ) ; // Test with the start point itself
278+ expect ( measurement . containsPointOnShape ( testPoint ) ) . toBe ( false ) ;
279+
280+ const anotherTestPoint = new Point ( 50 , 50 ) ; // Test with an arbitrary point
281+ expect ( measurement . containsPointOnShape ( anotherTestPoint ) ) . toBe ( false ) ;
282+ } ) ;
283+
284+ it ( 'should correctly identify points on a measurement with negative offset (text below)' , ( ) => {
285+ const measurement = createMeasurement (
286+ new Point ( 0 , 100 ) , // Start
287+ new Point ( 100 , 100 ) , // End
288+ new Point ( 50 , 50 ) // Offset below the line (y is smaller)
289+ ) ;
290+ const drawPoints = ( measurement as any ) . getDrawPoints ( ) ;
291+
292+ const pointOnMainLineMid = new Point (
293+ ( drawPoints . offsetStartPoint . x + drawPoints . offsetEndPoint . x ) / 2 ,
294+ drawPoints . offsetStartPoint . y
295+ ) ;
296+ expect ( measurement . containsPointOnShape ( pointOnMainLineMid ) ) . toBe ( true ) ;
297+ expect ( measurement . containsPointOnShape ( drawPoints . offsetStartPoint . clone ( ) ) ) . toBe ( true ) ;
298+ expect ( measurement . containsPointOnShape ( drawPoints . offsetEndPoint . clone ( ) ) ) . toBe ( true ) ;
299+
300+ const pointOnExtLine1Mid = new Point (
301+ drawPoints . offsetStartPointMargin . x ,
302+ ( drawPoints . offsetStartPointMargin . y + drawPoints . offsetStartPointExtend . y ) / 2
303+ ) ;
304+ expect ( measurement . containsPointOnShape ( pointOnExtLine1Mid ) ) . toBe ( true ) ;
305+ } ) ;
306+
307+ it ( 'should correctly identify points on a vertical measurement' , ( ) => {
308+ const measurement = createMeasurement (
309+ new Point ( 50 , 0 ) , // Start
310+ new Point ( 50 , 100 ) , // End
311+ new Point ( 100 , 50 ) // Offset to the right
312+ ) ;
313+ const drawPoints = ( measurement as any ) . getDrawPoints ( ) ;
314+
315+ const pointOnMainLineMid = new Point (
316+ drawPoints . offsetStartPoint . x ,
317+ ( drawPoints . offsetStartPoint . y + drawPoints . offsetEndPoint . y ) / 2
318+ ) ;
319+ expect ( measurement . containsPointOnShape ( pointOnMainLineMid ) ) . toBe ( true ) ;
320+ expect ( measurement . containsPointOnShape ( drawPoints . offsetStartPoint . clone ( ) ) ) . toBe ( true ) ;
321+ expect ( measurement . containsPointOnShape ( drawPoints . offsetEndPoint . clone ( ) ) ) . toBe ( true ) ;
322+
323+ const pointOnExtLine2Mid = new Point (
324+ ( drawPoints . offsetEndPointMargin . x + drawPoints . offsetEndPointExtend . x ) / 2 ,
325+ drawPoints . offsetEndPointMargin . y
326+ ) ;
327+ expect ( measurement . containsPointOnShape ( pointOnExtLine2Mid ) ) . toBe ( true ) ;
328+ } ) ;
329+ } ) ;
0 commit comments