import { render, act } from '@toptal/picasso-test-utils'
import type { Ref } from 'react'
import React, { createRef, useEffect } from 'react'
import type { ReferenceObject } from './index'
import {
capitalize,
getNameInitials,
isBoolean,
isNumber,
isString,
isSubstring,
toTitleCase,
kebabToCamelCase,
useCombinedRefs,
useWidthOf,
useSafeState,
forwardRef,
documentable,
disableUnsupportedProps,
sum,
getReactNodeTextContent,
isBrowser,
} from './index'
import unsafeErrorLog from './unsafe-error-log'
jest.mock('./unsafe-error-log')
describe('capitalize', () => {
it('should capitalize first letter', () => {
const string = capitalize('test string')
expect(string).toBe('Test string')
})
})
describe('getNameInitials', () => {
it('should extract first letters', () => {
expect(getNameInitials('John Doe')).toBe('JD')
})
it('should ignore extra spaces', () => {
expect(getNameInitials(' John Doe ')).toBe('JD')
})
it('should ignore single letter middle names', () => {
expect(getNameInitials('John T Doe')).toBe('JD')
})
it('should extract up to 3 letters', () => {
expect(getNameInitials('John Doe John Doe')).toBe('JDJ')
})
})
describe('isBoolean', () => {
it('should return true for booleans', () => {
expect(isBoolean(true)).toBe(true)
expect(isBoolean(false)).toBe(true)
})
it('should return false for other types', () => {
expect(isBoolean(1)).toBe(false)
expect(isBoolean('1')).toBe(false)
expect(isBoolean({})).toBe(false)
expect(isBoolean(null)).toBe(false)
expect(isBoolean(undefined)).toBe(false)
})
})
describe('isNumber', () => {
it('should return true for numbers', () => {
expect(isNumber(1)).toBe(true)
})
it('should return false for other types', () => {
expect(isNumber(true)).toBe(false)
expect(isNumber('1')).toBe(false)
expect(isNumber({})).toBe(false)
expect(isNumber(null)).toBe(false)
expect(isNumber(undefined)).toBe(false)
})
})
describe('isString', () => {
it('should return true for strings', () => {
expect(isString('')).toBe(true)
expect(isString('1')).toBe(true)
})
it('should return false for other types', () => {
expect(isString(true)).toBe(false)
expect(isString(1)).toBe(false)
expect(isString({})).toBe(false)
expect(isString(null)).toBe(false)
expect(isString(undefined)).toBe(false)
})
})
describe('toTitleCase', () => {
it('should convert strings', () => {
expect(toTitleCase('ab bc')).toBe('Ab Bc')
})
it('should ignore react nodes', () => {
const node =
ab bc
expect(toTitleCase(node)).toBe(node)
})
})
describe('isSubstring', () => {
it('should check if a string contains another ignoring case', () => {
expect(isSubstring('TEST', 'a test string')).toBe(true)
expect(isSubstring('test', 'a test string')).toBe(true)
expect(isSubstring('test word', 'a test string')).toBe(false)
})
})
describe('kebabToCamelCase', () => {
it('should convert kebab to camel case', () => {
expect(kebabToCamelCase('a-test-string')).toBe('aTestString')
})
})
describe('sum', () => {
it('returns the total of all numbers in an array', () => {
expect(sum([0, 1, 2, 3])).toBe(6)
})
})
const TestUseCombinedRefs = ({ refs }: { refs: Ref[] }) => {
return
}
describe('useCombinedRefs', () => {
it('should combine object and function refs', async () => {
const refObject = createRef()
const refFunction = jest.fn()
render()
expect(refObject.current).toBeDefined()
expect(refFunction.mock.calls[0][0]).toBeDefined()
})
})
const TestForwardRef = documentable(
forwardRef(
(
{ item }: { ref: Ref; item: T },
ref: Ref
) => {
return {item}
}
)
)
describe('forwardRef', () => {
it('should forward a ref with generic component', () => {
const ref = createRef()
render()
expect(ref.current).toBeDefined()
expect(ref.current?.tagName).toBe('DIV')
})
})
const TestUseSafeState = () => {
const [state, setState] = useSafeState('initial')
useEffect(() => {
setTimeout(() => setState('changed'), 100)
}, [setState])
return {state}
}
describe('useSafeState', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('should use initial state', () => {
const { queryByText } = render()
expect(queryByText('initial')).toBeInTheDocument()
})
it('should set state in an async effect', () => {
const { queryByText } = render()
act(() => {
jest.runAllTimers()
})
expect(queryByText('changed')).toBeInTheDocument()
})
it('should not throw when state is set after unmounting', () => {
const { unmount } = render()
unmount()
expect(() =>
act(() => {
jest.runAllTimers()
})
).not.toThrow()
})
})
const TestUseWidthOf = ({ element }: { element: ReferenceObject }) => {
const width = useWidthOf(element)
return {width}
}
describe('useWidthOf', () => {
it('should measure width of passed element', () => {
const rect = {
top: 10,
left: 10,
right: 110,
bottom: 30,
width: 100,
height: 20,
}
const element = {
getBoundingClientRect: () => rect,
} as ReferenceObject
const { queryByText } = render()
const message = queryByText('100px')
expect(message).toBeInTheDocument()
})
})
const TestDisableUnsupportedProps = (props: {
type: string
max?: number | string
}) => {
const { type, max } = disableUnsupportedProps(
'TestDisableUnsupportedProps',
props,
{
featureProps: {
type: 'text',
},
unsupportedProps: {
max: '',
},
}
)
return
}
describe('disableUnsupportedProps', () => {
it('should render with supported props', () => {
const { getByRole } = render(
)
const input = getByRole('spinbutton')
expect(input).toHaveProperty('type', 'number')
expect(input).toHaveProperty('max', '2')
expect(unsafeErrorLog).not.toHaveBeenCalled()
})
it('should override unsupported props and warn the developer', () => {
const { getByRole } = render(
)
const input = getByRole('textbox')
expect(input).toHaveProperty('type', 'text')
expect(input).toHaveProperty('max', '')
expect(unsafeErrorLog).toHaveBeenCalledWith(
'TestDisableUnsupportedProps doesn\'t support: max props when used with {"type":"text"}'
)
})
})
describe('isBrowser', () => {
let windowSpy: any
beforeEach(() => {
windowSpy = jest.spyOn(window, 'window', 'get')
})
afterEach(() => {
windowSpy?.mockRestore()
})
it('should return true if window is undefined', () => {
windowSpy?.mockImplementation(() => undefined)
expect(isBrowser()).toBe(false)
})
it('should return true for window is not undefined', () => {
windowSpy.mockImplementation(() => ({}))
expect(isBrowser()).toBe(true)
})
})
describe('getRectNodeTextContent', () => {
describe('when getting text from string node', () => {
it.each(['foo', ''])(
"returns its content original content, value: '%s'",
txt => {
expect(getReactNodeTextContent(txt)).toBe(txt)
}
)
it('strips spaces from start and end of the text', () => {
expect(getReactNodeTextContent(' foo ')).toBe('foo')
})
})
describe('when getting text from a number node', () => {
it.each([42, -12, Infinity, NaN, -0])(
'returns its content original content, value: %s',
num => {
expect(getReactNodeTextContent(num)).toBe(String(num))
}
)
})
describe('when getting text from a array node', () => {
it('returns the contents of its elements joined by space', () => {
expect(getReactNodeTextContent(['foo', bar
, 45])).toBe(
'foo bar 45'
)
})
})
describe('when getting text from non printable nodes in react', () => {
it.each([null, undefined, true, false])(
'returns an empty string, value: "%s"',
nonOp => {
expect(getReactNodeTextContent(nonOp)).toBe('')
}
)
})
describe('when getting text from a complex node', () => {
it("returns it's children content recursively", () => {
expect(
getReactNodeTextContent(
Title
Subtitle
Amet quidem quod doloribus dignissimos
)
).toBe('Title Subtitle Amet quidem quod doloribus dignissimos')
})
})
})