Skip to content

Commit

Permalink
fix(form-builder): improve inputmode detection for number input and f…
Browse files Browse the repository at this point in the history
…ix tests

Co-authored-by: Raul de Melo <melo.raulf@gmail.com>
Co-authored-by: Bjørge Næss <bjoerge@gmail.com>
  • Loading branch information
bjoerge and raulfdm committed Nov 22, 2022
1 parent 9d75577 commit 38df0fb
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 14 deletions.
14 changes: 14 additions & 0 deletions dev/test-studio/schema/debug/validation.js
Expand Up @@ -144,6 +144,20 @@ export default {
description: 'Only integers',
validation: (Rule) => Rule.integer(),
},
{
name: 'precision',
type: 'number',
title: 'Precision',
description: 'Max precision of 2',
validation: (Rule) => Rule.precision(2),
},
{
name: 'zeroPrecision',
type: 'number',
title: 'Zero precision',
description: 'Max precision of 0',
validation: (Rule) => Rule.precision(0),
},
{
name: 'quotes',
title: 'Quotes',
Expand Down
89 changes: 77 additions & 12 deletions packages/sanity/src/core/form/inputs/NumberInput.test.tsx
@@ -1,34 +1,99 @@
import {defineField} from '@sanity/types'
// eslint-disable-next-line import/no-unassigned-import
import '@testing-library/jest-dom/extend-expect'
import React from 'react'
import {renderNumberInput} from '../../../../test/form'
import {NumberInput} from './NumberInput'

const defs = {
num: defineField({name: 'num', title: 'Number', type: 'number'}),
}

describe('NumberInput', () => {
describe('number-input', () => {
it('renders the number input field', async () => {
const {result} = await renderNumberInput({
fieldDefinition: defs.num,
fieldDefinition: {
name: 'defaultNumber',
title: 'Integer',
type: 'number',
},
render: (inputProps) => <NumberInput {...inputProps} />,
})

const input = result.container.querySelector('input')
expect(input).toBeDefined()
expect(input).toHaveAttribute('type', 'number')
})

it('accepts decimals by default', async () => {
const {result} = await renderNumberInput({
fieldDefinition: defs.num,
fieldDefinition: {
name: 'defaultNumber',
title: 'Integer',
type: 'number',
},
render: (inputProps) => <NumberInput {...inputProps} />,
})

const input = result.container.querySelector('input')

input!.value = '1.2'
expect(input!.value).toBe('1.2')
expect(input!.valueAsNumber).toBe(1.2)
expect(input!.checkValidity()).toBe(true)
})

it('renders inputMode equals text if there is no min rule', async () => {
// Note: we want "text" because devices may or may not show a minus key.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode#values
const {result} = await renderNumberInput({
fieldDefinition: {
name: 'defaultNumber',
title: 'Integer',
type: 'number',
},
render: (inputProps) => <NumberInput {...inputProps} />,
})

const input = result.container.querySelector('input')!
expect(input.inputMode).toBe('text')
})

it('renders inputMode equals "decimal" if there is a min rule', async () => {
const {result} = await renderNumberInput({
fieldDefinition: {
name: 'positiveNumber',
title: 'A positive number',
type: 'number',
validation: (Rule) => Rule.positive(),
},
render: (inputProps) => <NumberInput {...inputProps} />,
})

const input = result.container.querySelector('input')!

expect(input.inputMode).toBe('decimal')
})

it('renders inputMode equals "numeric" if there is a min rule and integer rule', async () => {
const {result} = await renderNumberInput({
fieldDefinition: {
name: 'positiveInteger',
title: 'Integer',
type: 'number',
validation: (Rule) => Rule.integer().positive(),
},
render: (inputProps) => <NumberInput {...inputProps} />,
})

const input = result.container.querySelector('input')!
expect(input.inputMode).toBe('numeric')
})

it('renders inputMode equals "numeric" if there is a min rule and zero precision rule', async () => {
const {result} = await renderNumberInput({
fieldDefinition: {
// should be handled the same way as an integer
name: 'positiveZeroPrecisionNumber',
title: 'Integer',
type: 'number',
validation: (Rule) => Rule.precision(0).positive(),
},
render: (inputProps) => <NumberInput {...inputProps} />,
})

const input = result.container.querySelector('input')!
expect(input.inputMode).toBe('numeric')
})
})
10 changes: 8 additions & 2 deletions packages/sanity/src/core/form/inputs/NumberInput.tsx
Expand Up @@ -12,14 +12,20 @@ export function NumberInput(props: NumberInputProps) {

// Show numpad on mobile if only positive numbers is preferred
const minRule = getValidationRule(schemaType, 'min')
const onlyPositiveNumber = (minRule?.constraint || 0) >= 0
const integerRule = getValidationRule(schemaType, 'integer')
const precisionRule = getValidationRule(schemaType, 'precision')
const onlyPositiveNumber = typeof minRule?.constraint === 'number' && minRule?.constraint >= 0
const onlyIntegers = integerRule || precisionRule?.constraint === 0

// eslint-disable-next-line no-nested-ternary
const inputMode = onlyPositiveNumber ? (onlyIntegers ? 'numeric' : 'decimal') : 'text'

return (
<TextInput
{...elementProps}
type="number"
step="any"
inputMode={onlyPositiveNumber ? 'numeric' : 'text'}
inputMode={inputMode}
customValidity={validationError}
value={value}
placeholder={schemaType.placeholder}
Expand Down

0 comments on commit 38df0fb

Please sign in to comment.