Skip to content
Merged
113 changes: 76 additions & 37 deletions packages/devui-vue/devui/tag-input/__tests__/tag-input.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import { mount } from '@vue/test-utils';
import { reactive, nextTick } from 'vue';
import DTagInput from '../src/tag-input';
import { useNamespace } from '../../shared/hooks/use-namespace';
import { Suggestion } from '../src/tag-input-types';

interface StateType {
tags: Array<Suggestion>;
suggestionList: Array<Suggestion>;
}

jest.mock('../../locale/create', () => ({
createI18nTranslate: () => jest.fn(),
}));

const customMount = (state) => mount({
const ns = useNamespace('tag-input', true);

const customMount = (state: StateType) => mount({
components: { DTagInput },
template: `
<d-tag-input
v-model:tags="state.tags"
v-model="state.tags"
v-model:suggestionList="state.suggestionList"
displayProperty="cname"></d-tag-input>
displayProperty="cname"
></d-tag-input>
`,
setup () {
setup() {
return {
state
state,
};
}
},
});

describe('DTagInput', () => {
Expand All @@ -27,17 +37,16 @@ describe('DTagInput', () => {
tags: [
{ cname: 'Y.Chen' },
{ cname: 'b' },
{ cname: 'c' }
{ cname: 'c' },
],
suggestionList: [
{ cname: 'd' },
{ cname: 'e' },
{ cname: 'f' },
]
],
});
const wrapper = customMount(state);

expect(wrapper.find('.devui-tags-host').exists()).toBe(true);
expect(wrapper.find(ns.b()).exists()).toBe(true);
expect(wrapper.find('.devui-tags').exists()).toBe(true);
expect(wrapper.find('.devui-tag-list').exists()).toBe(true);
expect(wrapper.find('.devui-input').exists()).toBe(true);
Expand All @@ -49,6 +58,8 @@ describe('DTagInput', () => {
state.tags[0] = { cname: 'X.Zhang' };
await nextTick();
expect(itemA.text()).toBe('X.Zhang');

wrapper.unmount();
});

it('tag-input show suggestion work', async () => {
Expand All @@ -58,14 +69,19 @@ describe('DTagInput', () => {
],
suggestionList: [
{ cname: 'b' },
]
],
});
const wrapper = customMount(state);
const input = wrapper.find('input.devui-input');

expect(wrapper.find('.devui-suggestion-list').exists()).toBe(false);
await input.trigger('focus');
expect(wrapper.find('.devui-suggestion-list').exists()).toBe(true);

// 是否存在 devui-suggestion-list
const suggestionList = !!document.querySelectorAll('.devui-suggestion-list')[0];
expect(suggestionList).toBe(true);

wrapper.unmount();
});

it('tag-input disabled work', async () => {
Expand All @@ -79,19 +95,21 @@ describe('DTagInput', () => {
props: {
tags,
suggestionList,
disabled: false
}
disabled: false,
},
});

expect(wrapper.find('.devui-disabled').exists()).toBe(false);
expect(wrapper.find('.devui-input').isVisible()).toBe(true);

await wrapper.setProps({
disabled: true
disabled: true,
});
expect(wrapper.find('.devui-disabled').exists()).toBe(true);
expect(wrapper.find('.devui-input').isVisible()).toBe(false);
expect(wrapper.find('.remove-button').exists()).toBe(false);

wrapper.unmount();
});

it('tag-input maxTags work', () => {
Expand All @@ -104,13 +122,15 @@ describe('DTagInput', () => {
]);
const wrapper = mount(DTagInput, {
props: {
tags,
modelValue: tags,
suggestionList,
maxTags: 1
}
maxTags: 1,
},
});

expect(wrapper.find('input').attributes('disabled')).toBe('');

wrapper.unmount();
});

it('tag-input removeTag work', async () => {
Expand All @@ -121,14 +141,16 @@ describe('DTagInput', () => {
],
suggestionList: [
{ cname: 'c' },
]
],
});
const wrapper = customMount(state);
const removeSvg = wrapper.find('.remove-button');
await removeSvg.trigger('mousedown');
await removeSvg.trigger('click');
expect(wrapper.findAll('.devui-tag-item').length).toBe(1);
expect(state.tags.length).toBe(1);
expect(state.suggestionList.length).toBe(2);

wrapper.unmount();
});

it('tag-input keydown work', async () => {
Expand All @@ -139,8 +161,8 @@ describe('DTagInput', () => {
],
suggestionList: [
{ cname: 'c' },
{ cname: 'xyz' }
]
{ cname: 'xyz' },
],
});
const wrapper = customMount(state);
const input = wrapper.find('input');
Expand All @@ -154,6 +176,8 @@ describe('DTagInput', () => {
expect(state.tags.length).toBe(4);
expect(state.tags[3].cname).toBe('xyz');
expect(state.suggestionList.length).toBe(1);

wrapper.unmount();
});

it('tag-input filter suggestion work', async () => {
Expand All @@ -165,22 +189,27 @@ describe('DTagInput', () => {
suggestionList: [
{ cname: 'x' },
{ cname: 'xy' },
{ cname: 'xyz' }
]
{ cname: 'xyz' },
],
});
const wrapper = customMount(state);
const input = wrapper.find('input');

await input.trigger('focus');
expect(wrapper.findAll('.devui-suggestion-item').length).toBe(3);
let suggestionList = document.querySelectorAll('.devui-suggestion-item');
expect(suggestionList.length).toBe(3);

await input.setValue('xy');
await input.trigger('input');
expect(wrapper.findAll('.devui-suggestion-item').length).toBe(2);
suggestionList = document.querySelectorAll('.devui-suggestion-item');
expect(suggestionList.length).toBe(2);

await input.setValue('xxx');
await input.trigger('input');
expect(wrapper.findAll('.devui-suggestion-item.devui-disabled').length).toBe(1);
suggestionList = document.querySelectorAll('.devui-suggestion-item');
expect(suggestionList.length).toBe(1);

wrapper.unmount();
});

