input-number.test.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import { nextTick, ref } from 'vue'
  2. import { mount } from '@vue/test-utils'
  3. import { describe, expect, it, test, vi } from 'vitest'
  4. import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
  5. import { ElFormItem } from '@element-plus/components/form'
  6. import InputNumber from '../src/input-number.vue'
  7. const mouseup = new Event('mouseup')
  8. describe('InputNumber.vue', () => {
  9. test('create', async () => {
  10. const num = ref(1)
  11. const wrapper = mount(() => (
  12. <InputNumber label="描述文字" v-model={num.value} />
  13. ))
  14. expect(wrapper.find('input').exists()).toBe(true)
  15. })
  16. test('modelValue', () => {
  17. const inputText = ref(1)
  18. const wrapper = mount(() => <InputNumber modelValue={inputText.value} />)
  19. expect(wrapper.find('input').element.value).toEqual('1')
  20. })
  21. test('set modelValue undefined to form validate', async () => {
  22. const num = ref(undefined)
  23. const wrapper = mount(() => (
  24. <>
  25. <InputNumber modelValue={num.value} placeholder="input number" />
  26. <p>{num.value}</p>
  27. </>
  28. ))
  29. await nextTick()
  30. expect(wrapper.find('p').element.innerText).toBeUndefined()
  31. })
  32. test('set modelValue undefined to display placeholder', async () => {
  33. const inputText = ref<number | undefined>(1)
  34. const wrapper = mount(() => (
  35. <InputNumber modelValue={inputText.value} placeholder="input number" />
  36. ))
  37. expect(wrapper.find('input').element.value).toEqual('1')
  38. inputText.value = undefined
  39. await nextTick()
  40. expect(wrapper.find('input').element.value).toEqual('')
  41. expect(wrapper.find('input').element.getAttribute('aria-valuenow')).toEqual(
  42. 'null'
  43. )
  44. })
  45. // fix: #10328
  46. test('Make sure the input action can trigger the modelValue update', async () => {
  47. const num = ref<number>(0)
  48. const handleUpdate = (data: number | undefined) => {
  49. num.value = data!
  50. }
  51. const wrapper = mount(() => (
  52. <InputNumber modelValue={num.value} onUpdate:modelValue={handleUpdate} />
  53. ))
  54. const el = wrapper.find('input').element
  55. const simulateEvent = (text: string, event: string) => {
  56. el.value = text
  57. el.dispatchEvent(new Event(event))
  58. }
  59. simulateEvent('3', 'input')
  60. await nextTick()
  61. expect(num.value).toEqual(3)
  62. })
  63. test('min', async () => {
  64. const num = ref(1)
  65. const wrapper = mount(() => <InputNumber min={3} v-model={num.value} />)
  66. expect(wrapper.find('input').element.value).toEqual('3')
  67. wrapper.find('.el-input-number__decrease').trigger('mousedown')
  68. document.dispatchEvent(mouseup)
  69. await nextTick()
  70. expect(wrapper.find('input').element.value).toEqual('3')
  71. })
  72. test('max', async () => {
  73. const num = ref(5)
  74. const wrapper = mount(() => <InputNumber max={3} v-model={num.value} />)
  75. expect(wrapper.find('input').element.value).toEqual('3')
  76. wrapper.find('.el-input-number__increase').trigger('mousedown')
  77. document.dispatchEvent(mouseup)
  78. await nextTick()
  79. expect(wrapper.find('input').element.value).toEqual('3')
  80. })
  81. test('step, increase and decrease', async () => {
  82. const num = ref(0)
  83. const wrapper = mount(() => <InputNumber v-model={num.value} step={2} />)
  84. wrapper.find('.el-input-number__decrease').trigger('mousedown')
  85. document.dispatchEvent(mouseup)
  86. await nextTick()
  87. expect(wrapper.find('input').element.value).toEqual('-2')
  88. wrapper.find('.el-input-number__increase').trigger('mousedown')
  89. document.dispatchEvent(mouseup)
  90. await nextTick()
  91. expect(wrapper.find('input').element.value).toEqual('0')
  92. wrapper.find('.el-input-number__increase').trigger('mousedown')
  93. document.dispatchEvent(mouseup)
  94. await nextTick()
  95. expect(wrapper.find('input').element.value).toEqual('2')
  96. })
  97. test('step-strictly', async () => {
  98. const num = ref(0)
  99. const wrapper = mount(() => (
  100. <InputNumber step-strictly={true} step={2} v-model={num.value} />
  101. ))
  102. await wrapper.find('input').setValue(3)
  103. expect(wrapper.find('input').element.value).toEqual('4')
  104. })
  105. test('value decimals miss prop precision', async () => {
  106. const num = ref(0.2)
  107. const wrapper = mount(() => (
  108. <InputNumber step-strictly={true} step={0.1} v-model={num.value} />
  109. ))
  110. const elInputNumber = wrapper.findComponent({ name: 'ElInputNumber' }).vm
  111. elInputNumber.increase()
  112. await nextTick()
  113. expect(wrapper.find('input').element.value).toEqual('0.3')
  114. num.value = 0.4
  115. await nextTick()
  116. elInputNumber.decrease()
  117. await nextTick()
  118. expect(wrapper.find('input').element.value).toEqual('0.3')
  119. })
  120. describe('precision accuracy 2', () => {
  121. const num = ref(0)
  122. const wrapper = mount(() => (
  123. <InputNumber precision={2} v-model={num.value} />
  124. ))
  125. it.each([
  126. [1.1111111111, '1.11'],
  127. [17.275, '17.28'],
  128. [17.2745, '17.27'],
  129. [1.09, '1.09'],
  130. [1.009, '1.01'],
  131. [10.999, '11.00'],
  132. [15, '15.00'],
  133. [15.5, '15.50'],
  134. [15.555, '15.56'],
  135. ])(
  136. 'each precision accuracy test: $input $output',
  137. async (input, output) => {
  138. await wrapper.find('input').setValue(input)
  139. expect(wrapper.find('input').element.value).toEqual(`${output}`)
  140. }
  141. )
  142. })
  143. describe('precision accuracy 3', () => {
  144. const num = ref(0)
  145. const wrapper = mount(() => (
  146. <InputNumber precision={3} v-model={num.value} />
  147. ))
  148. it.each([
  149. [1.1111111111, '1.111'],
  150. [17.275, '17.275'],
  151. [17.2745, '17.275'],
  152. [1.09, '1.090'],
  153. [10.999, '10.999'],
  154. [10.9999, '11.000'],
  155. [15.555, '15.555'],
  156. [1.3335, '1.334'],
  157. ])(
  158. 'each precision accuracy test: $input $output',
  159. async (input, output) => {
  160. await wrapper.find('input').setValue(input)
  161. expect(wrapper.find('input').element.value).toEqual(`${output}`)
  162. }
  163. )
  164. })
  165. test('readonly', async () => {
  166. const num = ref(0)
  167. const handleFocus = vi.fn()
  168. const wrapper = mount(() => (
  169. <InputNumber readonly v-model={num.value} onFocus={handleFocus} />
  170. ))
  171. wrapper.find('.el-input__inner').trigger('focus')
  172. await nextTick()
  173. expect(handleFocus).toHaveBeenCalledTimes(1)
  174. wrapper.find('.el-input-number__decrease').trigger('mousedown')
  175. document.dispatchEvent(mouseup)
  176. await nextTick()
  177. expect(wrapper.find('input').element.value).toEqual('0')
  178. wrapper.find('.el-input-number__increase').trigger('mousedown')
  179. document.dispatchEvent(mouseup)
  180. await nextTick()
  181. expect(wrapper.find('input').element.value).toEqual('0')
  182. })
  183. test('disabled', async () => {
  184. const num = ref(0)
  185. const wrapper = mount(() => (
  186. <InputNumber disabled={true} v-model={num.value} />
  187. ))
  188. wrapper.find('.el-input-number__decrease').trigger('mousedown')
  189. document.dispatchEvent(mouseup)
  190. await nextTick()
  191. expect(wrapper.find('input').element.value).toEqual('0')
  192. wrapper.find('.el-input-number__increase').trigger('mousedown')
  193. document.dispatchEvent(mouseup)
  194. await nextTick()
  195. expect(wrapper.find('input').element.value).toEqual('0')
  196. })
  197. test('controls', async () => {
  198. const num = ref(0)
  199. const wrapper = mount(() => (
  200. <InputNumber controls={false} v-model={num.value} />
  201. ))
  202. expect(wrapper.find('.el-input-number__increase').exists()).toBe(false)
  203. expect(wrapper.find('.el-input-number__decrease').exists()).toBe(false)
  204. })
  205. test('controls-position', async () => {
  206. const num = ref(0)
  207. const wrapper = mount(() => (
  208. <InputNumber controls-position="right" v-model={num.value} />
  209. ))
  210. expect(wrapper.findComponent(ArrowDown).exists()).toBe(true)
  211. expect(wrapper.findComponent(ArrowUp).exists()).toBe(true)
  212. })
  213. test('input-event', async () => {
  214. const handleInput = vi.fn()
  215. const num = ref(0)
  216. const wrapper = mount(() => (
  217. <InputNumber v-model={num.value} onInput={handleInput} />
  218. ))
  219. const inputWrapper = wrapper.find('input')
  220. const nativeInput = inputWrapper.element
  221. nativeInput.value = '0'
  222. await inputWrapper.trigger('input')
  223. expect(handleInput).toBeCalledTimes(0)
  224. nativeInput.value = '1'
  225. await inputWrapper.trigger('input')
  226. expect(handleInput).toBeCalledTimes(1)
  227. expect(handleInput).toHaveBeenCalledWith(1)
  228. nativeInput.value = '2'
  229. await inputWrapper.trigger('input')
  230. expect(handleInput).toBeCalledTimes(2)
  231. expect(handleInput).toHaveBeenCalledWith(2)
  232. nativeInput.value = ''
  233. await inputWrapper.trigger('input')
  234. expect(handleInput).toBeCalledTimes(3)
  235. expect(handleInput).toHaveBeenCalledWith(null)
  236. })
  237. test('change-event', async () => {
  238. const num = ref(0)
  239. const wrapper = mount(() => <InputNumber v-model={num.value} />)
  240. wrapper.find('.el-input-number__increase').trigger('mousedown')
  241. document.dispatchEvent(mouseup)
  242. await nextTick()
  243. expect(wrapper.getComponent(InputNumber).emitted('change')).toHaveLength(1)
  244. expect(wrapper.getComponent(InputNumber).emitted().change[0]).toEqual([
  245. 1, 0,
  246. ])
  247. expect(
  248. wrapper.getComponent(InputNumber).emitted('update:modelValue')
  249. ).toHaveLength(1)
  250. wrapper.find('.el-input-number__increase').trigger('mousedown')
  251. document.dispatchEvent(mouseup)
  252. await nextTick()
  253. expect(wrapper.getComponent(InputNumber).emitted('change')).toHaveLength(2)
  254. expect(wrapper.getComponent(InputNumber).emitted().change[1]).toEqual([
  255. 2, 1,
  256. ])
  257. expect(
  258. wrapper.getComponent(InputNumber).emitted('update:modelValue')
  259. ).toHaveLength(2)
  260. await wrapper.find('input').setValue(0)
  261. expect(wrapper.getComponent(InputNumber).emitted('change')).toHaveLength(3)
  262. expect(wrapper.getComponent(InputNumber).emitted().change[2]).toEqual([
  263. 0, 2,
  264. ])
  265. expect(
  266. wrapper.getComponent(InputNumber).emitted('update:modelValue')
  267. ).toHaveLength(4)
  268. })
  269. test('blur-event', async () => {
  270. const num = ref(0)
  271. const wrapper = mount(() => <InputNumber v-model={num.value} />)
  272. await wrapper.find('input').trigger('blur')
  273. expect(wrapper.getComponent(InputNumber).emitted('blur')).toHaveLength(1)
  274. })
  275. test('focus-event', async () => {
  276. const num = ref(0)
  277. const wrapper = mount(() => <InputNumber v-model={num.value} />)
  278. await wrapper.find('input').trigger('focus')
  279. expect(wrapper.getComponent(InputNumber).emitted('focus')).toHaveLength(1)
  280. })
  281. test('clear with :value-on-clear="null"', async () => {
  282. const num = ref(2)
  283. const wrapper = mount(() => (
  284. <InputNumber v-model={num.value} min={1} max={10} />
  285. ))
  286. const elInput = wrapper.findComponent({ name: 'ElInputNumber' }).vm
  287. elInput.handleInputChange('')
  288. await nextTick()
  289. expect(num.value).toBe(null)
  290. elInput.increase()
  291. await nextTick()
  292. expect(num.value).toBe(1)
  293. elInput.increase()
  294. await nextTick()
  295. expect(num.value).toBe(2)
  296. elInput.handleInputChange('')
  297. await nextTick()
  298. expect(num.value).toBe(null)
  299. elInput.decrease()
  300. await nextTick()
  301. expect(num.value).toBe(1)
  302. elInput.decrease()
  303. await nextTick()
  304. expect(num.value).toBe(1)
  305. })
  306. test('clear with value-on-clear="min"', async () => {
  307. const num = ref(2)
  308. const wrapper = mount(() => (
  309. <InputNumber value-on-clear="min" v-model={num.value} min={1} max={10} />
  310. ))
  311. const elInput = wrapper.findComponent({ name: 'ElInputNumber' }).vm
  312. elInput.handleInputChange('')
  313. await nextTick()
  314. expect(num.value).toBe(1)
  315. elInput.increase()
  316. await nextTick()
  317. expect(num.value).toBe(2)
  318. elInput.handleInputChange('')
  319. await nextTick()
  320. expect(num.value).toBe(1)
  321. elInput.decrease()
  322. await nextTick()
  323. expect(num.value).toBe(1)
  324. })
  325. test('clear with value-on-clear="max"', async () => {
  326. const num = ref(2)
  327. const wrapper = mount(() => (
  328. <InputNumber value-on-clear="max" v-model={num.value} min={1} max={10} />
  329. ))
  330. const elInput = wrapper.findComponent({ name: 'ElInputNumber' }).vm
  331. elInput.handleInputChange('')
  332. await nextTick()
  333. expect(num.value).toBe(10)
  334. elInput.increase()
  335. await nextTick()
  336. expect(num.value).toBe(10)
  337. elInput.handleInputChange('')
  338. await nextTick()
  339. expect(num.value).toBe(10)
  340. elInput.decrease()
  341. await nextTick()
  342. expect(num.value).toBe(9)
  343. })
  344. test('clear with :value-on-clear="5"', async () => {
  345. const num = ref(2)
  346. const wrapper = mount(() => (
  347. <InputNumber value-on-clear={5} v-model={num.value} min={1} max={10} />
  348. ))
  349. const elInput = wrapper.findComponent({ name: 'ElInputNumber' }).vm
  350. elInput.handleInputChange('')
  351. await nextTick()
  352. expect(num.value).toBe(5)
  353. elInput.increase()
  354. await nextTick()
  355. expect(num.value).toBe(6)
  356. elInput.handleInputChange('')
  357. await nextTick()
  358. expect(num.value).toBe(5)
  359. elInput.decrease()
  360. await nextTick()
  361. expect(num.value).toBe(4)
  362. })
  363. test('check increase and decrease button when modelValue not in [min, max]', async () => {
  364. const num1 = ref(-5)
  365. const num2 = ref(15)
  366. const wrapper = mount({
  367. setup() {
  368. return () => (
  369. <>
  370. <InputNumber
  371. ref="inputNumber1"
  372. v-model={num1.value}
  373. min={1}
  374. max={10}
  375. />
  376. <InputNumber
  377. ref="inputNumber2"
  378. v-model={num2.value}
  379. min={1}
  380. max={10}
  381. />
  382. </>
  383. )
  384. },
  385. })
  386. const inputNumber1 = wrapper.findComponent({ ref: 'inputNumber1' }).vm
  387. const inputNumber2 = wrapper.findComponent({ ref: 'inputNumber2' }).vm
  388. expect(num1.value).toBe(1)
  389. expect(num2.value).toBe(10)
  390. inputNumber1.decrease()
  391. await nextTick()
  392. expect(num1.value).toBe(1)
  393. inputNumber1.increase()
  394. await nextTick()
  395. expect(num1.value).toBe(2)
  396. inputNumber1.increase()
  397. await nextTick()
  398. expect(num1.value).toBe(3)
  399. inputNumber2.increase()
  400. await nextTick()
  401. expect(num2.value).toBe(10)
  402. inputNumber2.decrease()
  403. await nextTick()
  404. expect(num2.value).toBe(9)
  405. inputNumber2.decrease()
  406. await nextTick()
  407. expect(num2.value).toBe(8)
  408. })
  409. describe('form item accessibility integration', () => {
  410. test('automatic id attachment', async () => {
  411. const wrapper = mount(() => (
  412. <ElFormItem label="Foobar" data-test-ref="item">
  413. <InputNumber />
  414. </ElFormItem>
  415. ))
  416. await nextTick()
  417. const formItem = wrapper.find('[data-test-ref="item"]')
  418. const formItemLabel = formItem.find('.el-form-item__label')
  419. const innerInput = wrapper.find('.el-input__inner')
  420. expect(formItem.attributes().role).toBeFalsy()
  421. expect(formItemLabel.attributes().for).toBe(innerInput.attributes().id)
  422. })
  423. test('specified id attachment', async () => {
  424. const wrapper = mount(() => (
  425. <ElFormItem label="Foobar" data-test-ref="item">
  426. <InputNumber id="foobar" />
  427. </ElFormItem>
  428. ))
  429. await nextTick()
  430. const formItem = wrapper.find('[data-test-ref="item"]')
  431. const formItemLabel = formItem.find('.el-form-item__label')
  432. const innerInput = wrapper.find('.el-input__inner')
  433. expect(formItem.attributes().role).toBeFalsy()
  434. expect(innerInput.attributes().id).toBe('foobar')
  435. expect(formItemLabel.attributes().for).toBe(innerInput.attributes().id)
  436. })
  437. test('form item role is group when multiple inputs', async () => {
  438. const wrapper = mount(() => (
  439. <ElFormItem label="Foobar" data-test-ref="item">
  440. <InputNumber />
  441. <InputNumber />
  442. </ElFormItem>
  443. ))
  444. await nextTick()
  445. const formItem = wrapper.find('[data-test-ref="item"]')
  446. expect(formItem.attributes().role).toBe('group')
  447. })
  448. })
  449. })