befly 3.10.1 → 3.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.gitignore +0 -0
  2. package/configs/presetFields.ts +10 -0
  3. package/configs/presetRegexp.ts +225 -0
  4. package/package.json +15 -16
  5. package/tests/_mocks/mockSqliteDb.ts +0 -204
  6. package/tests/addonHelper-cache.test.ts +0 -32
  7. package/tests/api-integration-array-number.test.ts +0 -282
  8. package/tests/apiHandler-routePath-only.test.ts +0 -32
  9. package/tests/befly-config-env.test.ts +0 -78
  10. package/tests/cacheHelper.test.ts +0 -323
  11. package/tests/cacheKeys.test.ts +0 -41
  12. package/tests/checkApi-routePath-strict.test.ts +0 -166
  13. package/tests/checkMenu.test.ts +0 -346
  14. package/tests/checkTable-smoke.test.ts +0 -157
  15. package/tests/cipher.test.ts +0 -249
  16. package/tests/dbDialect-cache.test.ts +0 -23
  17. package/tests/dbDialect.test.ts +0 -46
  18. package/tests/dbHelper-advanced.test.ts +0 -723
  19. package/tests/dbHelper-all-array-types.test.ts +0 -316
  20. package/tests/dbHelper-array-serialization.test.ts +0 -258
  21. package/tests/dbHelper-batch-write.test.ts +0 -90
  22. package/tests/dbHelper-columns.test.ts +0 -234
  23. package/tests/dbHelper-execute.test.ts +0 -187
  24. package/tests/dbHelper-joins.test.ts +0 -221
  25. package/tests/fields-redis-cache.test.ts +0 -127
  26. package/tests/fields-validate.test.ts +0 -99
  27. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  28. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  29. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  30. package/tests/getClientIp.test.ts +0 -54
  31. package/tests/integration.test.ts +0 -189
  32. package/tests/jwt.test.ts +0 -65
  33. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  34. package/tests/logger.test.ts +0 -325
  35. package/tests/redisHelper.test.ts +0 -495
  36. package/tests/redisKeys.test.ts +0 -9
  37. package/tests/scanConfig.test.ts +0 -144
  38. package/tests/scanFiles-routePath.test.ts +0 -46
  39. package/tests/smoke-sql.test.ts +0 -24
  40. package/tests/sqlBuilder-advanced.test.ts +0 -608
  41. package/tests/sqlBuilder.test.ts +0 -209
  42. package/tests/sync-connection.test.ts +0 -183
  43. package/tests/sync-init-guard.test.ts +0 -105
  44. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  45. package/tests/syncApi-obsolete-records.test.ts +0 -69
  46. package/tests/syncApi-type-compat.test.ts +0 -72
  47. package/tests/syncDev-permissions.test.ts +0 -81
  48. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  49. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  50. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  51. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  52. package/tests/syncMenu-paths.test.ts +0 -59
  53. package/tests/syncTable-apply.test.ts +0 -279
  54. package/tests/syncTable-array-number.test.ts +0 -160
  55. package/tests/syncTable-constants.test.ts +0 -101
  56. package/tests/syncTable-db-integration.test.ts +0 -237
  57. package/tests/syncTable-ddl.test.ts +0 -245
  58. package/tests/syncTable-helpers.test.ts +0 -99
  59. package/tests/syncTable-schema.test.ts +0 -99
  60. package/tests/syncTable-testkit.test.ts +0 -25
  61. package/tests/syncTable-types.test.ts +0 -122
  62. package/tests/tableRef-and-deserialize.test.ts +0 -67
  63. package/tests/util.test.ts +0 -100
  64. package/tests/validator-array-number.test.ts +0 -310
  65. package/tests/validator-default.test.ts +0 -373
  66. package/tests/validator.test.ts +0 -679
