Преглед на файлове

chore: disable sorting of non-core folder object fields (#4547)

* chore: disable sorting of non-core folder object fields

* chore: ci error
Vben преди 11 месеца
родител
ревизия
d1e1256202

+ 8 - 1
internal/lint-configs/eslint-config/src/custom-config.ts

@@ -15,10 +15,17 @@ const customConfig: Linter.Config[] = [
     },
   },
   {
-    files: ['packages/effects/**/**', 'packages/types/**/**'],
+    files: [
+      'apps/**/**',
+      'packages/effects/**/**',
+      'packages/utils/**/**',
+      'packages/types/**/**',
+      'packages/locales/**/**',
+    ],
     ignores: restrictedImportIgnores,
     rules: {
       'perfectionist/sort-interfaces': 'off',
+      'perfectionist/sort-objects': 'off',
     },
   },
   {

+ 77 - 1
packages/@core/base/shared/src/utils/__tests__/util.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, it } from 'vitest';
 
-import { bindMethods } from '../util';
+import { bindMethods, getNestedValue } from '../util';
 
 class TestClass {
   public value: string;
@@ -78,3 +78,79 @@ describe('bindMethods', () => {
     expect(value).toBe('test');
   });
 });
+
+describe('getNestedValue', () => {
+  interface UserProfile {
+    age: number;
+    name: string;
+  }
+
+  interface UserSettings {
+    theme: string;
+  }
+
+  interface Data {
+    user: {
+      profile: UserProfile;
+      settings: UserSettings;
+    };
+  }
+
+  const data: Data = {
+    user: {
+      profile: {
+        age: 25,
+        name: 'Alice',
+      },
+      settings: {
+        theme: 'dark',
+      },
+    },
+  };
+
+  it('should get a nested value when the path is valid', () => {
+    const result = getNestedValue(data, 'user.profile.name');
+    expect(result).toBe('Alice');
+  });
+
+  it('should return undefined for non-existent property', () => {
+    const result = getNestedValue(data, 'user.profile.gender');
+    expect(result).toBeUndefined();
+  });
+
+  it('should return undefined when accessing a non-existent deep path', () => {
+    const result = getNestedValue(data, 'user.nonexistent.field');
+    expect(result).toBeUndefined();
+  });
+
+  it('should return undefined if a middle level is undefined', () => {
+    const result = getNestedValue({ user: undefined }, 'user.profile.name');
+    expect(result).toBeUndefined();
+  });
+
+  it('should return the correct value for a nested setting', () => {
+    const result = getNestedValue(data, 'user.settings.theme');
+    expect(result).toBe('dark');
+  });
+
+  it('should work for a single-level path', () => {
+    const result = getNestedValue({ a: 1, b: 2 }, 'b');
+    expect(result).toBe(2);
+  });
+
+  it('should return the entire object if path is empty', () => {
+    expect(() => getNestedValue(data, '')()).toThrow();
+  });
+
+  it('should handle paths with array indexes', () => {
+    const complexData = { list: [{ name: 'Item1' }, { name: 'Item2' }] };
+    const result = getNestedValue(complexData, 'list.1.name');
+    expect(result).toBe('Item2');
+  });
+
+  it('should return undefined when accessing an out-of-bounds array index', () => {
+    const complexData = { list: [{ name: 'Item1' }] };
+    const result = getNestedValue(complexData, 'list.2.name');
+    expect(result).toBeUndefined();
+  });
+});

+ 9 - 0
packages/@core/base/shared/src/utils/merge.ts

@@ -1 +1,10 @@
+import { createDefu } from 'defu';
+
 export { createDefu as createMerge, defu as merge } from 'defu';
+
+export const mergeWithArrayOverride = createDefu((originObj, key, updates) => {
+  if (Array.isArray(originObj[key]) && Array.isArray(updates)) {
+    originObj[key] = updates;
+    return true;
+  }
+});

+ 25 - 0
packages/@core/base/shared/src/utils/util.ts

@@ -17,3 +17,28 @@ export function bindMethods<T extends object>(instance: T): void {
     }
   });
 }
+
+/**
+ * 获取嵌套对象的字段值
+ * @param obj - 要查找的对象
+ * @param path - 用于查找字段的路径,使用小数点分隔
+ * @returns 字段值,或者未找到时返回 undefined
+ */
+export function getNestedValue<T>(obj: T, path: string): any {
+  if (typeof path !== 'string' || path.length === 0) {
+    throw new Error('Path must be a non-empty string');
+  }
+  // 把路径字符串按 "." 分割成数组
+  const keys = path.split('.') as (number | string)[];
+
+  let current: any = obj;
+
+  for (const key of keys) {
+    if (current === null || current === undefined) {
+      return undefined;
+    }
+    current = current[key as keyof typeof current];
+  }
+
+  return current;
+}

+ 0 - 18
packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts

@@ -1,24 +1,7 @@
-// 假设这个文件为 FormApi.ts
 import { beforeEach, describe, expect, it, vi } from 'vitest';
 
 import { FormApi } from '../src/form-api';
 
-vi.mock('@vben-core/shared/utils', () => ({
-  bindMethods: vi.fn(),
-  createMerge: vi.fn((mergeFn) => {
-    return (stateOrFn: any, prev: any) => {
-      mergeFn(prev, 'key', stateOrFn);
-      return { ...prev, ...stateOrFn };
-    };
-  }),
-  isFunction: (fn: any) => typeof fn === 'function',
-  StateHandler: vi.fn().mockImplementation(() => ({
-    reset: vi.fn(),
-    setConditionTrue: vi.fn(),
-    waitForCondition: vi.fn().mockResolvedValue(true),
-  })),
-}));
-
 describe('formApi', () => {
   let formApi: FormApi;
 
@@ -128,7 +111,6 @@ describe('formApi', () => {
   it('should unmount form and reset state', () => {
     formApi.unmounted();
     expect(formApi.isMounted).toBe(false);
-    expect(formApi.stateHandler.reset).toHaveBeenCalled();
   });
 
   it('should validate form', async () => {

+ 7 - 11
packages/@core/ui-kit/form-ui/src/form-api.ts

@@ -12,20 +12,13 @@ import { toRaw } from 'vue';
 import { Store } from '@vben-core/shared/store';
 import {
   bindMethods,
-  createMerge,
   isFunction,
+  mergeWithArrayOverride,
   StateHandler,
 } from '@vben-core/shared/utils';
 
 import { objectPick } from '@vueuse/core';
 
-const merge = createMerge((originObj, key, updates) => {
-  if (Array.isArray(originObj[key]) && Array.isArray(updates)) {
-    originObj[key] = updates;
-    return true;
-  }
-});
-
 function getDefaultState(): VbenFormProps {
   return {
     actionWrapperClass: '',
@@ -218,10 +211,10 @@ export class FormApi {
   ) {
     if (isFunction(stateOrFn)) {
       this.store.setState((prev) => {
-        return merge(stateOrFn(prev), prev);
+        return mergeWithArrayOverride(stateOrFn(prev), prev);
       });
     } else {
-      this.store.setState((prev) => merge(stateOrFn, prev));
+      this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev));
     }
   }
 
@@ -287,7 +280,10 @@ export class FormApi {
     currentSchema.forEach((schema, index) => {
       const updatedData = updatedMap[schema.fieldName];
       if (updatedData) {
-        currentSchema[index] = merge(updatedData, schema) as FormSchema;
+        currentSchema[index] = mergeWithArrayOverride(
+          updatedData,
+          schema,
+        ) as FormSchema;
       }
     });
     this.setState({ schema: currentSchema });