-
-
+
+
+
+
-
+ 'devui-color-picker-container-wrap-current-color-transparent',
+ ]}>
+
-
-
- {showColorPicker.value ? (
-
-
-
- ) : null}
-
-
+
+ {showColorPicker.value ? (
+
+
+
+ ) : null}
+
);
};
- }
+ },
});
diff --git a/packages/devui-vue/devui/color-picker/src/components/color-alpha-slider/color-alpha-slider.tsx b/packages/devui-vue/devui/color-picker/src/components/color-alpha-slider/color-alpha-slider.tsx
index 56cd040e5f..2f03f3a914 100644
--- a/packages/devui-vue/devui/color-picker/src/components/color-alpha-slider/color-alpha-slider.tsx
+++ b/packages/devui-vue/devui/color-picker/src/components/color-alpha-slider/color-alpha-slider.tsx
@@ -9,7 +9,7 @@ export default defineComponent({
emits: ['update:modelValue'],
setup(props: colorPickerAlphaSliderProps, ctx) {
const DEFAULT_TRANSITION = { transition: 'all 0.3s ease' };
- const clickTransfrom = ref<{ transition: string } | null>(DEFAULT_TRANSITION);
+ const clickTransform = ref<{ transition: string } | null>(DEFAULT_TRANSITION);
const barElement = ref
(null);
const cursorElement = ref(null);
@@ -54,17 +54,17 @@ export default defineComponent({
return {
left: left + 'px',
top: 0,
- ...clickTransfrom.value
+ ...clickTransform.value
};
});
onMounted(() => {
const dragConfig = {
drag: (event: Event) => {
- clickTransfrom.value = null;
+ clickTransform.value = null;
onMoveBar(event as MouseEvent);
},
end: (event: Event) => {
- clickTransfrom.value = DEFAULT_TRANSITION;
+ clickTransform.value = DEFAULT_TRANSITION;
onMoveBar(event as MouseEvent);
}
};
diff --git a/packages/devui-vue/devui/color-picker/src/components/color-edit/color-edit.tsx b/packages/devui-vue/devui/color-picker/src/components/color-edit/color-edit.tsx
index 3abd81e46f..5d3c79894b 100644
--- a/packages/devui-vue/devui/color-picker/src/components/color-edit/color-edit.tsx
+++ b/packages/devui-vue/devui/color-picker/src/components/color-edit/color-edit.tsx
@@ -5,7 +5,7 @@ import './color-edit.scss';
import { fromHex, fromHexa, fromHSLA, fromHSVA, fromRGBA } from '../../utils/color-utils';
import Schema, { Rules } from 'async-validator';
// 默认 mode
-const DEFAUTL_MODE = 'rgb';
+const DEFAULT_MODE = 'rgb';
// MODE支持模式
const MODE_SUPPORT = ['rgb', 'hex', 'hsl', 'hsv'] as const;
@@ -65,13 +65,13 @@ export default defineComponent({
const isShowAlpha = inject('provideData') as ProvideColorOptions;
// 模式值
const modelValue = computed(
- () => `${props.mode ?? DEFAUTL_MODE}${isShowAlpha.showAlpha ? 'a' : ''}`
+ () => `${props.mode ?? DEFAULT_MODE}${isShowAlpha.showAlpha ? 'a' : ''}`
);
// 颜色值
const colorValue = ref | undefined>(props.color);
// 模式值类型
const modelValueType = computed(() =>
- (props.mode ?? DEFAUTL_MODE) === 'hex' ? 'string' : 'number'
+ (props.mode ?? DEFAULT_MODE) === 'hex' ? 'string' : 'number'
);
/**
@@ -96,7 +96,7 @@ export default defineComponent({
*/
function onChangeModel() {
// 安装MODE_SUPPORT列表进行更换
- const currentIndex = MODE_SUPPORT.findIndex((x) => x === props.mode ?? DEFAUTL_MODE);
+ const currentIndex = MODE_SUPPORT.findIndex((x) => x === props.mode ?? DEFAULT_MODE);
const mode = MODE_SUPPORT[(currentIndex + 1) % MODE_SUPPORT.length];
emit('changeTextModeColor', mode);
}
diff --git a/packages/devui-vue/devui/color-picker/src/components/color-history/color-history.tsx b/packages/devui-vue/devui/color-picker/src/components/color-history/color-history.tsx
index 53b83147ae..c76eb810f8 100644
--- a/packages/devui-vue/devui/color-picker/src/components/color-history/color-history.tsx
+++ b/packages/devui-vue/devui/color-picker/src/components/color-history/color-history.tsx
@@ -7,7 +7,7 @@ import { ProvideColorOptions, ColorPickerColor } from '../../utils/color-utils-t
import { debounce } from 'lodash';
const STORAGE_KEY = 'STORAGE_COLOR_PICKER_HISTORY_KEY';
-const MAX_HISOTRY_COUNT = 8;
+const MAX_HISTORY_COUNT = 8;
/**
* 创建支持存储Store
@@ -66,7 +66,7 @@ export default defineComponent({
history.value = [alphaInject.showAlpha ? value.hexa : value.hex, ...history.value].slice(
0,
- MAX_HISOTRY_COUNT
+ MAX_HISTORY_COUNT
);
}, 100);
diff --git a/packages/devui-vue/devui/color-picker/src/components/color-hue-slider/color-hue-slider.tsx b/packages/devui-vue/devui/color-picker/src/components/color-hue-slider/color-hue-slider.tsx
index 3cb78ca05b..d2daf442d1 100644
--- a/packages/devui-vue/devui/color-picker/src/components/color-hue-slider/color-hue-slider.tsx
+++ b/packages/devui-vue/devui/color-picker/src/components/color-hue-slider/color-hue-slider.tsx
@@ -13,7 +13,7 @@ export default defineComponent({
const DEFAULT_TRANSITION: DefaultTransition = { transition: 'all 0.3s ease' };
const barElement = ref(null);
const cursorElement = ref(null);
- const clickTransfrom = ref(DEFAULT_TRANSITION);
+ const clickTransform = ref(DEFAULT_TRANSITION);
const getCursorLeft = () => {
if (barElement.value && cursorElement.value) {
const rect = barElement.value.getBoundingClientRect();
@@ -32,7 +32,7 @@ export default defineComponent({
return {
left: left + 'px',
top: 0,
- ...clickTransfrom.value
+ ...clickTransform.value
};
});
@@ -67,11 +67,11 @@ export default defineComponent({
onMounted(() => {
const dragConfig = {
drag: (event: Event) => {
- clickTransfrom.value = null;
+ clickTransform.value = null;
onMoveBar(event as MouseEvent);
},
end: (event: Event) => {
- clickTransfrom.value = DEFAULT_TRANSITION;
+ clickTransform.value = DEFAULT_TRANSITION;
onMoveBar(event as MouseEvent);
}
};
diff --git a/packages/devui-vue/devui/color-picker/src/components/color-palette/color-palette.tsx b/packages/devui-vue/devui/color-picker/src/components/color-palette/color-palette.tsx
index e1f985f51d..a37853f349 100644
--- a/packages/devui-vue/devui/color-picker/src/components/color-palette/color-palette.tsx
+++ b/packages/devui-vue/devui/color-picker/src/components/color-palette/color-palette.tsx
@@ -8,14 +8,14 @@ import './color-palette.scss';
type DefaultTransition = { transition: string };
export default defineComponent({
- name: 'ColorPallete',
+ name: 'ColorPalette',
props: colorPickerPaletteProps,
emits: ['update:modelValue', 'changeTextColor'],
setup(props: ColorPickerPaletteProps, ctx) {
const DEFAULT_TRANSITION: DefaultTransition = { transition: 'all 0.3s ease' };
const dotSizeInject = inject('provideData') as ProvideColorOptions;
- const clickTransfrom = ref(DEFAULT_TRANSITION);
+ const clickTransform = ref(DEFAULT_TRANSITION);
const paletteElement = ref(null);
const canvasElement = ref(null);
const handlerElement = ref(null);
@@ -34,7 +34,7 @@ export default defineComponent({
return {
top: cursorTop.value + 'px',
left: cursorLeft.value + 'px',
- ...clickTransfrom.value
+ ...clickTransform.value
};
});
function renderCanvas() {
@@ -107,11 +107,11 @@ export default defineComponent({
if (paletteInstance && paletteInstance.vnode.el && handlerElement.value) {
DOMUtils.triggerDragEvent(paletteInstance.vnode.el as HTMLElement, {
drag: (event: Event) => {
- clickTransfrom.value = null;
+ clickTransform.value = null;
handleDrag(event as MouseEvent);
},
end: (event) => {
- clickTransfrom.value = DEFAULT_TRANSITION;
+ clickTransform.value = DEFAULT_TRANSITION;
handleDrag(event as MouseEvent);
}
});
diff --git a/packages/devui-vue/devui/color-picker/src/components/color-picker-panel/color-picker-panel.tsx b/packages/devui-vue/devui/color-picker/src/components/color-picker-panel/color-picker-panel.tsx
index 932ddee7e8..44ac683ed6 100644
--- a/packages/devui-vue/devui/color-picker/src/components/color-picker-panel/color-picker-panel.tsx
+++ b/packages/devui-vue/devui/color-picker/src/components/color-picker-panel/color-picker-panel.tsx
@@ -22,7 +22,7 @@ export default defineComponent({
colorHistory,
},
props: colorPickerProps,
- emits: ['update:modelValue', 'changeTextColor', 'changeTiggerColor', 'changePaletteColor', 'changeTextModeType'],
+ emits: ['update:modelValue', 'changeTextColor', 'changeTriggerColor', 'changePaletteColor', 'changeTextModeType'],
setup(props: ColorPickerProps, { emit }) {
const app = getCurrentInstance();
const t = createI18nTranslate('DColorPicker', app);
diff --git a/packages/devui-vue/devui/color-picker/src/utils/composeable.ts b/packages/devui-vue/devui/color-picker/src/utils/composable.ts
similarity index 100%
rename from packages/devui-vue/devui/color-picker/src/utils/composeable.ts
rename to packages/devui-vue/devui/color-picker/src/utils/composable.ts
diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx b/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx
index 5a8967d9dc..95efb3f013 100644
--- a/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx
+++ b/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx
@@ -31,6 +31,13 @@ window.ResizeObserver =
}));
describe('date-picker-pro test', () => {
+ afterEach(() => {
+ const baseDom = document.querySelector(baseClass);
+ baseDom?.parentNode?.removeChild(baseDom);
+ const pannelDomm = document.querySelector(pickerPanelClass);
+ pannelDomm?.parentNode?.removeChild(pannelDomm);
+ });
+
it('date-picker-pro init render', async () => {
const datePickerProValue = ref('');
const wrapper = mount({
@@ -429,7 +436,16 @@ describe('date-picker-pro test', () => {
const weekHeader = pickerPanel?.querySelector(weekHeaderClass);
expect(weekHeader?.getElementsByTagName('td').length).toBe(7);
const tableMonthItems = pickerPanel?.querySelectorAll(tableMonthClass);
- expect(tableMonthItems?.length).toBe(4);
+ const curMonth = new Date().getMonth() + 1;
+ if (curMonth >= 11 || curMonth <= 1) {
+ if (curMonth === 12) {
+ expect(tableMonthItems?.length).toBe(2);
+ } else {
+ expect(tableMonthItems?.length).toBe(3);
+ }
+ } else {
+ expect(tableMonthItems?.length).toBe(4);
+ }
const date = new Date();
const todayIndex = 7 - ((date.getDate() - date.getDay()) % 7) + date.getDate();
diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx b/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx
index 5f76e73bba..1e681ccd6a 100644
--- a/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx
+++ b/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx
@@ -32,6 +32,13 @@ window.ResizeObserver =
}));
describe('range-date-picker-pro test', () => {
+ afterEach(() => {
+ const baseDom = document.querySelector(baseClass);
+ baseDom?.parentNode?.removeChild(baseDom);
+ const pannelDomm = document.querySelector(pickerPanelClass);
+ pannelDomm?.parentNode?.removeChild(pannelDomm);
+ });
+
it('range-date-picker-pro init render', async () => {
const datePickerProValue = ref(['', '']);
const wrapper = mount({
@@ -126,9 +133,16 @@ describe('range-date-picker-pro test', () => {
});
const container = wrapper.find(baseClass);
- datePickerProValue.value[0] = new Date();
+
+ const date = new Date();
+ datePickerProValue.value[0] = date;
const time = 5 * 24 * 3600 * 1000;
- datePickerProValue.value[1] = new Date().getDate() > 20 ? new Date() : new Date(new Date().getTime() + time);
+
+ const todayIndex = getDateIndex(date);
+ // todayIndex 大于 20 赋值当前日期 否则加五天 对应下方getSelectedIndex逻辑
+ datePickerProValue.value[1] = todayIndex > 20 ? new Date() : new Date(new Date().getTime() + time);
+ const selectIndex = getSelectedIndex(todayIndex, 5);
+
await nextTick();
const inputs = container.findAll('input');
await inputs[0].trigger('focus');
@@ -138,13 +152,11 @@ describe('range-date-picker-pro test', () => {
expect(pickerPanel).toBeTruthy();
const tableMonthItems = pickerPanel?.querySelectorAll(tableMonthClass);
- const date = new Date();
- const todayIndx = getDateIndex(date);
- const selectIndex = getSelectedIndex(todayIndx, 5);
+
// 虚拟列表 当前面板呈现月为虚拟列表的第二个tableMonthItem
const monthContentContainer = tableMonthItems?.[1].querySelector(datePickerNs.e('table-month-content'));
const Items = monthContentContainer?.getElementsByTagName('td');
- expect(Items?.[todayIndx].classList).toContain(noDotDatePickerNs.e('table-date-start'));
+ expect(Items?.[todayIndex].classList).toContain(noDotDatePickerNs.e('table-date-start'));
await inputs[1].trigger('focus');
await nextTick();
@@ -170,7 +182,8 @@ describe('range-date-picker-pro test', () => {
onToggleChange={onToggleChange}
onConfirmEvent={onConfirmEvent}
onFocus={onFocus}
- onBlur={onBlur}>
+ onBlur={onBlur}
+ >
);
},
});
@@ -277,13 +290,15 @@ describe('range-date-picker-pro test', () => {
color="primary"
onClick={() => {
setDate(-30);
- }}>
+ }}
+ >
一个月前
),
- }}>
+ }}
+ >
);
},
});
@@ -334,7 +349,8 @@ describe('range-date-picker-pro test', () => {
),
- }}>
+ }}
+ >
);
},
});
@@ -379,7 +395,8 @@ describe('range-date-picker-pro test', () => {
+ limitDateRange={limitDateRange.value}
+ >
);
},
});
@@ -397,7 +414,16 @@ describe('range-date-picker-pro test', () => {
const weekHeader = pickerPanel?.querySelector(weekHeaderClass);
expect(weekHeader?.getElementsByTagName('td').length).toBe(7);
const tableMonthItems = pickerPanel?.querySelectorAll(tableMonthClass);
- expect(tableMonthItems?.length).toBe(4);
+ const curMonth = new Date().getMonth() + 1;
+ if (curMonth >= 11 || curMonth <= 1) {
+ if (curMonth === 12) {
+ expect(tableMonthItems?.length).toBe(2);
+ } else {
+ expect(tableMonthItems?.length).toBe(3);
+ }
+ } else {
+ expect(tableMonthItems?.length).toBe(4);
+ }
const date = new Date();
const todayIndex = 7 - ((date.getDate() - date.getDay()) % 7) + date.getDate();
diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts b/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts
index 017f5e685c..2f4ba9fedf 100644
--- a/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts
+++ b/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts
@@ -6,9 +6,9 @@ export const getDateIndex = (date: Date): number => {
};
export const getSelectedIndex = (todayIndex: number, intervalDay = 1): number => {
- return todayIndex > 20 ? todayIndex - 1 : todayIndex + intervalDay;
+ return todayIndex > 20 ? todayIndex : todayIndex + intervalDay;
};
export const getSelectedDate = (todayIndex: number, date: Date, intervalDay = 1): string => {
- return todayIndex > 20 ? dayjs(date).subtract(1, 'day').format(DATE_FORMAT) : dayjs(date).add(intervalDay, 'day').format(DATE_FORMAT);
+ return todayIndex > 20 ? dayjs(date).format(DATE_FORMAT) : dayjs(date).add(intervalDay, 'day').format(DATE_FORMAT);
};
diff --git a/packages/devui-vue/devui/form/__tests__/form.spec.ts b/packages/devui-vue/devui/form/__tests__/form.spec.ts
index 97d15e0a5a..71ee11a70d 100644
--- a/packages/devui-vue/devui/form/__tests__/form.spec.ts
+++ b/packages/devui-vue/devui/form/__tests__/form.spec.ts
@@ -1,7 +1,14 @@
import { mount } from '@vue/test-utils';
-import { reactive, ref } from 'vue';
-import { Form, FormItem } from '../index';
+import { reactive, ref, nextTick } from 'vue';
+import { Form, FormItem, FormOperation } from '../index';
import { Input } from '../../input';
+import { Select } from '../../select';
+import { AutoComplete } from '../../auto-complete';
+import { Radio, RadioGroup } from '../../radio';
+import { Switch } from '../../switch';
+import { Checkbox, CheckboxGroup } from '../../checkbox';
+import { DatePickerPro, DRangeDatePickerPro } from '../../date-picker-pro';
+import { Textarea } from '../../textarea';
import { useNamespace } from '../../shared/hooks/use-namespace';
jest.mock('../../locale/create', () => ({
@@ -9,6 +16,15 @@ jest.mock('../../locale/create', () => ({
}));
const ns = useNamespace('form', true);
+const inputNs = useNamespace('input', true);
+const selectNs = useNamespace('select', true);
+const autoCompleteNs = useNamespace('auto-complete', true);
+const radioNs = useNamespace('radio', true);
+const switchNs = useNamespace('switch', true);
+const checkboxNs = useNamespace('checkbox', true);
+const textareaNs = useNamespace('textarea', true);
+const datePickerProNs = useNamespace('date-picker-pro', true);
+const buttonNs = useNamespace('button', true);
describe('form', () => {
it('render form', async () => {
@@ -33,15 +49,351 @@ describe('form', () => {
`
});
expect(wrapper.find(ns.b()).exists()).toBeTruthy();
+ wrapper.unmount();
});
- it.todo('props label-size/label-align work well.');
+ it('props label-size/label-align work well.', async () => {
+ const formModel = reactive({
+ name: '',
+ description: '',
+ executionDay: [],
+ });
+ const size = ref('sm');
+ const align = ref('start');
+ const wrapper = mount({
+ components: { 'd-form': Form, 'd-form-item': FormItem, 'd-input': Input },
+ setup() {
+ return { formModel, size, align };
+ },
+ template: `
+
+
+
+
+
+ `
+ });
+ expect(wrapper.find(ns.e('label')).classes().includes(ns.em('label', 'sm')));
+ expect(wrapper.find(ns.e('label')).classes().includes(ns.em('label', 'start')));
+ size.value = 'md';
+ align.value = 'center';
+ await nextTick();
+ expect(wrapper.find(ns.e('label')).classes().includes(ns.em('label', 'md')));
+ expect(wrapper.find(ns.e('label')).classes().includes(ns.em('label', 'center')));
+ size.value = 'lg';
+ align.value = 'end';
+ await nextTick();
+ expect(wrapper.find(ns.e('label')).classes().includes(ns.em('label', 'lg')));
+ expect(wrapper.find(ns.e('label')).classes().includes(ns.em('label', 'end')));
+ wrapper.unmount();
+ });
- it.todo('props layout work well.');
+ it('props layout work well.', async () => {
+ const formModel = reactive({
+ name: '',
+ description: '',
+ executionDay: [],
+ });
+ const layout = ref('horizontal');
+ const wrapper = mount({
+ components: { 'd-form': Form, 'd-form-item': FormItem, 'd-input': Input },
+ setup() {
+ return { formModel, layout };
+ },
+ template: `
+
+
+
+
+
+ `
+ });
+ expect(wrapper.find(ns.em('item', 'horizontal')).exists()).toBe(true);
+ layout.value = 'vertical';
+ await nextTick();
+ expect(wrapper.find(ns.em('item', 'vertical')).exists()).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('props size work well.');
+ it('props size work well.', async () => {
+ const formModel = reactive({
+ name: '',
+ description: '',
+ executionDay: [],
+ select: '',
+ autoComplete: '',
+ radio: '',
+ switch: true,
+ datePickerPro: '',
+ });
+ const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
+ const source = ref(['C#', 'C', 'C++']);
+ const size = ref('sm');
+ const wrapper = mount({
+ components: {
+ 'd-form': Form,
+ 'd-form-item': FormItem,
+ 'd-input': Input,
+ 'd-select': Select,
+ 'd-auto-complete': AutoComplete,
+ 'd-radio': Radio,
+ 'd-radio-group': RadioGroup,
+ 'd-switch': Switch,
+ 'd-checkbox': Checkbox,
+ 'd-checkbox-group': CheckboxGroup,
+ 'd-date-picker-pro': DatePickerPro,
+ },
+ setup() {
+ return { formModel, size, selectOptions, source };
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ Manual execution
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+ });
+ expect(wrapper.find(inputNs.m('sm')).exists()).toBe(true);
+ expect(wrapper.find(selectNs.m('sm')).exists()).toBe(true);
+ expect(wrapper.find(autoCompleteNs.m('sm')).exists()).toBe(true);
+ expect(wrapper.find(radioNs.m('sm')).exists()).toBe(true);
+ expect(wrapper.find(switchNs.m('sm')).exists()).toBe(true);
+ expect(wrapper.find(checkboxNs.m('sm')).exists()).toBe(true);
+ expect(wrapper.find(datePickerProNs.b()).find(inputNs.m('sm')).exists()).toBe(true);
+ size.value = 'md';
+ await nextTick();
+ expect(wrapper.find(inputNs.m('md')).exists()).toBe(true);
+ expect(wrapper.find(selectNs.b()).classes().includes(selectNs.m('sm'))).toBe(false);
+ expect(wrapper.find(selectNs.b()).classes().includes(selectNs.m('lg'))).toBe(false);
+ expect(wrapper.find(autoCompleteNs.m('md')).exists()).toBe(true);
+ expect(wrapper.find(radioNs.m('md')).exists()).toBe(true);
+ expect(wrapper.find(switchNs.m('md')).exists()).toBe(true);
+ expect(wrapper.find(checkboxNs.m('md')).exists()).toBe(true);
+ expect(wrapper.find(datePickerProNs.b()).find(inputNs.m('md')).exists()).toBe(true);
+ size.value = 'lg';
+ await nextTick();
+ expect(wrapper.find(inputNs.m('lg')).exists()).toBe(true);
+ expect(wrapper.find(selectNs.m('lg')).exists()).toBe(true);
+ expect(wrapper.find(autoCompleteNs.m('lg')).exists()).toBe(true);
+ expect(wrapper.find(radioNs.m('lg')).exists()).toBe(true);
+ expect(wrapper.find(switchNs.m('lg')).exists()).toBe(true);
+ expect(wrapper.find(checkboxNs.m('lg')).exists()).toBe(true);
+ expect(wrapper.find(datePickerProNs.b()).find(inputNs.m('lg')).exists()).toBe(true);
+ wrapper.unmount();
+ });
+
+ it('props disabled work well.', async () => {
+ const formModel = reactive({
+ name: '',
+ description: '',
+ executionDay: [],
+ select: '',
+ autoComplete: '',
+ radio: '',
+ switch: true,
+ datePickerPro: '',
+ });
+ const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
+ const source = ref(['C#', 'C', 'C++']);
+ const size = ref('sm');
+ const wrapper = mount({
+ components: {
+ 'd-form': Form,
+ 'd-form-item': FormItem,
+ 'd-input': Input,
+ 'd-select': Select,
+ 'd-auto-complete': AutoComplete,
+ 'd-radio': Radio,
+ 'd-radio-group': RadioGroup,
+ 'd-switch': Switch,
+ 'd-checkbox': Checkbox,
+ 'd-checkbox-group': CheckboxGroup,
+ 'd-date-picker-pro': DatePickerPro,
+ 'd-textarea': Textarea,
+ },
+ setup() {
+ return { formModel, size, selectOptions, source };
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Manual execution
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+ });
+ expect(wrapper.find(inputNs.m('disabled')).exists()).toBe(true);
+ expect(wrapper.find(textareaNs.m('disabled')).exists()).toBe(true);
+ expect(wrapper.find(selectNs.m('disabled')).exists()).toBe(true);
+ expect(wrapper.find(autoCompleteNs.m('disabled')).exists()).toBe(true);
+ expect(wrapper.find(radioNs.b()).classes().includes('disabled')).toBe(true);
+ expect(wrapper.find(switchNs.m('disabled')).exists()).toBe(true);
+ expect(wrapper.find(datePickerProNs.b()).find(inputNs.m('disabled')).exists()).toBe(true);
+ wrapper.unmount();
+ });
+
+ // TODO: 可增加对datePickPro的验证
+ it('form validate work well.', async () => {
+ let isValid, invalidFields = {};
+ const formData = reactive({
+ userInfo: '',
+ age: '',
+ select: '',
+ autoComplete: '',
+ executionDay: [],
+ radio: '',
+ });
- it.todo('props disabled work well.');
+ const wrapper = mount({
+ components: {
+ 'd-form': Form,
+ 'd-form-item': FormItem,
+ 'd-input': Input,
+ 'd-select': Select,
+ 'd-auto-complete': AutoComplete,
+ 'd-radio': Radio,
+ 'd-radio-group': RadioGroup,
+ 'd-switch': Switch,
+ 'd-checkbox': Checkbox,
+ 'd-checkbox-group': CheckboxGroup,
+ 'd-date-picker-pro': DatePickerPro,
+ 'd-textarea': Textarea,
+ 'd-range-date-picker-pro': DRangeDatePickerPro,
+ 'd-form-operation': FormOperation,
+ },
+ setup() {
+ const formRef = ref(null);
+ const selectOptions = reactive(['Options1', 'Options2', 'Options3']);
+ const source = ref(['C#', 'C', 'C++']);
- it.todo('form validate work well.');
+ const rules = {
+ userInfo: [{ required: true, message: '用户信息不能为空', trigger: 'blur' }],
+ age: [{ required: true, message: '不能为空', trigger: 'blur' }],
+ select: [{ required: true, message: '请选择', trigger: 'change' }],
+ autoComplete: [{ required: true, message: '请选择', trigger: 'change' }],
+ executionDay: [{ type: 'array', required: true, message: '请至少选择一个执行时间', trigger: 'change' }],
+ radio: [{ required: true, message: '请选择', trigger: 'change' }],
+ };
+
+ const onClick = () => {
+ formRef.value.validate((a: boolean, b: unknown) => {
+ isValid = a;
+ invalidFields = b;
+ });
+ };
+
+ const onClear = () => {
+ formRef.value.clearValidate();
+ };
+
+ const onReset = () => {
+ formRef.value.resetFields();
+ };
+
+ return { formRef, formData, selectOptions, source, rules, onClick, onClear, onReset };
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Manual execution
+ Daily execution
+ Weekly execution
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+ 清除校验结果
+ 重置
+
+
+ `
+ });
+ await wrapper.find('.form-operation-wrap').findAll(buttonNs.b())[0].trigger('click');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(isValid).toBe(false);
+ expect(Object.keys(invalidFields).length).toBe(6);
+ formData.userInfo = '用户信息';
+ formData.age = '18';
+ formData.select = 'Options1';
+ formData.autoComplete = '请选择';
+ formData.executionDay = ['Mon'];
+ formData.radio = '1';
+ await wrapper.find('.form-operation-wrap').findAll(buttonNs.b())[0].trigger('click');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(isValid).toBe(true);
+ expect(invalidFields).toBeFalsy();
+ wrapper.unmount();
+ });
});
diff --git a/packages/devui-vue/devui/input-number/__tests__/input-number.spec.tsx b/packages/devui-vue/devui/input-number/__tests__/input-number.spec.tsx
index 5a46b62031..b7dc65b61d 100644
--- a/packages/devui-vue/devui/input-number/__tests__/input-number.spec.tsx
+++ b/packages/devui-vue/devui/input-number/__tests__/input-number.spec.tsx
@@ -2,10 +2,16 @@ import { mount } from '@vue/test-utils';
import { nextTick, ref } from 'vue';
import DInputNumber from '../src/input-number';
import { useNamespace } from '../../shared/hooks/use-namespace';
+import { Form as DForm, FormItem as DFormItem } from '../../form';
const ns = useNamespace('input-number', true);
const noDotNs = useNamespace('input-number');
+const inputNumberClass = ns.b();
+const sizeSmClass = noDotNs.m('sm');
+const sizeMdClass = noDotNs.m('md');
+const sizeLgClass = noDotNs.m('lg');
+
describe('d-input-number', () => {
it('visible', () => {
const num = ref(0);
@@ -117,6 +123,47 @@ describe('d-input-number', () => {
wrapper.unmount();
});
+ it('props size priority', async () => {
+ const dFormSize = ref('lg');
+ const dInputNumberSize = ref('sm');
+
+ const wrapper = mount({
+ components: { DInputNumber, DForm, DFormItem },
+ template: `
+
+
+
+
+ `,
+ setup() {
+ return {
+ dFormSize,
+ dInputNumberSize,
+ };
+ },
+ });
+
+ const dSearch = wrapper.find(inputNumberClass);
+ // form 与 元素同时存在size 属性,以元素为准。
+ expect(dSearch.classes()).toContain(sizeSmClass);
+
+ dInputNumberSize.value = '';
+ await nextTick();
+
+ // 元素不存在 size ,form 存在,以表单为准
+ expect(dSearch.classes()).toContain(sizeLgClass);
+
+ dFormSize.value = '';
+ await nextTick();
+
+ // form 与 元素都不存在 size 属性,使用默认值。
+ expect(dSearch.classes()).toContain(sizeMdClass);
+
+ wrapper.unmount();
+ });
+
it('regular expression check', async () => {
const num = ref(2);
const wrapper = mount({
diff --git a/packages/devui-vue/devui/input-number/src/input-number-types.ts b/packages/devui-vue/devui/input-number/src/input-number-types.ts
index 48f02b2c77..b2e97f09a3 100644
--- a/packages/devui-vue/devui/input-number/src/input-number-types.ts
+++ b/packages/devui-vue/devui/input-number/src/input-number-types.ts
@@ -23,8 +23,7 @@ export const inputNumberProps = {
default: -Infinity,
},
size: {
- type: String as PropType
,
- default: 'md',
+ type: String as PropType
},
modelValue: {
type: Number,
diff --git a/packages/devui-vue/devui/input-number/src/use-input-number.ts b/packages/devui-vue/devui/input-number/src/use-input-number.ts
index 98e7a385dc..ee845be730 100644
--- a/packages/devui-vue/devui/input-number/src/use-input-number.ts
+++ b/packages/devui-vue/devui/input-number/src/use-input-number.ts
@@ -1,19 +1,23 @@
-import { computed, reactive, toRefs, watch, ref } from 'vue';
+import { computed, reactive, toRefs, watch, ref, inject } from 'vue';
import type { SetupContext, Ref, CSSProperties } from 'vue';
import { InputNumberProps, UseEvent, UseRender, IState, UseExpose } from './input-number-types';
import { useNamespace } from '../../shared/hooks/use-namespace';
import { isNumber, isUndefined } from '../../shared/utils';
+import { FORM_TOKEN } from '../../form';
const ns = useNamespace('input-number');
export function useRender(props: InputNumberProps, ctx: SetupContext): UseRender {
+ const formContext = inject(FORM_TOKEN, undefined);
const { style, class: customClass, ...otherAttrs } = ctx.attrs;
const customStyle = { style: style as CSSProperties };
+ const inputNumberSize = computed(() => props.size || formContext?.size || 'md');
+
const wrapClass = computed(() => [
{
[ns.b()]: true,
- [ns.m(props.size)]: true,
+ [ns.m(inputNumberSize.value)]: true,
},
customClass,
]);
@@ -179,7 +183,7 @@ export function useEvent(props: InputNumberProps, ctx: SetupContext, inputRef: R
(val) => {
state.currentValue = correctValue(val);
},
- { immediate: true },
+ { immediate: true }
);
const onInput = (event: Event) => {
diff --git a/packages/devui-vue/devui/mention/__tests__/mention.spec.tsx b/packages/devui-vue/devui/mention/__tests__/mention.spec.tsx
index 2a57370899..a133d2c652 100644
--- a/packages/devui-vue/devui/mention/__tests__/mention.spec.tsx
+++ b/packages/devui-vue/devui/mention/__tests__/mention.spec.tsx
@@ -1,13 +1,213 @@
+import { mount } from '@vue/test-utils';
+import { ref, Ref, nextTick } from 'vue';
+import DMention from '../src/mention';
+import { useNamespace } from '../../shared/hooks/use-namespace';
+
+const ns = useNamespace('mention', true);
+const noDotNs = useNamespace('mention');
+
describe('d-mention', () => {
- it.todo('basic function work well.');
+ it('basic function work well.', async () => {
+ const suggestions = ref([
+ {
+ id: 2,
+ value: 'Vue',
+ },
+ {
+ id: 3,
+ value: 'React',
+ },
+ {
+ id: 4,
+ value: 'Angular',
+ },
+ ]);
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ );
+ },
+ });
+ expect(wrapper.classes().includes(noDotNs.b()));
+ await wrapper.find('.devui-textarea').trigger('focus');
+ wrapper.find('.devui-textarea').setValue('@');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions')).exists()).toBe(true);
+ await wrapper.find(ns.e('suggestions-item')).trigger('click');
+ expect(wrapper.find(ns.e('suggestions')).exists()).toBe(false);
+ expect(wrapper.find('.devui-textarea').element.value).toBe('@Vue');
+ wrapper.unmount();
+ });
- it.todo('props trigger work well.');
+ it('props trigger work well.', async () => {
+ const trigger = ref(['@', '#']);
+ const suggestions = ref([
+ {
+ id: 2,
+ value: 'Vue',
+ },
+ {
+ id: 3,
+ value: 'React',
+ },
+ {
+ id: 4,
+ value: 'Angular',
+ },
+ ]);
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ );
+ },
+ });
+ await wrapper.find('.devui-textarea').trigger('focus');
+ wrapper.find('.devui-textarea').setValue('@');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions')).exists()).toBe(true);
+ wrapper.find('.devui-textarea').setValue('');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions')).exists()).toBe(false);
+ wrapper.find('.devui-textarea').setValue('#');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions')).exists()).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('async loading work well.');
+ it('async loading work well.', async () => {
+ const loading = ref(true);
+ const suggestions: Ref = ref([]);
+ const onSearchChange = async () => {
+ loading.value = true;
+ await new Promise(resolve => setTimeout(resolve, 1500));
+ suggestions.value = [
+ {
+ id: 2,
+ value: 'Vue',
+ },
+ {
+ id: 3,
+ value: 'React',
+ },
+ {
+ id: 4,
+ value: 'Angular',
+ },
+ ];
+ loading.value = false;
+ };
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ );
+ },
+ });
+ await wrapper.find('.devui-textarea').trigger('focus');
+ wrapper.find('.devui-textarea').setValue('@');
+ await wrapper.find('.devui-textarea').trigger('input');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions-loading')).exists()).toBe(true);
+ expect(wrapper.find(ns.e('suggestions-item')).exists()).toBe(false);
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions-item')).exists()).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('props position work well.');
+ it('props position work well.', async () => {
+ const position: Ref = ref('bottom');
+ const suggestions = ref([
+ {
+ id: 2,
+ value: 'Vue',
+ },
+ {
+ id: 3,
+ value: 'React',
+ },
+ {
+ id: 4,
+ value: 'Angular',
+ },
+ ]);
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ );
+ },
+ });
+ await wrapper.find('.devui-textarea').trigger('focus');
+ wrapper.find('.devui-textarea').setValue('@');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions')).attributes().style.includes('margin-top: -16px')).toBe(true);
+ wrapper.setProps({
+ position: 'top',
+ });
+ await nextTick();
+ expect(wrapper.find(ns.e('suggestions')).attributes().style.includes('margin-top: 0px')).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('props not-found-content work well.');
+ it('props not-found-content work well.', async () => {
+ const suggestions = ref([
+ {
+ id: 2,
+ value: 'Vue',
+ },
+ {
+ id: 3,
+ value: 'React',
+ },
+ {
+ id: 4,
+ value: 'Angular',
+ },
+ ]);
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ );
+ },
+ });
+ await wrapper.find('.devui-textarea').trigger('focus');
+ wrapper.find('.devui-textarea').setValue('@devui');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ expect(wrapper.find(ns.e('suggestions')).html().includes('not found content')).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('event select/change work well.');
+ it('event select work well.', async () => {
+ const suggestions = ref([
+ {
+ id: 2,
+ value: 'Vue',
+ },
+ {
+ id: 3,
+ value: 'React',
+ },
+ {
+ id: 4,
+ value: 'Angular',
+ },
+ ]);
+ const onSelect = jest.fn();
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ );
+ },
+ });
+ await wrapper.find('.devui-textarea').trigger('focus');
+ wrapper.find('.devui-textarea').setValue('@');
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ await wrapper.find(ns.e('suggestions-item')).trigger('click');
+ expect(onSelect).toBeCalledTimes(1);
+ wrapper.unmount();
+ });
});
diff --git a/packages/devui-vue/devui/mention/src/mention.tsx b/packages/devui-vue/devui/mention/src/mention.tsx
index 2674e3d4bf..cca8a58b46 100644
--- a/packages/devui-vue/devui/mention/src/mention.tsx
+++ b/packages/devui-vue/devui/mention/src/mention.tsx
@@ -1,4 +1,4 @@
-import { defineComponent, ref, onMounted, watch, onUnmounted, nextTick, computed, getCurrentInstance } from 'vue';
+import { defineComponent, ref, onMounted, watch, onUnmounted, computed, getCurrentInstance } from 'vue';
import { IMentionSuggestionItem, mentionProps, type MentionProps } from './mention-types';
import DTextarea from '../../textarea/src/textarea';
import DIcon from '../../icon/src/icon';
@@ -30,10 +30,10 @@ export default defineComponent({
if (props.trigger.includes(val[0])) {
showSuggestions.value = true;
if (props.position === 'top') {
- nextTick(() => {
+ setTimeout(() => {
const height = window.getComputedStyle(suggestionsDom.value as Element, null).height;
suggestionsTop.value = -Number(height.replace('px', ''));
- });
+ }, 0);
}
filteredSuggestions.value = (suggestions.value as IMentionSuggestionItem[]).filter((item: IMentionSuggestionItem) =>
String(item[props.dmValueParse.value as keyof IMentionSuggestionItem])
diff --git a/packages/devui-vue/devui/menu/__tests__/menu.spec.ts b/packages/devui-vue/devui/menu/__tests__/menu.spec.ts
index e7796d0693..6ca2031f8a 100644
--- a/packages/devui-vue/devui/menu/__tests__/menu.spec.ts
+++ b/packages/devui-vue/devui/menu/__tests__/menu.spec.ts
@@ -15,7 +15,19 @@ const dotMenuItemVerticalWrapper = dotNs.b() + '-item-vertical-wrapper';
const dotSubMenu = dotSubNs.b();
const submenuDisabled = SubNs.b() + '-disabled';
const menuitemDisabled = ns.b() + '-item-disabled';
+const dotMenuItemSelect = dotNs.b() + '-item-select';
+// fix: TypeError: Array.from(...).at is not a function
+!Array.prototype.at && (Array.prototype.at = function at (n) {
+ // Convert the argument to an integer
+ n = Math.trunc(n) || 0; // 去掉小数点
+ // Allow negative indexing from the end
+ if (n < 0) { n += this.length; }
+ // Out-of-bounds access returns undefined
+ if (n < 0 || n >= this.length) { return undefined; }
+ // Otherwise, this is just normal property access
+ return this[n];
+});
describe('menu test', () => {
let wrapper: VueWrapper;
@@ -140,13 +152,74 @@ describe('menu test', () => {
expect(wrapper.findAll('i')[0].classes().includes('is-opened')).toBe(true);
expect(wrapper.findAll('i')[1].classes().includes('is-opened')).toBe(false);
});
- it.todo('props mode(vertical/horizontal) work well.');
-
- it.todo('props multiple work well.');
+ it('props mode(vertical/horizontal) work well.', async () => {
+ wrapper = mount({
+ components: {
+ 'd-menu': Menu,
+ 'd-menu-item': MenuItem,
+ },
+ template: `
+
+ 首页
+ 个人
+ Link To Baidu
+
+ `,
+ });
+ await wrapper.setProps({
+ mode: 'horizontal',
+ });
+ expect(wrapper.classes().includes(menuHorizontal)).toBe(true);
+ await wrapper.setProps({
+ mode: 'vertical',
+ });
+ expect(wrapper.classes().includes(menuVertical)).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('props collapsed-indent work well.');
+ it('props multiple work well.', async () => {
+ wrapper = mount({
+ components: {
+ 'd-menu': Menu,
+ 'd-menu-item': MenuItem,
+ },
+ template: `
+
+ 首页
+ 个人
+ Link To Baidu
+
+ `,
+ });
+ wrapper.findAll(dotMenuItem)[0].trigger('click');
+ await nextTick();
+ expect(wrapper.findAll(dotMenuItemSelect)).toHaveLength(1);
+ wrapper.findAll(dotMenuItem)[1].trigger('click');
+ await nextTick();
+ expect(wrapper.findAll(dotMenuItemSelect)).toHaveLength(2);
+ wrapper.findAll(dotMenuItem)[2].trigger('click');
+ await nextTick();
+ expect(wrapper.findAll(dotMenuItemSelect)).toHaveLength(3);
+ wrapper.unmount();
+ });
- it.todo('props disabled work well.');
+ it('props collapsed-indent work well.', async () => {
+ wrapper = mount({
+ components: {
+ 'd-menu': Menu,
+ 'd-menu-item': MenuItem,
+ },
+ template: `
+
+ 首页
+ 个人
+ Link To Baidu
+
+ `,
+ });
+ expect(wrapper.attributes('style')).toContain('width: 96px');
+ wrapper.unmount();
+ });
it.todo('props router work well.');
diff --git a/packages/devui-vue/devui/message/__tests__/message.spec.tsx b/packages/devui-vue/devui/message/__tests__/message.spec.tsx
index 1ef4b5eb96..4883d0ada0 100644
--- a/packages/devui-vue/devui/message/__tests__/message.spec.tsx
+++ b/packages/devui-vue/devui/message/__tests__/message.spec.tsx
@@ -5,6 +5,11 @@ import { useNamespace } from '../../shared/hooks/use-namespace';
const ns = useNamespace('message', true);
describe('d-message', () => {
describe('service', () => {
+ afterEach(() => {
+ const messageDom = document.querySelector(ns.b());
+ messageDom?.parentNode?.removeChild(messageDom);
+ });
+
it('render correctly when using service', async () => {
message({
message: 'message content',
@@ -51,9 +56,32 @@ describe('d-message', () => {
expect(closeCallback).toBeCalled();
});
- it.todo('bordered should work well.');
+ it('bordered should work well.', async () => {
+ message({
+ message: 'message bordered should work well',
+ bordered: false,
+ });
+ await nextTick();
+ const messageDom = document.querySelector(ns.b()) as HTMLElement;
- it.todo('shadow should work well.');
+ expect(messageDom).toBeTruthy();
+ expect(messageDom.style['border-top']).toBeFalsy();
+ expect(messageDom.style['border-bottom']).toBeFalsy();
+ expect(messageDom.style['border-left']).toBeFalsy();
+ expect(messageDom.style['border-right']).toBeFalsy();
+ });
+
+ it('shadow should work well.', async () => {
+ message({
+ message: 'message shadow should work well',
+ shadow: false,
+ });
+ await nextTick();
+ const messageDom = document.querySelector(ns.b()) as HTMLElement;
+
+ expect(messageDom).toBeTruthy();
+ expect(messageDom.style['box-shadow']).toBe('none');
+ });
});
describe('function', () => {
diff --git a/packages/devui-vue/devui/message/src/message.scss b/packages/devui-vue/devui/message/src/message.scss
index e7a2e95c46..fd7424708f 100644
--- a/packages/devui-vue/devui/message/src/message.scss
+++ b/packages/devui-vue/devui/message/src/message.scss
@@ -40,7 +40,8 @@
.#{$devui-prefix}-message__close {
margin-left: auto;
padding-left: 10px;
- margin-top: -2px;
+ line-height: 0;
+ cursor: pointer;
}
// 图标样式
.#{$devui-prefix}-message__image {
diff --git a/packages/devui-vue/devui/message/src/message.tsx b/packages/devui-vue/devui/message/src/message.tsx
index a42c60f626..bced2c2c7f 100644
--- a/packages/devui-vue/devui/message/src/message.tsx
+++ b/packages/devui-vue/devui/message/src/message.tsx
@@ -31,7 +31,7 @@ export default defineComponent({
// 鼠标移入后结束定时器
const interrupt = () => {
- if (timer) {
+ if (timer && props.duration) {
clearTimeout(timer);
timer = null;
}
@@ -39,7 +39,7 @@ export default defineComponent({
// 鼠标移出后重新计算时间 如果超时则直接移除message
const removeReset = () => {
- if (props.visible) {
+ if (props.visible && props.duration) {
const remainTime = props.duration - (Date.now() - timestamp);
timer = setTimeout(close, remainTime);
}
diff --git a/packages/devui-vue/devui/modal/src/composables/use-draggable.ts b/packages/devui-vue/devui/modal/src/composables/use-draggable.ts
index 0daad8e758..82c75c18a1 100644
--- a/packages/devui-vue/devui/modal/src/composables/use-draggable.ts
+++ b/packages/devui-vue/devui/modal/src/composables/use-draggable.ts
@@ -13,10 +13,9 @@ function addUnit(value?: string | number, defaultUnit = 'px'): string {
}
}
-export const modalPosition = ref('translate(-50%, -50%)');
-
interface Draggable {
clearPosition: () => void;
+ modalPosition: Ref;
}
export const useDraggable = (
@@ -24,6 +23,8 @@ export const useDraggable = (
dragRef: Ref,
draggable: ComputedRef
): Draggable => {
+ const modalPosition = ref('translate(-50%, -50%)');
+
let transform = {
offsetX: 0,
offsetY: 0,
@@ -103,5 +104,6 @@ export const useDraggable = (
return {
clearPosition,
+ modalPosition,
};
};
diff --git a/packages/devui-vue/devui/modal/src/modal.tsx b/packages/devui-vue/devui/modal/src/modal.tsx
index 0063666524..af5afbff12 100644
--- a/packages/devui-vue/devui/modal/src/modal.tsx
+++ b/packages/devui-vue/devui/modal/src/modal.tsx
@@ -3,7 +3,7 @@ import { modalProps, ModalProps, ModalType } from './modal-types';
import { Icon } from '../../icon';
import { FixedOverlay } from '../../overlay';
import { useModal, useModalRender } from './composables/use-modal';
-import { useDraggable, modalPosition } from './composables/use-draggable';
+import { useDraggable } from './composables/use-draggable';
import DModalHeader from './components/header';
import DModalBody from './components/body';
import { useNamespace } from '../../shared/hooks/use-namespace';
@@ -28,7 +28,7 @@ export default defineComponent({
const dialogRef = ref();
const headerRef = ref();
const draggable = computed(() => props.draggable);
- const { clearPosition } = useDraggable(dialogRef, headerRef, draggable);
+ const { clearPosition, modalPosition } = useDraggable(dialogRef, headerRef, draggable);
watch(modelValue, (val) => {
if (val && !keepLast.value) {
diff --git a/packages/devui-vue/devui/notification/src/use-notification.ts b/packages/devui-vue/devui/notification/src/use-notification.ts
index 708fb9f2cc..d14c8b6eba 100644
--- a/packages/devui-vue/devui/notification/src/use-notification.ts
+++ b/packages/devui-vue/devui/notification/src/use-notification.ts
@@ -25,14 +25,14 @@ export function useEvent(
};
const interrupt = () => {
- if (timer) {
+ if (timer && props.duration) {
clearTimeout(timer);
timer = null;
}
};
const removeReset = () => {
- if (props.modelValue) {
+ if (props.modelValue && props.duration) {
const remainTime = props.duration - (Date.now() - timestamp);
timer = setTimeout(close, remainTime);
}
diff --git a/packages/devui-vue/devui/pagination/src/components/page-size.tsx b/packages/devui-vue/devui/pagination/src/components/page-size.tsx
index a3740fe225..67d28ed41f 100644
--- a/packages/devui-vue/devui/pagination/src/components/page-size.tsx
+++ b/packages/devui-vue/devui/pagination/src/components/page-size.tsx
@@ -28,7 +28,9 @@ export default defineComponent({
menu: () => (
{pageSizeOptions.value.map((item, index) => (
- -
+
-
{item}
))}
diff --git a/packages/devui-vue/devui/pagination/src/pagination.scss b/packages/devui-vue/devui/pagination/src/pagination.scss
index 3e8a1c1736..6214e6eb41 100644
--- a/packages/devui-vue/devui/pagination/src/pagination.scss
+++ b/packages/devui-vue/devui/pagination/src/pagination.scss
@@ -387,9 +387,14 @@
transition: color $devui-animation-duration-fast $devui-animation-ease-in-out-smooth,
background-color $devui-animation-duration-fast $devui-animation-ease-in-out-smooth;
- &:hover {
+ &:hover:not(.active) {
color: $devui-list-item-hover-text;
background-color: $devui-list-item-hover-bg;
}
+
+ &.active {
+ color: $devui-list-item-active-text;
+ background-color: $devui-list-item-active-bg;
+ }
}
}
diff --git a/packages/devui-vue/devui/popover/__tests__/popover.spec.tsx b/packages/devui-vue/devui/popover/__tests__/popover.spec.tsx
index b0e8cd140a..5d5918da18 100644
--- a/packages/devui-vue/devui/popover/__tests__/popover.spec.tsx
+++ b/packages/devui-vue/devui/popover/__tests__/popover.spec.tsx
@@ -2,14 +2,22 @@ import { mount } from '@vue/test-utils';
import { nextTick, ref } from 'vue';
import DPopover from '../src/popover';
import { useNamespace } from '../../shared/hooks/use-namespace';
+import { Placement } from '../src/popover-types';
+import { wait } from '../../shared/utils';
const ns = useNamespace('popover', true);
const buttonNs = useNamespace('button', true);
const buttonBaseClass = buttonNs.b();
const popoverContentClass = ns.e('content');
const popoverIconClass = useNamespace('popover').e('icon');
+const popoverArrowClass = '.devui-flexible-overlay__arrow';
describe('d-popover', () => {
+ beforeEach(() => {
+ const popoverContent = document.body.querySelector(popoverContentClass);
+ popoverContent && popoverContent.parentNode?.removeChild(popoverContent);
+ });
+
it('visible', async () => {
const wrapper = mount({
setup() {
@@ -56,11 +64,9 @@ describe('d-popover', () => {
},
});
await wrapper.find(buttonBaseClass).trigger('mouseenter');
- setTimeout(() => {
- const popoverContent = document.body.querySelector(popoverContentClass);
- expect(popoverContent).toBeTruthy();
- wrapper.unmount();
- }, 150);
+ await wait(500);
+ const popoverContent = document.body.querySelector(popoverContentClass);
+ expect(popoverContent).toBeTruthy();
});
it('trigger manually', async () => {
@@ -117,7 +123,7 @@ describe('d-popover', () => {
});
it('popover disabled work', async () => {
- let disabled = ref(false);
+ const disabled = ref(false);
const wrapper = mount({
setup() {
return () => (
@@ -128,23 +134,153 @@ describe('d-popover', () => {
},
});
await wrapper.find(buttonBaseClass).trigger('mouseenter');
- const popoverContent = document.body.querySelector(popoverContentClass);
- setTimeout(() => {
- expect(popoverContent).toBeTruthy();
- }, 150);
- disabled = ref(true);
+ await wait(500);
+ let popoverContent = document.body.querySelector(popoverContentClass);
+ expect(popoverContent).toBeTruthy();
+ disabled.value = true;
await nextTick();
+ popoverContent = document.body.querySelector(popoverContentClass);
expect(popoverContent).toBeFalsy();
wrapper.unmount();
});
- it.todo('props position work well.');
+ it('props position work well.', async () => {
+ let position = ref>(['top']);
+ let wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('click');
+ await wait(500);
+ expect(document.querySelector(popoverArrowClass)?.style.bottom).toBe('-4px');
+ const popoverContent = document.querySelector(popoverContentClass);
+ expect(popoverContent?.getAttribute('style')?.includes('transform-origin: 50% calc(100% + 8px)')).toBe(true);
+ wrapper.unmount();
- it.todo('props align work well.');
+ position = ref>(['bottom']);
+ wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('click');
+ await wait(500);
+ expect(document.querySelector(popoverArrowClass)?.style.top).toBe('-4px');
+ expect(document.querySelector(popoverContentClass)?.getAttribute('style')?.includes('transform-origin: 50% -8px')).toBe(true);
+ wrapper.unmount();
+
+ position = ref>(['left']);
+ wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('click');
+ await wait(500);
+ expect(document.querySelector(popoverArrowClass)?.style.right).toBe('-4px');
+ expect(document.querySelector(popoverContentClass)?.getAttribute('style')?.includes('transform-origin: calc(100% + 8px)')).toBe(true);
+ wrapper.unmount();
+
+ position = ref>(['right']);
+ wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('click');
+ await wait(500);
+ expect(document.querySelector(popoverArrowClass)?.style.left).toBe('-4px');
+ expect(document.querySelector(popoverContentClass)?.getAttribute('style')?.includes('transform-origin: -8px 50%')).toBe(true);
+ wrapper.unmount();
- it.todo('props offset work well.');
+ position = ref>(['right-start']);
+ wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('click');
+ await wait(500);
+ expect(document.querySelector(popoverArrowClass)?.style.left).toBe('-4px');
+ expect(document.querySelector(popoverContentClass)?.getAttribute('style')?.includes('transform-origin: -8px 50%')).toBe(true);
+ wrapper.unmount();
- it.todo('props mouse-enter-delay work well.');
+ position = ref>(['right-end']);
+ wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('click');
+ await wait(500);
+ expect(document.querySelector(popoverArrowClass)?.style.left).toBe('-4px');
+ expect(document.querySelector(popoverContentClass)?.getAttribute('style')?.includes('transform-origin: -8px 50%')).toBe(true);
+ wrapper.unmount();
+ });
- it.todo('props mouse-leave-delay work well.');
+ it.todo('props align work well.');
+
+ it('props mouse-enter-delay work well.', async () => {
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('mouseenter');
+ await wait(500);
+ expect(document.querySelector(popoverContentClass)).toBeFalsy();
+ await wait(1100);
+ expect(document.querySelector(popoverContentClass)).toBeTruthy();
+ wrapper.unmount();
+ });
+
+ it('props mouse-leave-delay work well.', async () => {
+ const wrapper = mount({
+ setup() {
+ return () => (
+
+ default
+
+ );
+ },
+ });
+ await wrapper.find(buttonBaseClass).trigger('mouseenter');
+ await wait(500);
+ expect(document.querySelector(popoverContentClass)).toBeTruthy();
+ await wrapper.find(buttonBaseClass).trigger('mouseleave');
+ await wait(500);
+ expect(document.querySelector(popoverContentClass)).toBeTruthy();
+ await wait(1100);
+ expect(document.querySelector(popoverContentClass)).toBeFalsy();
+ wrapper.unmount();
+ });
});
diff --git a/packages/devui-vue/devui/radio/__tests__/radio-group.spec.ts b/packages/devui-vue/devui/radio/__tests__/radio-group.spec.ts
index 0d9f78ee50..7148524d9a 100644
--- a/packages/devui-vue/devui/radio/__tests__/radio-group.spec.ts
+++ b/packages/devui-vue/devui/radio/__tests__/radio-group.spec.ts
@@ -168,7 +168,7 @@ describe('RadioGroup', () => {
const radio1 = wrapper.findAllComponents({ name: 'DRadio' })[0];
const radio1Wrapper = radio1.find(radioBaseClass);
- expect(radio1Wrapper.classes()).not.toContain(sizeNs);
+ expect(radio1Wrapper.classes()).toContain(sizeNs);
await wrapper.setProps({
border: true,
});
diff --git a/packages/devui-vue/devui/radio/__tests__/radio.spec.ts b/packages/devui-vue/devui/radio/__tests__/radio.spec.ts
index 76d5f0af02..e8fa3ee8e7 100644
--- a/packages/devui-vue/devui/radio/__tests__/radio.spec.ts
+++ b/packages/devui-vue/devui/radio/__tests__/radio.spec.ts
@@ -119,7 +119,7 @@ describe('Radio', () => {
},
});
const container = wrapper.find(baseClass);
- expect(container.classes()).not.toContain(sizeNs);
+ expect(container.classes()).toContain(sizeNs);
await wrapper.setProps({
border: true,
});
diff --git a/packages/devui-vue/devui/radio/src/radio-button.scss b/packages/devui-vue/devui/radio/src/radio-button.scss
index 36b3ba94cd..44c3d300b1 100644
--- a/packages/devui-vue/devui/radio/src/radio-button.scss
+++ b/packages/devui-vue/devui/radio/src/radio-button.scss
@@ -29,7 +29,6 @@ $button-padding-map: (
color: $devui-text;
cursor: pointer;
border: 1px solid $devui-line;
- border-left: none;
user-select: none;
@each $size in ('lg', 'md', 'sm') {
&.#{$devui-prefix}-radio-button--#{$size} {
@@ -69,16 +68,41 @@ $button-padding-map: (
background-color: #ffffff;
border-color: $devui-disabled-line;
}
+}
+
+.#{$devui-prefix}-radio-group {
+ &.is-row {
+ .#{$devui-prefix}-radio-button {
+ border-left: none;
- &:first-child {
- border-top-left-radius: $devui-border-radius;
- border-bottom-left-radius: $devui-border-radius;
- border-left: 1px solid $devui-disabled-line;
- box-shadow: none;
+ &:first-child {
+ border-top-left-radius: $devui-border-radius;
+ border-bottom-left-radius: $devui-border-radius;
+ border-left: 1px solid $devui-disabled-line;
+ }
+
+ &:last-child {
+ border-top-right-radius: $devui-border-radius;
+ border-bottom-right-radius: $devui-border-radius;
+ }
+ }
}
- &:last-child {
- border-top-right-radius: $devui-border-radius;
- border-bottom-right-radius: $devui-border-radius;
+ &.is-column {
+ .#{$devui-prefix}-radio-button {
+ width: 100%;
+ border-top: none;
+
+ &:first-child {
+ border-top-left-radius: $devui-border-radius;
+ border-top-right-radius: $devui-border-radius;
+ border-top: 1px solid $devui-disabled-line;
+ }
+
+ &:last-child {
+ border-bottom-left-radius: $devui-border-radius;
+ border-bottom-right-radius: $devui-border-radius;
+ }
+ }
}
}
diff --git a/packages/devui-vue/devui/radio/src/radio-types.ts b/packages/devui-vue/devui/radio/src/radio-types.ts
index 7ab79f763b..2692dfda3f 100644
--- a/packages/devui-vue/devui/radio/src/radio-types.ts
+++ b/packages/devui-vue/devui/radio/src/radio-types.ts
@@ -25,8 +25,7 @@ const radioCommonProps = {
default: false,
},
size: {
- type: String as PropType,
- default: 'md',
+ type: String as PropType
},
};
diff --git a/packages/devui-vue/devui/radio/src/radio.tsx b/packages/devui-vue/devui/radio/src/radio.tsx
index b3853a13f9..0b80854ca6 100644
--- a/packages/devui-vue/devui/radio/src/radio.tsx
+++ b/packages/devui-vue/devui/radio/src/radio.tsx
@@ -20,7 +20,7 @@ export default defineComponent({
disabled: isDisabled.value,
[ns.b()]: true,
[ns.m('bordered')]: border.value,
- [ns.m(size.value)]: border.value,
+ [ns.m(size.value)]: size.value,
};
return (
diff --git a/packages/devui-vue/devui/radio/src/use-radio.ts b/packages/devui-vue/devui/radio/src/use-radio.ts
index 73b5896a0f..0c55fdfd99 100644
--- a/packages/devui-vue/devui/radio/src/use-radio.ts
+++ b/packages/devui-vue/devui/radio/src/use-radio.ts
@@ -60,8 +60,9 @@ export function useRadio(props: RadioProps, ctx: SetupContext): UseRadioFn {
});
const size = computed(() => {
- return formContext?.size || radioGroupConf?.size.value || props.size;
+ return props.size || radioGroupConf?.size.value || formContext?.size || 'md';
});
+
watch(
() => props.modelValue,
() => {
@@ -79,7 +80,9 @@ export function useRadio(props: RadioProps, ctx: SetupContext): UseRadioFn {
}
export function useRadioGroup(props: RadioGroupProps, ctx: SetupContext): void {
+ const formContext = inject(FORM_TOKEN, undefined);
const formItemContext = inject(FORM_ITEM_TOKEN, undefined);
+
/** change 事件 */
const emitChange = (radioValue: valueTypes) => {
ctx.emit('update:modelValue', radioValue);
@@ -93,13 +96,16 @@ export function useRadioGroup(props: RadioGroupProps, ctx: SetupContext): void {
}
);
+ // 组件 size 优先于表单 size
+ const radioGroupSize = computed(() => props.size || formContext?.size || '');
+
// 注入给子组件
provide(radioGroupInjectionKey, {
modelValue: toRef(props, 'modelValue'),
name: toRef(props, 'name'),
disabled: toRef(props, 'disabled'),
border: toRef(props, 'border'),
- size: toRef(props, 'size'),
+ size: radioGroupSize,
beforeChange: props.beforeChange,
emitChange,
fill: toRef(props, 'fill'),
diff --git a/packages/devui-vue/devui/search/__tests__/search.spec.ts b/packages/devui-vue/devui/search/__tests__/search.spec.ts
index bde1fbbd74..6b476a2cba 100644
--- a/packages/devui-vue/devui/search/__tests__/search.spec.ts
+++ b/packages/devui-vue/devui/search/__tests__/search.spec.ts
@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
import DSearch from '../src/search';
import { ref, nextTick } from 'vue';
import { useNamespace } from '../../shared/hooks/use-namespace';
+import { Form as DForm, FormItem as DFormItem } from '../../form';
const searchNs = useNamespace('search');
const dotSearchNs = useNamespace('search', true);
@@ -13,27 +14,23 @@ const disableSearchClass = searchNs.m('disabled');
const dotSearchClass = dotSearchNs.b();
const dotClearSearchClass = dotSearchNs.e('clear');
const dotIconSearchClass = dotSearchNs.e('icon');
+const leftIconPositionClass = searchNs.m('left');
+const rightIconPositionClass = searchNs.m('right');
+const noBorderClass = searchNs.m('no-border');
describe('search test', () => {
- // TODO: 这个单测应该按功能进行拆分
it('should render correctly', async () => {
const value = ref('test');
- const size = ref('');
- const disabled = ref(false);
const wrapper = mount({
components: { DSearch },
template: `
`,
setup() {
return {
value,
- size,
- disabled,
};
},
});
@@ -42,7 +39,94 @@ describe('search test', () => {
const input = search.find('input');
expect(input.element.value).toBe('test');
- // test size
+ wrapper.unmount();
+ });
+
+ it('should event correctly', async () => {
+ const value = ref('test');
+ const onSearch = jest.fn();
+ const wrapper = mount({
+ components: { DSearch },
+ template: `
+
+ `,
+ setup() {
+ return {
+ value,
+ onSearch,
+ };
+ },
+ });
+ const search = wrapper.find(dotSearchClass);
+ const searchBtn = search.find(dotIconSearchClass);
+
+ await searchBtn.trigger('click');
+ await onSearch((str: string) => {
+ expect(str).toBe('test');
+ });
+ expect(onSearch).toBeCalledTimes(1);
+
+ // test input focus after trigger search button
+ // TODO: 在单元测试环境中,input虽然处于focus状态,但是无法通过document.activeElement获取到
+ // expect(input.element === document.activeElement).toBe(true);
+ wrapper.unmount();
+ });
+
+ it('props v-model should work well.', async () => {
+ const value = ref('test');
+
+ const wrapper = mount({
+ components: { DSearch },
+ template: `
+
+ `,
+ setup() {
+ return {
+ value,
+ };
+ },
+ });
+
+ const search = wrapper.find(dotSearchClass);
+ const input = search.find('input');
+ expect(input.element.value).toBe('test');
+
+ // test v-model
+ await input.setValue('def');
+ expect(value.value).toBe('def');
+
+ value.value = 'change value';
+ await nextTick();
+ expect(input.element.value).toBe('change value');
+
+ wrapper.unmount();
+ });
+
+ it('props size(sm/md/lg) should work well.', async () => {
+ const size = ref('');
+
+ const wrapper = mount({
+ components: { DSearch },
+ template: `
+
+ `,
+ setup() {
+ return {
+ size,
+ };
+ },
+ });
+
+ const search = wrapper.find(dotSearchClass);
+ const input = search.find('input');
+
expect(input.classes()).not.toContain(smSearchClass);
expect(input.classes()).not.toContain(lgSearchClass);
@@ -55,81 +139,153 @@ describe('search test', () => {
expect(wrapper.classes()).not.toContain(smSearchClass);
expect(wrapper.classes()).toContain(lgSearchClass);
- // test v-model
- await input.setValue('def');
- expect(value.value).toBe('def');
+ wrapper.unmount();
+ });
- value.value = 'change value';
- await nextTick();
- expect(input.element.value).toBe('change value');
+ it('props size priority', async () => {
+ const dFormSize = ref('lg');
+ const dSearchSize = ref('sm');
- // test clear
- const clear = wrapper.find(dotClearSearchClass);
- await clear.trigger('click');
- expect(input.element.value).toBe('');
- expect(value.value).toBe('');
+ const wrapper = mount({
+ components: {DSearch, DForm, DFormItem},
+ template: `
+
+
+
+
+ `,
+ setup() {
+ return {
+ dFormSize,
+ dSearchSize
+ };
+ },
+ });
- // test input focus after trigger clear button
- // TODO: 在单元测试环境中,input虽然处于focus状态,但是无法通过document.activeElement获取到
- // expect(input.element === document.activeElement).toBe(true);
+ const dSearch = wrapper.find(dotSearchClass);
+ // form 与 元素同时存在size 属性,以元素为准。
+ expect(dSearch.classes()).toContain(smSearchClass);
- // test disabled
- expect(input.attributes('disabled')).toBe(undefined);
- expect(wrapper.classes()).not.toContain(disableSearchClass);
+ dSearchSize.value = '';
+ await nextTick();
- disabled.value = true;
+ // 元素不存在 size ,form 存在,以表单为准
+ expect(dSearch.classes()).toContain(lgSearchClass);
+
+ dFormSize.value = '';
await nextTick();
- expect(wrapper.classes()).toContain(disableSearchClass);
- expect(input.attributes('disabled')).toBe('');
+
+ // form 与 元素都不存在 size 属性,使用默认值。
+ expect(dSearch.classes()).not.toContain(smSearchClass);
+ expect(dSearch.classes()).not.toContain(lgSearchClass);
+
+ wrapper.unmount();
});
- it('should event correctly', async () => {
+ it('clear operation should work well.', async () => {
const value = ref('test');
- const onSearch = jest.fn();
const wrapper = mount({
components: { DSearch },
template: `
`,
setup() {
return {
value,
- onSearch,
};
},
});
+
const search = wrapper.find(dotSearchClass);
- const searchBtn = search.find(dotIconSearchClass);
- // const input = search.find('input');
- await searchBtn.trigger('click');
- await onSearch((str: string) => {
- expect(str).toBe('test');
+ const input = search.find('input');
+ expect(input.element.value).toBe('test');
+
+ // test clear
+ const clear = wrapper.find(dotClearSearchClass);
+ await clear.trigger('click');
+ expect(input.element.value).toBe('');
+ expect(value.value).toBe('');
+
+ wrapper.unmount();
+ });
+
+ it('props disabled should work well.', async () => {
+ const disabled = ref(false);
+ const wrapper = mount({
+ components: { DSearch },
+ template: `
+
+ `,
+ setup() {
+ return {
+ disabled,
+ };
+ },
});
- expect(onSearch).toBeCalledTimes(1);
+ const search = wrapper.find(dotSearchClass);
+ const input = search.find('input');
- // test input focus after trigger search button
- // TODO: 在单元测试环境中,input虽然处于focus状态,但是无法通过document.activeElement获取到
- // expect(input.element === document.activeElement).toBe(true);
+ // test disabled
+ expect(input.attributes('disabled')).toBe(undefined);
+ expect(wrapper.classes()).not.toContain(disableSearchClass);
+
+ disabled.value = true;
+ await nextTick();
+ expect(wrapper.classes()).toContain(disableSearchClass);
+ expect(input.attributes('disabled')).toBe('');
+
+ wrapper.unmount();
});
- it.todo('props size(sm/md/lg) should work well.');
+ it('props icon-position(right/left) should work well.', async () => {
+ const wrapper = mount(DSearch);
- it.todo('props auto-focus should work well.');
+ const iconSearch = wrapper.find(dotIconSearchClass);
- it.todo('props is-keyup-search should work well.');
+ expect(iconSearch.exists()).toBe(true);
- it.todo('props delay should work well.');
+ expect(wrapper.classes()).toContain(rightIconPositionClass);
+
+ await wrapper.setProps({
+ iconPosition: 'left',
+ });
+ expect(wrapper.classes()).toContain(leftIconPositionClass);
- it.todo('props disabled should work well.');
+ await wrapper.setProps({
+ iconPosition: 'right',
+ });
+ expect(wrapper.classes()).toContain(rightIconPositionClass);
+
+ wrapper.unmount();
+ });
- it.todo('props icon-position should work well.');
+ it('props no-border should work well.', async () => {
+ const wrapper = mount(DSearch);
+
+ expect(wrapper.classes()).not.toContain(noBorderClass);
+
+ await wrapper.setProps({
+ noBorder: true,
+ });
+
+ expect(wrapper.classes()).toContain(noBorderClass);
+
+ wrapper.unmount();
+ });
it.todo('props placeholder should work well.');
- it.todo('props no-border should work well.');
+ it.todo('props auto-focus should work well.');
+
+ it.todo('props is-keyup-search should work well.');
+
+ it.todo('props delay should work well.');
it.todo('props max-length should work well.');
});
diff --git a/packages/devui-vue/devui/search/src/components/search-close-icon.tsx b/packages/devui-vue/devui/search/src/components/search-close-icon.tsx
new file mode 100644
index 0000000000..15cc1e88e1
--- /dev/null
+++ b/packages/devui-vue/devui/search/src/components/search-close-icon.tsx
@@ -0,0 +1,11 @@
+const SearchCloseIcon = (): JSX.Element => (
+
+);
+export default SearchCloseIcon;
diff --git a/packages/devui-vue/devui/search/src/components/search-icon.tsx b/packages/devui-vue/devui/search/src/components/search-icon.tsx
new file mode 100644
index 0000000000..616065e652
--- /dev/null
+++ b/packages/devui-vue/devui/search/src/components/search-icon.tsx
@@ -0,0 +1,10 @@
+const SearchIcon = (): JSX.Element => (
+
+);
+export default SearchIcon;
diff --git a/packages/devui-vue/devui/search/src/composables/use-search-class.ts b/packages/devui-vue/devui/search/src/composables/use-search-class.ts
index ab07917553..e662e94dcb 100644
--- a/packages/devui-vue/devui/search/src/composables/use-search-class.ts
+++ b/packages/devui-vue/devui/search/src/composables/use-search-class.ts
@@ -1,29 +1,35 @@
/**
* 定义组件class
*/
-import { computed, ComputedRef } from 'vue';
+import { computed, inject } from 'vue';
import type { Ref } from 'vue';
-import { SearchProps } from '../search-types';
+import { UseSearchClassTypes, SearchProps } from '../search-types';
+import { FORM_TOKEN } from '../../../form';
import { useNamespace } from '../../../shared/hooks/use-namespace';
-const SIZE_CLASS = {
- lg: 'lg',
- md: 'md',
- sm: 'sm',
-} as const;
-const ICON_POSITION = {
- right: 'right',
- left: 'left',
-};
-const ns = useNamespace('search');
+export const useSearchClass = (props: SearchProps, isFocus: Ref): UseSearchClassTypes => {
+ const formContext = inject(FORM_TOKEN, undefined);
+
+ const ICON_POSITION = {
+ right: 'right',
+ left: 'left',
+ };
+
+ const ns = useNamespace('search');
-export const getRootClass = (props: SearchProps, isFocus: Ref): ComputedRef => {
- return computed(() => ({
+ const searchSize = computed(() => props.size || formContext?.size || 'md');
+
+ const rootClass = computed(() => ({
[ns.b()]: true,
[ns.m('focus')]: isFocus.value,
[ns.m('disabled')]: props.disabled,
[ns.m('no-border')]: props.noBorder,
- [ns.m(props.size)]: SIZE_CLASS[props.size],
+ [ns.m(searchSize.value)]: !!searchSize.value,
[ns.m(props.iconPosition)]: ICON_POSITION[props.iconPosition],
}));
+
+ return {
+ rootClass,
+ searchSize
+ };
};
diff --git a/packages/devui-vue/devui/search/src/search-types.ts b/packages/devui-vue/devui/search/src/search-types.ts
index 9edc84d615..75ca6e7b42 100644
--- a/packages/devui-vue/devui/search/src/search-types.ts
+++ b/packages/devui-vue/devui/search/src/search-types.ts
@@ -5,8 +5,7 @@ export type IconPosition = 'right' | 'left';
export const searchProps = {
size: {
- type: String as PropType,
- default: 'md',
+ type: String as PropType
},
placeholder: {
type: String,
@@ -56,6 +55,11 @@ export const searchProps = {
export type SearchProps = ExtractPropTypes;
+export interface UseSearchClassTypes {
+ rootClass: ComputedRef<{ [p: string]: string | boolean }>;
+ searchSize: ComputedRef;
+}
+
export interface KeywordsReturnTypes {
keywords: Ref;
clearIconShow: ComputedRef;
diff --git a/packages/devui-vue/devui/search/src/search.scss b/packages/devui-vue/devui/search/src/search.scss
index ac654e09a3..59cbad5404 100644
--- a/packages/devui-vue/devui/search/src/search.scss
+++ b/packages/devui-vue/devui/search/src/search.scss
@@ -44,18 +44,23 @@
}
}
- svg.svg-icon-clear path,
- svg.svg-icon-search path {
- fill: $devui-icon-text;
+ &__clear,
+ &__icon {
+ @include size($devui-size-md, $devui-size-md);
+ @include flex;
+
+ & svg {
+ path {
+ fill: $devui-icon-fill;
+ }
+ @include size($devui-font-size-md, $devui-font-size-md);
+ }
}
&__clear {
position: absolute;
right: $devui-size-md;
cursor: pointer;
- height: 100%;
- @include size($devui-size-md, $devui-size-md);
- @include flex;
&::after {
content: '';
@@ -76,9 +81,6 @@
z-index: 1;
right: 0;
top: 0;
- width: $devui-size-md;
- height: $devui-size-md;
- @include flex;
}
&--sm {
@@ -91,15 +93,15 @@
}
}
- .#{$devui-prefix}-search__icon {
- font-size: $devui-font-size-sm;
+ .#{$devui-prefix}-search__icon, .#{$devui-prefix}-search__clear {
@include size($devui-size-sm, $devui-size-sm);
+
+ svg {
+ @include size($devui-font-size-sm, $devui-font-size-sm);
+ }
}
.#{$devui-prefix}-search__clear {
- font-size: $devui-font-size-sm;
- @include size($devui-size-sm, $devui-size-sm);
-
right: $devui-size-sm;
}
}
@@ -114,15 +116,15 @@
}
}
- .#{$devui-prefix}-search__icon {
- font-size: $devui-font-size-lg;
+ .#{$devui-prefix}-search__icon, .#{$devui-prefix}-search__clear {
@include size($devui-size-lg, $devui-size-lg);
+
+ svg {
+ @include size($devui-font-size-lg, $devui-font-size-lg);
+ }
}
.#{$devui-prefix}-search__clear {
- font-size: $devui-font-size-lg;
- @include size($devui-size-lg, $devui-size-lg);
-
right: $devui-size-lg;
}
}
diff --git a/packages/devui-vue/devui/search/src/search.tsx b/packages/devui-vue/devui/search/src/search.tsx
index 7c285ff5b4..c7f3efd888 100644
--- a/packages/devui-vue/devui/search/src/search.tsx
+++ b/packages/devui-vue/devui/search/src/search.tsx
@@ -1,13 +1,14 @@
import { defineComponent, getCurrentInstance, ref } from 'vue';
import { SearchProps, searchProps } from './search-types';
-import { getRootClass } from './composables/use-search-class';
+import { useSearchClass } from './composables/use-search-class';
import { keywordsHandles } from './composables/use-search-keywords';
import { keydownHandles } from './composables/use-search-keydown';
import DInput from '../../input/src/input';
-import { Icon } from '../../icon';
import { useNamespace } from '../../shared/hooks/use-namespace';
import './search.scss';
import { createI18nTranslate } from '../../locale/create';
+import SearchCloseIcon from './components/search-close-icon';
+import SearchIcon from './components/search-icon';
export default defineComponent({
name: 'DSearch',
@@ -19,7 +20,7 @@ export default defineComponent({
const ns = useNamespace('search');
const isFocus = ref(false);
- const rootClasses = getRootClass(props, isFocus);
+ const {rootClass, searchSize} = useSearchClass(props, isFocus);
const { keywords, clearIconShow, onClearHandle } = keywordsHandles(ctx, props);
const { onInputKeydown, onClickHandle, useEmitKeyword } = keydownHandles(ctx, keywords, props);
@@ -40,7 +41,7 @@ export default defineComponent({
return () => {
const inputProps = {
- size: props.size,
+ size: searchSize.value,
disabled: props.disabled,
autoFocus: props.autoFocus,
modelValue: keywords.value,
@@ -50,22 +51,23 @@ export default defineComponent({
onFocus: onFocus,
onBlur: onBlur,
};
+
return (
-