mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-16 05:09:17 +00:00
refactor: optimize the router system and implement unit tests for router (#495)
This commit is contained in:
231
src/router/__tests__/constants.spec.ts
Normal file
231
src/router/__tests__/constants.spec.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ROUTE_NAMES, ROUTE_PATHS, META_KEYS, DEFAULT_ROUTE } from "../constants";
|
||||
|
||||
describe("Router Constants", () => {
|
||||
describe("ROUTE_NAMES", () => {
|
||||
it("should define all required route names", () => {
|
||||
expect(ROUTE_NAMES).toHaveProperty("MARKETPLACE");
|
||||
expect(ROUTE_NAMES).toHaveProperty("DASHBOARD");
|
||||
expect(ROUTE_NAMES).toHaveProperty("ALARM");
|
||||
expect(ROUTE_NAMES).toHaveProperty("SETTINGS");
|
||||
expect(ROUTE_NAMES).toHaveProperty("NOT_FOUND");
|
||||
expect(ROUTE_NAMES).toHaveProperty("LAYER");
|
||||
});
|
||||
|
||||
it("should have correct route name values", () => {
|
||||
expect(ROUTE_NAMES.MARKETPLACE).toBe("Marketplace");
|
||||
expect(ROUTE_NAMES.DASHBOARD).toBe("Dashboard");
|
||||
expect(ROUTE_NAMES.ALARM).toBe("Alarm");
|
||||
expect(ROUTE_NAMES.SETTINGS).toBe("Settings");
|
||||
expect(ROUTE_NAMES.NOT_FOUND).toBe("NotFound");
|
||||
expect(ROUTE_NAMES.LAYER).toBe("Layer");
|
||||
});
|
||||
|
||||
it("should be defined as constants", () => {
|
||||
// Note: Constants are not actually frozen in the implementation
|
||||
// but they should be treated as constants by convention
|
||||
expect(ROUTE_NAMES).toBeDefined();
|
||||
expect(typeof ROUTE_NAMES).toBe("object");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ROUTE_PATHS", () => {
|
||||
it("should define root path", () => {
|
||||
expect(ROUTE_PATHS).toHaveProperty("ROOT");
|
||||
expect(ROUTE_PATHS.ROOT).toBe("/");
|
||||
});
|
||||
|
||||
it("should define marketplace path", () => {
|
||||
expect(ROUTE_PATHS).toHaveProperty("MARKETPLACE");
|
||||
expect(ROUTE_PATHS.MARKETPLACE).toBe("/marketplace");
|
||||
});
|
||||
|
||||
it("should define dashboard paths", () => {
|
||||
expect(ROUTE_PATHS).toHaveProperty("DASHBOARD");
|
||||
expect(ROUTE_PATHS.DASHBOARD).toHaveProperty("LIST");
|
||||
expect(ROUTE_PATHS.DASHBOARD).toHaveProperty("NEW");
|
||||
expect(ROUTE_PATHS.DASHBOARD).toHaveProperty("EDIT");
|
||||
expect(ROUTE_PATHS.DASHBOARD).toHaveProperty("VIEW");
|
||||
expect(ROUTE_PATHS.DASHBOARD).toHaveProperty("WIDGET");
|
||||
});
|
||||
|
||||
it("should have correct dashboard path values", () => {
|
||||
expect(ROUTE_PATHS.DASHBOARD.LIST).toBe("/dashboard/list");
|
||||
expect(ROUTE_PATHS.DASHBOARD.NEW).toBe("/dashboard/new");
|
||||
expect(ROUTE_PATHS.DASHBOARD.EDIT).toBe("/dashboard/:layerId/:entity/:name");
|
||||
expect(ROUTE_PATHS.DASHBOARD.VIEW).toBe("/dashboard/:layerId/:entity/:serviceId/:name");
|
||||
expect(ROUTE_PATHS.DASHBOARD.WIDGET).toBe(
|
||||
"/page/:layer/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:config/:duration?",
|
||||
);
|
||||
});
|
||||
|
||||
it("should define alarm path", () => {
|
||||
expect(ROUTE_PATHS).toHaveProperty("ALARM");
|
||||
expect(ROUTE_PATHS.ALARM).toBe("/alerting");
|
||||
});
|
||||
|
||||
it("should define settings path", () => {
|
||||
expect(ROUTE_PATHS).toHaveProperty("SETTINGS");
|
||||
expect(ROUTE_PATHS.SETTINGS).toBe("/settings");
|
||||
});
|
||||
|
||||
it("should define not found path", () => {
|
||||
expect(ROUTE_PATHS).toHaveProperty("NOT_FOUND");
|
||||
expect(ROUTE_PATHS.NOT_FOUND).toBe("/:pathMatch(.*)*");
|
||||
});
|
||||
|
||||
it("should be defined as constants", () => {
|
||||
// Note: Constants are not actually frozen in the implementation
|
||||
// but they should be treated as constants by convention
|
||||
expect(ROUTE_PATHS).toBeDefined();
|
||||
expect(typeof ROUTE_PATHS).toBe("object");
|
||||
expect(ROUTE_PATHS.DASHBOARD).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("META_KEYS", () => {
|
||||
it("should define all required meta keys", () => {
|
||||
expect(META_KEYS).toHaveProperty("I18N_KEY");
|
||||
expect(META_KEYS).toHaveProperty("ICON");
|
||||
expect(META_KEYS).toHaveProperty("HAS_GROUP");
|
||||
expect(META_KEYS).toHaveProperty("ACTIVATE");
|
||||
expect(META_KEYS).toHaveProperty("TITLE");
|
||||
expect(META_KEYS).toHaveProperty("DESC_KEY");
|
||||
expect(META_KEYS).toHaveProperty("LAYER");
|
||||
expect(META_KEYS).toHaveProperty("NOT_SHOW");
|
||||
expect(META_KEYS).toHaveProperty("REQUIRES_AUTH");
|
||||
expect(META_KEYS).toHaveProperty("BREADCRUMB");
|
||||
});
|
||||
|
||||
it("should have correct meta key values", () => {
|
||||
expect(META_KEYS.I18N_KEY).toBe("i18nKey");
|
||||
expect(META_KEYS.ICON).toBe("icon");
|
||||
expect(META_KEYS.HAS_GROUP).toBe("hasGroup");
|
||||
expect(META_KEYS.ACTIVATE).toBe("activate");
|
||||
expect(META_KEYS.TITLE).toBe("title");
|
||||
expect(META_KEYS.DESC_KEY).toBe("descKey");
|
||||
expect(META_KEYS.LAYER).toBe("layer");
|
||||
expect(META_KEYS.NOT_SHOW).toBe("notShow");
|
||||
expect(META_KEYS.REQUIRES_AUTH).toBe("requiresAuth");
|
||||
expect(META_KEYS.BREADCRUMB).toBe("breadcrumb");
|
||||
});
|
||||
|
||||
it("should be defined as constants", () => {
|
||||
// Note: Constants are not actually frozen in the implementation
|
||||
// but they should be treated as constants by convention
|
||||
expect(META_KEYS).toBeDefined();
|
||||
expect(typeof META_KEYS).toBe("object");
|
||||
});
|
||||
});
|
||||
|
||||
describe("DEFAULT_ROUTE", () => {
|
||||
it("should be defined", () => {
|
||||
expect(DEFAULT_ROUTE).toBeDefined();
|
||||
});
|
||||
|
||||
it("should match marketplace path", () => {
|
||||
expect(DEFAULT_ROUTE).toBe(ROUTE_PATHS.MARKETPLACE);
|
||||
});
|
||||
|
||||
it("should be defined as a constant", () => {
|
||||
// Note: Constants are not actually frozen in the implementation
|
||||
// but they should be treated as constants by convention
|
||||
expect(DEFAULT_ROUTE).toBeDefined();
|
||||
expect(typeof DEFAULT_ROUTE).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Constants Integration", () => {
|
||||
it("should have consistent route names and paths", () => {
|
||||
// Check that route names correspond to actual route paths
|
||||
expect(ROUTE_NAMES.MARKETPLACE).toBe("Marketplace");
|
||||
expect(ROUTE_PATHS.MARKETPLACE).toBe("/marketplace");
|
||||
|
||||
expect(ROUTE_NAMES.DASHBOARD).toBe("Dashboard");
|
||||
expect(ROUTE_PATHS.DASHBOARD.LIST).toBe("/dashboard/list");
|
||||
|
||||
expect(ROUTE_NAMES.ALARM).toBe("Alarm");
|
||||
expect(ROUTE_PATHS.ALARM).toBe("/alerting");
|
||||
|
||||
expect(ROUTE_NAMES.SETTINGS).toBe("Settings");
|
||||
expect(ROUTE_PATHS.SETTINGS).toBe("/settings");
|
||||
});
|
||||
|
||||
it("should have valid path patterns", () => {
|
||||
// Check that parameterized paths have valid syntax
|
||||
expect(ROUTE_PATHS.DASHBOARD.EDIT).toMatch(/^\/dashboard\/:[^/]+\/:[^/]+\/:[^/]+$/);
|
||||
expect(ROUTE_PATHS.DASHBOARD.VIEW).toMatch(/^\/dashboard\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+$/);
|
||||
expect(ROUTE_PATHS.DASHBOARD.WIDGET).toMatch(
|
||||
/^\/page\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/:[^/]+\/?$/,
|
||||
);
|
||||
});
|
||||
|
||||
it("should have consistent meta key usage", () => {
|
||||
// Check that meta keys are used consistently across route definitions
|
||||
const expectedMetaKeys = Object.values(META_KEYS);
|
||||
expect(expectedMetaKeys).toContain("i18nKey");
|
||||
expect(expectedMetaKeys).toContain("icon");
|
||||
expect(expectedMetaKeys).toContain("hasGroup");
|
||||
expect(expectedMetaKeys).toContain("activate");
|
||||
expect(expectedMetaKeys).toContain("title");
|
||||
expect(expectedMetaKeys).toContain("breadcrumb");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Type Safety", () => {
|
||||
it("should have consistent string types", () => {
|
||||
// All route names should be strings
|
||||
Object.values(ROUTE_NAMES).forEach((value) => {
|
||||
expect(typeof value).toBe("string");
|
||||
});
|
||||
|
||||
// All meta keys should be strings
|
||||
Object.values(META_KEYS).forEach((value) => {
|
||||
expect(typeof value).toBe("string");
|
||||
});
|
||||
|
||||
// Root path should be string
|
||||
expect(typeof ROUTE_PATHS.ROOT).toBe("string");
|
||||
|
||||
// Default route should be string
|
||||
expect(typeof DEFAULT_ROUTE).toBe("string");
|
||||
});
|
||||
|
||||
it("should have non-empty values", () => {
|
||||
// All route names should be non-empty
|
||||
Object.values(ROUTE_NAMES).forEach((value) => {
|
||||
expect(value).toBeTruthy();
|
||||
expect(value.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// All meta keys should be non-empty
|
||||
Object.values(META_KEYS).forEach((value) => {
|
||||
expect(value).toBeTruthy();
|
||||
expect(value.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// All paths should be non-empty
|
||||
expect(ROUTE_PATHS.ROOT).toBeTruthy();
|
||||
expect(ROUTE_PATHS.MARKETPLACE).toBeTruthy();
|
||||
expect(ROUTE_PATHS.ALARM).toBeTruthy();
|
||||
expect(ROUTE_PATHS.SETTINGS).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
253
src/router/__tests__/guards.spec.ts
Normal file
253
src/router/__tests__/guards.spec.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { createRootGuard, createAuthGuard, createValidationGuard, createErrorGuard, applyGuards } from "../guards";
|
||||
import { getDefaultRoute } from "../utils";
|
||||
import { ROUTE_PATHS } from "../constants";
|
||||
|
||||
// Mock utils
|
||||
vi.mock("../utils", () => ({
|
||||
getDefaultRoute: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("Router Guards", () => {
|
||||
const mockNext = vi.fn();
|
||||
const mockRoutes = [
|
||||
{ path: "/marketplace", name: "Marketplace" },
|
||||
{ path: "/dashboard", name: "Dashboard" },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(getDefaultRoute as any).mockReturnValue("/marketplace");
|
||||
});
|
||||
|
||||
describe("createRootGuard", () => {
|
||||
it("should redirect root path to default route", () => {
|
||||
const rootGuard = createRootGuard(mockRoutes);
|
||||
const to = { path: ROUTE_PATHS.ROOT };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
rootGuard(to, from, mockNext);
|
||||
|
||||
expect(getDefaultRoute).toHaveBeenCalledWith(mockRoutes);
|
||||
expect(mockNext).toHaveBeenCalledWith({ path: "/marketplace" });
|
||||
});
|
||||
|
||||
it("should allow non-root paths to pass through", () => {
|
||||
const rootGuard = createRootGuard(mockRoutes);
|
||||
const to = { path: "/dashboard" };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
rootGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should handle different default routes", () => {
|
||||
(getDefaultRoute as any).mockReturnValue("/dashboard");
|
||||
const rootGuard = createRootGuard(mockRoutes);
|
||||
const to = { path: ROUTE_PATHS.ROOT };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
rootGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith({ path: "/dashboard" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("createAuthGuard", () => {
|
||||
it("should allow all routes to pass through (placeholder implementation)", () => {
|
||||
const authGuard = createAuthGuard();
|
||||
const to = { path: "/protected", meta: { requiresAuth: true } };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
authGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should handle routes without auth requirements", () => {
|
||||
const authGuard = createAuthGuard();
|
||||
const to = { path: "/public", meta: {} };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
authGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should handle routes with requiresAuth: false", () => {
|
||||
const authGuard = createAuthGuard();
|
||||
const to = { path: "/public", meta: { requiresAuth: false } };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
authGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createValidationGuard", () => {
|
||||
it("should allow routes without parameters to pass through", () => {
|
||||
const validationGuard = createValidationGuard();
|
||||
const to = { path: "/simple", params: {} };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
validationGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should allow routes with valid parameters to pass through", () => {
|
||||
const validationGuard = createValidationGuard();
|
||||
const to = { path: "/valid", params: { id: "123", name: "test" } };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
validationGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should redirect to NotFound for routes with invalid parameters", () => {
|
||||
const validationGuard = createValidationGuard();
|
||||
const to = { path: "/invalid", params: { id: "", name: null } };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
validationGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith({ name: "NotFound" });
|
||||
});
|
||||
|
||||
it("should redirect to NotFound for routes with undefined parameters", () => {
|
||||
const validationGuard = createValidationGuard();
|
||||
const to = { path: "/invalid", params: { id: undefined } };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
validationGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith({ name: "NotFound" });
|
||||
});
|
||||
|
||||
it("should handle mixed valid and invalid parameters", () => {
|
||||
const validationGuard = createValidationGuard();
|
||||
const to = { path: "/mixed", params: { id: "123", name: "" } };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
validationGuard(to, from, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith({ name: "NotFound" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("createErrorGuard", () => {
|
||||
it("should handle NavigationDuplicated errors silently", () => {
|
||||
const errorGuard = createErrorGuard();
|
||||
const error = { name: "NavigationDuplicated" };
|
||||
|
||||
expect(() => errorGuard(error)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should re-throw non-NavigationDuplicated errors", () => {
|
||||
const errorGuard = createErrorGuard();
|
||||
const error = { name: "OtherError", message: "Something went wrong" };
|
||||
|
||||
expect(() => errorGuard(error)).toThrow();
|
||||
});
|
||||
|
||||
it("should log router errors", () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
const errorGuard = createErrorGuard();
|
||||
const error = { name: "TestError", message: "Test error" };
|
||||
|
||||
try {
|
||||
errorGuard(error);
|
||||
} catch {
|
||||
// Expected to throw
|
||||
}
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledWith("Router error:", error);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyGuards", () => {
|
||||
it("should apply all navigation guards to router", () => {
|
||||
const mockRouter = {
|
||||
beforeEach: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
};
|
||||
|
||||
applyGuards(mockRouter, mockRoutes);
|
||||
|
||||
expect(mockRouter.beforeEach).toHaveBeenCalledTimes(3);
|
||||
expect(mockRouter.onError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should apply guards in correct order", () => {
|
||||
const mockRouter = {
|
||||
beforeEach: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
};
|
||||
|
||||
applyGuards(mockRouter, mockRoutes);
|
||||
|
||||
// Verify the order: rootGuard, authGuard, validationGuard
|
||||
const calls = mockRouter.beforeEach.mock.calls;
|
||||
expect(calls).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("should apply error guard", () => {
|
||||
const mockRouter = {
|
||||
beforeEach: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
};
|
||||
|
||||
applyGuards(mockRouter, mockRoutes);
|
||||
|
||||
expect(mockRouter.onError).toHaveBeenCalledWith(expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Guard Integration", () => {
|
||||
it("should work together without conflicts", () => {
|
||||
const mockRouter = {
|
||||
beforeEach: vi.fn(),
|
||||
onError: vi.fn(),
|
||||
};
|
||||
|
||||
// Apply all guards
|
||||
applyGuards(mockRouter, mockRoutes);
|
||||
|
||||
// Test root guard
|
||||
const rootGuard = mockRouter.beforeEach.mock.calls[0][0];
|
||||
const to = { path: ROUTE_PATHS.ROOT };
|
||||
const from = { path: "/some-path" };
|
||||
|
||||
rootGuard(to, from, mockNext);
|
||||
expect(mockNext).toHaveBeenCalledWith({ path: "/marketplace" });
|
||||
|
||||
// Test validation guard
|
||||
const validationGuard = mockRouter.beforeEach.mock.calls[2][0];
|
||||
const validTo = { path: "/valid", params: { id: "123" } };
|
||||
|
||||
validationGuard(validTo, from, mockNext);
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
263
src/router/__tests__/index.spec.ts
Normal file
263
src/router/__tests__/index.spec.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { META_KEYS } from "../constants";
|
||||
|
||||
// Mock route modules to avoid Vue component import issues
|
||||
vi.mock("../dashboard", () => ({
|
||||
routesDashboard: [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/dashboard",
|
||||
meta: {
|
||||
title: "Dashboards",
|
||||
i18nKey: "dashboards",
|
||||
icon: "dashboard_customize",
|
||||
hasGroup: true,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../marketplace", () => ({
|
||||
routesMarketplace: [
|
||||
{
|
||||
name: "Marketplace",
|
||||
path: "/marketplace",
|
||||
meta: {
|
||||
title: "Marketplace",
|
||||
i18nKey: "marketplace",
|
||||
icon: "marketplace",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../alarm", () => ({
|
||||
routesAlarm: [
|
||||
{
|
||||
name: "Alarm",
|
||||
path: "/alarm",
|
||||
meta: {
|
||||
title: "Alarm",
|
||||
i18nKey: "alarm",
|
||||
icon: "alarm",
|
||||
hasGroup: true,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../layer", () => ({
|
||||
default: [
|
||||
{
|
||||
name: "Layer",
|
||||
path: "/layer",
|
||||
meta: {
|
||||
title: "Layer",
|
||||
i18nKey: "layer",
|
||||
icon: "layers",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../settings", () => ({
|
||||
routesSettings: [
|
||||
{
|
||||
name: "Settings",
|
||||
path: "/settings",
|
||||
meta: {
|
||||
title: "Settings",
|
||||
i18nKey: "settings",
|
||||
icon: "settings",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../notFound", () => ({
|
||||
routesNotFound: [
|
||||
{
|
||||
name: "NotFound",
|
||||
path: "/:pathMatch(.*)*",
|
||||
meta: {
|
||||
title: "Not Found",
|
||||
i18nKey: "notFound",
|
||||
icon: "error",
|
||||
hasGroup: false,
|
||||
activate: false,
|
||||
breadcrumb: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
// Mock guards
|
||||
vi.mock("../guards", () => ({
|
||||
applyGuards: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock environment
|
||||
vi.mock("import.meta.env", () => ({
|
||||
BASE_URL: "/",
|
||||
}));
|
||||
|
||||
// Import after mocks
|
||||
import { routes } from "../index";
|
||||
|
||||
describe("Router Index - Route Structure", () => {
|
||||
describe("Route Configuration", () => {
|
||||
it("should combine all route modules correctly", () => {
|
||||
expect(routes).toEqual([
|
||||
expect.objectContaining({ name: "Marketplace" }),
|
||||
expect.objectContaining({ name: "Layer" }),
|
||||
expect.objectContaining({ name: "Alarm" }),
|
||||
expect.objectContaining({ name: "Dashboard" }),
|
||||
expect.objectContaining({ name: "Settings" }),
|
||||
expect.objectContaining({ name: "NotFound" }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("should include marketplace routes", () => {
|
||||
expect(routes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "Marketplace",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should include dashboard routes", () => {
|
||||
expect(routes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "Dashboard",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should include alarm routes", () => {
|
||||
expect(routes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "Alarm",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should include settings routes", () => {
|
||||
expect(routes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "Settings",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should include not found routes", () => {
|
||||
expect(routes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "NotFound",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Route Export", () => {
|
||||
it("should export routes array", () => {
|
||||
expect(routes).toBeDefined();
|
||||
expect(Array.isArray(routes)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Route Structure Validation", () => {
|
||||
it("should have valid route structure", () => {
|
||||
routes.forEach((route) => {
|
||||
expect(route).toHaveProperty("name");
|
||||
expect(route).toHaveProperty("meta");
|
||||
expect(route.meta).toHaveProperty("title");
|
||||
});
|
||||
});
|
||||
|
||||
it("should have proper meta structure", () => {
|
||||
routes.forEach((route) => {
|
||||
expect(route.meta).toHaveProperty("i18nKey");
|
||||
expect(route.meta).toHaveProperty("icon");
|
||||
expect(route.meta).toHaveProperty("hasGroup");
|
||||
expect(route.meta).toHaveProperty("activate");
|
||||
expect(route.meta).toHaveProperty("breadcrumb");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Route Metadata Validation", () => {
|
||||
it("should have correct marketplace metadata", () => {
|
||||
const marketplaceRoute = routes.find((r) => r.name === "Marketplace");
|
||||
expect(marketplaceRoute).toBeDefined();
|
||||
expect(marketplaceRoute?.meta[META_KEYS.TITLE]).toBe("Marketplace");
|
||||
expect(marketplaceRoute?.meta[META_KEYS.I18N_KEY]).toBe("marketplace");
|
||||
expect(marketplaceRoute?.meta[META_KEYS.ICON]).toBe("marketplace");
|
||||
expect(marketplaceRoute?.meta[META_KEYS.HAS_GROUP]).toBe(false);
|
||||
expect(marketplaceRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(marketplaceRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct dashboard metadata", () => {
|
||||
const dashboardRoute = routes.find((r) => r.name === "Dashboard");
|
||||
expect(dashboardRoute).toBeDefined();
|
||||
expect(dashboardRoute?.meta[META_KEYS.TITLE]).toBe("Dashboards");
|
||||
expect(dashboardRoute?.meta[META_KEYS.I18N_KEY]).toBe("dashboards");
|
||||
expect(dashboardRoute?.meta[META_KEYS.ICON]).toBe("dashboard_customize");
|
||||
expect(dashboardRoute?.meta[META_KEYS.HAS_GROUP]).toBe(true);
|
||||
expect(dashboardRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(dashboardRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct alarm metadata", () => {
|
||||
const alarmRoute = routes.find((r) => r.name === "Alarm");
|
||||
expect(alarmRoute).toBeDefined();
|
||||
expect(alarmRoute?.meta[META_KEYS.TITLE]).toBe("Alarm");
|
||||
expect(alarmRoute?.meta[META_KEYS.I18N_KEY]).toBe("alarm");
|
||||
expect(alarmRoute?.meta[META_KEYS.ICON]).toBe("alarm");
|
||||
expect(alarmRoute?.meta[META_KEYS.HAS_GROUP]).toBe(true);
|
||||
expect(alarmRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(alarmRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct not found metadata", () => {
|
||||
const notFoundRoute = routes.find((r) => r.name === "NotFound");
|
||||
expect(notFoundRoute).toBeDefined();
|
||||
expect(notFoundRoute?.meta[META_KEYS.TITLE]).toBe("Not Found");
|
||||
expect(notFoundRoute?.meta[META_KEYS.I18N_KEY]).toBe("notFound");
|
||||
expect(notFoundRoute?.meta[META_KEYS.ICON]).toBe("error");
|
||||
expect(notFoundRoute?.meta[META_KEYS.HAS_GROUP]).toBe(false);
|
||||
expect(notFoundRoute?.meta[META_KEYS.ACTIVATE]).toBe(false);
|
||||
expect(notFoundRoute?.meta[META_KEYS.BREADCRUMB]).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
518
src/router/__tests__/route-modules.spec.ts
Normal file
518
src/router/__tests__/route-modules.spec.ts
Normal file
@@ -0,0 +1,518 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { ROUTE_NAMES, META_KEYS } from "../constants";
|
||||
import type { AppRouteRecordRaw } from "@/types/router";
|
||||
|
||||
// Mock route modules to avoid Vue component import issues
|
||||
vi.mock("../dashboard", () => ({
|
||||
routesDashboard: [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/dashboard",
|
||||
meta: {
|
||||
title: "Dashboards",
|
||||
i18nKey: "dashboards",
|
||||
icon: "dashboard_customize",
|
||||
hasGroup: true,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: "DashboardList",
|
||||
path: "/dashboard/list",
|
||||
meta: {
|
||||
title: "Dashboard List",
|
||||
i18nKey: "dashboardList",
|
||||
icon: "list",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DashboardNew",
|
||||
path: "/dashboard/new",
|
||||
meta: {
|
||||
title: "New Dashboard",
|
||||
i18nKey: "dashboardNew",
|
||||
icon: "add",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DashboardEdit",
|
||||
path: "/dashboard/edit/:id",
|
||||
meta: {
|
||||
title: "Edit Dashboard",
|
||||
i18nKey: "dashboardEdit",
|
||||
icon: "edit",
|
||||
hasGroup: false,
|
||||
activate: false,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../marketplace", () => ({
|
||||
routesMarketplace: [
|
||||
{
|
||||
name: "Marketplace",
|
||||
path: "/marketplace",
|
||||
meta: {
|
||||
title: "Marketplace",
|
||||
i18nKey: "marketplace",
|
||||
icon: "marketplace",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: "MenusManagement",
|
||||
path: "", // Empty path for child route
|
||||
meta: {
|
||||
title: "Marketplace",
|
||||
i18nKey: "menusManagement",
|
||||
icon: "menu",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../alarm", () => ({
|
||||
routesAlarm: [
|
||||
{
|
||||
name: "Alarm",
|
||||
path: "/alarm",
|
||||
meta: {
|
||||
title: "Alarm",
|
||||
i18nKey: "alarm",
|
||||
icon: "alarm",
|
||||
hasGroup: true,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: "AlarmList",
|
||||
path: "/alarm/list",
|
||||
meta: {
|
||||
title: "Alarm List",
|
||||
i18nKey: "alarmList",
|
||||
icon: "list",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AlarmNew",
|
||||
path: "/alarm/new",
|
||||
meta: {
|
||||
title: "New Alarm",
|
||||
i18nKey: "alarmNew",
|
||||
icon: "add",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../layer", () => ({
|
||||
default: [
|
||||
{
|
||||
name: "Layer",
|
||||
path: "/layer",
|
||||
meta: {
|
||||
title: "Layer",
|
||||
i18nKey: "layer",
|
||||
icon: "layers",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: "LayerList",
|
||||
path: "/layer/list",
|
||||
meta: {
|
||||
title: "Layer List",
|
||||
i18nKey: "layerList",
|
||||
icon: "list",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../settings", () => ({
|
||||
routesSettings: [
|
||||
{
|
||||
name: "Settings",
|
||||
path: "/settings",
|
||||
meta: {
|
||||
title: "Settings",
|
||||
i18nKey: "settings",
|
||||
icon: "settings",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: "SettingsGeneral",
|
||||
path: "/settings/general",
|
||||
meta: {
|
||||
title: "General Settings",
|
||||
i18nKey: "settingsGeneral",
|
||||
icon: "settings",
|
||||
hasGroup: false,
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
vi.mock("../notFound", () => ({
|
||||
routesNotFound: [
|
||||
{
|
||||
name: "NotFound",
|
||||
path: "/:pathMatch(.*)*",
|
||||
meta: {
|
||||
title: "Not Found",
|
||||
i18nKey: "notFound",
|
||||
icon: "error",
|
||||
hasGroup: false,
|
||||
activate: false,
|
||||
breadcrumb: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
// Import after mocks
|
||||
import { routesDashboard } from "../dashboard";
|
||||
import { routesMarketplace } from "../marketplace";
|
||||
import { routesAlarm } from "../alarm";
|
||||
import routesLayers from "../layer";
|
||||
import { routesSettings } from "../settings";
|
||||
import { routesNotFound } from "../notFound";
|
||||
|
||||
describe("Route Modules", () => {
|
||||
describe("Marketplace Routes", () => {
|
||||
it("should export marketplace routes", () => {
|
||||
expect(routesMarketplace).toBeDefined();
|
||||
expect(Array.isArray(routesMarketplace)).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct marketplace route structure", () => {
|
||||
const marketplaceRoute = routesMarketplace[0];
|
||||
expect(marketplaceRoute.name).toBe(ROUTE_NAMES.MARKETPLACE);
|
||||
expect(marketplaceRoute.meta[META_KEYS.I18N_KEY]).toBe("marketplace");
|
||||
expect(marketplaceRoute.meta[META_KEYS.ICON]).toBe("marketplace");
|
||||
expect(marketplaceRoute.meta[META_KEYS.HAS_GROUP]).toBe(false);
|
||||
expect(marketplaceRoute.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(marketplaceRoute.meta[META_KEYS.TITLE]).toBe("Marketplace");
|
||||
expect(marketplaceRoute.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have marketplace child route", () => {
|
||||
const marketplaceRoute = routesMarketplace[0];
|
||||
expect(marketplaceRoute.children).toBeDefined();
|
||||
expect(marketplaceRoute.children).toHaveLength(1);
|
||||
|
||||
const childRoute = marketplaceRoute.children![0];
|
||||
expect(childRoute.path).toBe(""); // Empty path for child route
|
||||
expect(childRoute.name).toBe("MenusManagement");
|
||||
expect(childRoute.meta[META_KEYS.TITLE]).toBe("Marketplace");
|
||||
expect(childRoute.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dashboard Routes", () => {
|
||||
it("should export dashboard routes", () => {
|
||||
expect(routesDashboard).toBeDefined();
|
||||
expect(Array.isArray(routesDashboard)).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct dashboard route structure", () => {
|
||||
const dashboardRoute = routesDashboard[0];
|
||||
expect(dashboardRoute.name).toBe(ROUTE_NAMES.DASHBOARD);
|
||||
expect(dashboardRoute.meta[META_KEYS.I18N_KEY]).toBe("dashboards");
|
||||
expect(dashboardRoute.meta[META_KEYS.ICON]).toBe("dashboard_customize");
|
||||
expect(dashboardRoute.meta[META_KEYS.HAS_GROUP]).toBe(true);
|
||||
expect(dashboardRoute.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(dashboardRoute.meta[META_KEYS.TITLE]).toBe("Dashboards");
|
||||
expect(dashboardRoute.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have dashboard list route", () => {
|
||||
const dashboardRoute = routesDashboard[0];
|
||||
const listRoute = dashboardRoute.children?.find((r) => r.name === "DashboardList");
|
||||
|
||||
expect(listRoute).toBeDefined();
|
||||
expect(listRoute?.path).toBe("/dashboard/list");
|
||||
expect(listRoute?.meta[META_KEYS.I18N_KEY]).toBe("dashboardList");
|
||||
expect(listRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(listRoute?.meta[META_KEYS.TITLE]).toBe("Dashboard List");
|
||||
expect(listRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have dashboard new route", () => {
|
||||
const dashboardRoute = routesDashboard[0];
|
||||
const newRoute = dashboardRoute.children?.find((r) => r.name === "DashboardNew");
|
||||
|
||||
expect(newRoute).toBeDefined();
|
||||
expect(newRoute?.path).toBe("/dashboard/new");
|
||||
expect(newRoute?.meta[META_KEYS.I18N_KEY]).toBe("dashboardNew");
|
||||
expect(newRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(newRoute?.meta[META_KEYS.TITLE]).toBe("New Dashboard");
|
||||
expect(newRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have dashboard edit routes", () => {
|
||||
const dashboardRoute = routesDashboard[0];
|
||||
const editRoute = dashboardRoute.children?.find((r) => r.name === "DashboardEdit");
|
||||
|
||||
expect(editRoute).toBeDefined();
|
||||
expect(editRoute?.path).toBe("/dashboard/edit/:id");
|
||||
expect(editRoute?.meta[META_KEYS.I18N_KEY]).toBe("dashboardEdit");
|
||||
expect(editRoute?.meta[META_KEYS.ACTIVATE]).toBe(false);
|
||||
expect(editRoute?.meta[META_KEYS.TITLE]).toBe("Edit Dashboard");
|
||||
expect(editRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Alarm Routes", () => {
|
||||
it("should export alarm routes", () => {
|
||||
expect(routesAlarm).toBeDefined();
|
||||
expect(Array.isArray(routesAlarm)).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct alarm route structure", () => {
|
||||
const alarmRoute = routesAlarm[0];
|
||||
expect(alarmRoute.name).toBe(ROUTE_NAMES.ALARM);
|
||||
expect(alarmRoute.meta[META_KEYS.I18N_KEY]).toBe("alarm");
|
||||
expect(alarmRoute.meta[META_KEYS.ICON]).toBe("alarm");
|
||||
expect(alarmRoute.meta[META_KEYS.HAS_GROUP]).toBe(true);
|
||||
expect(alarmRoute.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(alarmRoute.meta[META_KEYS.TITLE]).toBe("Alarm");
|
||||
expect(alarmRoute.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have alarm list route", () => {
|
||||
const alarmRoute = routesAlarm[0];
|
||||
const listRoute = alarmRoute.children?.find((r) => r.name === "AlarmList");
|
||||
|
||||
expect(listRoute).toBeDefined();
|
||||
expect(listRoute?.path).toBe("/alarm/list");
|
||||
expect(listRoute?.meta[META_KEYS.I18N_KEY]).toBe("alarmList");
|
||||
expect(listRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(listRoute?.meta[META_KEYS.TITLE]).toBe("Alarm List");
|
||||
expect(listRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have alarm new route", () => {
|
||||
const alarmRoute = routesAlarm[0];
|
||||
const newRoute = alarmRoute.children?.find((r) => r.name === "AlarmNew");
|
||||
|
||||
expect(newRoute).toBeDefined();
|
||||
expect(newRoute?.path).toBe("/alarm/new");
|
||||
expect(newRoute?.meta[META_KEYS.I18N_KEY]).toBe("alarmNew");
|
||||
expect(newRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(newRoute?.meta[META_KEYS.TITLE]).toBe("New Alarm");
|
||||
expect(newRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Layer Routes", () => {
|
||||
it("should export layer routes", () => {
|
||||
expect(routesLayers).toBeDefined();
|
||||
expect(Array.isArray(routesLayers)).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct layer route structure", () => {
|
||||
const layerRoute = routesLayers[0];
|
||||
expect(layerRoute.name).toBe(ROUTE_NAMES.LAYER);
|
||||
expect(layerRoute.meta[META_KEYS.I18N_KEY]).toBe("layer");
|
||||
expect(layerRoute.meta[META_KEYS.ICON]).toBe("layers");
|
||||
expect(layerRoute.meta[META_KEYS.HAS_GROUP]).toBe(false);
|
||||
expect(layerRoute.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(layerRoute.meta[META_KEYS.TITLE]).toBe("Layer");
|
||||
expect(layerRoute.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have layer list route", () => {
|
||||
const layerRoute = routesLayers[0];
|
||||
const listRoute = layerRoute.children?.find((r) => r.name === "LayerList");
|
||||
|
||||
expect(listRoute).toBeDefined();
|
||||
expect(listRoute?.path).toBe("/layer/list");
|
||||
expect(listRoute?.meta[META_KEYS.I18N_KEY]).toBe("layerList");
|
||||
expect(listRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(listRoute?.meta[META_KEYS.TITLE]).toBe("Layer List");
|
||||
expect(listRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Settings Routes", () => {
|
||||
it("should export settings routes", () => {
|
||||
expect(routesSettings).toBeDefined();
|
||||
expect(Array.isArray(routesSettings)).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct settings route structure", () => {
|
||||
const settingsRoute = routesSettings[0];
|
||||
expect(settingsRoute.name).toBe(ROUTE_NAMES.SETTINGS);
|
||||
expect(settingsRoute.meta[META_KEYS.I18N_KEY]).toBe("settings");
|
||||
expect(settingsRoute.meta[META_KEYS.ICON]).toBe("settings");
|
||||
expect(settingsRoute.meta[META_KEYS.HAS_GROUP]).toBe(false);
|
||||
expect(settingsRoute.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(settingsRoute.meta[META_KEYS.TITLE]).toBe("Settings");
|
||||
expect(settingsRoute.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
|
||||
it("should have settings general route", () => {
|
||||
const settingsRoute = routesSettings[0];
|
||||
const generalRoute = settingsRoute.children?.find((r) => r.name === "SettingsGeneral");
|
||||
|
||||
expect(generalRoute).toBeDefined();
|
||||
expect(generalRoute?.path).toBe("/settings/general");
|
||||
expect(generalRoute?.meta[META_KEYS.I18N_KEY]).toBe("settingsGeneral");
|
||||
expect(generalRoute?.meta[META_KEYS.ACTIVATE]).toBe(true);
|
||||
expect(generalRoute?.meta[META_KEYS.TITLE]).toBe("General Settings");
|
||||
expect(generalRoute?.meta[META_KEYS.BREADCRUMB]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Not Found Routes", () => {
|
||||
it("should export not found routes", () => {
|
||||
expect(routesNotFound).toBeDefined();
|
||||
expect(Array.isArray(routesNotFound)).toBe(true);
|
||||
});
|
||||
|
||||
it("should have correct not found route structure", () => {
|
||||
const notFoundRoute = routesNotFound[0];
|
||||
expect(notFoundRoute.name).toBe(ROUTE_NAMES.NOT_FOUND);
|
||||
expect(notFoundRoute.path).toBe("/:pathMatch(.*)*");
|
||||
expect(notFoundRoute.meta[META_KEYS.I18N_KEY]).toBe("notFound");
|
||||
expect(notFoundRoute.meta[META_KEYS.ICON]).toBe("error");
|
||||
expect(notFoundRoute.meta[META_KEYS.HAS_GROUP]).toBe(false);
|
||||
expect(notFoundRoute.meta[META_KEYS.ACTIVATE]).toBe(false);
|
||||
expect(notFoundRoute.meta[META_KEYS.BREADCRUMB]).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Route Uniqueness", () => {
|
||||
it("should have unique route names across all modules", () => {
|
||||
const allRoutes = [
|
||||
...routesMarketplace,
|
||||
...routesLayers,
|
||||
...routesAlarm,
|
||||
...routesDashboard,
|
||||
...routesSettings,
|
||||
...routesNotFound,
|
||||
];
|
||||
|
||||
const routeNames = allRoutes.map((r) => r.name);
|
||||
const uniqueNames = new Set(routeNames);
|
||||
|
||||
expect(uniqueNames.size).toBe(routeNames.length);
|
||||
});
|
||||
|
||||
it("should have unique route paths across all modules", () => {
|
||||
const allRoutes = [
|
||||
...routesMarketplace,
|
||||
...routesLayers,
|
||||
...routesAlarm,
|
||||
...routesDashboard,
|
||||
...routesSettings,
|
||||
...routesNotFound,
|
||||
];
|
||||
|
||||
const getAllPaths = (routes: AppRouteRecordRaw[]): string[] => {
|
||||
const paths: string[] = [];
|
||||
routes.forEach((route) => {
|
||||
if (route.path) {
|
||||
paths.push(route.path);
|
||||
}
|
||||
if (route.children) {
|
||||
paths.push(...getAllPaths(route.children));
|
||||
}
|
||||
});
|
||||
return paths;
|
||||
};
|
||||
|
||||
const allPaths = getAllPaths(allRoutes);
|
||||
const uniquePaths = new Set(allPaths);
|
||||
|
||||
expect(uniquePaths.size).toBe(allPaths.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Route Metadata Consistency", () => {
|
||||
it("should have consistent meta structure across all routes", () => {
|
||||
const allRoutes = [
|
||||
...routesMarketplace,
|
||||
...routesLayers,
|
||||
...routesAlarm,
|
||||
...routesDashboard,
|
||||
...routesSettings,
|
||||
...routesNotFound,
|
||||
];
|
||||
|
||||
const validateRouteMeta = (route: AppRouteRecordRaw) => {
|
||||
expect(route.meta).toHaveProperty(META_KEYS.TITLE);
|
||||
expect(route.meta).toHaveProperty(META_KEYS.I18N_KEY);
|
||||
expect(route.meta).toHaveProperty(META_KEYS.ICON);
|
||||
expect(route.meta).toHaveProperty(META_KEYS.HAS_GROUP);
|
||||
expect(route.meta).toHaveProperty(META_KEYS.ACTIVATE);
|
||||
expect(route.meta).toHaveProperty(META_KEYS.BREADCRUMB);
|
||||
|
||||
if (route.children) {
|
||||
route.children.forEach(validateRouteMeta);
|
||||
}
|
||||
};
|
||||
|
||||
allRoutes.forEach(validateRouteMeta);
|
||||
});
|
||||
});
|
||||
});
|
465
src/router/__tests__/utils.spec.ts
Normal file
465
src/router/__tests__/utils.spec.ts
Normal file
@@ -0,0 +1,465 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
findActivatedRoute,
|
||||
getDefaultRoute,
|
||||
requiresAuth,
|
||||
generateBreadcrumb,
|
||||
validateRoute,
|
||||
flattenRoutes,
|
||||
} from "../utils";
|
||||
import { DEFAULT_ROUTE } from "../constants";
|
||||
import type { AppRouteRecordRaw } from "@/types/router";
|
||||
|
||||
describe("Router Utils", () => {
|
||||
const mockRoutes: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: "/marketplace",
|
||||
name: "Marketplace",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Marketplace",
|
||||
activate: false,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/dashboard",
|
||||
name: "Dashboard",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Dashboard",
|
||||
activate: false,
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/dashboard/list",
|
||||
name: "DashboardList",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Dashboard List",
|
||||
activate: true,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/dashboard/new",
|
||||
name: "DashboardNew",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Dashboard New",
|
||||
activate: false,
|
||||
breadcrumb: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
name: "Settings",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Settings",
|
||||
activate: false,
|
||||
breadcrumb: true,
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe("findActivatedRoute", () => {
|
||||
it("should find first activated route from nested routes", () => {
|
||||
const result = findActivatedRoute(mockRoutes);
|
||||
expect(result).toBe("/dashboard/list");
|
||||
});
|
||||
|
||||
it("should find activated route from children", () => {
|
||||
const result = findActivatedRoute(mockRoutes);
|
||||
expect(result).toBe("/dashboard/list");
|
||||
});
|
||||
|
||||
it("should return null when no activated routes exist", () => {
|
||||
const routesWithoutActivate: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
activate: false,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = findActivatedRoute(routesWithoutActivate);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle routes with no meta", () => {
|
||||
const routesWithoutMeta: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {},
|
||||
},
|
||||
];
|
||||
|
||||
const result = findActivatedRoute(routesWithoutMeta);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDefaultRoute", () => {
|
||||
it("should return activated route when available", () => {
|
||||
const result = getDefaultRoute(mockRoutes);
|
||||
expect(result).toBe("/dashboard/list");
|
||||
});
|
||||
|
||||
it("should return DEFAULT_ROUTE when no activated routes exist", () => {
|
||||
const routesWithoutActivate: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
activate: false,
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = getDefaultRoute(routesWithoutActivate);
|
||||
expect(result).toBe(DEFAULT_ROUTE);
|
||||
});
|
||||
|
||||
it("should handle empty routes array", () => {
|
||||
const result = getDefaultRoute([]);
|
||||
expect(result).toBe(DEFAULT_ROUTE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("requiresAuth", () => {
|
||||
it("should return true for routes requiring authentication", () => {
|
||||
const authRoute = mockRoutes[2]; // Settings route
|
||||
const result = requiresAuth(authRoute);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for routes not requiring authentication", () => {
|
||||
const publicRoute = mockRoutes[0]; // Marketplace route
|
||||
const result = requiresAuth(publicRoute);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for routes without requiresAuth meta", () => {
|
||||
const routeWithoutAuth: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = requiresAuth(routeWithoutAuth);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for routes with requiresAuth: false", () => {
|
||||
const routeWithFalseAuth: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
requiresAuth: false,
|
||||
breadcrumb: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = requiresAuth(routeWithFalseAuth);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateBreadcrumb", () => {
|
||||
it("should generate breadcrumb from route with title", () => {
|
||||
const route = mockRoutes[0]; // Marketplace route
|
||||
const result = generateBreadcrumb(route);
|
||||
expect(result).toEqual(["Marketplace"]);
|
||||
});
|
||||
|
||||
it("should generate breadcrumb from route with children", () => {
|
||||
const route = mockRoutes[1]; // Dashboard route
|
||||
const result = generateBreadcrumb(route);
|
||||
expect(result).toEqual(["Dashboard", "Dashboard List"]);
|
||||
});
|
||||
|
||||
it("should exclude children with breadcrumb: false", () => {
|
||||
const route = mockRoutes[1]; // Dashboard route
|
||||
const result = generateBreadcrumb(route);
|
||||
expect(result).not.toContain("Dashboard New");
|
||||
});
|
||||
|
||||
it("should handle routes without title", () => {
|
||||
const routeWithoutTitle: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
breadcrumb: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = generateBreadcrumb(routeWithoutTitle);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should handle routes with no meta", () => {
|
||||
const routeWithoutMeta: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {},
|
||||
};
|
||||
|
||||
const result = generateBreadcrumb(routeWithoutMeta);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should handle empty children array", () => {
|
||||
const routeWithEmptyChildren: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
|
||||
const result = generateBreadcrumb(routeWithEmptyChildren);
|
||||
expect(result).toEqual(["Test"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateRoute", () => {
|
||||
it("should validate valid route", () => {
|
||||
const validRoute = mockRoutes[0];
|
||||
const result = validateRoute(validRoute);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should validate route with children", () => {
|
||||
const routeWithChildren = mockRoutes[1];
|
||||
const result = validateRoute(routeWithChildren);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for route without name", () => {
|
||||
const invalidRoute: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateRoute(invalidRoute);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for route without component", () => {
|
||||
const invalidRoute: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: null as any,
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateRoute(invalidRoute);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for route without path", () => {
|
||||
const invalidRoute: AppRouteRecordRaw = {
|
||||
path: "",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = validateRoute(invalidRoute);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for route with invalid children", () => {
|
||||
const routeWithInvalidChildren: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/test/child",
|
||||
name: "", // Invalid: no name
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Child",
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = validateRoute(routeWithInvalidChildren);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("flattenRoutes", () => {
|
||||
it("should flatten nested routes into single array", () => {
|
||||
const result = flattenRoutes(mockRoutes);
|
||||
expect(result).toHaveLength(5); // 3 parent + 2 children
|
||||
});
|
||||
|
||||
it("should preserve route order", () => {
|
||||
const result = flattenRoutes(mockRoutes);
|
||||
expect(result[0].name).toBe("Marketplace");
|
||||
expect(result[1].name).toBe("Dashboard");
|
||||
expect(result[2].name).toBe("DashboardList");
|
||||
expect(result[3].name).toBe("DashboardNew");
|
||||
expect(result[4].name).toBe("Settings");
|
||||
});
|
||||
|
||||
it("should handle routes without children", () => {
|
||||
const routesWithoutChildren = [mockRoutes[0], mockRoutes[2]];
|
||||
const result = flattenRoutes(routesWithoutChildren);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should handle empty routes array", () => {
|
||||
const result = flattenRoutes([]);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should handle deeply nested routes", () => {
|
||||
const deeplyNestedRoutes: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: "/level1",
|
||||
name: "Level1",
|
||||
component: {},
|
||||
meta: { title: "Level 1" },
|
||||
children: [
|
||||
{
|
||||
path: "/level1/level2",
|
||||
name: "Level2",
|
||||
component: {},
|
||||
meta: { title: "Level 2" },
|
||||
children: [
|
||||
{
|
||||
path: "/level1/level2/level3",
|
||||
name: "Level3",
|
||||
component: {},
|
||||
meta: { title: "Level 3" },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = flattenRoutes(deeplyNestedRoutes);
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0].name).toBe("Level1");
|
||||
expect(result[1].name).toBe("Level2");
|
||||
expect(result[2].name).toBe("Level3");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle routes with null/undefined values gracefully", () => {
|
||||
const routeWithNulls: AppRouteRecordRaw = {
|
||||
path: "/test",
|
||||
name: "Test",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Test",
|
||||
breadcrumb: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/test/child",
|
||||
name: "Child",
|
||||
component: {},
|
||||
meta: {
|
||||
title: "Child",
|
||||
breadcrumb: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => validateRoute(routeWithNulls)).not.toThrow();
|
||||
expect(() => generateBreadcrumb(routeWithNulls)).not.toThrow();
|
||||
expect(() => flattenRoutes([routeWithNulls])).not.toThrow();
|
||||
});
|
||||
|
||||
it("should handle circular references gracefully", () => {
|
||||
const route1: AppRouteRecordRaw = {
|
||||
path: "/route1",
|
||||
name: "Route1",
|
||||
component: {},
|
||||
meta: { title: "Route 1" },
|
||||
children: [],
|
||||
};
|
||||
|
||||
const route2: AppRouteRecordRaw = {
|
||||
path: "/route2",
|
||||
name: "Route2",
|
||||
component: {},
|
||||
meta: { title: "Route 2" },
|
||||
children: [route1],
|
||||
};
|
||||
|
||||
// Create circular reference
|
||||
route1.children = [route2];
|
||||
|
||||
// Should not cause infinite loops
|
||||
expect(() => flattenRoutes([route1])).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user