Feet-and-inches entry: type 2' 7\" instead of 2.6 (#59) (#62)
Build image / build-and-push (push) Successful in 6s
Build image / build-and-push (push) Successful in 6s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #62.
This commit is contained in:
+124
-8
@@ -1,13 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
cmFromDisplay,
|
||||
cmFromFtIn,
|
||||
cmFromMeters,
|
||||
cmFromSpacing,
|
||||
dimensionInputMode,
|
||||
displayFromCm,
|
||||
formatCm,
|
||||
formatDimensionInput,
|
||||
formatDimensions,
|
||||
formatSpacing,
|
||||
parseDimension,
|
||||
spacingFromCm,
|
||||
} from './units'
|
||||
|
||||
@@ -34,14 +36,12 @@ describe('cm conversions', () => {
|
||||
expect(cmFromSpacing(1, 'imperial')).toBe(2.54)
|
||||
})
|
||||
|
||||
it('round-trips display → cm → display at display precision', () => {
|
||||
const imperial = [0.5, 1, 2.5, 4, 8, 12.5, 24]
|
||||
for (const ft of imperial) {
|
||||
expect(displayFromCm(cmFromDisplay(ft, 'imperial'), 'imperial')).toBe(ft)
|
||||
it('round-trips typed → cm → displayed at display precision', () => {
|
||||
for (const ft of [0.5, 1, 2.5, 4, 8, 12.5, 24]) {
|
||||
expect(displayFromCm(parseDimension(String(ft), 'imperial')!, 'imperial')).toBe(ft)
|
||||
}
|
||||
const metric = [0.15, 1, 1.22, 3.05, 10, 24.5]
|
||||
for (const m of metric) {
|
||||
expect(displayFromCm(cmFromDisplay(m, 'metric'), 'metric')).toBe(m)
|
||||
for (const m of [0.15, 1, 1.22, 3.05, 10, 24.5]) {
|
||||
expect(displayFromCm(parseDimension(String(m), 'metric')!, 'metric')).toBe(m)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -93,3 +93,119 @@ describe('plant spacing (small scale)', () => {
|
||||
expect(spacingFromCm(30.48, 'imperial')).toBe(12) // an exactly-12in grid reads back as 12
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseDimension (imperial compound entry)', () => {
|
||||
const cm = (inches: number) => inches * 2.54
|
||||
|
||||
it('round-trips the whole-inch marks decimal feet could not represent', () => {
|
||||
// The table from #59: at 0.1 ft granularity only 0", 6" and 12" were exact.
|
||||
for (let inch = 0; inch <= 12; inch++) {
|
||||
const input = `2' ${inch}"`
|
||||
const parsed = parseDimension(input, 'imperial')
|
||||
expect(parsed).toBeCloseTo(cm(24 + inch), 9)
|
||||
expect(formatDimensionInput(parsed!, 'imperial')).toBe(`${inch === 12 ? 3 : 2}′ ${inch === 12 ? 0 : inch}″`)
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts both quote styles and the spelled-out units', () => {
|
||||
const want = cm(31)
|
||||
for (const input of [`2' 7"`, '2′ 7″', '2ft 7in', '2 ft 7 in', "2' 7", '31"', '31 inches', '2FT 7IN']) {
|
||||
expect(parseDimension(input, 'imperial')).toBeCloseTo(want, 9)
|
||||
}
|
||||
})
|
||||
|
||||
it('still reads a bare number as feet', () => {
|
||||
expect(parseDimension('2.5', 'imperial')).toBeCloseTo(cm(30), 9)
|
||||
expect(parseDimension('24', 'imperial')).toBeCloseTo(cm(288), 9)
|
||||
})
|
||||
|
||||
it('reads feet alone', () => {
|
||||
expect(parseDimension("2'", 'imperial')).toBeCloseTo(cm(24), 9)
|
||||
expect(parseDimension('2ft', 'imperial')).toBeCloseTo(cm(24), 9)
|
||||
})
|
||||
|
||||
// The bug this issue would otherwise produce: attaching the sign to the feet
|
||||
// term alone gives -2ft + 6in = -18", not -30".
|
||||
it('applies the sign to the whole value, not just the feet', () => {
|
||||
expect(parseDimension(`-2' 6"`, 'imperial')).toBeCloseTo(cm(-30), 9)
|
||||
expect(parseDimension('−2′ 6″', 'imperial')).toBeCloseTo(cm(-30), 9) // unicode minus
|
||||
expect(parseDimension('-31"', 'imperial')).toBeCloseTo(cm(-31), 9)
|
||||
expect(parseDimension('-2', 'imperial')).toBeCloseTo(cm(-24), 9)
|
||||
})
|
||||
|
||||
it('rejects garbage rather than committing a zero', () => {
|
||||
for (const input of ['', ' ', 'abc', `'`, `"`, `2' 7" 3`, '2 3', 'ft', '--2', '2..5']) {
|
||||
expect(parseDimension(input, 'imperial')).toBeNull()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseDimension (metric)', () => {
|
||||
it('takes decimal meters, with or without the unit', () => {
|
||||
expect(parseDimension('1.22', 'metric')).toBe(122)
|
||||
expect(parseDimension('1.22m', 'metric')).toBe(122)
|
||||
expect(parseDimension('10 m', 'metric')).toBe(1000)
|
||||
expect(parseDimension('-3', 'metric')).toBe(-300)
|
||||
})
|
||||
|
||||
it('does not accept feet-and-inches syntax', () => {
|
||||
expect(parseDimension(`2' 7"`, 'metric')).toBeNull()
|
||||
expect(parseDimension('31"', 'metric')).toBeNull()
|
||||
})
|
||||
|
||||
it('rejects garbage', () => {
|
||||
expect(parseDimension('', 'metric')).toBeNull()
|
||||
expect(parseDimension('abc', 'metric')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatDimensionInput', () => {
|
||||
it('renders imperial as feet and inches, carrying 12″ up', () => {
|
||||
expect(formatDimensionInput(0, 'imperial')).toBe('0′ 0″')
|
||||
expect(formatDimensionInput(30.48, 'imperial')).toBe('1′ 0″')
|
||||
expect(formatDimensionInput(78.74, 'imperial')).toBe('2′ 7″')
|
||||
expect(formatDimensionInput(-76.2, 'imperial')).toBe('-2′ 6″')
|
||||
})
|
||||
|
||||
it('keeps one decimal inch, so a dragged position reads honestly', () => {
|
||||
// A bed dragged to an arbitrary cm must not render as a whole inch — that's
|
||||
// what would let a mere blur snap it.
|
||||
expect(formatDimensionInput(80.90185676392574, 'imperial')).toBe('2′ 7.9″')
|
||||
})
|
||||
|
||||
it('is the exact string parseDimension round-trips', () => {
|
||||
for (const value of [0, 30.48, 78.74, 80.9, -76.2, 731.52]) {
|
||||
const text = formatDimensionInput(value, 'imperial')
|
||||
const back = parseDimension(text, 'imperial')
|
||||
expect(formatDimensionInput(back!, 'imperial')).toBe(text)
|
||||
}
|
||||
})
|
||||
|
||||
it('leaves metric as decimal meters', () => {
|
||||
expect(formatDimensionInput(122, 'metric')).toBe('1.22')
|
||||
expect(formatDimensionInput(1000, 'metric')).toBe('10')
|
||||
})
|
||||
})
|
||||
|
||||
describe('negative values that round to zero', () => {
|
||||
// A hair below zero is 0′ 0″. "-0′ 0″" would be both wrong and, since
|
||||
// parseDimension gives -0, not something that round-trips.
|
||||
it('does not sign a value that rounded to nothing', () => {
|
||||
expect(formatDimensionInput(-0.1, 'imperial')).toBe('0′ 0″')
|
||||
expect(formatDimensionInput(-0, 'imperial')).toBe('0′ 0″')
|
||||
expect(formatCm(-0.1, 'imperial')).toBe('0′ 0″')
|
||||
})
|
||||
|
||||
it('still signs a value that rounded to something', () => {
|
||||
expect(formatDimensionInput(-1, 'imperial')).toBe('-0′ 0.4″')
|
||||
expect(formatDimensionInput(-76.2, 'imperial')).toBe('-2′ 6″')
|
||||
expect(formatCm(-76.2, 'imperial')).toBe('-2′ 6″')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dimensionInputMode', () => {
|
||||
it("gives imperial the full keyboard, since ' and \" are on no keypad", () => {
|
||||
expect(dimensionInputMode('imperial')).toBe('text')
|
||||
expect(dimensionInputMode('metric')).toBe('decimal')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user