select.spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. import { createTest, createVue, triggerEvent, destroyVM } from '../util';
  2. import Select from 'packages/select';
  3. describe('Select', () => {
  4. const getSelectVm = (configs = {}, options) => {
  5. ['multiple', 'clearable', 'filterable', 'allowCreate', 'remote', 'collapseTags'].forEach(config => {
  6. configs[config] = configs[config] || false;
  7. });
  8. configs.multipleLimit = configs.multipleLimit || 0;
  9. if (!options) {
  10. options = [{
  11. value: '选项1',
  12. label: '黄金糕',
  13. disabled: false
  14. }, {
  15. value: '选项2',
  16. label: '双皮奶',
  17. disabled: false
  18. }, {
  19. value: '选项3',
  20. label: '蚵仔煎',
  21. disabled: false
  22. }, {
  23. value: '选项4',
  24. label: '龙须面',
  25. disabled: false
  26. }, {
  27. value: '选项5',
  28. label: '北京烤鸭',
  29. disabled: false
  30. }];
  31. }
  32. const vm = createVue({
  33. template: `
  34. <div>
  35. <el-select
  36. ref="select"
  37. v-model="value"
  38. :multiple="multiple"
  39. :multiple-limit="multipleLimit"
  40. :popper-class="popperClass"
  41. :clearable="clearable"
  42. :filterable="filterable"
  43. :collapse-tags="collapseTags"
  44. :allow-create="allowCreate"
  45. :filterMethod="filterMethod"
  46. :remote="remote"
  47. :loading="loading"
  48. :remoteMethod="remoteMethod">
  49. <el-option
  50. v-for="item in options"
  51. :label="item.label"
  52. :key="item.value"
  53. :disabled="item.disabled"
  54. :value="item.value">
  55. </el-option>
  56. </el-select>
  57. </div>
  58. `,
  59. data() {
  60. return {
  61. options,
  62. multiple: configs.multiple,
  63. multipleLimit: configs.multipleLimit,
  64. clearable: configs.clearable,
  65. filterable: configs.filterable,
  66. collapseTags: configs.collapseTags,
  67. allowCreate: configs.allowCreate,
  68. popperClass: configs.popperClass,
  69. loading: false,
  70. filterMethod: configs.filterMethod && configs.filterMethod(this),
  71. remote: configs.remote,
  72. remoteMethod: configs.remoteMethod && configs.remoteMethod(this),
  73. value: configs.multiple ? [] : ''
  74. };
  75. }
  76. }, true);
  77. return vm;
  78. };
  79. let vm;
  80. afterEach(() => {
  81. destroyVM(vm);
  82. });
  83. it('create', () => {
  84. vm = createTest(Select, true);
  85. expect(vm.$el.className).to.equal('el-select');
  86. expect(vm.$el.querySelector('.el-input__inner').placeholder).to.equal('请选择');
  87. vm.toggleMenu();
  88. expect(vm.visible).to.true;
  89. });
  90. it('options rendered correctly', () => {
  91. vm = getSelectVm();
  92. const options = vm.$el.querySelectorAll('.el-select-dropdown__item');
  93. const result = [].every.call(options, (option, index) => {
  94. let text = option.querySelector('span').textContent;
  95. return text === vm.options[index].label;
  96. });
  97. expect(result).to.true;
  98. });
  99. it('custom dropdown class', () => {
  100. vm = getSelectVm({ popperClass: 'custom-dropdown' });
  101. const dropdown = vm.$el.querySelector('.el-select-dropdown');
  102. expect(dropdown.classList.contains('custom-dropdown')).to.true;
  103. });
  104. it('default value', done => {
  105. vm = createVue({
  106. template: `
  107. <div>
  108. <el-select v-model="value">
  109. <el-option
  110. v-for="item in options"
  111. :label="item.label"
  112. :key="item.value"
  113. :value="item.value">
  114. </el-option>
  115. </el-select>
  116. </div>
  117. `,
  118. data() {
  119. return {
  120. options: [{
  121. value: '选项1',
  122. label: '黄金糕'
  123. }, {
  124. value: '选项2',
  125. label: '双皮奶'
  126. }],
  127. value: '选项2'
  128. };
  129. }
  130. }, true);
  131. setTimeout(() => {
  132. expect(vm.$el.querySelector('.el-input__inner').value).to.equal('双皮奶');
  133. done();
  134. }, 100);
  135. });
  136. it('single select', done => {
  137. vm = createVue({
  138. template: `
  139. <div>
  140. <el-select v-model="value" @change="handleChange">
  141. <el-option
  142. v-for="item in options"
  143. :label="item.label"
  144. :key="item.value"
  145. :value="item.value">
  146. <p>{{item.label}} {{item.value}}</p>
  147. </el-option>
  148. </el-select>
  149. </div>
  150. `,
  151. data() {
  152. return {
  153. options: [{
  154. value: '选项1',
  155. label: '黄金糕'
  156. }, {
  157. value: '选项2',
  158. label: '双皮奶'
  159. }, {
  160. value: '选项3',
  161. label: '蚵仔煎'
  162. }, {
  163. value: '选项4',
  164. label: '龙须面'
  165. }, {
  166. value: '选项5',
  167. label: '北京烤鸭'
  168. }],
  169. value: '',
  170. count: 0
  171. };
  172. },
  173. methods: {
  174. handleChange() {
  175. this.count++;
  176. }
  177. }
  178. }, true);
  179. const options = vm.$el.querySelectorAll('.el-select-dropdown__item');
  180. expect(vm.value).to.equal('');
  181. triggerEvent(options[2], 'mouseenter');
  182. options[2].click();
  183. setTimeout(() => {
  184. expect(vm.value).to.equal('选项3');
  185. expect(vm.count).to.equal(1);
  186. triggerEvent(options[2], 'mouseenter');
  187. options[4].click();
  188. setTimeout(() => {
  189. expect(vm.value).to.equal('选项5');
  190. expect(vm.count).to.equal(2);
  191. done();
  192. }, 100);
  193. }, 100);
  194. });
  195. it('disabled option', done => {
  196. vm = getSelectVm();
  197. vm.options[1].disabled = true;
  198. setTimeout(() => {
  199. const options = vm.$el.querySelectorAll('.el-select-dropdown__item');
  200. expect(options[1].classList.contains('is-disabled')).to.true;
  201. options[1].click();
  202. setTimeout(() => {
  203. expect(vm.value).to.equal('');
  204. done();
  205. }, 100);
  206. }, 100);
  207. });
  208. it('disabled select', () => {
  209. vm = createTest(Select, { disabled: true }, true);
  210. expect(vm.$el.querySelector('.el-input').classList.contains('is-disabled')).to.true;
  211. });
  212. it('visible event', done => {
  213. vm = createVue({
  214. template: `
  215. <div>
  216. <el-select v-model="value" @visible-change="handleVisibleChange">
  217. <el-option
  218. v-for="item in options"
  219. :label="item.label"
  220. :key="item.value"
  221. :value="item.value">
  222. </el-option>
  223. </el-select>
  224. </div>
  225. `,
  226. data() {
  227. return {
  228. options: [],
  229. value: '',
  230. visible: ''
  231. };
  232. },
  233. methods: {
  234. handleVisibleChange(val) {
  235. this.visible = val;
  236. }
  237. }
  238. }, true);
  239. vm.$children[0].visible = true;
  240. setTimeout(() => {
  241. expect(vm.visible).to.true;
  242. done();
  243. }, 50);
  244. });
  245. it('keyboard operations', done => {
  246. vm = getSelectVm();
  247. const select = vm.$children[0];
  248. let i = 8;
  249. while (i--) {
  250. select.navigateOptions('next');
  251. }
  252. select.navigateOptions('prev');
  253. setTimeout(() => {
  254. expect(select.hoverIndex).to.equal(0);
  255. select.selectOption();
  256. setTimeout(() => {
  257. expect(select.value).to.equal('选项1');
  258. done();
  259. }, 100);
  260. }, 100);
  261. });
  262. it('clearable', done => {
  263. vm = getSelectVm({ clearable: true });
  264. const select = vm.$children[0];
  265. vm.value = '选项1';
  266. select.inputHovering = true;
  267. setTimeout(() => {
  268. const icon = vm.$el.querySelector('.el-input__icon');
  269. expect(icon.classList.contains('el-icon-circle-close')).to.true;
  270. icon.click();
  271. expect(vm.value).to.equal('');
  272. done();
  273. }, 100);
  274. });
  275. it('object typed value', done => {
  276. vm = createVue({
  277. template: `
  278. <div>
  279. <el-select v-model="value" value-key="id">
  280. <el-option
  281. v-for="item in options"
  282. :label="item.label"
  283. :key="item.id"
  284. :value="item">
  285. </el-option>
  286. </el-select>
  287. </div>
  288. `,
  289. data() {
  290. return {
  291. options: [{
  292. id: 1,
  293. label: 'label1'
  294. }, {
  295. id: 2,
  296. label: 'label2'
  297. }],
  298. value: {
  299. id: 1,
  300. label: 'label1'
  301. }
  302. };
  303. }
  304. }, true);
  305. setTimeout(() => {
  306. expect(vm.$el.querySelector('.el-input__inner').value).to.equal('label1');
  307. expect(vm.$el.querySelector('.el-select-dropdown__item').classList.contains('selected'));
  308. done();
  309. }, 100);
  310. });
  311. it('custom el-option template', () => {
  312. vm = createVue({
  313. template: `
  314. <div>
  315. <el-select v-model="value">
  316. <el-option
  317. v-for="item in options"
  318. :label="item.label"
  319. :key="item.value"
  320. :value="item.value">
  321. <p>{{item.label}} {{item.value}}</p>
  322. </el-option>
  323. </el-select>
  324. </div>
  325. `,
  326. data() {
  327. return {
  328. options: [{
  329. value: 'value',
  330. label: 'label'
  331. }],
  332. value: ''
  333. };
  334. }
  335. }, true);
  336. expect(vm.$el.querySelector('.el-select-dropdown__item p').textContent).to.equal('label value');
  337. });
  338. it('option group', () => {
  339. vm = createVue({
  340. template: `
  341. <div>
  342. <el-select v-model="value">
  343. <el-option-group
  344. v-for="group in options"
  345. :key="group.label"
  346. :disabled="group.disabled"
  347. :label="group.label">
  348. <el-option
  349. v-for="item in group.options"
  350. :label="item.label"
  351. :key="item.value"
  352. :value="item.value">
  353. </el-option>
  354. </el-option-group>
  355. </el-select>
  356. </div>
  357. `,
  358. data() {
  359. return {
  360. options: [{
  361. label: '热门城市',
  362. options: [{
  363. value: 'Shanghai',
  364. label: '上海'
  365. }, {
  366. value: 'Beijing',
  367. label: '北京'
  368. }]
  369. }, {
  370. label: '城市名',
  371. disabled: true,
  372. options: [{
  373. value: 'Chengdu',
  374. label: '成都'
  375. }, {
  376. value: 'Shenzhen',
  377. label: '深圳'
  378. }, {
  379. value: 'Guangzhou',
  380. label: '广州'
  381. }, {
  382. value: 'Dalian',
  383. label: '大连'
  384. }]
  385. }],
  386. value: ''
  387. };
  388. }
  389. }, true);
  390. const groups = vm.$el.querySelectorAll('.el-select-group__wrap');
  391. const options = groups[1].querySelectorAll('.el-select-dropdown__item');
  392. expect(groups.length).to.equal(2);
  393. expect(options.length).to.equal(4);
  394. expect(options[0].querySelector('span').textContent).to.equal('成都');
  395. });
  396. it('filterable', done => {
  397. vm = getSelectVm({ filterable: true });
  398. const select = vm.$children[0];
  399. setTimeout(() => {
  400. select.selectedLabel = '面';
  401. select.onInputChange();
  402. select.visible = true;
  403. setTimeout(() => {
  404. expect(select.filteredOptionsCount).to.equal(1);
  405. done();
  406. }, 10);
  407. }, 10);
  408. });
  409. it('filterable with custom filter-method', done => {
  410. const filterMethod = vm => {
  411. return query => {
  412. vm.options = vm.options.filter(option => option.label.indexOf(query) === -1);
  413. };
  414. };
  415. vm = getSelectVm({ filterable: true, filterMethod });
  416. const select = vm.$children[0];
  417. select.$el.click();
  418. setTimeout(() => {
  419. select.selectedLabel = '面';
  420. select.onInputChange();
  421. setTimeout(() => {
  422. expect(select.filteredOptionsCount).to.equal(4);
  423. done();
  424. }, 10);
  425. }, 10);
  426. });
  427. it('default-first-option', done => {
  428. vm = createVue({
  429. template: `
  430. <div>
  431. <el-select
  432. v-model="value"
  433. default-first-option
  434. filterable
  435. >
  436. <el-option
  437. v-for="item in options"
  438. :label="item"
  439. :key="item"
  440. :value="item"
  441. />
  442. </el-select>
  443. </div>
  444. `,
  445. data() {
  446. return {
  447. options: ['1', '2', '3', '4', '5'],
  448. value: ''
  449. };
  450. }
  451. }, true);
  452. const select = vm.$children[0];
  453. setTimeout(() => {
  454. select.$el.click();
  455. select.query = '3';
  456. select.handleQueryChange('3');
  457. select.selectOption();
  458. setTimeout(() => {
  459. expect(select.value).to.equal('3');
  460. done();
  461. }, 10);
  462. }, 10);
  463. });
  464. it('allow create', done => {
  465. vm = getSelectVm({ filterable: true, allowCreate: true });
  466. const select = vm.$children[0];
  467. select.$el.querySelector('input').focus();
  468. setTimeout(() => {
  469. select.selectedLabel = 'new';
  470. select.onInputChange();
  471. setTimeout(() => {
  472. const options = document.querySelectorAll('.el-select-dropdown__item span');
  473. const target = [].filter.call(options, option => option.innerText === 'new');
  474. target[0].click();
  475. setTimeout(() => {
  476. expect(select.value.indexOf('new') > -1).to.true;
  477. done();
  478. }, 10);
  479. }, 10);
  480. }, 10);
  481. });
  482. it('multiple select', done => {
  483. vm = getSelectVm({ multiple: true });
  484. const options = vm.$el.querySelectorAll('.el-select-dropdown__item');
  485. vm.value = ['选项1'];
  486. setTimeout(() => {
  487. options[1].click();
  488. setTimeout(() => {
  489. options[3].click();
  490. setTimeout(() => {
  491. expect(vm.value.indexOf('选项2') > -1 && vm.value.indexOf('选项4') > -1).to.true;
  492. const tagCloseIcons = vm.$el.querySelectorAll('.el-tag__close');
  493. tagCloseIcons[0].click();
  494. setTimeout(() => {
  495. expect(vm.value.indexOf('选项1')).to.equal(-1);
  496. done();
  497. }, 100);
  498. }, 100);
  499. }, 100);
  500. }, 100);
  501. });
  502. it('multiple remove-tag', done => {
  503. sinon.stub(window.console, 'log');
  504. vm = createVue({
  505. template: `
  506. <div>
  507. <el-select v-model="value" multiple @remove-tag="handleRemoveTag">
  508. <el-option
  509. v-for="item in options"
  510. :label="item.label"
  511. :key="item.value"
  512. :value="item.value">
  513. <p>{{item.label}} {{item.value}}</p>
  514. </el-option>
  515. </el-select>
  516. </div>
  517. `,
  518. data() {
  519. return {
  520. options: [{
  521. value: '选项1',
  522. label: '黄金糕'
  523. }, {
  524. value: '选项2',
  525. label: '双皮奶'
  526. }, {
  527. value: '选项3',
  528. label: '蚵仔煎'
  529. }, {
  530. value: '选项4',
  531. label: '龙须面'
  532. }, {
  533. value: '选项5',
  534. label: '北京烤鸭'
  535. }],
  536. value: ['选项1', '选项3']
  537. };
  538. },
  539. methods: {
  540. handleRemoveTag() {
  541. console.log('remove tag');
  542. }
  543. }
  544. }, true);
  545. expect(vm.value.length).to.equal(2);
  546. setTimeout(() => {
  547. const tagCloseIcons = vm.$el.querySelectorAll('.el-tag__close');
  548. tagCloseIcons[1].click();
  549. setTimeout(() => {
  550. expect(vm.value.length).to.equal(1);
  551. expect(window.console.log.callCount).to.equal(1);
  552. tagCloseIcons[0].click();
  553. setTimeout(() => {
  554. expect(vm.value.length).to.equal(0);
  555. expect(window.console.log.callCount).to.equal(2);
  556. window.console.log.restore();
  557. done();
  558. }, 50);
  559. }, 50);
  560. }, 50);
  561. });
  562. it('multiple limit', done => {
  563. vm = getSelectVm({ multiple: true, multipleLimit: 1 });
  564. const options = vm.$el.querySelectorAll('.el-select-dropdown__item');
  565. options[1].click();
  566. setTimeout(() => {
  567. expect(vm.value.indexOf('选项2') > -1).to.true;
  568. options[3].click();
  569. setTimeout(() => {
  570. expect(vm.value.indexOf('选项4')).to.equal(-1);
  571. done();
  572. }, 50);
  573. }, 50);
  574. });
  575. it('multiple remote search', done => {
  576. const remoteMethod = vm => {
  577. return query => {
  578. vm.loading = true;
  579. setTimeout(() => {
  580. vm.options = vm.options.filter(option => {
  581. return option.label.indexOf(query) > -1;
  582. });
  583. vm.loading = false;
  584. }, 200);
  585. };
  586. };
  587. vm = getSelectVm({
  588. multiple: true,
  589. remote: true,
  590. filterable: true,
  591. remoteMethod
  592. });
  593. const select = vm.$children[0];
  594. vm.$nextTick(() => {
  595. select.handleQueryChange('面');
  596. setTimeout(() => {
  597. expect(select.filteredOptionsCount).to.equal(1);
  598. select.options[0].$el.click();
  599. vm.$nextTick(() => {
  600. expect(vm.value[0]).to.equal('选项4');
  601. select.deletePrevTag({ target: select.$refs.input });
  602. select.deletePrevTag({ target: select.$refs.input });
  603. select.resetInputState({ keyCode: 1 });
  604. vm.$nextTick(() => {
  605. expect(vm.value.length).to.equal(0);
  606. done();
  607. });
  608. });
  609. }, 250);
  610. });
  611. });
  612. it('event:focus & blur', done => {
  613. vm = createVue({
  614. template: `
  615. <el-select ref="select"></el-select>
  616. `
  617. }, true);
  618. const spyFocus = sinon.spy();
  619. const spyBlur = sinon.spy();
  620. vm.$refs.select.$on('focus', spyFocus);
  621. vm.$refs.select.$on('blur', spyBlur);
  622. vm.$el.querySelector('input').focus();
  623. vm.$el.querySelector('input').blur();
  624. vm.$nextTick(_ => {
  625. expect(spyFocus.calledOnce).to.be.true;
  626. expect(spyBlur.calledOnce).to.be.true;
  627. done();
  628. });
  629. });
  630. it('soft focus', done => {
  631. vm = createVue({
  632. template: `
  633. <div>
  634. <el-select v-model="value" ref="select">
  635. <el-option label="1" :value="1" />
  636. </el-select>
  637. </div>
  638. `,
  639. data() {
  640. return {
  641. value: ''
  642. };
  643. }
  644. }, true);
  645. const spyInputFocus = sinon.spy();
  646. const spySelectFocus = sinon.spy();
  647. vm.$refs.select.$on('focus', spySelectFocus);
  648. vm.$refs.select.$refs.reference.$on('focus', spyInputFocus);
  649. const option = vm.$el.querySelectorAll('.el-select-dropdown__item')[0];
  650. triggerEvent(option, 'mouseenter');
  651. option.click();
  652. vm.$nextTick(_ => {
  653. expect(spyInputFocus.calledOnce).to.be.true;
  654. expect(spySelectFocus.calledOnce).not.to.be.true;
  655. done();
  656. });
  657. });
  658. it('focus', done => {
  659. vm = createVue({
  660. template: `
  661. <el-select ref="select"></el-select>
  662. `
  663. }, true);
  664. const spy = sinon.spy();
  665. vm.$refs.select.$on('focus', spy);
  666. vm.$refs.select.focus();
  667. vm.$nextTick(_ => {
  668. expect(spy.calledOnce).to.be.true;
  669. done();
  670. });
  671. });
  672. it('only emit change on user input', done => {
  673. let callCount = 0;
  674. vm = createVue({
  675. template: `
  676. <div>
  677. <el-select v-model="value" @change="change" ref="select">
  678. <el-option label="1" :value="1" />
  679. <el-option label="2" :value="2" />
  680. <el-option label="3" :value="3" />
  681. </el-select>
  682. </div>
  683. `,
  684. data() {
  685. return {
  686. value: 1,
  687. change: () => ++callCount
  688. };
  689. }
  690. });
  691. vm.value = 2;
  692. setTimeout(() => {
  693. expect(callCount).to.equal(0);
  694. const options = vm.$el.querySelectorAll('.el-select-dropdown__item');
  695. triggerEvent(options[2], 'mouseenter');
  696. options[2].click();
  697. setTimeout(() => {
  698. expect(callCount).to.equal(1);
  699. done();
  700. }, 10);
  701. }, 10);
  702. });
  703. describe('resetInputHeight', () => {
  704. const getSelectComponentVm = (configs) => {
  705. vm = getSelectVm(configs || {});
  706. return vm.$refs.select;
  707. };
  708. it('should reset height if collapse-tags option is disabled', () => {
  709. const select = getSelectComponentVm();
  710. sinon.stub(select, '$nextTick');
  711. select.resetInputHeight();
  712. expect(select.$nextTick.callCount).to.equal(1);
  713. });
  714. it('should not reset height if collapse-tags option is enabled', () => {
  715. const select = getSelectComponentVm({ collapseTags: true });
  716. sinon.stub(select, '$nextTick');
  717. select.resetInputHeight();
  718. expect(select.$nextTick.callCount).to.equal(0);
  719. });
  720. it('should reset height if both collapse-tags and filterable are enabled', () => {
  721. const select = getSelectComponentVm({ collapseTags: true, filterable: true });
  722. sinon.stub(select, '$nextTick');
  723. select.resetInputHeight();
  724. expect(select.$nextTick.callCount).to.equal(1);
  725. });
  726. });
  727. });