merge: combine all features from both codebases - mobile dashboard + layer panel + notifications + shares + all fixes
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { formatDistance, parseDistance, formatCoordinate, formatInputValue } from '../format';
|
||||
|
||||
describe('formatDistance', () => {
|
||||
it('formats mm correctly with default scaleFactor', () => {
|
||||
expect(formatDistance(100, 'mm')).toBe('100 mm');
|
||||
expect(formatDistance(0, 'mm')).toBe('0 mm');
|
||||
expect(formatDistance(1234.5, 'mm')).toBe('1235 mm');
|
||||
});
|
||||
|
||||
it('formats cm correctly with default scaleFactor', () => {
|
||||
expect(formatDistance(100, 'cm')).toBe('10.0 cm');
|
||||
expect(formatDistance(0, 'cm')).toBe('0.0 cm');
|
||||
expect(formatDistance(55, 'cm')).toBe('5.5 cm');
|
||||
});
|
||||
|
||||
it('formats m correctly with default scaleFactor', () => {
|
||||
expect(formatDistance(1000, 'm')).toBe('1.00 m');
|
||||
expect(formatDistance(0, 'm')).toBe('0.00 m');
|
||||
expect(formatDistance(1500, 'm')).toBe('1.50 m');
|
||||
});
|
||||
|
||||
it('applies scaleFactor correctly', () => {
|
||||
expect(formatDistance(100, 'mm', 10)).toBe('1000 mm');
|
||||
expect(formatDistance(100, 'cm', 10)).toBe('100.0 cm');
|
||||
expect(formatDistance(100, 'm', 10)).toBe('1.00 m');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseDistance', () => {
|
||||
it('parses mm input correctly', () => {
|
||||
expect(parseDistance('500', 'mm')).toBe(500);
|
||||
expect(parseDistance('0', 'mm')).toBe(0);
|
||||
});
|
||||
|
||||
it('parses cm input correctly', () => {
|
||||
expect(parseDistance('10', 'cm')).toBe(100);
|
||||
expect(parseDistance('5.5', 'cm')).toBe(55);
|
||||
});
|
||||
|
||||
it('parses m input correctly', () => {
|
||||
expect(parseDistance('1', 'm')).toBe(1000);
|
||||
expect(parseDistance('0.5', 'm')).toBe(500);
|
||||
});
|
||||
|
||||
it('applies scaleFactor correctly', () => {
|
||||
expect(parseDistance('500', 'mm', 10)).toBe(50);
|
||||
expect(parseDistance('10', 'cm', 10)).toBe(10);
|
||||
expect(parseDistance('1', 'm', 10)).toBe(100);
|
||||
});
|
||||
|
||||
it('returns 0 for invalid input', () => {
|
||||
expect(parseDistance('abc', 'mm')).toBe(0);
|
||||
expect(parseDistance('', 'cm')).toBe(0);
|
||||
expect(parseDistance('NaN', 'm')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatCoordinate', () => {
|
||||
it('formats mm coordinate', () => {
|
||||
expect(formatCoordinate(123.7, 'mm')).toBe('124');
|
||||
});
|
||||
|
||||
it('formats cm coordinate', () => {
|
||||
expect(formatCoordinate(123.7, 'cm')).toBe('12.4');
|
||||
});
|
||||
|
||||
it('formats m coordinate with 3 decimal places', () => {
|
||||
expect(formatCoordinate(1234.5, 'm')).toBe('1.235');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatInputValue', () => {
|
||||
it('formats mm input value without unit suffix', () => {
|
||||
expect(formatInputValue(100, 'mm')).toBe('100');
|
||||
});
|
||||
|
||||
it('formats cm input value', () => {
|
||||
expect(formatInputValue(100, 'cm')).toBe('10.0');
|
||||
});
|
||||
|
||||
it('formats m input value', () => {
|
||||
expect(formatInputValue(1500, 'm')).toBe('1.500');
|
||||
});
|
||||
});
|
||||
|
||||
describe('round-trip: parseDistance to formatDistance', () => {
|
||||
it('round-trips mm values correctly', () => {
|
||||
const input = '500';
|
||||
const world = parseDistance(input, 'mm');
|
||||
const formatted = formatDistance(world, 'mm');
|
||||
expect(formatted).toBe('500 mm');
|
||||
});
|
||||
|
||||
it('round-trips cm values correctly', () => {
|
||||
const input = '10.5';
|
||||
const world = parseDistance(input, 'cm');
|
||||
const formatted = formatDistance(world, 'cm');
|
||||
expect(formatted).toBe('10.5 cm');
|
||||
});
|
||||
|
||||
it('round-trips m values correctly', () => {
|
||||
const input = '2.50';
|
||||
const world = parseDistance(input, 'm');
|
||||
const formatted = formatDistance(world, 'm');
|
||||
expect(formatted).toBe('2.50 m');
|
||||
});
|
||||
|
||||
it('round-trips with scaleFactor', () => {
|
||||
const input = '500';
|
||||
const world = parseDistance(input, 'mm', 10);
|
||||
const formatted = formatDistance(world, 'mm', 10);
|
||||
expect(formatted).toBe('500 mm');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Unit formatting and parsing utilities for CAD distance values.
|
||||
* All internal CAD coordinates are stored in world units.
|
||||
* scaleFactor converts world units to millimeters (1 world unit = scaleFactor mm).
|
||||
*/
|
||||
|
||||
export type UnitType = 'mm' | 'cm' | 'm';
|
||||
|
||||
/**
|
||||
* Format a raw world-unit distance into a human-readable string with the given unit.
|
||||
* @param raw - distance in world units
|
||||
* @param unit - target display unit ('mm' | 'cm' | 'm')
|
||||
* @param scaleFactor - millimeters per world unit (default 1)
|
||||
* @returns formatted string like "500 mm", "5.0 cm", "0.05 m"
|
||||
*/
|
||||
export function formatDistance(
|
||||
raw: number,
|
||||
unit: UnitType,
|
||||
scaleFactor: number = 1,
|
||||
): string {
|
||||
const mm = raw * scaleFactor;
|
||||
switch (unit) {
|
||||
case 'mm':
|
||||
return `${mm.toFixed(0)} mm`;
|
||||
case 'cm':
|
||||
return `${(mm / 10).toFixed(1)} cm`;
|
||||
case 'm':
|
||||
return `${(mm / 1000).toFixed(2)} m`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a user-entered distance string back into world units.
|
||||
* @param input - numeric string (e.g. "500", "5.5", "0.05")
|
||||
* @param unit - the unit the user is entering values in
|
||||
* @param scaleFactor - millimeters per world unit (default 1)
|
||||
* @returns world-unit distance, or 0 if input is invalid
|
||||
*/
|
||||
export function parseDistance(
|
||||
input: string,
|
||||
unit: UnitType,
|
||||
scaleFactor: number = 1,
|
||||
): number {
|
||||
const value = parseFloat(input);
|
||||
if (isNaN(value)) return 0;
|
||||
switch (unit) {
|
||||
case 'mm':
|
||||
return value / scaleFactor;
|
||||
case 'cm':
|
||||
return (value * 10) / scaleFactor;
|
||||
case 'm':
|
||||
return (value * 1000) / scaleFactor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a raw world-unit coordinate value for compact display (e.g. status bar).
|
||||
* Uses fewer decimal places than formatDistance.
|
||||
*/
|
||||
export function formatCoordinate(
|
||||
raw: number,
|
||||
unit: UnitType,
|
||||
scaleFactor: number = 1,
|
||||
): string {
|
||||
const mm = raw * scaleFactor;
|
||||
switch (unit) {
|
||||
case 'mm':
|
||||
return `${mm.toFixed(0)}`;
|
||||
case 'cm':
|
||||
return `${(mm / 10).toFixed(1)}`;
|
||||
case 'm':
|
||||
return `${(mm / 1000).toFixed(3)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a raw world-unit value for an input field (without unit suffix).
|
||||
* This is what the user sees in text inputs so they can edit the numeric value.
|
||||
*/
|
||||
export function formatInputValue(
|
||||
raw: number,
|
||||
unit: UnitType,
|
||||
scaleFactor: number = 1,
|
||||
): string {
|
||||
const mm = raw * scaleFactor;
|
||||
switch (unit) {
|
||||
case 'mm':
|
||||
return `${mm.toFixed(0)}`;
|
||||
case 'cm':
|
||||
return `${(mm / 10).toFixed(1)}`;
|
||||
case 'm':
|
||||
return `${(mm / 1000).toFixed(3)}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user