Design system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

264 lines
5.9 KiB

  1. import * as React from 'react';
  2. import {
  3. cleanup,
  4. render,
  5. screen,
  6. } from '@testing-library/react';
  7. import userEvent from '@testing-library/user-event';
  8. import { TextControl } from '@tesseract-design/web-base';
  9. import {
  10. vi,
  11. expect,
  12. describe,
  13. it,
  14. afterEach,
  15. } from 'vitest';
  16. import matchers from '@testing-library/jest-dom/matchers';
  17. import {
  18. TextInput,
  19. TextInputDerivedElement,
  20. } from '.';
  21. expect.extend(matchers);
  22. describe('TextInput', () => {
  23. afterEach(() => {
  24. cleanup();
  25. });
  26. it('renders a textbox', () => {
  27. render(
  28. <TextInput />,
  29. );
  30. const textbox = screen.getByRole('textbox');
  31. expect(textbox).toBeInTheDocument();
  32. expect(textbox).toHaveProperty('type', 'text');
  33. });
  34. it('renders a border', () => {
  35. render(
  36. <TextInput
  37. border
  38. />,
  39. );
  40. const border = screen.getByTestId('border');
  41. expect(border).toBeInTheDocument();
  42. });
  43. it('renders a label', () => {
  44. render(
  45. <TextInput
  46. label="foo"
  47. />,
  48. );
  49. const textbox = screen.getByLabelText('foo');
  50. expect(textbox).toBeInTheDocument();
  51. const label = screen.getByTestId('label');
  52. expect(label).toHaveTextContent('foo');
  53. });
  54. it('renders a hidden label', () => {
  55. render(
  56. <TextInput
  57. label="foo"
  58. hiddenLabel
  59. />,
  60. );
  61. const textbox = screen.getByLabelText('foo');
  62. expect(textbox).toBeInTheDocument();
  63. const label = screen.queryByTestId('label');
  64. expect(label).toBeInTheDocument();
  65. expect(label).toHaveClass('sr-only');
  66. });
  67. it('renders a hint', () => {
  68. render(
  69. <TextInput
  70. hint="foo"
  71. />,
  72. );
  73. const hint = screen.getByTestId('hint');
  74. expect(hint).toBeInTheDocument();
  75. });
  76. it('renders an indicator', () => {
  77. render(
  78. <TextInput
  79. indicator={
  80. <div />
  81. }
  82. />,
  83. );
  84. const indicator = screen.getByTestId('indicator');
  85. expect(indicator).toBeInTheDocument();
  86. });
  87. describe.each`
  88. size | inputClassName | hintClassName | indicatorClassName
  89. ${'small'} | ${'h-10'} | ${'pr-10'} | ${'w-10'}
  90. ${'medium'} | ${'h-12'} | ${'pr-12'} | ${'w-12'}
  91. ${'large'} | ${'h-16'} | ${'pr-16'} | ${'w-16'}
  92. `('on $size size', ({
  93. size,
  94. inputClassName,
  95. hintClassName,
  96. indicatorClassName,
  97. }: {
  98. size: TextControl.Size,
  99. inputClassName: string,
  100. hintClassName: string,
  101. indicatorClassName: string,
  102. }) => {
  103. it('renders input styles', () => {
  104. render(
  105. <TextInput
  106. size={size}
  107. />,
  108. );
  109. const input = screen.getByTestId('input');
  110. expect(input).toHaveClass(inputClassName);
  111. });
  112. it('renders label styles with indicator', () => {
  113. render(
  114. <TextInput
  115. size={size}
  116. label="foo"
  117. indicator={<div />}
  118. />,
  119. );
  120. const label = screen.getByTestId('label');
  121. expect(label).toHaveClass(hintClassName);
  122. });
  123. it('renders hint styles when indicator is present', () => {
  124. render(
  125. <TextInput
  126. size={size}
  127. hint="hint"
  128. indicator={<div />}
  129. />,
  130. );
  131. const hint = screen.getByTestId('hint');
  132. expect(hint).toHaveClass(hintClassName);
  133. });
  134. it('renders indicator styles', () => {
  135. render(
  136. <TextInput
  137. size={size}
  138. indicator={
  139. <div />
  140. }
  141. />,
  142. );
  143. const indicator = screen.getByTestId('indicator');
  144. expect(indicator).toHaveClass(indicatorClassName);
  145. });
  146. });
  147. it('renders a block textbox', () => {
  148. render(
  149. <TextInput
  150. block
  151. />,
  152. );
  153. const base = screen.getByTestId('base');
  154. expect(base).toHaveClass('block');
  155. });
  156. it.each(TextControl.AVAILABLE_INPUT_TYPES)('renders a textbox with type %s', (inputType) => {
  157. render(
  158. <TextInput
  159. type={inputType}
  160. />,
  161. );
  162. const textbox = screen.getByTestId('input');
  163. expect(textbox).toHaveProperty('type', inputType);
  164. });
  165. it('falls back to text input mode when it clashes with the input type', () => {
  166. render(
  167. <TextInput
  168. type="text"
  169. inputMode="search"
  170. />,
  171. );
  172. const textbox = screen.getByTestId('input');
  173. expect(textbox).toHaveProperty('inputMode', 'text');
  174. });
  175. describe.each`
  176. variant | inputClassName | hintClassName
  177. ${'default'} | ${'pl-4'} | ${'bottom-0 pl-4 pb-1'}
  178. ${'alternate'} | ${'pl-1.5 pt-4'} | ${'top-0.5'}
  179. `('on $variant style', ({
  180. variant,
  181. inputClassName,
  182. hintClassName,
  183. }: {
  184. variant: TextControl.Variant,
  185. inputClassName: string,
  186. hintClassName: string,
  187. }) => {
  188. it('renders input styles', () => {
  189. render(
  190. <TextInput
  191. variant={variant}
  192. />,
  193. );
  194. const input = screen.getByTestId('input');
  195. expect(input).toHaveClass(inputClassName);
  196. });
  197. it('renders hint styles', () => {
  198. render(
  199. <TextInput
  200. variant={variant}
  201. hint="hint"
  202. />,
  203. );
  204. const hint = screen.getByTestId('hint');
  205. expect(hint).toHaveClass(hintClassName);
  206. });
  207. });
  208. it('handles change events', async () => {
  209. const onChange = vi.fn().mockImplementationOnce(
  210. (e: React.ChangeEvent<TextInputDerivedElement>) => {
  211. e.preventDefault();
  212. },
  213. );
  214. render(
  215. <TextInput
  216. onChange={onChange}
  217. />,
  218. );
  219. const textbox: HTMLInputElement = screen.getByRole('textbox');
  220. await userEvent.type(textbox, 'foobar');
  221. expect(onChange).toBeCalled();
  222. });
  223. it('handles input events', async () => {
  224. const onInput = vi.fn().mockImplementationOnce(
  225. (e: React.SyntheticEvent<TextInputDerivedElement>) => {
  226. e.preventDefault();
  227. },
  228. );
  229. render(
  230. <TextInput
  231. onInput={onInput}
  232. />,
  233. );
  234. const textbox: HTMLInputElement = screen.getByTestId('input');
  235. await userEvent.type(textbox, 'foobar');
  236. expect(onInput).toBeCalled();
  237. });
  238. });