it('tag-input click suggestion work', async () => {
Expand All @@ -192,17 +221,20 @@ describe('DTagInput', () => {
suggestionList: [
{ cname: 'x' },
{ cname: 'yyy' },
{ cname: 'xyz' }
]
{ cname: 'xyz' },
],
});
const wrapper = customMount(state);
await wrapper.find('input').trigger('focus');
const yyy = wrapper.findAll('.devui-suggestion-item')[1];
const suggestionList = document.querySelectorAll('.devui-suggestion-item');
const yyy = suggestionList[1];
yyy.dispatchEvent(new Event('click'));

await yyy.trigger('mousedown');
expect(state.tags.length).toBe(3);
expect(state.tags[2].cname).toBe('yyy');
expect(state.suggestionList.length).toBe(2);

wrapper.unmount();
});

it('tag-input arrow work', async () => {
Expand All @@ -214,24 +246,31 @@ describe('DTagInput', () => {
suggestionList: [
{ cname: 'x' },
{ cname: 'yyy' },
{ cname: 'xyz' }
]
{ cname: 'xyz' },
],
});
const wrapper = customMount(state);
const input = wrapper.find('input');
await input.trigger('focus');
let suggestionList = document.querySelectorAll('.devui-suggestion-item');
// 获取焦点默认第一个选中
expect(suggestionList[0].className).toContain('selected');

expect(wrapper.findAll('.devui-suggestion-item')[0].classes()).toContain('selected');

// 按下 下箭头,选中第二个数组第一个
await input.trigger('keydown', { key: 'ArrowDown' });
expect(wrapper.findAll('.devui-suggestion-item')[1].classes()).toContain('selected');
suggestionList = document.querySelectorAll('.devui-suggestion-item');
expect(suggestionList[1].className).toContain('selected');

await input.trigger('keydown', { key: 'ArrowUp' });
await input.trigger('keydown', { key: 'ArrowUp' });
expect(wrapper.findAll('.devui-suggestion-item')[2].classes()).toContain('selected');
suggestionList = document.querySelectorAll('.devui-suggestion-item');
expect(suggestionList[2].className).toContain('selected');

// 按下Enter选中数据
await input.trigger('keydown', { key: 'Enter' });
expect(state.tags[2].cname).toBe('xyz');
expect(state.suggestionList.length).toBe(2);

wrapper.unmount();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { TagInputProps, HandleEnter, OnSelectIndexChange, UseInputKeydownReturnTypes } from '../tag-input-types';

export const useInputKeydown = (
props: TagInputProps,
handleEnter: HandleEnter,
onSelectIndexChange: OnSelectIndexChange): UseInputKeydownReturnTypes => {

const KEYS_MAP = {
tab: 'Tab',
down: 'ArrowDown',
up: 'ArrowUp',
enter: 'Enter',
space: ' ',
} as const;

const onInputKeydown = ($event: KeyboardEvent) => {
switch ($event.key) {
case KEYS_MAP.tab:
case KEYS_MAP.enter:
case KEYS_MAP.space:
if (!props.isAddBySpace && KEYS_MAP.space) {
return;
}
handleEnter();
break;
case KEYS_MAP.down:
onSelectIndexChange(true);
break;
case KEYS_MAP.up:
onSelectIndexChange(false);
break;
default:
break;
}
};

return { onInputKeydown };
};
10 changes: 9 additions & 1 deletion packages/devui-vue/devui/tag-input/src/tag-input-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import type { ExtractPropTypes, PropType } from 'vue';

export interface Suggestion {
__index?: number;

[x: string]: unknown;
}

export const tagInputProps = {
tags: {
modelValue: {
type: Array as PropType<Suggestion[]>,
default: (): [] => [],
},
Expand Down Expand Up @@ -65,3 +66,10 @@ export const tagInputProps = {
} as const;

export type TagInputProps = ExtractPropTypes<typeof tagInputProps>;

export type HandleEnter = () => void;
export type OnSelectIndexChange = (isUp: boolean) => void;

export interface UseInputKeydownReturnTypes {
onInputKeydown: (e: KeyboardEvent) => void;
}
15 changes: 5 additions & 10 deletions packages/devui-vue/devui/tag-input/src/tag-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
outline: none;
}

.#{$devui-prefix}-tags-host {
.#{$devui-prefix}-tag-input {
position: relative;
height: 100%;
outline: none;
Expand Down Expand Up @@ -153,18 +153,12 @@
}

.#{$devui-prefix}-tags-autocomplete {
position: absolute;
padding-bottom: 5px;
z-index: $devui-z-index-dropdown;
width: 100%;
padding: 8px;
border-radius: $devui-border-radius;
background-color: $devui-connected-overlay-bg;
box-shadow: $devui-shadow-length-connected-overlay $devui-shadow;

&.#{$devui-prefix}-dropdown-menu {
display: block;
margin: 4px 0;
}

.#{$devui-prefix}-suggestion-list {
margin: 0;
padding: 0;
Expand All @@ -180,6 +174,7 @@
text-overflow: ellipsis;
font-size: $devui-font-size;
line-height: 20px;
border-radius: $devui-border-radius;

&:not(.#{$devui-prefix}-disabled) {
cursor: pointer;
Expand All @@ -191,7 +186,7 @@

&.selected {
color: $devui-brand;
background-color: $devui-list-item-hover-bg;
background-color: $devui-list-item-active-bg;
}
}
}
Expand Down
Loading