@@ -1,72 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { syncApi } from "../sync/syncApi.js";
4
-
5
- describe("syncApi - type compatibility", () => {
6
- test("缺少 type 时应视为 api;非 api type 应被跳过", async () => {
7
- const existingRecords = [
8
- { id: 1, routePath: "/api/app/keep", name: "Keep", addonName: "", state: 0 },
9
- { id: 2, routePath: "/api/app/skip", name: "Skip", addonName: "", state: 0 }
10
- ];
11
-
12
- const calls = {
13
- delForceBatch: [] as any[],
14
- getAllArgs: null as any
15
- };
16
-
17
- const dbHelper = {
18
- tableExists: async () => true,
19
- updBatch: async () => 0,
20
- insBatch: async () => [],
21
- getAll: async (options: any) => {
22
- calls.getAllArgs = options;
23
- return { lists: existingRecords };
24
- },
25
- delForceBatch: async (_table: any, ids: any[]) => {
26
- calls.delForceBatch.push(ids);
27
- return ids.length;
28
- }
29
- } as any;
30
-
31
- const ctx = {
32
- db: dbHelper,
33
- addons: [],
34
- cache: {
35
- cacheApis: async () => {},
36
- rebuildRoleApiPermissions: async () => {}
37
- }
38
- } as any;
39
-
40
- const apiItems = [
41
- // 不带 type:应按 api 处理,保留 keep
42
- {
43
- source: "app",
44
- sourceName: "项目",
45
- filePath: "DUMMY",
46
- relativePath: "keep",
47
- fileName: "keep",
48
- moduleName: "app_keep",
49
- name: "Keep",
50
- routePath: "/api/app/keep",
51
- addonName: "",
52
- fileBaseName: "keep.ts",
53
- fileDir: "DUMMY",
54
- content: { name: "Keep", handler: async () => {} }
55
- },
56
- // 带非 api type:应被跳过,因此 DB 中的 skip 会被当作“配置不存在”而删除
57
- {
58
- type: "menu",
59
- name: "Skip",
60
- routePath: "/api/app/skip",
61
- addonName: ""
62
- }
63
- ] as any;
64
-
65
- await syncApi(ctx, apiItems);
66
-
67
- expect(calls.getAllArgs?.fields).toEqual(["id", "routePath", "name", "addonName", "state"]);
68
-
69
- expect(calls.delForceBatch).toHaveLength(1);
70
- expect(calls.delForceBatch[0]).toEqual([2]);
71
- });
72
- });
@@ -1,81 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { syncDev } from "../sync/syncDev.js";
4
-
5
- describe("syncDev - dev role permissions", () => {
6
- test("dev 角色应拥有所有菜单和接口(state>=0)", async () => {
7
- const calls = {
8
- getAll: [],
9
- insData: [],
10
- updData: [],
11
- rebuildRoleApiPermissionsCount: 0
12
- };
13
-
14
- let nextId = 100;
15
-
16
- const ctx = {
17
- db: {
18
- tableExists: async (table) => {
19
- return table === "addon_admin_admin" || table === "addon_admin_role" || table === "addon_admin_menu" || table === "addon_admin_api";
20
- },
21
- getAll: async (options) => {
22
- calls.getAll.push(options);
23
-
24
- if (options?.table === "addon_admin_menu") {
25
- return {
26
- lists: [
27
- { path: "/dashboard", state: 0 },
28
- { path: "/permission/role", state: 0 }
29
- ]
30
- };
31
- }
32
- if (options?.table === "addon_admin_api") {
33
- return {
34
- lists: [
35
- { routePath: "/api/health", state: 0 },
36
- { routePath: "/api/addon/addonAdmin/auth/login", state: 0 }
37
- ]
38
- };
39
- }
40
-
41
- return { lists: [] };
42
- },
43
- getOne: async (_options) => {
44
- // 让所有角色/管理员都走插入逻辑,便于断言插入数据
45
- return null;
46
- },
47
- insData: async (options) => {
48
- calls.insData.push(options);
49
- nextId += 1;
50
- return nextId;
51
- },
52
- updData: async (options) => {
53
- calls.updData.push(options);
54
- return 1;
55
- }
56
- },
57
- cache: {
58
- rebuildRoleApiPermissions: async () => {
59
- calls.rebuildRoleApiPermissionsCount += 1;
60
- }
61
- }
62
- };
63
-
64
- await syncDev(ctx, { devEmail: "dev@qq.com", devPassword: "dev-password" });
65
-
66
- // 断言读取菜单/接口时按 state>=0 查询
67
- const menuGetAll = calls.getAll.find((c) => c?.table === "addon_admin_menu");
68
- expect(menuGetAll?.where?.state$gte).toBe(0);
69
-
70
- const apiGetAll = calls.getAll.find((c) => c?.table === "addon_admin_api");
71
- expect(apiGetAll?.where?.state$gte).toBe(0);
72
-
73
- // 断言 dev 角色写入时包含“所有路径”(按查询结果写入;统一为 pathname,不包含 method)
74
- const devRoleInsert = calls.insData.find((c) => c?.table === "addon_admin_role" && c?.data?.code === "dev");
75
- expect(devRoleInsert).toBeTruthy();
76
- expect(devRoleInsert.data.menus).toEqual(["/dashboard", "/permission/role"]);
77
- expect(devRoleInsert.data.apis).toEqual(["/api/health", "/api/addon/addonAdmin/auth/login"]);
78
-
79
- expect(calls.rebuildRoleApiPermissionsCount).toBe(0);
80
- });
81
- });
@@ -1,88 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- import { checkMenu } from "../checks/checkMenu.js";
6
- import { syncMenu } from "../sync/syncMenu.js";
7
-
8
- describe("syncMenu - disableMenus hard delete", () => {
9
- test("命中 disableMenus 的菜单应被强制删除(不分 state)", async () => {
10
- const originalCwd = process.cwd();
11
- const projectDir = join(originalCwd, "temp", `syncMenu-disableMenus-hard-delete-${Date.now()}-${Math.random().toString(16).slice(2)}`);
12
- const menusJsonPath = join(projectDir, "menus.json");
13
-
14
- // /addon/admin/403 为禁用项,即使 state=-1 也应该被硬删除
15
- const existingMenus = [
16
- { id: 1, path: "/addon/admin/403", parentPath: "", name: "403", sort: 1, state: -1 },
17
- { id: 2, path: "/keep", parentPath: "", name: "Keep", sort: 2, state: 0 }
18
- ];
19
-
20
- const calls = {
21
- delForceBatch: [] as Array<{ table: string; ids: number[] }>
22
- };
23
-
24
- const dbHelper = {
25
- tableExists: async () => true,
26
- trans: async (callback: any) => {
27
- return await callback(dbHelper);
28
- },
29
- getAll: async (options: any) => {
30
- // syncMenu 会调用一次“全量不带 where”,一次 state>=0 的逻辑已在内存过滤
31
- if (options?.table === "addon_admin_menu") {
32
- return { lists: existingMenus };
33
- }
34
- return { lists: [] };
35
- },
36
- updBatch: async () => 0,
37
- insBatch: async () => [],
38
- delForceBatch: async (table: string, ids: number[]) => {
39
- calls.delForceBatch.push({ table: table, ids: ids });
40
- return ids.length;
41
- }
42
- } as any;
43
-
44
- const ctx = {
45
- db: dbHelper,
46
- addons: [],
47
- config: {
48
- disableMenus: ["**/403"]
49
- },
50
- cache: {
51
- cacheMenus: async () => {}
52
- }
53
- } as any;
54
-
55
- try {
56
- mkdirSync(projectDir, { recursive: true });
57
- process.chdir(projectDir);
58
-
59
- // 配置中仅保留 /keep
60
- writeFileSync(
61
- menusJsonPath,
62
- JSON.stringify(
63
- [
64
- {
65
- name: "Keep",
66
- path: "/keep",
67
- sort: 2
68
- }
69
- ],
70
- null,
71
- 4
72
- ),
73
- { encoding: "utf8" }
74
- );
75
-
76
- const menus = await checkMenu(ctx.addons, { disableMenus: ctx.config.disableMenus });
77
- await syncMenu(ctx, menus);
78
- } finally {
79
- process.chdir(originalCwd);
80
- rmSync(projectDir, { recursive: true, force: true });
81
- }
82
-
83
- expect(calls.delForceBatch).toHaveLength(1);
84
- expect(calls.delForceBatch[0].table).toBe("addon_admin_menu");
85
- // 应包含禁用菜单 id=1;/keep 不应被删
86
- expect(calls.delForceBatch[0].ids).toEqual([1]);
87
- });
88
- });
@@ -1,122 +0,0 @@
1
- import { describe, expect, test, beforeEach, afterEach, mock } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- import { checkMenu } from "../checks/checkMenu.js";
6
- import { setMockLogger } from "../lib/logger.js";
7
- import { syncMenu } from "../sync/syncMenu.js";
8
-
9
- // Mock pino logger
10
- const mockPino = {
11
- info: mock(() => {}),
12
- warn: mock(() => {}),
13
- error: mock(() => {}),
14
- debug: mock(() => {}),
15
- fatal: mock(() => {}),
16
- trace: mock(() => {}),
17
- silent: mock(() => {}),
18
- child: mock(() => mockPino),
19
- level: "info"
20
- };
21
-
22
- describe("syncMenu - duplicate path records", () => {
23
- beforeEach(() => {
24
- setMockLogger(mockPino as any);
25
- mockPino.warn.mockClear();
26
- });
27
-
28
- afterEach(() => {
29
- setMockLogger(null);
30
- });
31
-
32
- test("应检测重复 path 并删除多余记录(保留 id 最大的一条)", async () => {
33
- const originalCwd = process.cwd();
34
- const projectDir = join(originalCwd, "temp", `syncMenu-duplicate-path-${Date.now()}-${Math.random().toString(16).slice(2)}`);
35
- const menusJsonPath = join(projectDir, "menus.json");
36
-
37
- const existingMenus = [
38
- { id: 10, path: "/keep", parentPath: "", name: "Keep", sort: 999, state: 0 },
39
- { id: 20, path: "/keep", parentPath: "", name: "Keep", sort: 999, state: 0 }
40
- ];
41
-
42
- const calls = {
43
- delForceBatch: [] as any[],
44
- updBatchCount: 0,
45
- insBatchCount: 0
46
- };
47
-
48
- const dbHelper = {
49
- tableExists: async () => true,
50
- trans: async (callback: any) => {
51
- return await callback(dbHelper);
52
- },
53
- getAll: async (options: any) => {
54
- const stateGte = options?.where?.state$gte;
55
- if (typeof stateGte === "number") {
56
- return { lists: existingMenus.filter((m) => typeof m.state === "number" && m.state >= stateGte) };
57
- }
58
- return { lists: existingMenus };
59
- },
60
- updBatch: async () => {
61
- calls.updBatchCount += 1;
62
- return 0;
63
- },
64
- insBatch: async () => {
65
- calls.insBatchCount += 1;
66
- return [];
67
- },
68
- delForceBatch: async (table: string, ids: number[]) => {
69
- calls.delForceBatch.push({ table: table, ids: ids });
70
- return ids.length;
71
- }
72
- } as any;
73
-
74
- const ctx = {
75
- db: dbHelper,
76
- addons: [],
77
- config: {
78
- disableMenus: []
79
- },
80
- cache: {
81
- cacheMenus: async () => {}
82
- }
83
- } as any;
84
-
85
- try {
86
- mkdirSync(projectDir, { recursive: true });
87
- process.chdir(projectDir);
88
-
89
- writeFileSync(
90
- menusJsonPath,
91
- JSON.stringify(
92
- [
93
- {
94
- name: "Keep",
95
- path: "/keep",
96
- sort: 999
97
- }
98
- ],
99
- null,
100
- 4
101
- ),
102
- { encoding: "utf8" }
103
- );
104
-
105
- const menus = await checkMenu(ctx.addons);
106
- await syncMenu(ctx, menus);
107
- } finally {
108
- process.chdir(originalCwd);
109
- rmSync(projectDir, { recursive: true, force: true });
110
- }
111
-
112
- expect(mockPino.warn).toHaveBeenCalledTimes(1);
113
- expect(calls.updBatchCount).toBe(0);
114
- expect(calls.insBatchCount).toBe(0);
115
-
116
- expect(calls.delForceBatch).toHaveLength(1);
117
- expect(calls.delForceBatch[0].table).toBe("addon_admin_menu");
118
-
119
- // 只应删除较小 id 的重复记录,保留 id=20
120
- expect(calls.delForceBatch[0].ids).toEqual([10]);
121
- });
122
- });
@@ -1,161 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- import { checkMenu } from "../checks/checkMenu.js";
6
- import { syncMenu } from "../sync/syncMenu.js";
7
-
8
- describe("syncMenu - delete obsolete records", () => {
9
- test("应删除不在配置中的菜单记录(仅 state>=0)", async () => {
10
- const originalCwd = process.cwd();
11
- const projectDir = join(originalCwd, "temp", `syncMenu-obsolete-records-${Date.now()}-${Math.random().toString(16).slice(2)}`);
12
- const menusJsonPath = join(projectDir, "menus.json");
13
-
14
- const existingMenus = [
15
- { id: 1, path: "/a", parentPath: "", state: 0 },
16
- { id: 2, path: "/b", parentPath: "", state: -1 },
17
- { id: 3, path: "", parentPath: "", state: 0 }
18
- ];
19
-
20
- const calls = {
21
- getAllCount: 0,
22
- delForceBatch: [] as any[]
23
- };
24
-
25
- const dbHelper = {
26
- tableExists: async () => true,
27
- trans: async (callback: any) => {
28
- return await callback(dbHelper);
29
- },
30
- getAll: async (options: any) => {
31
- calls.getAllCount += 1;
32
- const stateGte = options?.where?.state$gte;
33
- if (typeof stateGte === "number") {
34
- return { lists: existingMenus.filter((m) => typeof m.state === "number" && m.state >= stateGte) };
35
- }
36
- return { lists: existingMenus };
37
- },
38
- updBatch: async () => {
39
- return 0;
40
- },
41
- insBatch: async () => {
42
- return [];
43
- },
44
- delForceBatch: async (table: string, ids: number[]) => {
45
- calls.delForceBatch.push({ table: table, ids: ids });
46
- return ids.length;
47
- }
48
- } as any;
49
-
50
- const ctx = {
51
- db: dbHelper,
52
- addons: [],
53
- config: {
54
- disableMenus: []
55
- },
56
- cache: {
57
- cacheMenus: async () => {}
58
- }
59
- } as any;
60
-
61
- try {
62
- mkdirSync(projectDir, { recursive: true });
63
- process.chdir(projectDir);
64
-
65
- writeFileSync(menusJsonPath, "[]", { encoding: "utf8" });
66
- const menus = await checkMenu(ctx.addons);
67
- await syncMenu(ctx, menus);
68
- } finally {
69
- process.chdir(originalCwd);
70
- rmSync(projectDir, { recursive: true, force: true });
71
- }
72
-
73
- expect(calls.getAllCount).toBe(1);
74
- expect(calls.delForceBatch).toHaveLength(1);
75
- expect(calls.delForceBatch[0].table).toBe("addon_admin_menu");
76
- expect(calls.delForceBatch[0].ids).toEqual([1]);
77
- });
78
-
79
- test("不应删除仍在配置中的菜单记录", async () => {
80
- const originalCwd = process.cwd();
81
- const projectDir = join(originalCwd, "temp", `syncMenu-obsolete-records-${Date.now()}-${Math.random().toString(16).slice(2)}`);
82
- const menusJsonPath = join(projectDir, "menus.json");
83
-
84
- const existingMenus = [
85
- { id: 1, path: "/keep", parentPath: "", name: "Keep", sort: 999, state: 0 },
86
- { id: 2, path: "/remove", parentPath: "", name: "Remove", sort: 999, state: 0 }
87
- ];
88
-
89
- const calls = {
90
- delForceBatch: [] as any[],
91
- updBatchCount: 0
92
- };
93
-
94
- const dbHelper = {
95
- tableExists: async () => true,
96
- trans: async (callback: any) => {
97
- return await callback(dbHelper);
98
- },
99
- getAll: async (options: any) => {
100
- const stateGte = options?.where?.state$gte;
101
- if (typeof stateGte === "number") {
102
- return { lists: existingMenus.filter((m) => typeof m.state === "number" && m.state >= stateGte) };
103
- }
104
- return { lists: existingMenus };
105
- },
106
- updBatch: async () => {
107
- calls.updBatchCount += 1;
108
- return 0;
109
- },
110
- insBatch: async () => {
111
- return [];
112
- },
113
- delForceBatch: async (table: string, ids: number[]) => {
114
- calls.delForceBatch.push({ table: table, ids: ids });
115
- return ids.length;
116
- }
117
- } as any;
118
-
119
- const ctx = {
120
- db: dbHelper,
121
- addons: [],
122
- config: {
123
- disableMenus: []
124
- },
125
- cache: {
126
- cacheMenus: async () => {}
127
- }
128
- } as any;
129
-
130
- try {
131
- mkdirSync(projectDir, { recursive: true });
132
- process.chdir(projectDir);
133
-
134
- writeFileSync(
135
- menusJsonPath,
136
- JSON.stringify(
137
- [
138
- {
139
- name: "Keep",
140
- path: "/keep",
141
- sort: 999
142
- }
143
- ],
144
- null,
145
- 4
146
- ),
147
- { encoding: "utf8" }
148
- );
149
- const menus = await checkMenu(ctx.addons);
150
- await syncMenu(ctx, menus);
151
- } finally {
152
- process.chdir(originalCwd);
153
- rmSync(projectDir, { recursive: true, force: true });
154
- }
155
-
156
- expect(calls.delForceBatch).toHaveLength(1);
157
- expect(calls.delForceBatch[0].table).toBe("addon_admin_menu");
158
- expect(calls.delForceBatch[0].ids).toEqual([2]);
159
- expect(calls.updBatchCount).toBe(0);
160
- });
161
- });
@@ -1,75 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { __test__ } from "../sync/syncMenu.js";
4
-
5
- describe("syncMenu - parentPath derived from tree", () => {
6
- test("根级菜单不应强制按 URL path 推导 parentPath(避免把同级菜单挂到首页下面)", () => {
7
- const mergedMenus: any[] = [
8
- {
9
- name: "首页",
10
- path: "/addon/admin",
11
- sort: 1
12
- },
13
- {
14
- name: "日志管理",
15
- path: "/addon/admin/log",
16
- sort: 40
17
- },
18
- {
19
- name: "配置管理",
20
- path: "/addon/admin/config",
21
- sort: 30
22
- }
23
- ];
24
-
25
- const map = __test__.flattenMenusToDefMap(mergedMenus as any);
26
-
27
- expect(map.get("/addon/admin")?.parentPath).toBe("");
28
- expect(map.get("/addon/admin/log")?.parentPath).toBe("");
29
- expect(map.get("/addon/admin/config")?.parentPath).toBe("");
30
- });
31
-
32
- test("子菜单应跟随 children 嵌套关系设置 parentPath", () => {
33
- const mergedMenus: any[] = [
34
- {
35
- name: "日志管理",
36
- path: "/addon/admin/log",
37
- sort: 40,
38
- children: [
39
- {
40
- name: "登录日志",
41
- path: "/addon/admin/log/login",
42
- sort: 1
43
- }
44
- ]
45
- }
46
- ];
47
-
48
- const map = __test__.flattenMenusToDefMap(mergedMenus as any);
49
-
50
- expect(map.get("/addon/admin/log")?.parentPath).toBe("");
51
- expect(map.get("/addon/admin/log/login")?.parentPath).toBe("/addon/admin/log");
52
- });
53
-
54
- test("显式 parentPath(包括空字符串)优先生效", () => {
55
- const mergedMenus: any[] = [
56
- {
57
- name: "自定义根",
58
- path: "/x",
59
- parentPath: "/custom",
60
- sort: 1
61
- },
62
- {
63
- name: "显式根",
64
- path: "/y",
65
- parentPath: "",
66
- sort: 2
67
- }
68
- ];
69
-
70
- const map = __test__.flattenMenusToDefMap(mergedMenus as any);
71
-
72
- expect(map.get("/x")?.parentPath).toBe("/custom");
73
- expect(map.get("/y")?.parentPath).toBe("");
74
- });
75
- });
@@ -1,59 +0,0 @@
1
- import { describe, test, expect, beforeAll, afterAll } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
-
5
- import { __test__ } from "../sync/syncMenu.js";
6
-
7
- const testRootDir = join(process.cwd(), "temp", "test-sync-menu-views");
8
- const viewsDir = join(testRootDir, "views");
9
-
10
- function writeVueIndex(dir: string, title: string, order?: number): void {
11
- if (!existsSync(dir)) {
12
- mkdirSync(dir, { recursive: true });
13
- }
14
-
15
- const metaOrder = typeof order === "number" ? `, order: ${order}` : "";
16
-
17
- const content = `<script setup>\ndefinePage({ meta: { title: "${title}"${metaOrder} } });\n</script>\n\n<template>\n <div />\n</template>\n`;
18
- writeFileSync(join(dir, "index.vue"), content, { encoding: "utf8" });
19
- }
20
-
21
- beforeAll(() => {
22
- if (existsSync(testRootDir)) {
23
- rmSync(testRootDir, { recursive: true, force: true });
24
- }
25
- mkdirSync(viewsDir, { recursive: true });
26
-
27
- // 根级 index:应映射为 prefix 本身(不带尾随 /)
28
- writeVueIndex(join(viewsDir, "index"), "Root", 2);
29
-
30
- // 带数字后缀目录:应清理后缀
31
- writeVueIndex(join(viewsDir, "user_1"), "User", 1);
32
- });
33
-
34
- afterAll(() => {
35
- if (existsSync(testRootDir)) {
36
- rmSync(testRootDir, { recursive: true, force: true });
37
- }
38
- });
39
-
40
- describe("syncMenu - scanViewsDir paths", () => {
41
- test("根级 index 不应生成尾随斜杠", async () => {
42
- const prefix = "/addon/addonAdmin";
43
- const menus = await __test__.scanViewsDir(viewsDir, prefix);
44
-
45
- const root = menus.find((m) => m.path === prefix);
46
- expect(root).toBeDefined();
47
- expect(root?.path).toBe(prefix);
48
- });
49
-
50
- test("目录名 _数字 后缀应被清理", async () => {
51
- const prefix = "/addon/addonAdmin";
52
- const menus = await __test__.scanViewsDir(viewsDir, prefix);
53
-
54
- const user = menus.find((m) => m.path === "/addon/addonAdmin/user");
55
- expect(user).toBeDefined();
56
- expect(user?.name).toBe("User");
57
- expect(user?.sort).toBe(1);
58
- });
59
- });