diff --git a/packages/form/src/utils/form.ts b/packages/form/src/utils/form.ts index 9881364c..06d088f2 100644 --- a/packages/form/src/utils/form.ts +++ b/packages/form/src/utils/form.ts @@ -46,8 +46,8 @@ interface DefaultItem { names?: string[]; } -const isTableSelect = (type?: string | TypeFunction) => - typeof type === 'string' && ['table-select', 'tableSelect'].includes(type); +const TABLE_SELECT_TYPES = new Set(['table-select', 'tableSelect']); +const isTableSelect = (type?: string | TypeFunction) => typeof type === 'string' && TABLE_SELECT_TYPES.has(type); const asyncLoadConfig = (value: FormValue, initValue: FormValue, { asyncLoad, name, type }: HtmlField) => { // 富文本配置了异步加载 @@ -57,9 +57,15 @@ const asyncLoadConfig = (value: FormValue, initValue: FormValue, { asyncLoad, na } }; -const isMultipleValue = (type?: string | TypeFunction) => - typeof type === 'string' && - ['checkbox-group', 'checkboxGroup', 'table', 'cascader', 'group-list', 'groupList'].includes(type); +const MULTIPLE_VALUE_TYPES = new Set([ + 'checkbox-group', + 'checkboxGroup', + 'table', + 'cascader', + 'group-list', + 'groupList', +]); +const isMultipleValue = (type?: string | TypeFunction) => typeof type === 'string' && MULTIPLE_VALUE_TYPES.has(type); const initItemsValue = ( mForm: FormState | undefined, @@ -309,7 +315,8 @@ export const datetimeFormatter = ( let time: string | number; if (['x', 'timestamp'].includes(format)) { time = dayjs(Number.isNaN(Number(v)) ? v : Number(v)).valueOf(); - } else if ((typeof v === 'string' && v.includes('Z')) || v.constructor === Date) { + } else if ((typeof v === 'string' && v.includes('Z')) || v instanceof Date) { + // dayjs.extend 内部有防重复机制 (plugin.$i),无需额外判断 dayjs.extend(utc); // UTC字符串时间或Date对象格式化为北京时间 time = dayjs(v).utcOffset(8).format(format); @@ -325,10 +332,10 @@ export const datetimeFormatter = ( return defaultValue; }; -export const getDataByPage = (data: any[] = [], pagecontext: number, pagesize: number) => - data.filter( - (item: any, index: number) => index >= pagecontext * pagesize && index + 1 <= (pagecontext + 1) * pagesize, - ); +export const getDataByPage = (data: any[] = [], pagecontext: number, pagesize: number) => { + const start = pagecontext * pagesize; + return data.slice(start, start + pagesize); +}; export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortKey?: string) => { if (newIndex === oldIndex) { @@ -339,7 +346,9 @@ export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortK return data; } - const newData = data.toSpliced(newIndex, 0, ...data.splice(oldIndex, 1)); + // 先取出要移动的元素,再使用 toSpliced 避免修改原数组 + const item = data[oldIndex]; + const newData = data.toSpliced(oldIndex, 1).toSpliced(newIndex, 0, item); if (sortKey) { for (let i = newData.length - 1, v = 0; i >= 0; i--, v++) { @@ -352,8 +361,8 @@ export const sortArray = (data: any[], newIndex: number, oldIndex: number, sortK export const sortChange = (data: any[], { prop, order }: SortProp) => { if (order === 'ascending') { - data = data.sort((a: any, b: any) => a[prop] - b[prop]); + data.sort((a: any, b: any) => a[prop] - b[prop]); } else if (order === 'descending') { - data = data.sort((a: any, b: any) => b[prop] - a[prop]); + data.sort((a: any, b: any) => b[prop] - a[prop]); } }; diff --git a/packages/form/tests/unit/utils/form.spec.ts b/packages/form/tests/unit/utils/form.spec.ts index 2e6d47b5..ba9c0eef 100644 --- a/packages/form/tests/unit/utils/form.spec.ts +++ b/packages/form/tests/unit/utils/form.spec.ts @@ -18,7 +18,17 @@ import { describe, expect, test } from 'vitest'; import type { FormState } from '@form/index'; -import { datetimeFormatter, display, filterFunction, getRules, initValue, sortArray } from '@form/utils/form'; +import { + createValues, + datetimeFormatter, + display, + filterFunction, + getDataByPage, + getRules, + initValue, + sortArray, + sortChange, +} from '@form/utils/form'; // form state mock 数据 const mForm: FormState = { @@ -70,6 +80,10 @@ describe('display', () => { expect(display(mForm, () => false, {})).toBe(false); expect(display(mForm, () => 1, {})).toBe(1); }); + + test('config 为 expand', () => { + expect(display(mForm, 'expand', {})).toBe('expand'); + }); }); describe('getRules', () => { @@ -419,4 +433,136 @@ describe('sortArray', () => { expect(sortArray(data, 5, 1)).toEqual(data); expect(sortArray(data, 1, 5)).toEqual(data); }); + + test('不修改原数组', () => { + const data = [1, 2, 3, 4, 5]; + const original = [...data]; + + sortArray(data, 0, 3); + + expect(data).toEqual(original); + }); + + test('返回的新数组与原数组不是同一引用', () => { + const data = [1, 2, 3, 4, 5]; + + const result = sortArray(data, 0, 3); + + expect(result).not.toBe(data); + }); +}); + +describe('getDataByPage', () => { + test('获取第一页数据', () => { + const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + expect(getDataByPage(data, 0, 3)).toEqual([1, 2, 3]); + }); + + test('获取中间页数据', () => { + const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + expect(getDataByPage(data, 1, 3)).toEqual([4, 5, 6]); + }); + + test('获取最后一页数据(不足一页)', () => { + const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + expect(getDataByPage(data, 3, 3)).toEqual([10]); + }); + + test('页码超出范围返回空数组', () => { + const data = [1, 2, 3, 4, 5]; + + expect(getDataByPage(data, 10, 3)).toEqual([]); + }); + + test('空数组返回空数组', () => { + expect(getDataByPage([], 0, 10)).toEqual([]); + }); + + test('不修改原数组', () => { + const data = [1, 2, 3, 4, 5]; + const original = [...data]; + + getDataByPage(data, 0, 2); + + expect(data).toEqual(original); + }); + + test('默认空数组参数', () => { + expect(getDataByPage(undefined as any, 0, 10)).toEqual([]); + }); +}); + +describe('sortChange', () => { + test('升序排序', () => { + const data = [{ value: 3 }, { value: 1 }, { value: 2 }]; + + sortChange(data, { prop: 'value', order: 'ascending' }); + + expect(data).toEqual([{ value: 1 }, { value: 2 }, { value: 3 }]); + }); + + test('降序排序', () => { + const data = [{ value: 1 }, { value: 3 }, { value: 2 }]; + + sortChange(data, { prop: 'value', order: 'descending' }); + + expect(data).toEqual([{ value: 3 }, { value: 2 }, { value: 1 }]); + }); + + test('无效的order不进行排序', () => { + const data = [{ value: 3 }, { value: 1 }, { value: 2 }]; + const original = [{ value: 3 }, { value: 1 }, { value: 2 }]; + + sortChange(data, { prop: 'value', order: '' as any }); + + expect(data).toEqual(original); + }); + + test('空数组不报错', () => { + const data: any[] = []; + + expect(() => sortChange(data, { prop: 'value', order: 'ascending' })).not.toThrow(); + }); + + test('原地修改数组', () => { + const data = [{ value: 2 }, { value: 1 }]; + + sortChange(data, { prop: 'value', order: 'ascending' }); + + expect(data[0].value).toBe(1); + expect(data[1].value).toBe(2); + }); +}); + +describe('createValues', () => { + test('config为空数组返回空对象', () => { + expect(createValues(mForm, [], {})).toEqual({}); + }); + + test('config不是数组返回传入的value', () => { + const value = { a: 1 }; + + expect(createValues(mForm, undefined as any, {}, value)).toEqual(value); + }); + + test('处理简单的text配置', () => { + const config = [{ type: 'text', name: 'field1' }]; + const initValues = { field1: 'hello' }; + + const result = createValues(mForm, config, initValues, {}); + + expect(result.field1).toBe('hello'); + }); + + test('处理number类型转换', () => { + const config = [{ type: 'number', name: 'num' }]; + const initValues = { num: '123' }; + + const result = createValues(mForm, config, initValues, {}); + + expect(result.num).toBe(123); + }); });