Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/Array/additional.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {render, screen} from '@testing-library/react'
import Field from '../Field'
import Form from '../Form'
import ArrayField from './index'
import {FieldProps} from '../types'
import '@testing-library/jest-dom'

jest.useFakeTimers()

function DummyInput(props: FieldProps) {
return (
<input
value={props.value || ''}
onChange={e => props.onChange(e.target.value)}
/>
)
}

test('autoAddItem renders an empty item when value is empty', () => {
const {container} = render(
<Form>
<Field fieldName="items" type={ArrayField} autoAddItem>
<Field fieldName="name" type={DummyInput} />
</Field>
</Form>,
)
expect(container.querySelectorAll('input')).toHaveLength(1)
})

test('renderProps provides the current index', () => {
render(
<Form state={{items: [{}]}}>
<Field fieldName="items" type={ArrayField} renderProps>
{index => <div data-testid={`item-${index}`}>Item {index}</div>}
</Field>
</Form>,
)
expect(screen.getByTestId('item-0')).toBeInTheDocument()
})

test('renderItem customizes each item element', () => {
render(
<Form state={{items: [{name: 'A'}]}}>
<Field
fieldName="items"
type={ArrayField}
renderItem={(item, i) => <div data-testid={`custom-${i}`}>{item.name}</div>}
/>
</Form>,
)
expect(screen.getByTestId('custom-0')).toHaveTextContent('A')
})

test('honors showAddButton and custom addLabel', () => {
const {container} = render(
<Form>
<Field fieldName="items" type={ArrayField} showAddButton={false} addLabel="Plus">
<Field fieldName="name" type={DummyInput} />
</Field>
</Form>,
)
expect(screen.queryByText('Plus')).not.toBeInTheDocument()
expect(container.querySelector('.srf_addButton')).not.toBeInTheDocument()
})

test('honors showRemoveButton and custom removeLabel', () => {
const {container} = render(
<Form state={{items: [{}]}}>
<Field
fieldName="items"
type={ArrayField}
showRemoveButton={false}
removeLabel="Del"
>
<Field fieldName="name" type={DummyInput} />
</Field>
</Form>,
)
expect(screen.queryByText('Del')).not.toBeInTheDocument()
expect(container.querySelector('.srf_removeButton')).not.toBeInTheDocument()
})
17 changes: 17 additions & 0 deletions src/Form/getNewValue.additional.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import getNewValue from './getNewValue'

test('supports updater functions', () => {
const original = {counter: 1}
const result = getNewValue(original, 'counter', n => (n || 0) + 1)
expect(result).toEqual({counter: 2})
expect(original).toEqual({counter: 1})
})

test('creates nested objects when missing', () => {
const result = getNewValue({}, 'person.name.first', 'John')
expect(result).toEqual({person: {name: {first: 'John'}}})
})

test('throws if path expects object but finds primitive', () => {
expect(() => getNewValue({person: 5}, 'person.name', 'John')).toThrow('Expected plain object for key person')
})
35 changes: 35 additions & 0 deletions src/Form/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,38 @@ test('should allow to reset state', () => {

expect(form.getValue()).toEqual({name: 'Nico'})
})

test('does not render form tag in React Native environment', () => {
const original: any = (global as any).navigator
Object.defineProperty(global, 'navigator', {
value: {product: 'ReactNative'},
configurable: true,
enumerable: true,
writable: true,
})

const {container} = render(
<Form>
<Field fieldName="name" type={DummyInput} />
</Form>,
)

expect(container.querySelector('form')).not.toBeInTheDocument()

Object.defineProperty(global, 'navigator', {
value: original,
configurable: true,
enumerable: true,
writable: true,
})
})

test('passes dom props to the form element', () => {
const {container} = render(
<Form target="/submit-here">
<Field fieldName="name" type={DummyInput} />
</Form>,
)

expect(container.querySelector('form').getAttribute('target')).toBe('/submit-here')
})
28 changes: 27 additions & 1 deletion src/WithValue/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {render, screen} from '@testing-library/react'
import {render, screen, fireEvent, act} from '@testing-library/react'
import React from 'react'
import Field from '../Field'
import Form from '../Form'
Expand All @@ -7,6 +7,8 @@ import {FieldProps} from '../types'
import WithValue from './index'
import '@testing-library/jest-dom'

jest.useFakeTimers()

class DummyInput extends React.Component<FieldProps> {
render() {
return (
Expand Down Expand Up @@ -59,3 +61,27 @@ it('should pass the value of the form on sub object data', () => {
const content = screen.getByText('Nicolás')
expect(content.innerHTML).toBe('Nicolás')
})

test('updates the render prop when the form value changes', () => {
function Test() {
const [state, setState] = React.useState({name: ''})
return (
<Form state={state} onChange={setState}>
<WithValue>{value => <div data-testid="val">{value.name}</div>}</WithValue>
<Field fieldName="name" type={DummyInput} />
</Form>
)
}

const {container} = render(<Test />)

expect(screen.getByTestId('val')).toHaveTextContent('')

fireEvent.change(container.querySelector('input'), {target: {value: 'John'}})

act(() => {
jest.advanceTimersByTime(0)
})

expect(screen.getByTestId('val')).toHaveTextContent('John')
})
36 changes: 36 additions & 0 deletions src/useValue.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {render, screen, fireEvent} from '@testing-library/react'
import React from 'react'
import {ValueContext} from './Contexts'
import useValue from './useValue'
import '@testing-library/jest-dom'

function DisplayValue() {
const value = useValue()
return <div data-testid="value">{String(value)}</div>
}

test('useValue returns the current context value', () => {
render(
<ValueContext.Provider value="context-value">
<DisplayValue />
</ValueContext.Provider>,
)
expect(screen.getByTestId('value')).toHaveTextContent('context-value')
})

test('useValue updates when the context value changes', () => {
function Wrapper() {
const [val, setVal] = React.useState('first')
return (
<ValueContext.Provider value={val}>
<button onClick={() => setVal('second')}>change</button>
<DisplayValue />
</ValueContext.Provider>
)
}

render(<Wrapper />)
expect(screen.getByTestId('value')).toHaveTextContent('first')
fireEvent.click(screen.getByText('change'))
expect(screen.getByTestId('value')).toHaveTextContent('second')
})
39 changes: 39 additions & 0 deletions src/utility/isReactNative.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import isReactNative from './isReactNative'

test('returns false in default browser environment', () => {
expect(isReactNative()).toBe(false)
})

test('detects React Native when navigator.product is ReactNative', () => {
const original: any = (global as any).navigator
Object.defineProperty(global, 'navigator', {
value: {product: 'ReactNative'},
configurable: true,
enumerable: true,
writable: true,
})
expect(isReactNative()).toBe(true)
Object.defineProperty(global, 'navigator', {
value: original,
configurable: true,
enumerable: true,
writable: true,
})
})

test('returns false when navigator is undefined', () => {
const original: any = (global as any).navigator
Object.defineProperty(global, 'navigator', {
value: undefined,
configurable: true,
enumerable: true,
writable: true,
})
expect(isReactNative()).toBe(false)
Object.defineProperty(global, 'navigator', {
value: original,
configurable: true,
enumerable: true,
writable: true,
})
})