From 322f3d5011ee70b21ca0b4144a5aa9bb06830d55 Mon Sep 17 00:00:00 2001 From: "hela.ben-khalfallah" Date: Wed, 26 Feb 2025 21:59:45 +0100 Subject: [PATCH] [REFACTOR]: Frontend: unit tests --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .../DoraMetricsUtils-test.ts | 6 +- .../LighthouseConfig-test.ts | 2 +- .../LighthouseUtils-test.ts | 4 +- .../CodeComplexityConfig-test.ts | 4 +- .../CodeComplexityUtils-test.ts | 2 +- .../CodeCouplingUtils-test.ts | 4 +- .../CodeDuplicationUtils-test.ts | 4 +- .../CodeModularityUtils-test.ts | 4 +- .../CodeSecurityUtils-test.ts | 4 +- .../DependenciesUtils-test.ts | 4 +- v6y-apps/front-bo/src/__tests__/.gitkeep | 0 v6y-apps/front/setupTests.tsx | 475 +++++++++++++++++- .../VitalityAuditReportsView-test.tsx | 190 +++++++ .../VitalityDependenciesView-test.tsx | 195 +++++++ .../VitalityEvolutionsView-test.tsx | 207 ++++++++ .../VitalityGeneralInformationView-test.tsx | 192 +++++++ .../VitalityLighthouseReportItem-test.tsx | 143 ++++++ .../VitalityQualityIndicatorsView-test.tsx | 201 ++++++++ .../app-list/VitalityAppListView-test.tsx | 104 ++++ .../__tests__/auth/VitalityLoginForm-test.tsx | 112 +++++ .../commons/VitalityAppInfos-test.tsx | 208 ++++++++ .../commons/VitalityHelpView-test.tsx | 181 +++++++ .../commons/VitalityModuleListItem-test.tsx | 218 ++++++++ .../dashboard/VitalityDashboardView-test.tsx | 55 ++ .../__tests__/faq/VitalityFaqView-test.tsx | 91 ++++ .../VitalityNotificationView-test.tsx | 95 ++++ .../components/VitalitySelectGrouperView.tsx | 1 + .../components/VitalityTabGrouperView.tsx | 4 +- .../__tests__/VitalityAppInfos-test.tsx | 102 ---- .../__tests__/VitalityHelpView-test.tsx | 146 ------ .../__tests__/VitalityLinks-test.tsx | 70 --- .../__tests__/VitalityModulesView-test.tsx | 104 ---- .../VitalitySelectGrouperView-test.tsx | 150 ------ .../__tests__/VitalityTabGrouperView-test.tsx | 85 ---- .../application-info/VitalityAppInfos.tsx | 9 +- .../components/help/VitalityHelpView.tsx | 17 +- .../components/modules/VitalityModuleList.tsx | 58 +++ .../modules/VitalityModuleListItem.tsx | 122 +++++ .../modules/VitalityModulesView.tsx | 168 ------- .../front/src/commons/config/VitalityTerms.ts | 2 +- v6y-apps/front/src/commons/hooks/useAuth.tsx | 7 +- .../renderWithQueryClientProvider.tsx | 0 .../components/VitalityAppDetailsView.tsx | 3 +- .../VitalityAuditReportsTypeGrouper-test.tsx | 87 ---- .../VitalityAuditReportsView-test.tsx | 140 ------ ...ityCodeStatusReportsBranchGrouper-test.tsx | 43 -- ...lityCodeStatusReportsSmellGrouper-test.tsx | 48 -- ...VitalityDependenciesStatusGrouper-test.tsx | 87 ---- .../VitalityEvolutionBranchGrouper-test.tsx | 78 --- .../VitalityEvolutionStatusGrouper-test.tsx | 77 --- .../VitalityGeneralInformationView-test.tsx | 70 --- ...yLighthouseReportsCategoryGrouper-test.tsx | 108 ---- ...lityQualityIndicatorStatusGrouper-test.tsx | 57 --- .../VitalityAuditReportsTypeGrouper.tsx | 7 +- .../VitalityAuditReportsView.tsx | 3 +- ...VitalityCodeStatusReportsBranchGrouper.tsx | 2 +- .../VitalityCodeStatusReportsSmellGrouper.tsx | 12 +- .../VitalityLighthouseReportItem.tsx | 92 ++++ ...talityLighthouseReportsCategoryGrouper.tsx | 76 +-- ...VitalityLighthouseReportsDeviceGrouper.tsx | 2 +- .../VitalityDependenciesBranchGrouper.tsx | 2 +- .../VitalityDependenciesStatusGrouper.tsx | 12 +- .../dependencies/VitalityDependenciesView.tsx | 3 +- .../VitalityEvolutionBranchGrouper.tsx | 2 +- .../VitalityEvolutionStatusGrouper.tsx | 12 +- .../evolutions/VitalityEvolutionsView.tsx | 3 +- .../infos/VitalityGeneralInformationView.tsx | 3 +- .../VitalityQualityIndicatorBranchGrouper.tsx | 2 +- .../VitalityQualityIndicatorStatusGrouper.tsx | 22 +- .../VitalityQualityIndicatorsView.tsx | 3 +- .../app-list/components/VitalityAppList.tsx | 39 +- .../components/VitalityAppListView.tsx | 3 +- .../__tests__/VitalityAppListHeader-test.tsx | 131 ----- .../auth/components/VitalityLoginForm.tsx | 31 +- .../__tests__/VitalityLoginForm-test.tsx | 82 --- .../__tests__/VitalityDashboardMenu-test.tsx | 35 -- .../VitalityDashboardMenuItem-test.tsx | 36 -- .../faq/components/VitalityFaqView.tsx | 5 +- .../__tests__/VitalityFaqList-test.tsx | 78 --- .../components/VitalityNotificationView.tsx | 5 +- .../VitalityNotificationList-test.tsx | 77 --- v6y-apps/front/vite.config.ts | 15 +- v6y-apps/front/vitest.setup.ts | 6 - v6y-libs/core-logic/src/__tests__/.gitkeep | 0 .../{core => }/__tests__/AuditUtils-test.ts | 2 +- .../__tests__/AuthenticationHelper-test.ts | 8 +- .../{core => }/__tests__/SemverUtils-test.ts | 2 +- .../{core => }/__tests__/ServerUtils-test.ts | 2 +- .../{core => }/__tests__/StringUtils-test.ts | 2 +- v6y-libs/core-logic/src/types/index.ts | 1 + v6y-libs/shared-ui/src/__tests__/.gitkeep | 0 .../src/components/atoms/commons/Collapse.tsx | 3 + .../src/components/atoms/commons/List.tsx | 5 +- .../shared-ui/src/components/atoms/index.ts | 3 +- .../organisms/app/VitalityCollapse.tsx | 4 +- .../organisms/app}/VitalityDynamicLoader.tsx | 3 +- .../organisms/app/VitalityLinks.tsx | 2 +- .../organisms/app}/VitalityLoadMoreList.tsx | 11 +- .../organisms/app}/VitalityPaginatedList.tsx | 7 +- .../src/components/organisms/index.ts | 3 + .../src/components/types/PaginatedListType.ts | 17 + .../shared-ui/src/components/types/index.ts | 1 + 104 files changed, 3136 insertions(+), 2293 deletions(-) rename v6y-apps/bfb-devops-auditor/src/{auditors/dora-metrics => __tests__}/DoraMetricsUtils-test.ts (94%) rename v6y-apps/bfb-dynamic-auditor/src/{auditors/lighthouse => __tests__}/LighthouseConfig-test.ts (96%) rename v6y-apps/bfb-dynamic-auditor/src/{auditors/lighthouse => __tests__}/LighthouseUtils-test.ts (99%) rename v6y-apps/bfb-static-auditor/src/{auditors/code-complexity => __tests__}/CodeComplexityConfig-test.ts (96%) rename v6y-apps/bfb-static-auditor/src/{auditors/code-complexity => __tests__}/CodeComplexityUtils-test.ts (94%) rename v6y-apps/bfb-static-auditor/src/{auditors/code-coupling => __tests__}/CodeCouplingUtils-test.ts (98%) rename v6y-apps/bfb-static-auditor/src/{auditors/code-duplication => __tests__}/CodeDuplicationUtils-test.ts (97%) rename v6y-apps/bfb-static-auditor/src/{auditors/code-modularity => __tests__}/CodeModularityUtils-test.ts (97%) rename v6y-apps/bfb-static-auditor/src/{auditors/code-security => __tests__}/CodeSecurityUtils-test.ts (95%) rename v6y-apps/bfb-static-auditor/src/{auditors/dependencies-auditor => __tests__}/DependenciesUtils-test.ts (97%) create mode 100644 v6y-apps/front-bo/src/__tests__/.gitkeep create mode 100644 v6y-apps/front/src/__tests__/app-details/VitalityAuditReportsView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/app-details/VitalityDependenciesView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/app-details/VitalityEvolutionsView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/app-details/VitalityLighthouseReportItem-test.tsx create mode 100644 v6y-apps/front/src/__tests__/app-details/VitalityQualityIndicatorsView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/app-list/VitalityAppListView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/auth/VitalityLoginForm-test.tsx create mode 100644 v6y-apps/front/src/__tests__/commons/VitalityAppInfos-test.tsx create mode 100644 v6y-apps/front/src/__tests__/commons/VitalityHelpView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/commons/VitalityModuleListItem-test.tsx create mode 100644 v6y-apps/front/src/__tests__/dashboard/VitalityDashboardView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/faq/VitalityFaqView-test.tsx create mode 100644 v6y-apps/front/src/__tests__/notifications/VitalityNotificationView-test.tsx delete mode 100644 v6y-apps/front/src/commons/components/__tests__/VitalityAppInfos-test.tsx delete mode 100644 v6y-apps/front/src/commons/components/__tests__/VitalityHelpView-test.tsx delete mode 100644 v6y-apps/front/src/commons/components/__tests__/VitalityLinks-test.tsx delete mode 100644 v6y-apps/front/src/commons/components/__tests__/VitalityModulesView-test.tsx delete mode 100644 v6y-apps/front/src/commons/components/__tests__/VitalitySelectGrouperView-test.tsx delete mode 100644 v6y-apps/front/src/commons/components/__tests__/VitalityTabGrouperView-test.tsx create mode 100644 v6y-apps/front/src/commons/components/modules/VitalityModuleList.tsx create mode 100644 v6y-apps/front/src/commons/components/modules/VitalityModuleListItem.tsx delete mode 100644 v6y-apps/front/src/commons/components/modules/VitalityModulesView.tsx rename v6y-apps/front/src/commons/{utils => test-utils}/renderWithQueryClientProvider.tsx (100%) delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityAuditReportsTypeGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityAuditReportsView-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityCodeStatusReportsBranchGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityCodeStatusReportsSmellGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityDependenciesStatusGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityEvolutionBranchGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityEvolutionStatusGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityGeneralInformationView-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityLighthouseReportsCategoryGrouper-test.tsx delete mode 100644 v6y-apps/front/src/features/app-details/components/__tests__/VitalityQualityIndicatorStatusGrouper-test.tsx create mode 100644 v6y-apps/front/src/features/app-details/components/audit-reports/auditors/lighthouse/VitalityLighthouseReportItem.tsx delete mode 100644 v6y-apps/front/src/features/app-list/components/__tests__/VitalityAppListHeader-test.tsx delete mode 100644 v6y-apps/front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx delete mode 100644 v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenu-test.tsx delete mode 100644 v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenuItem-test.tsx delete mode 100644 v6y-apps/front/src/features/faq/components/__tests__/VitalityFaqList-test.tsx delete mode 100644 v6y-apps/front/src/features/notifications/components/__tests__/VitalityNotificationList-test.tsx create mode 100644 v6y-libs/core-logic/src/__tests__/.gitkeep rename v6y-libs/core-logic/src/{core => }/__tests__/AuditUtils-test.ts (98%) rename v6y-libs/core-logic/src/{core => }/__tests__/AuthenticationHelper-test.ts (97%) rename v6y-libs/core-logic/src/{core => }/__tests__/SemverUtils-test.ts (97%) rename v6y-libs/core-logic/src/{core => }/__tests__/ServerUtils-test.ts (97%) rename v6y-libs/core-logic/src/{core => }/__tests__/StringUtils-test.ts (97%) create mode 100644 v6y-libs/shared-ui/src/__tests__/.gitkeep create mode 100644 v6y-libs/shared-ui/src/components/atoms/commons/Collapse.tsx rename {v6y-apps/front/src/commons/components => v6y-libs/shared-ui/src/components/organisms/app}/VitalityDynamicLoader.tsx (90%) rename {v6y-apps/front/src/commons/components => v6y-libs/shared-ui/src/components/organisms/app}/VitalityLoadMoreList.tsx (76%) rename {v6y-apps/front/src/commons/components => v6y-libs/shared-ui/src/components/organisms/app}/VitalityPaginatedList.tsx (83%) create mode 100644 v6y-libs/shared-ui/src/components/types/PaginatedListType.ts diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6d4ec8d3..292a9654 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,7 +17,7 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error -### ✅ Expected Behavior +### Expected Behavior A clear and concise description of what you expected to happen. ### ❌ Actual Behavior diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cd9e659c..ef472ee2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,7 +9,7 @@ ## 📄 Description Please explain in detail what this PR does, why it is needed, and how it addresses a specific problem. Include any relevant context and background information. -## ✅ Checklist +## Checklist - [ ] I have read and followed the [Contribution Guide](https://github.com/ekino/v6y/wiki/Contribution-Guide). - [ ] My code follows the style guidelines of this project. - [ ] I have performed a self-review of my own code. diff --git a/v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsUtils-test.ts b/v6y-apps/bfb-devops-auditor/src/__tests__/DoraMetricsUtils-test.ts similarity index 94% rename from v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsUtils-test.ts rename to v6y-apps/bfb-devops-auditor/src/__tests__/DoraMetricsUtils-test.ts index 5ebbcf29..97212273 100644 --- a/v6y-apps/bfb-devops-auditor/src/auditors/dora-metrics/DoraMetricsUtils-test.ts +++ b/v6y-apps/bfb-devops-auditor/src/__tests__/DoraMetricsUtils-test.ts @@ -2,9 +2,9 @@ import { auditStatus } from '@v6y/core-logic'; import { devOpsCategories, devOpsType } from '@v6y/core-logic/src/config/DevOpsConfig.ts'; import { describe, expect, it } from 'vitest'; -import DoraMetricsUtils from './DoraMetricsUtils.ts'; -import mockDeployments from './mockDeploymentsData.json' with { type: 'json' }; -import mockMergeRequests from './mockMergeRequestsData.json' with { type: 'json' }; +import DoraMetricsUtils from '../auditors/dora-metrics/DoraMetricsUtils.ts'; +import mockDeployments from '../auditors/dora-metrics/mockDeploymentsData.json' with { type: 'json' }; +import mockMergeRequests from '../auditors/dora-metrics/mockMergeRequestsData.json' with { type: 'json' }; const mockApplication = { _id: 1, name: 'TestApp' }; const mockDateRange = { dateStart: '2025-02-01', dateEnd: '2025-02-10' }; diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseConfig-test.ts b/v6y-apps/bfb-dynamic-auditor/src/__tests__/LighthouseConfig-test.ts similarity index 96% rename from v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseConfig-test.ts rename to v6y-apps/bfb-dynamic-auditor/src/__tests__/LighthouseConfig-test.ts index 287cfca2..6f4a1c3d 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseConfig-test.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/__tests__/LighthouseConfig-test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import LighthouseConfig from './LighthouseConfig.ts'; +import LighthouseConfig from '../auditors/lighthouse/LighthouseConfig.ts'; // Adjust the path accordingly diff --git a/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseUtils-test.ts b/v6y-apps/bfb-dynamic-auditor/src/__tests__/LighthouseUtils-test.ts similarity index 99% rename from v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseUtils-test.ts rename to v6y-apps/bfb-dynamic-auditor/src/__tests__/LighthouseUtils-test.ts index 13e16ee4..ded95c20 100644 --- a/v6y-apps/bfb-dynamic-auditor/src/auditors/lighthouse/LighthouseUtils-test.ts +++ b/v6y-apps/bfb-dynamic-auditor/src/__tests__/LighthouseUtils-test.ts @@ -2,11 +2,11 @@ import { AuditType, auditStatus } from '@v6y/core-logic'; import { describe, expect, it } from 'vitest'; +import LighthouseUtils from '../auditors/lighthouse/LighthouseUtils.ts'; import { LighthouseAuditCategoryType, LighthouseAuditMetricType, -} from '../types/LighthouseAuditType.ts'; -import LighthouseUtils from './LighthouseUtils.ts'; +} from '../auditors/types/LighthouseAuditType.ts'; describe('LighthouseUtils', () => { it('should return true for "warning" or "error" status', () => { diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityConfig-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/CodeComplexityConfig-test.ts similarity index 96% rename from v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityConfig-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/CodeComplexityConfig-test.ts index 3ac61538..9e32d669 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityConfig-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/CodeComplexityConfig-test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; +import CodeComplexityConfig from '../auditors/code-complexity/CodeComplexityConfig.ts'; import { CodeComplexityReportSummaryType, HalsteadMetricType, -} from '../types/CodeComplexityAuditType.ts'; -import CodeComplexityConfig from './CodeComplexityConfig.ts'; +} from '../auditors/types/CodeComplexityAuditType.ts'; describe('CodeComplexityConfig', () => { it('should format maintainability status correctly', () => { diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityUtils-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/CodeComplexityUtils-test.ts similarity index 94% rename from v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityUtils-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/CodeComplexityUtils-test.ts index 2cb511b7..2140df24 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-complexity/CodeComplexityUtils-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/CodeComplexityUtils-test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import CodeComplexityUtils from './CodeComplexityUtils.ts'; +import CodeComplexityUtils from '../auditors/code-complexity/CodeComplexityUtils.ts'; describe('CodeComplexityUtils', () => { const mockWorkspaceFolder = './src/auditors/code-complexity'; diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingUtils-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/CodeCouplingUtils-test.ts similarity index 98% rename from v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingUtils-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/CodeCouplingUtils-test.ts index 349bd9de..7aa75f4d 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-coupling/CodeCouplingUtils-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/CodeCouplingUtils-test.ts @@ -3,8 +3,8 @@ import { auditStatus } from '@v6y/core-logic'; import Madge from 'madge'; import { describe, expect, it, vi } from 'vitest'; -import { AuditCommonsType } from '../types/AuditCommonsType.ts'; -import CodeCouplingUtils from './CodeCouplingUtils.ts'; +import CodeCouplingUtils from '../auditors/code-coupling/CodeCouplingUtils.ts'; +import { AuditCommonsType } from '../auditors/types/AuditCommonsType.ts'; // Mock external modules vi.mock('madge', () => ({ diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationUtils-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/CodeDuplicationUtils-test.ts similarity index 97% rename from v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationUtils-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/CodeDuplicationUtils-test.ts index 3317bd33..a4a9ba89 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-duplication/CodeDuplicationUtils-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/CodeDuplicationUtils-test.ts @@ -2,8 +2,8 @@ import { AppLogger, auditStatus } from '@v6y/core-logic'; import { describe, expect, it, vi } from 'vitest'; -import { CodeDuplicationAuditType } from '../types/CodeDuplicationAuditType.ts'; -import CodeDuplicationUtils from './CodeDuplicationUtils.ts'; +import CodeDuplicationUtils from '../auditors/code-duplication/CodeDuplicationUtils.ts'; +import { CodeDuplicationAuditType } from '../auditors/types/CodeDuplicationAuditType.ts'; describe('CodeDuplicationUtils.formatCodeDuplicationReports', () => { it('should return an empty array when duplicationFiles is empty or missing', () => { diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityUtils-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/CodeModularityUtils-test.ts similarity index 97% rename from v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityUtils-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/CodeModularityUtils-test.ts index 39bfadb7..3f69f4a8 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-modularity/CodeModularityUtils-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/CodeModularityUtils-test.ts @@ -2,8 +2,8 @@ import { AppLogger } from '@v6y/core-logic'; import { describe, expect, it, vi } from 'vitest'; -import { CodeModularityAuditType, ProjectTree } from '../types/CodeModularityAuditType.ts'; -import CodeModularityUtils from './CodeModularityUtils.ts'; +import CodeModularityUtils from '../auditors/code-modularity/CodeModularityUtils.ts'; +import { CodeModularityAuditType, ProjectTree } from '../auditors/types/CodeModularityAuditType.ts'; // Mock external modules vi.mock('xml2js', () => { diff --git a/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityUtils-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/CodeSecurityUtils-test.ts similarity index 95% rename from v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityUtils-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/CodeSecurityUtils-test.ts index 18cac738..7cfb1246 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/code-security/CodeSecurityUtils-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/CodeSecurityUtils-test.ts @@ -1,8 +1,8 @@ import { AuditUtils, auditStatus } from '@v6y/core-logic'; import { Mock, describe, expect, it, vi } from 'vitest'; -import { AuditCommonsType } from '../types/AuditCommonsType.ts'; -import CodeSecurityUtils from './CodeSecurityUtils.ts'; +import CodeSecurityUtils from '../auditors/code-security/CodeSecurityUtils.ts'; +import { AuditCommonsType } from '../auditors/types/AuditCommonsType.ts'; // Mock commons modules vi.mock('@v6y/core-logic', async () => { diff --git a/v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesUtils-test.ts b/v6y-apps/bfb-static-auditor/src/__tests__/DependenciesUtils-test.ts similarity index 97% rename from v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesUtils-test.ts rename to v6y-apps/bfb-static-auditor/src/__tests__/DependenciesUtils-test.ts index 91924d1e..d7a2c2b0 100644 --- a/v6y-apps/bfb-static-auditor/src/auditors/dependencies-auditor/DependenciesUtils-test.ts +++ b/v6y-apps/bfb-static-auditor/src/__tests__/DependenciesUtils-test.ts @@ -2,8 +2,8 @@ import { AuditUtils, DeprecatedDependencyProvider, SemverUtils } from '@v6y/core-logic'; import { describe, expect, it, vi } from 'vitest'; -import { AuditCommonsType } from '../types/AuditCommonsType.ts'; -import DependenciesUtils from './DependenciesUtils.ts'; +import DependenciesUtils from '../auditors/dependencies-auditor/DependenciesUtils.ts'; +import { AuditCommonsType } from '../auditors/types/AuditCommonsType.ts'; // Mock commons modules vi.mock('@v6y/core-logic', async () => { diff --git a/v6y-apps/front-bo/src/__tests__/.gitkeep b/v6y-apps/front-bo/src/__tests__/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/v6y-apps/front/setupTests.tsx b/v6y-apps/front/setupTests.tsx index aa333c91..87b822cc 100644 --- a/v6y-apps/front/setupTests.tsx +++ b/v6y-apps/front/setupTests.tsx @@ -1,18 +1,469 @@ -import { vi } from 'vitest'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { cleanup } from '@testing-library/react'; +import { List } from '@v6y/shared-ui'; +import dynamic from 'next/dynamic'; +import { afterEach, beforeEach, vi } from 'vitest'; vi.mock('next/dynamic', async () => { - const dynamicModule = await vi.importActual('next/dynamic'); + const dynamicModule = await vi.importActual('next/dynamic'); return { - default: (loader: unknown) => { - const dynamicActualComp = dynamicModule.default; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const RequiredComponent = dynamicActualComp(loader); - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - RequiredComponent.preload - ? RequiredComponent.preload() - : RequiredComponent.render.preload(); - return RequiredComponent; + default: (loader: () => Promise<{ default: React.ComponentType }>) => { + const DynamicComponent = dynamicModule.default(loader, { ssr: false }); + + // Force preload if available + if (DynamicComponent.preload) { + DynamicComponent.preload(); + } + + return DynamicComponent; + }, + }; +}); + +const formErrors: Record = {}; // Global error state for the mock + +beforeEach(() => { + Object.keys(formErrors).forEach((key) => delete formErrors[key]); // Clear all errors before each test +}); + +afterEach(() => { + cleanup(); +}); + +vi.mock('@v6y/shared-ui', () => { + return { + useNavigationAdapter: vi.fn(() => { + return { + createUrlQueryParam: vi.fn((key, value) => `${key}=${value}`), + removeUrlQueryParam: vi.fn(), + getUrlParams: vi.fn(() => ['']), + pathname: '/dashboard', + router: { push: vi.fn(), replace: vi.fn() }, + }; + }), + useThemeConfigProvider: () => ({ + currentConfig: { + status: { error: 'red', success: 'green', warning: 'yellow', default: 'gray' }, + statusIcons: { error: '❌', success: '✅', warning: '⚠ī¸', default: 'ℹī¸' }, + }, + }), + useForm: vi.fn(() => ({ + control: {}, + handleSubmit: vi.fn((callback) => () => { + if (Object.keys(formErrors).length > 0) { + return callback(formErrors); + } else { + return callback(); // Executes callback if no errors + } + }), + formState: { + get errors() { + return formErrors; + }, + }, + clearErrors: (name) => { + delete formErrors[name]; + }, + })), + Form: Object.assign( + ({ children, onFinish }: { children: React.ReactNode; onFinish?: () => void }) => { + return ( +
{ + event.preventDefault(); // Prevent default form behavior + if (onFinish) { + onFinish(); // Manually trigger form fail submission handler + } + }} + > + {children} +
+ ); + }, + { + useForm: vi.fn(() => [{ getFieldValue: vi.fn(), setFieldsValue: vi.fn() }]), + Item: ({ + children, + label, + help, + }: { + children: React.ReactNode; + label: React.ReactNode; + help: React.ReactNode; + }) => { + return ( +
+ {label} + {help} + {children} +
+ ); + }, + }, + ), + VitalityInput: vi.fn(({ name, 'aria-label': ariaLabel, rules }) => ( + { + const { value } = e.target; + if (rules?.validate) { + const validationResult = rules.validate(value); + if (validationResult !== true) { + formErrors[name] = { message: validationResult }; + } else { + delete formErrors[name]; + } + } + }} + /> + )), + Input: { + Search: vi.fn(({ placeholder, onSearch }) => ( + { + onSearch(e.target.value); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + onSearch(e.target.value); + } + }} + /> + )), + }, + Message: { + useMessage: vi.fn(() => [ + { + open: vi.fn(), // Mock the `open` method + }, +
Mock Message
, + ]), + }, + Avatar: ({ children }: { children: React.ReactNode }) =>
{children}
, + Space: ({ children }: { children: React.ReactNode }) => <>{children}, + Card: Object.assign( + ({ + title, + actions, + children, + }: { + title: string; + actions?: React.ReactNode[]; + children: React.ReactNode; + }) => { + return ( +
+
{title}
+
{children}
+ {actions?.length > 0 && ( +
+ {actions.map((action, index) => { + return ( +
+ {action} +
+ ); + })} +
+ )} +
+ ); + }, + { + Meta: ({ description }: { description: React.ReactNode }) => ( +
{description}
+ ), + }, + ), + FormOutlined: () => , + Col: ({ children }: { children: React.ReactNode }) =>
{children}
, + Divider: () =>
, + Statistic: ({ + value, + suffix, + valueStyle, + prefix, + }: { + value: number; + suffix?: string; + valueStyle?: React.CSSProperties; + prefix?: React.ReactNode; + }) => { + return ( +
+ {prefix && {prefix}} + {value} + {suffix && {suffix}} +
+ ); + }, + Row: ({ children }: { children: React.ReactNode }) =>
{children}
, + Tag: ({ color, children }: { color: string; children: React.ReactNode }) => ( + + {children} + + ), + Button: ({ + children, + onClick, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + icon, + ...others + }: { + children: React.ReactNode; + icon: React.ReactNode; + onClick: () => void; + }) => { + return ( + + ); + }, + ExportOutlined: () => ExportIcon, + Tabs: ({ + items, + onChange, + }: { + items: Array<{ key: string }>; + onChange: (key: string) => void; + }) => ( +
+ {items.map((tab) => ( + + ))} +
+ ), + Select: ({ + placeholder, + options = [], + onChange, + }: { + placeholder: string; + options: any[]; + onChange?: (val: string) => void; + }) => ( + + ), + Checkbox: Object.assign( + ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + { + Group: ({ + value, + options, + onChange, + }: { + value: string[]; + options: any[]; + onChange: (values: string[]) => void; + }) => ( +
+ {options.map((option) => ( + + ))} +
+ ), + }, + ), + Descriptions: Object.assign( + ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + { + Item: ({ label, children }: { label: string; children: React.ReactNode }) => { + return ( +
+
{label}
+
{children}
+
+ ); + }, + }, + ), + List: Object.assign( + ({ + dataSource = [], + renderItem, + pagination, + ...props + }: { + dataSource: any[]; + renderItem: (item: any, index: number) => React.ReactNode; + pagination?: { pageSize: number }; + }) => { + if (!dataSource || dataSource.length === 0) { + return
No Data Available
; + } + + // Handle pagination if provided + const paginatedData = pagination + ? dataSource.slice(0, pagination.pageSize) + : dataSource; + + return ( +
+ {paginatedData.map((item, index) => ( +
+ {renderItem ? renderItem(item, index) : item || 'No Title'} +
+ ))} +
+ ); + }, + { + Item: Object.assign( + ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + { + Meta: ({ + title, + description, + }: { + title: React.ReactNode; + description: React.ReactNode; + }) => ( +
+
{title}
+
{description}
+
+ ), + }, + ), + }, + ), + ListItem: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + ListItemMeta: ({ + title, + description, + }: { + title: React.ReactNode; + description: React.ReactNode; + }) => ( +
+
{title}
+
{description}
+
+ ), + NotificationOutlined: () =>
, + BulbOutlined: () =>
, + ProductOutlined: () =>
, + QuestionOutlined: () =>
, + DashboardOutlined: () =>
, + AppstoreAddOutlined: () =>
, + SplitCellsOutlined: () =>
, + ApiOutlined: () =>
, + PieChartOutlined: () =>
, + InfoCircleOutlined: () => , + InfoOutlined: () => , + PushpinOutlined: () => , + CompassOutlined: () => , + VitalityModal: ({ isOpen, children }: { isOpen: boolean; children: React.ReactNode }) => + isOpen ?
{children}
: null, + VitalityLinks: ({ links }: { links: Array<{ label: string; value: string }> }) => ( +
    + {links.map((link, index) => ( +
  • + {link.label} +
  • + ))} +
+ ), + VitalityCheckbox: vi.fn(({ name, 'aria-label': ariaLabel }) => ( + + )), + VitalityEmptyView: () =>
No Data Available
, + VitalityText: ({ text }: { text: string }) => {text}, + VitalityTitle: ({ title }: { title: string }) =>

{title}

, + VitalityLoader: () =>
Loading...
, + VitalityDynamicLoader: (importFn: () => Promise<{ default: React.ComponentType }>) => { + // eslint-disable-next-line react/display-name + return (props: any) => { + const LazyComponent = dynamic(() => importFn(), { ssr: false }); + return ; + }; + }, + VitalityPaginatedList: ({ + dataSource, + renderItem, + }: { + dataSource: any[]; + renderItem: (item: any) => React.ReactNode; + }) => { + if (!dataSource || !Array.isArray(dataSource)) { + return
No Data Available
; + } + + return ( +
+ +
+ ); + }, + VitalityLoadMoreList: ({ + dataSource, + renderItem, + }: { + dataSource: any[]; + renderItem: (item: any) => React.ReactNode; + }) => { + if (!dataSource || !Array.isArray(dataSource)) { + return
No Data Available
; + } + + return ( +
+ +
+ ); }, + VitalityCollapse: vi.fn(({ dataSource }) => ( +
+ {dataSource?.length ? ( + dataSource.map((item: any, index: number) => ( +
+
{item.label}
+
{item.children}
+
+ )) + ) : ( +
No Data Available
+ )} +
+ )), }; }); diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityAuditReportsView-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityAuditReportsView-test.tsx new file mode 100644 index 00000000..9c409153 --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-details/VitalityAuditReportsView-test.tsx @@ -0,0 +1,190 @@ +import '@testing-library/jest-dom/vitest'; +import { act, render, screen, waitFor } from '@testing-library/react'; +import { Mock, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; + +import VitalityAuditReportsView from '../../features/app-details/components/audit-reports/VitalityAuditReportsView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), +})); + +vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({ + exportAppAuditReportsToCSV: vi.fn(), +})); + +const auditReports = { + isLoading: false, + data: { + getApplicationDetailsAuditReportsByParams: [ + { + _id: '1', + type: 'Code-Security', + category: 'Code-Security', // ✅ Moved from auditHelp + subCategory: 'Static Analysis', // ✅ Added for completeness + status: 'Completed', + score: 85, // ✅ Added for completeness + scoreUnit: '%', // ✅ Added for completeness + extraInfos: 'Some additional details.', // ✅ Added for completeness + dateStart: '2025-02-20', + dateEnd: '2025-02-21', + auditHelp: { + _id: 'h1', + title: 'Security Audit', + category: 'Code-Security', + description: 'Detailed security audit.', + explanation: 'This audit checks for security vulnerabilities.', + }, + module: { + branch: 'main', + path: 'src/security', + url: 'https://repo.example.com/module-a', + }, + }, + { + _id: '2', + type: 'Lighthouse', + category: 'Lighthouse', // ✅ Moved from auditHelp + subCategory: 'Performance', // ✅ Added for completeness + status: 'Completed', + score: 92, // ✅ Added for completeness + scoreUnit: '%', // ✅ Added for completeness + extraInfos: 'Page load speed and performance audit.', // ✅ Added for completeness + dateStart: '2025-02-25', + dateEnd: '2025-02-26', + auditHelp: { + _id: 'h2', + title: 'Lighthouse Performance Audit', + category: 'Lighthouse', + description: 'Performance analysis results.', + explanation: 'This audit evaluates page speed and performance optimizations.', + }, + module: { + branch: 'develop', + path: 'src/performance', + url: 'https://repo.example.com/module-b', + }, + }, + ], + }, +}; + +describe('VitalityAuditReportsView', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + beforeAll(() => { + // Mock `requestSubmit` globally + HTMLFormElement.prototype.requestSubmit = vi.fn(); + }); + + it('shows loading state until data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ isLoading: true, data: null }); + + await act(async () => { + render(); + }); + + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + }); + + it('renders audit reports when data is fetched successfully', async () => { + (useClientQuery as Mock).mockReturnValue(auditReports); + + await act(async () => { + render(); + }); + + // Wait for rendering + await waitFor(() => { + expect(screen.getByTestId('audit_reports_grouper_tab')).toBeInTheDocument(); + expect(screen.getByTestId('audit_reports_grouper_tab_content')).toBeInTheDocument(); + expect(screen.getByText('Lighthouse')).toBeInTheDocument(); + expect(screen.getByText('Code-Security')).toBeInTheDocument(); + }); + }); + + it('groups audit reports by category correctly', async () => { + (useClientQuery as Mock).mockReturnValue(auditReports); + + await act(async () => { + render(); + }); + + await waitFor(() => { + expect(screen.getByText('Code-Security')).toBeInTheDocument(); + expect(screen.getByText('Lighthouse')).toBeInTheDocument(); + }); + }); + + it('displays loading indicator while fetching data', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: true, + data: null, + }); + + await act(async () => { + render(); + }); + + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + }); + + it('shows "No Data Available" when there are no audit reports', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationDetailsAuditReportsByParams: [] }, + }); + + await act(async () => { + render(); + }); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('handles reports with missing category or auditHelp fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsAuditReportsByParams: [ + { + _id: '3', + type: 'Performance', + status: 'Pending', + dateStart: '2025-02-27', + dateEnd: '2025-02-28', + // ❌ Missing category + auditHelp: null, + module: { name: 'Module C', branch: 'feature' }, + }, + ], + }, + }); + + await act(async () => { + render(); + }); + + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + + it('displays empty state when no reports exist', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationDetailsAuditReportsByParams: [] }, + }); + + await act(async () => { + render(); + }); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityDependenciesView-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityDependenciesView-test.tsx new file mode 100644 index 00000000..8b405212 --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-details/VitalityDependenciesView-test.tsx @@ -0,0 +1,195 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityDependenciesView from '../../features/app-details/components/dependencies/VitalityDependenciesView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), +})); + +vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({ + exportAppDependenciesToCSV: vi.fn(), +})); + +describe('VitalityDependenciesView', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading state while fetching data', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: true, + data: null, + }); + + render(); + + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + }); + + it('renders dependencies correctly when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsDependenciesByParams: [ + { + _id: 1, + type: 'library', + name: 'React', + version: '17.0.2', + recommendedVersion: '18.0.0', + status: 'Outdated', + statusHelp: { + category: 'Security', + title: 'Version outdated', + }, + module: { + branch: 'main', + }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('React')).toBeInTheDocument(); + expect(screen.getByText('Outdated')).toBeInTheDocument(); + }); + }); + + it('handles missing dependencies gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationDetailsDependenciesByParams: [] }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('handles API errors gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: null, // Simulating an error where data is null + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('filters and maps dependencies correctly', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsDependenciesByParams: [ + { + _id: 3, + type: 'library', + name: 'Axios', + version: '1.2.3', + recommendedVersion: '1.2.5', + status: 'Warning', + statusHelp: { + category: 'Security', + title: 'Minor security vulnerability detected', + }, + module: { + branch: '', + }, + }, + { + _id: 4, + type: 'library', + name: 'Jest', + version: '27.0.0', + recommendedVersion: '27.4.3', + status: 'Updated', + statusHelp: { + category: 'Stability', + title: 'Stable version in use', + }, + module: { + branch: 'test', + }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.queryByText('Axios')).not.toBeInTheDocument(); // empty branch + expect(screen.queryByText('Warning')).not.toBeInTheDocument(); // empty branch + expect(screen.getByText('Jest')).toBeInTheDocument(); + expect(screen.getByText('Updated')).toBeInTheDocument(); + }); + }); + + it('renders dependencies correctly with missing optional fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsDependenciesByParams: [ + { + _id: 2, + type: 'library', + name: 'Lodash', + status: 'Updated', + // Missing `version`, `recommendedVersion`, and `statusHelp` + module: { branch: 'develop' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.queryByText('Lodash')).not.toBeInTheDocument(); + expect(screen.queryByText('Updated')).not.toBeInTheDocument(); + }); + }); + + it('does not render dependencies with missing required fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsDependenciesByParams: [ + { + _id: 3, + type: 'library', + name: '', // Invalid: Missing name + version: '1.0.0', + status: 'Updated', + module: { branch: '' }, // Invalid: Missing branch + }, + { + _id: 4, + type: 'library', + name: 'ValidDependency', + version: '2.0.0', + status: 'Up-to-date', + module: { branch: 'release' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityEvolutionsView-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityEvolutionsView-test.tsx new file mode 100644 index 00000000..500e6545 --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-details/VitalityEvolutionsView-test.tsx @@ -0,0 +1,207 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityEvolutionsView from '../../features/app-details/components/evolutions/VitalityEvolutionsView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), +})); + +vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({ + exportAppEvolutionsToCSV: vi.fn(), +})); + +describe('VitalityEvolutionsView', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading state while fetching data', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: true, + data: null, + }); + + render(); + + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + }); + + it('renders evolutions correctly when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsEvolutionsByParams: [ + { + _id: 1, + type: 'feature', + name: 'Dark Mode', + status: 'warning', + evolutionHelp: { + category: 'UI/UX', + title: 'Enhancing user experience', + status: 'Ongoing', + }, + module: { + branch: 'feature-branch', + }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Recommendations and Evolutions')).toBeInTheDocument(); + expect(screen.getByText('Dark Mode')).toBeInTheDocument(); + }); + }); + + it('handles missing evolutions gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationDetailsEvolutionsByParams: [] }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('handles API errors gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: null, // Simulating an error where data is null + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('filters and maps evolutions correctly', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsEvolutionsByParams: [ + { + _id: 3, + type: 'feature', + name: 'New Dashboard', + status: 'Completed', + evolutionHelp: { + category: 'UI/UX', + title: 'Redesigned dashboard', + status: 'Completed', + }, + module: { + branch: 'develop', + }, + }, + { + _id: 4, + type: 'feature', + name: 'Performance Boost', + status: 'In Review', + evolutionHelp: { + category: 'Optimization', + title: 'Code optimizations', + status: 'Under Review', + }, + module: { + branch: '', + }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.queryByText('Performance Boost')).not.toBeInTheDocument(); // Invalid: empty branch + expect(screen.getByText('New Dashboard')).toBeInTheDocument(); + }); + }); + + it('renders evolutions correctly with missing optional fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsEvolutionsByParams: [ + { + _id: 6, + type: 'feature', + name: 'Code Refactor', + status: 'Ongoing', + // Missing evolutionHelp fields + module: { branch: 'refactor' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + }); + }); + + it('does not render evolutions with missing required fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsEvolutionsByParams: [ + { + _id: 7, + type: 'feature', + name: '', // Invalid: Missing name + status: 'Planned', + evolutionHelp: { + category: 'Internationalization', + title: 'Expanding support', + status: 'Scheduled', + }, + module: { branch: 'i18n' }, + }, + { + _id: 8, + type: 'feature', + name: 'Dark Mode', + status: 'Released', + evolutionHelp: { + category: 'UI', + title: 'Better user experience', + status: 'Live', + }, + module: { branch: 'ui-update' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getAllByRole('option')).toHaveLength(4); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('All'); + expect(screen.getAllByRole('option')[1]).toHaveTextContent('All'); + expect(screen.getAllByRole('option')[2]).toHaveTextContent('i18n'); + expect(screen.getAllByRole('option')[3]).toHaveTextContent('ui-update'); + + expect(screen.getAllByRole('button')).toHaveLength(3); + expect(screen.getAllByRole('button')[0]).toHaveTextContent('Scheduled'); + expect(screen.getAllByRole('button')[1]).toHaveTextContent('Live'); + + expect(screen.queryByText('feature-Internationalization')).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx new file mode 100644 index 00000000..c7daddc8 --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx @@ -0,0 +1,192 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityGeneralInformationView from '../../features/app-details/components/infos/VitalityGeneralInformationView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), +})); + +vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({ + exportAppDetailsDataToCSV: vi.fn(), +})); + +describe('VitalityGeneralInformationView', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading state while fetching data', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: true, + data: null, + }); + + render(); + + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + }); + + it('renders application details correctly when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsInfoByParams: { + _id: 1, + name: 'Vitality App', + acronym: 'VAP', + description: 'A powerful application for testing.', + contactMail: 'contact@vitality.com', + repo: { + organization: 'Vitality Org', + webUrl: 'https://github.com/vitality-org', + allBranches: ['main', 'develop'], + }, + links: [ + { label: 'Website', value: 'https://vitality.com' }, + { label: 'Documentation', value: 'https://docs.vitality.com' }, + ], + }, + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Vitality App')).toBeInTheDocument(); + expect(screen.getByText('A powerful application for testing.')).toBeInTheDocument(); + expect(screen.getByText('Number of opened branches: 2')).toBeInTheDocument(); + expect(screen.getByText('Vitality Org')).toBeInTheDocument(); + expect(screen.getByText('Website')).toBeInTheDocument(); + expect(screen.getByText('Documentation')).toBeInTheDocument(); + }); + }); + + it('handles missing application data gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationDetailsInfoByParams: {} }, // Empty data + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('handles API errors gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: null, // Simulating an error + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('renders application without optional fields gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsInfoByParams: { + _id: 2, + name: 'Minimal App', + acronym: 'MAP', + // No description, contactMail, repo, or links + }, + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Minimal App')).toBeInTheDocument(); + }); + + expect(screen.queryByText('A powerful application for testing.')).not.toBeInTheDocument(); + expect(screen.queryByText('Website')).not.toBeInTheDocument(); + expect(screen.queryByText('Vitality Org')).not.toBeInTheDocument(); + }); + + it('does not render application with missing required fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsInfoByParams: { + _id: 3, + acronym: '', // Missing name and acronym + description: 'This should not be displayed', + }, + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + + expect(screen.queryByText('This should not be displayed')).not.toBeInTheDocument(); + }); + + it('renders repository and links correctly when present', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsInfoByParams: { + _id: 4, + name: 'Vitality Repo Test', + acronym: 'VRT', + repo: { + organization: 'Vitality Org', + webUrl: 'https://github.com/vitality-org', + allBranches: ['main', 'feature-x'], + }, + links: [{ label: 'Docs', value: 'https://docs.vitality.com' }], + }, + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Vitality Repo Test')).toBeInTheDocument(); + expect(screen.getByText('Vitality Org')).toBeInTheDocument(); + expect(screen.getByText('Docs')).toBeInTheDocument(); + }); + }); + + it('does not render empty repository fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsInfoByParams: { + _id: 5, + name: 'Vitality No Repo', + acronym: 'VNR', + repo: { + organization: '', + webUrl: '', + allBranches: [], + }, + links: [], + }, + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Vitality No Repo')).toBeInTheDocument(); + }); + + expect(screen.queryByText('Vitality Org')).not.toBeInTheDocument(); + expect(screen.queryByText('Docs')).not.toBeInTheDocument(); + }); +}); diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityLighthouseReportItem-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityLighthouseReportItem-test.tsx new file mode 100644 index 00000000..12d0231f --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-details/VitalityLighthouseReportItem-test.tsx @@ -0,0 +1,143 @@ +import '@testing-library/jest-dom/vitest'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityLighthouseReportItem from '../../features/app-details/components/audit-reports/auditors/lighthouse/VitalityLighthouseReportItem'; + +describe('VitalityLighthouseReportItem', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders audit report details correctly', () => { + const report = { + _id: 1, + type: 'Lighthouse', + category: 'Performance', + subCategory: 'Page Speed', + status: 'Completed', + score: 92, + scoreUnit: '%', + module: { url: 'https://example.com', appId: 2 }, + }; + + render(); + + expect(screen.getByText('Lighthouse')).toBeInTheDocument(); + expect(screen.getByText('Category: Performance')).toBeInTheDocument(); + expect(screen.getByTestId('mock-statistic')).toHaveTextContent('92%'); + expect(screen.getByRole('link', { name: 'Open application' })).toHaveAttribute( + 'href', + 'https://example.com', + ); + }); + + it('triggers onOpenHelpClicked when info button is clicked', () => { + const mockOnOpenHelpClicked = vi.fn(); + + const report = { + _id: 1, + type: 'Lighthouse', + category: 'Performance', + subCategory: 'Page Speed', + status: 'Completed', + score: 92, + scoreUnit: '%', + module: { url: 'https://example.com', appId: 2 }, + }; + + render( + , + ); + + const helpButton = screen.getByTestId('help-button'); + fireEvent.click(helpButton); + + expect(mockOnOpenHelpClicked).toHaveBeenCalledWith(report); + }); + + it('handles missing optional fields gracefully', () => { + const report = { + _id: 1, + type: 'Lighthouse', + category: 'Accessibility', + subCategory: 'Contrast Ratio', + status: 'Completed', + }; + + render(); + + expect(screen.getByText('Lighthouse')).toBeInTheDocument(); + expect(screen.getByText('Category: Accessibility')).toBeInTheDocument(); + expect(screen.getByTestId('mock-statistic')).toHaveTextContent('0'); + expect(screen.queryByRole('link')).not.toBeInTheDocument(); + }); + + it('handles reports with missing type gracefully', () => { + const report = { + _id: 1, + category: 'Accessibility', + subCategory: 'Contrast Ratio', + status: 'Completed', + score: 85, + scoreUnit: '%', + }; + + render(); + + expect(screen.getByText('Category: Accessibility')).toBeInTheDocument(); // Assuming default text + }); + + it('handles extreme score values correctly', () => { + const reports = [ + { _id: 1, type: 'Lighthouse', category: 'Performance', score: -5, scoreUnit: '%' }, + { _id: 2, type: 'Lighthouse', category: 'Accessibility', score: 9999, scoreUnit: '%' }, + ]; + + render( + <> + {reports.map((report, index) => ( + + ))} + , + ); + + const statisticValues = screen.getAllByTestId('mock-statistic-value'); + + expect(statisticValues[0]).toHaveTextContent('-5'); + expect(statisticValues[1]).toHaveTextContent('9999'); + }); + + it('opens help modal when the info button is clicked', () => { + const mockOnOpenHelpClicked = vi.fn(); + const report = { + _id: 1, + type: 'Lighthouse', + category: 'Performance', + status: 'Completed', + auditHelp: { + title: 'Audit Help Title', + description: 'Some detailed explanation.', + }, + }; + + render( + , + ); + + const helpButton = screen.getByTestId('help-button'); + fireEvent.click(helpButton); + + expect(mockOnOpenHelpClicked).toHaveBeenCalledWith(report); + }); +}); diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityQualityIndicatorsView-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityQualityIndicatorsView-test.tsx new file mode 100644 index 00000000..e41289be --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-details/VitalityQualityIndicatorsView-test.tsx @@ -0,0 +1,201 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityQualityIndicatorsView from '../../features/app-details/components/quality-indicators/VitalityQualityIndicatorsView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), +})); + +vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({ + exportAppQualityIndicatorsToCSV: vi.fn(), +})); + +describe('VitalityQualityIndicatorsView', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading state while fetching data', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: true, + data: null, + }); + + render(); + + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + }); + + it('renders quality indicators correctly when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsKeywordsByParams: [ + { + _id: 1, + label: 'Security', + status: 'up-to-date', + module: { branch: 'main' }, + }, + { + _id: 2, + label: 'Performance', + status: 'outdated', + module: { branch: 'develop' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Quality Indicators')).toBeInTheDocument(); + expect(screen.getByText('Performance')).toBeInTheDocument(); + + expect(screen.getAllByRole('option')).toHaveLength(4); + expect(screen.getAllByRole('option')[0]).toHaveTextContent('All'); + expect(screen.getAllByRole('option')[1]).toHaveTextContent('All'); + expect(screen.getAllByRole('option')[2]).toHaveTextContent('main'); + expect(screen.getAllByRole('option')[3]).toHaveTextContent('develop'); + }); + }); + + it('handles missing quality indicators gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationDetailsKeywordsByParams: [] }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('handles API errors gracefully', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: null, // Simulating an error + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('filters and maps indicators correctly', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsKeywordsByParams: [ + { + _id: 3, + label: 'Maintainability', + status: 'success', + module: { branch: 'stable' }, + }, + { + _id: 4, + label: 'Code Quality', + status: 'deprecated', + module: { branch: '' }, // Should be filtered out + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.queryByText('Code Quality')).not.toBeInTheDocument(); // Should be filtered out + expect(screen.getByText('Maintainability')).toBeInTheDocument(); + }); + }); + + it('renders indicators correctly with missing optional fields', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsKeywordsByParams: [ + { + _id: 5, + label: 'Scalability', + status: 'warning', + // No module branch + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + }); + }); + + it('renders indicators with different statuses correctly', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsKeywordsByParams: [ + { _id: 8, label: 'Scalability', status: 'warning', module: { branch: 'main' } }, + { _id: 9, label: 'Security', status: 'success', module: { branch: 'main' } }, + { + _id: 10, + label: 'Maintainability', + status: 'deprecated', + module: { branch: 'legacy' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getAllByRole('button')).toHaveLength(3); + expect(screen.getAllByRole('button')[0]).toHaveTextContent('deprecated'); + expect(screen.getAllByRole('button')[1]).toHaveTextContent('warning'); + expect(screen.getAllByRole('button')[2]).toHaveTextContent('success'); + + expect(screen.getByText('Quality Indicators')).toBeInTheDocument(); + expect(screen.getByText('Maintainability')).toBeInTheDocument(); + }); + }); + + it('renders repository and links correctly when present', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getApplicationDetailsKeywordsByParams: [ + { + _id: 11, + label: 'Reliability', + status: 'error', + module: { branch: 'fix-branch' }, + }, + { + _id: 12, + label: 'UX Testing', + status: 'success', + module: { branch: 'ui-improvements' }, + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Reliability')).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/app-list/VitalityAppListView-test.tsx b/v6y-apps/front/src/__tests__/app-list/VitalityAppListView-test.tsx new file mode 100644 index 00000000..d13031ad --- /dev/null +++ b/v6y-apps/front/src/__tests__/app-list/VitalityAppListView-test.tsx @@ -0,0 +1,104 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityAppList from '../../features/app-list/components/VitalityAppList'; +import VitalityAppListHeader from '../../features/app-list/components/VitalityAppListHeader'; +import VitalityAppListView from '../../features/app-list/components/VitalityAppListView'; +import { + useClientQuery, + useInfiniteClientQuery, +} from '../../infrastructure/adapters/api/useQueryAdapter'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => { + return { + useClientQuery: vi.fn(() => ({ + isLoading: false, + data: { getApplicationTotalByParams: 0 }, + refetch: vi.fn(), + })), + useInfiniteClientQuery: vi.fn(() => ({ + status: 'success', + data: { pages: [] }, // ✅ Always return a valid object + fetchNextPage: vi.fn(), + isFetching: false, + isFetchingNextPage: false, + })), + }; +}); + +vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({ + exportAppListDataToCSV: vi.fn(), +})); + +describe('VitalityAppListView', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders search bar, selectable indicators, and app list', async () => { + render(); + expect(screen.getByText('Search application')).toBeInTheDocument(); + expect( + screen.getByText( + 'You can search by application name, package name or keyword (eslint, maintainability, ...)', + ), + ).toBeInTheDocument(); + expect(screen.getByTestId('mock-search-input')).toBeInTheDocument(); + }); + + it('renders applications when data is available', async () => { + (useInfiniteClientQuery as Mock).mockReturnValue({ + status: 'success', + data: { + pages: [{ getApplicationListByPageAndParams: [{ _id: 1, name: 'Vitality App' }] }], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Vitality App')).toBeInTheDocument(); + }); + }); + + it('handles empty application list gracefully', async () => { + (useInfiniteClientQuery as Mock).mockReturnValue({ + status: 'success', + data: { pages: [] }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + }); + }); + + it('filters applications based on search and keywords', async () => { + (useInfiniteClientQuery as Mock).mockReturnValue({ + status: 'success', + data: { + pages: [{ getApplicationListByPageAndParams: [{ _id: 3, name: 'Filtered App' }] }], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Filtered App')).toBeInTheDocument(); + }); + }); + + it('shows total applications count when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getApplicationTotalByParams: 25 }, + }); + render(); + + await waitFor(() => { + expect(screen.getByText('TOTAL Applications: 25')).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/auth/VitalityLoginForm-test.tsx b/v6y-apps/front/src/__tests__/auth/VitalityLoginForm-test.tsx new file mode 100644 index 00000000..86fd3ef2 --- /dev/null +++ b/v6y-apps/front/src/__tests__/auth/VitalityLoginForm-test.tsx @@ -0,0 +1,112 @@ +import '@testing-library/jest-dom/vitest'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import VitalityLoginForm from '../../features/auth/components/VitalityLoginForm'; + +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + buildClientQuery: vi.fn(async ({ variables }) => { + console.log('📡 Mock API Call:', variables); + + if (variables.input.password === 'test') { + return { + loginAccount: { token: 'test', _id: 'test', role: 'test' }, + }; + } + + return { loginAccount: null }; + }), +})); + +describe('VitalityLoginForm', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should render the component', () => { + render(); + expect(screen.getByText('Email')).toBeInTheDocument(); + expect(screen.getByText('Password')).toBeInTheDocument(); + expect(screen.getByText('Login')).toBeInTheDocument(); + }); + + it('should redirect when submitting with correct credentials', async () => { + render(); + + fireEvent.change(screen.getByTestId('mock-input-email'), { + target: { value: 'test@test.test' }, + }); + fireEvent.change(screen.getByTestId('mock-input-password'), { + target: { value: 'testtesttest' }, + }); + fireEvent.click(screen.getByText('Login')); + + await waitFor(() => { + expect(screen.queryByText('Please enter a valid email')).not.toBeInTheDocument(); + expect(screen.queryByText('Please enter a valid password')).not.toBeInTheDocument(); + }); + }); + + it('should fail when submitting with incorrect password', async () => { + render(); + fireEvent.change(screen.getByTestId('mock-input-email'), { + target: { value: 'test@test.test' }, + }); + fireEvent.change(screen.getByTestId('mock-input-password'), { + target: { value: 'xx' }, + }); + fireEvent.click(screen.getByText('Login')); + + await waitFor(() => { + expect(screen.queryByText('Please enter a valid email')).not.toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('Please enter a valid password')), + ).toBeInTheDocument(); + }); + }); + + it('should fail when submitting with incorrect email', async () => { + render(); + fireEvent.change(screen.getByTestId('mock-input-email'), { + target: { value: 'test' }, + }); + fireEvent.change(screen.getByTestId('mock-input-password'), { + target: { value: 'testtest' }, // 8 characters, so it's valid + }); + fireEvent.click(screen.getByText('Login')); + + await waitFor(() => { + expect( + screen.getByText((content) => + content.includes('Please enter a valid email address'), + ), + ).toBeInTheDocument(); + + expect( + screen.queryByText((content) => content.includes('Please enter a valid password')), + ).not.toBeInTheDocument(); + }); + }); + + it('should fail when submitting with incorrect email and password', async () => { + render(); + fireEvent.change(screen.getByTestId('mock-input-email'), { + target: { value: 'test' }, + }); + fireEvent.change(screen.getByTestId('mock-input-password'), { + target: { value: 'xx' }, + }); + fireEvent.click(screen.getByText('Login')); + + await waitFor(() => { + expect( + screen.getByText((content) => + content.includes('Please enter a valid email address'), + ), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('Please enter a valid password')), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/commons/VitalityAppInfos-test.tsx b/v6y-apps/front/src/__tests__/commons/VitalityAppInfos-test.tsx new file mode 100644 index 00000000..abad8b8e --- /dev/null +++ b/v6y-apps/front/src/__tests__/commons/VitalityAppInfos-test.tsx @@ -0,0 +1,208 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityAppInfos from '../../commons/components/application-info/VitalityAppInfos'; +import { VitalityAppInfosProps } from '../../commons/types/VitalityAppInfosProps'; + +describe('VitalityAppInfos', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + const mockApp: VitalityAppInfosProps['app'] = { + _id: 1, + name: 'Test App', + description: 'This is a test application.', + contactMail: 'test@example.com', + repo: { + organization: 'TestOrg', + webUrl: 'https://github.com/TestOrg/TestApp', + allBranches: ['main', 'dev'], + }, + links: [{ label: 'GitHub', value: 'https://github.com/TestOrg/TestApp' }], + }; + + it('renders app details correctly', () => { + render(); + + expect(screen.getByText('Test App')).toBeInTheDocument(); + expect(screen.getByText('This is a test application.')).toBeInTheDocument(); + expect(screen.getByText('TestOrg')).toBeInTheDocument(); + expect(screen.getByTestId('tag')).toHaveTextContent('Number of opened branches: 2'); + }); + + it('handles missing optional fields gracefully', () => { + const incompleteApp = { _id: 2, name: 'No Info App' }; + + render(); + + expect(screen.getByText('No Info App')).toBeInTheDocument(); + expect(screen.queryByTestId('mock-tag')).not.toBeInTheDocument(); + expect(screen.queryByText('TestOrg')).not.toBeInTheDocument(); + }); + + it('shows the app details link when canOpenDetails is true', () => { + render(); + + expect(screen.getByText('See Details')).toBeInTheDocument(); + }); + + it('hides the app details link when canOpenDetails is false', () => { + render(); + + expect(screen.queryByText('See Details')).not.toBeInTheDocument(); + }); + + it('renders repository and external links correctly', () => { + render(); + + expect(screen.getByText('GitHub')).toBeInTheDocument(); + expect(screen.getByText('GitHub').closest('a')).toHaveAttribute( + 'href', + 'https://github.com/TestOrg/TestApp', + ); + expect(screen.getByText('TestOrg')).toBeInTheDocument(); + expect(screen.getByText('TestOrg').closest('a')).toHaveAttribute( + 'href', + 'https://github.com/TestOrg/TestApp', + ); + }); + + it('renders application details correctly', () => { + const app = { + _id: 1, + name: 'Vitality App', + description: 'An example application', + repo: { + organization: 'Vitality Org', + webUrl: 'https://repo.example.com', + allBranches: ['main', 'develop'], + }, + links: [{ label: 'Docs', value: 'https://docs.example.com' }], + }; + + render(); + + expect(screen.getByText('Vitality App')).toBeInTheDocument(); + expect(screen.getByText('An example application')).toBeInTheDocument(); + expect(screen.getByText('Vitality Org')).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'Docs' })).toHaveAttribute( + 'href', + 'https://docs.example.com', + ); + }); + + it('displays contact email when available', () => { + const app = { _id: 1, name: 'Contact App', contactMail: 'contact@example.com' }; + + render(); + + const emailLink = screen.getByRole('link', { name: 'Contact The Team' }); // Assuming this is the label + expect(emailLink).toHaveAttribute('href', 'mailto:contact@example.com'); + }); + + it('does not display contact email if missing', () => { + const app = { _id: 1, name: 'No Contact App' }; + + render(); + + expect(screen.queryByText('Contact the team')).not.toBeInTheDocument(); + }); + + it('displays open details link when canOpenDetails is true', () => { + const app = { _id: 1, name: 'Details App' }; + + render(); + + const detailsLink = screen.getByRole('link', { name: 'See Details' }); + expect(detailsLink).toBeInTheDocument(); + }); + + it('does not display open details link when canOpenDetails is false', () => { + const app = { _id: 1, name: 'No Details App' }; + + render(); + + expect(screen.queryByText('See Details')).not.toBeInTheDocument(); + }); + + it('renders multiple links correctly', () => { + const app = { + _id: 1, + name: 'Link App', + links: [ + { label: 'GitHub', value: 'https://github.com' }, + { label: 'Documentation', value: 'https://docs.example.com' }, + ], + }; + + render(); + + expect(screen.getByRole('link', { name: 'GitHub' })).toHaveAttribute( + 'href', + 'https://github.com', + ); + expect(screen.getByRole('link', { name: 'Documentation' })).toHaveAttribute( + 'href', + 'https://docs.example.com', + ); + }); + + it('renders without crashing when given an empty object', () => { + render(); + + expect(screen.getByText('Number of opened branches: 0')).toBeInTheDocument(); // Should not throw an error + }); + + it('handles repositories without organization correctly', () => { + const app = { + _id: 2, + name: 'Repo App', + repo: { + webUrl: 'https://example.com/repo', + }, + }; + + render(); + + const repoLink = screen.getByRole('link', { name: '' }); + expect(repoLink).toHaveAttribute('href', 'https://example.com/repo'); + }); + + it('applies correct tag color based on branch count', () => { + const app = { + _id: 4, + name: 'Branch Test App', + repo: { allBranches: ['main', 'develop', 'feature-1', 'hotfix-1', 'feature-2'] }, // 5 branches (should be error) + }; + + render(); + + const branchTag = screen.getByTestId('tag'); // Ensure this matches the actual test ID used + expect(branchTag).toHaveAttribute('style', 'color: red;'); // Assuming error status is red + }); + + it('renders long descriptions correctly without breaking layout', () => { + const longDescription = 'Lorem ipsum '.repeat(100); // Long text + + const app = { + _id: 5, + name: 'Long Desc App', + description: longDescription, + }; + + render(); + + expect(screen.getByText((content) => content.includes('Lorem ipsum'))).toBeInTheDocument(); + }); + + it('ensures contact email uses mailto format', () => { + const app = { _id: 7, name: 'Email App', contactMail: 'support@example.com' }; + + render(); + + const emailLink = screen.getByRole('link', { name: 'Contact The Team' }); + expect(emailLink).toHaveAttribute('href', 'mailto:support@example.com'); + }); +}); diff --git a/v6y-apps/front/src/__tests__/commons/VitalityHelpView-test.tsx b/v6y-apps/front/src/__tests__/commons/VitalityHelpView-test.tsx new file mode 100644 index 00000000..ef3b4156 --- /dev/null +++ b/v6y-apps/front/src/__tests__/commons/VitalityHelpView-test.tsx @@ -0,0 +1,181 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityHelpView from '../../commons/components/help/VitalityHelpView'; +import { VitalityModuleType } from '../../commons/types/VitalityModulesProps'; + +describe('VitalityHelpView', () => { + afterEach(() => { + // Clear mocks after each test + vi.clearAllMocks(); + }); + + it('renders audit help details correctly', () => { + const moduleItem: VitalityModuleType = { + name: 'Security Module', + label: '', + type: 'Security', + category: 'Code Quality', + score: 85, + scoreUnit: '%', + status: 'Completed', + branch: 'main', + path: 'src/security', + auditHelp: { + category: 'Code Quality', + title: 'Security Audit', + description: 'Ensures code security compliance.', + explanation: 'Detailed analysis of security vulnerabilities.', + }, + statusHelp: {}, + evolutionHelp: {}, + }; + + render(); + + expect(screen.getByText('Security Audit')).toBeInTheDocument(); + expect(screen.getByText('Ensures code security compliance.')).toBeInTheDocument(); + expect( + screen.getByText('Detailed analysis of security vulnerabilities.'), + ).toBeInTheDocument(); + expect(screen.getByText('main')).toBeInTheDocument(); + expect(screen.getByText('src/security')).toBeInTheDocument(); + }); + + it('renders status help details correctly', () => { + const moduleItem: VitalityModuleType = { + name: 'Performance Module', + label: '', + type: 'Performance', + category: 'Optimization', + score: 90, + scoreUnit: '%', + status: 'Success', + branch: 'develop', + path: 'src/performance', + auditHelp: {}, + statusHelp: { + category: 'Performance', + title: 'Performance Monitoring', + description: 'Monitors app performance over time.', + explanation: 'Provides insights into bottlenecks and speed improvements.', + }, + evolutionHelp: {}, + }; + + render(); + + expect(screen.getByText('Performance Monitoring')).toBeInTheDocument(); + expect(screen.getByText('Monitors app performance over time.')).toBeInTheDocument(); + expect( + screen.getByText('Provides insights into bottlenecks and speed improvements.'), + ).toBeInTheDocument(); + expect(screen.getByText('develop')).toBeInTheDocument(); + expect(screen.getByText('src/performance')).toBeInTheDocument(); + }); + + it('renders evolution help details correctly', () => { + const moduleItem: VitalityModuleType = { + name: 'Scalability Module', + label: '', + type: 'Infrastructure', + category: 'Scalability', + score: 80, + scoreUnit: '%', + status: 'Warning', + branch: 'feature-branch', + path: 'src/scalability', + auditHelp: {}, + statusHelp: {}, + evolutionHelp: { + category: 'Scalability', + title: 'Scalability Insights', + description: 'Measures system scalability under different loads.', + explanation: 'Provides recommendations for improving system performance.', + }, + }; + + render(); + + expect(screen.getByText('Scalability Insights')).toBeInTheDocument(); + expect( + screen.getByText('Measures system scalability under different loads.'), + ).toBeInTheDocument(); + expect( + screen.getByText('Provides recommendations for improving system performance.'), + ).toBeInTheDocument(); + expect(screen.getByText('feature-branch')).toBeInTheDocument(); + expect(screen.getByText('src/scalability')).toBeInTheDocument(); + }); + + it('handles missing optional fields gracefully', () => { + const moduleItem: VitalityModuleType = { + name: 'Module without Help', + label: '', + type: 'Unknown', + category: 'Misc', + score: 50, + scoreUnit: '%', + status: 'Unknown', + branch: '', + path: '', + auditHelp: {}, // No help data + statusHelp: {}, + evolutionHelp: {}, + }; + + render(); + + // Ensure it does NOT render empty descriptions + expect(screen.queryByText('Category')).not.toBeInTheDocument(); + expect(screen.queryByText('Title')).not.toBeInTheDocument(); + expect(screen.queryByText('Description')).not.toBeInTheDocument(); + expect(screen.queryByText('Explanation')).not.toBeInTheDocument(); + expect(screen.queryByText('Branch')).not.toBeInTheDocument(); + expect(screen.queryByText('Path')).not.toBeInTheDocument(); + }); + + it('renders only relevant help information if multiple help objects exist', () => { + const moduleItem: VitalityModuleType = { + name: 'Complex Module', + label: '', + type: 'Multiple', + category: 'MultiCategory', + score: 75, + scoreUnit: '%', + status: 'Mixed', + branch: 'main', + path: 'src/multiple', + auditHelp: { + category: 'Audit', + title: 'Audit Help Title', + description: 'Audit description example.', + explanation: 'Audit explanation.', + }, + statusHelp: { + category: 'Status', + title: 'Status Help Title', + description: 'Status description example.', + explanation: 'Status explanation.', + }, + evolutionHelp: { + category: 'Evolution', + title: 'Evolution Help Title', + description: 'Evolution description example.', + explanation: 'Evolution explanation.', + }, + }; + + render(); + + // Since `auditHelp` exists first in the priority order, it should be displayed + expect(screen.getByText('Audit Help Title')).toBeInTheDocument(); + expect(screen.getByText('Audit description example.')).toBeInTheDocument(); + expect(screen.getByText('Audit explanation.')).toBeInTheDocument(); + + // `statusHelp` and `evolutionHelp` should NOT be displayed + expect(screen.queryByText('Status Help Title')).not.toBeInTheDocument(); + expect(screen.queryByText('Evolution Help Title')).not.toBeInTheDocument(); + }); +}); diff --git a/v6y-apps/front/src/__tests__/commons/VitalityModuleListItem-test.tsx b/v6y-apps/front/src/__tests__/commons/VitalityModuleListItem-test.tsx new file mode 100644 index 00000000..b5946133 --- /dev/null +++ b/v6y-apps/front/src/__tests__/commons/VitalityModuleListItem-test.tsx @@ -0,0 +1,218 @@ +import '@testing-library/jest-dom/vitest'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import VitalityModuleListItem from '../../commons/components/modules/VitalityModuleListItem'; +import { VitalityModuleType } from '../../commons/types/VitalityModulesProps'; + +describe('VitalityModuleListItem', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders module details correctly', () => { + const moduleItem: VitalityModuleType = { + name: 'Security Module', + label: '', + type: 'Security', + category: 'Code Quality', + score: 85, + scoreUnit: '%', + status: 'Completed', + branch: 'main', + path: 'src/security', + auditHelp: {}, + statusHelp: {}, + evolutionHelp: {}, + }; + + render(); + + expect(screen.getByText('Security Module')).toBeInTheDocument(); + expect(screen.getByTestId('mock-statistic')).toHaveTextContent('85%'); + expect(screen.getByText('main')).toBeInTheDocument(); + expect(screen.getByText('src/security')).toBeInTheDocument(); + }); + + it('handles missing optional fields gracefully', () => { + const moduleItem: VitalityModuleType = { + name: 'Incomplete Module', + label: '', + type: 'Performance', + category: 'Optimization', + score: 0, // Score is missing + scoreUnit: '', + status: 'Warning', + branch: '', + path: '', + auditHelp: {}, + statusHelp: {}, + evolutionHelp: {}, + }; + + render(); + + expect(screen.getByText('Incomplete Module')).toBeInTheDocument(); + expect(screen.queryByTestId('mock-statistic')).not.toBeInTheDocument(); // Score is missing + expect(screen.queryByText('branch')).not.toBeInTheDocument(); // Branch is missing + expect(screen.queryByText('path')).not.toBeInTheDocument(); // Path is missing + }); + + it('triggers onModuleClicked when info button is clicked', () => { + const mockOnModuleClicked = vi.fn(); + + const moduleItem: VitalityModuleType = { + name: 'Security Module', + label: '', + type: 'Security', + category: 'Code Quality', + score: 85, + scoreUnit: '%', + status: 'Completed', + branch: 'main', + path: 'src/security', + auditHelp: { key: 'value' }, // Ensures info button appears + statusHelp: {}, + evolutionHelp: {}, + }; + + render( + , + ); + + const helpButton = screen.getByRole('button'); + fireEvent.click(helpButton); + + expect(mockOnModuleClicked).toHaveBeenCalledWith(moduleItem); + }); + + it('renders different statuses with correct styling', () => { + const moduleItem: VitalityModuleType = { + name: 'Status Module', + label: '', + type: 'Security', + category: 'Testing', + score: 92, + scoreUnit: '%', + status: 'error', // Should apply red styling + branch: '', + path: '', + auditHelp: {}, + statusHelp: {}, + evolutionHelp: {}, + }; + + render(); + + const statusIcon = screen.getByTestId('mock-statistic'); + + expect(statusIcon).toHaveAttribute('style', 'color: red;'); + }); + + it('handles extreme score values correctly', () => { + const modules = [ + { + path: '', + label: '', + branch: '', + name: 'Low Score', + type: 'Security', + category: 'Code Quality', + score: -10, + scoreUnit: '%', + status: 'default', + }, + { + path: '', + label: '', + branch: '', + name: 'High Score', + type: 'Security', + category: 'Code Quality', + score: 9999, + scoreUnit: '%', + status: 'default', + }, + ]; + + render( + <> + {modules.map((mod, index) => ( + + ))} + , + ); + + const statistics = screen.getAllByTestId('mock-statistic-value'); + + expect(statistics[0]).toHaveTextContent('-10'); + expect(statistics[1]).toHaveTextContent('9999'); + }); + + it('does not display info button when there is no auditHelp, statusHelp, or evolutionHelp', () => { + const moduleItem: VitalityModuleType = { + name: 'Module without Help', + label: '', + type: 'Security', + category: 'Testing', + score: 75, + scoreUnit: '%', + status: 'Completed', + branch: 'dev', + path: 'src/module', + auditHelp: {}, // No help info + statusHelp: {}, + evolutionHelp: {}, + }; + + render(); + + expect(screen.queryByRole('button')).not.toBeInTheDocument(); // No info button should be visible + }); + + it('renders multiple modules in a list correctly', () => { + const modules = [ + { + type: '', + path: '', + label: '', + branch: '', + name: 'Module A', + category: 'Security', + score: 85, + scoreUnit: '%', + status: 'success', + }, + { + type: '', + path: '', + label: '', + branch: '', + name: 'Module B', + category: 'Performance', + score: 92, + scoreUnit: '%', + status: 'warning', + }, + ]; + + render( +
+ {modules.map((mod, index) => ( + + ))} +
, + ); + + const items = screen.getAllByTestId('mock-statistic'); + expect(items.length).toBe(modules.length); + }); +}); diff --git a/v6y-apps/front/src/__tests__/dashboard/VitalityDashboardView-test.tsx b/v6y-apps/front/src/__tests__/dashboard/VitalityDashboardView-test.tsx new file mode 100644 index 00000000..612985d6 --- /dev/null +++ b/v6y-apps/front/src/__tests__/dashboard/VitalityDashboardView-test.tsx @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import '@testing-library/jest-dom/vitest'; +import { render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import VitalityDashboardView from '../../features/dashboard/components/VitalityDashboardView'; + +vi.mock('../../../commons/config/VitalityCommonConfig', () => ({ + buildDashboardMenuItems: vi.fn(() => [ + { title: 'Dashboard Item 1', url: '/item1', avatar: '📌', avatarColor: 'red' }, + { title: 'Dashboard Item 2', url: '/item2', avatar: '⚡', avatarColor: 'blue' }, + ]), +})); + +vi.mock('../../../commons/components/VitalitySearchBar', () => ({ + __esModule: true, + default: () =>
Mock Search Bar
, +})); + +vi.mock('../../features/dashboard/components/VitalityDashboardMenu', () => ({ + __esModule: true, + default: ({ options }: { options: any[] }) => ( +
+ {options.map((option) => ( +
+ {option.title} +
+ ))} +
+ ), +})); + +describe('VitalityDashboardView', () => { + it('renders the search bar', () => { + render(); + expect(screen.getByText('Search application')).toBeInTheDocument(); + expect( + screen.getByText( + 'You can search by application name, package name or keyword (eslint, maintainability, ...)', + ), + ).toBeInTheDocument(); + expect(screen.getByTestId('mock-search-input')).toBeInTheDocument(); + }); + + it('renders the dashboard menu with items', () => { + render(); + expect(screen.getByTestId('mock-dashboard-menu')).toBeInTheDocument(); + expect(screen.getAllByTestId('mock-menu-item')).toHaveLength(5); + expect(screen.getAllByTestId('mock-menu-item')[0]).toHaveTextContent('React'); + expect(screen.getAllByTestId('mock-menu-item')[1]).toHaveTextContent('Angular'); + expect(screen.getAllByTestId('mock-menu-item')[2]).toHaveTextContent('React Legacy'); + expect(screen.getAllByTestId('mock-menu-item')[3]).toHaveTextContent('Angular Legacy'); + expect(screen.getAllByTestId('mock-menu-item')[4]).toHaveTextContent('Health statistics'); + }); +}); diff --git a/v6y-apps/front/src/__tests__/faq/VitalityFaqView-test.tsx b/v6y-apps/front/src/__tests__/faq/VitalityFaqView-test.tsx new file mode 100644 index 00000000..69e156e0 --- /dev/null +++ b/v6y-apps/front/src/__tests__/faq/VitalityFaqView-test.tsx @@ -0,0 +1,91 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; + +import VitalityFaqView from '../../features/faq/components/VitalityFaqView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +// Mock dependencies +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), + buildClientQuery: vi.fn(), +})); + +vi.mock('../../../commons/components/VitalitySectionView', () => ({ + __esModule: true, + default: ({ + isLoading, + isEmpty, + children, + }: { + isLoading: boolean; + isEmpty: boolean; + children: React.ReactNode; + }) => ( +
+ {isLoading ?
Loading...
: null} + {isEmpty ?
No FAQs Available
: children} +
+ ), +})); + +describe('VitalityFaqView', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading state when fetching FAQs', () => { + (useClientQuery as Mock).mockReturnValue({ isLoading: true, data: undefined }); + + render(); + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('renders empty view when there are no FAQs', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getFaqListByPageAndParams: [] }, + }); + + render(); + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + }); + }); + + it('renders FAQ list when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getFaqListByPageAndParams: [ + { title: 'How to use Vitality?', description: 'Step-by-step guide', links: [] }, + { + title: 'How to reset my password?', + description: 'Go to settings', + links: [], + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Frequent Questions')).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('How to use Vitality?')), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('Step-by-step guide')), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('How to reset my password?')), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('Go to settings')), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/__tests__/notifications/VitalityNotificationView-test.tsx b/v6y-apps/front/src/__tests__/notifications/VitalityNotificationView-test.tsx new file mode 100644 index 00000000..c8687137 --- /dev/null +++ b/v6y-apps/front/src/__tests__/notifications/VitalityNotificationView-test.tsx @@ -0,0 +1,95 @@ +import '@testing-library/jest-dom/vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; + +import VitalityNotificationView from '../../features/notifications/components/VitalityNotificationView'; +import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter'; + +// Mock dependencies +vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({ + useClientQuery: vi.fn(), + buildClientQuery: vi.fn(), +})); + +vi.mock('../../../commons/components/VitalitySectionView', () => ({ + __esModule: true, + default: ({ + isLoading, + isEmpty, + children, + }: { + isLoading: boolean; + isEmpty: boolean; + children: React.ReactNode; + }) => ( +
+ {isLoading ?
Loading...
: null} + {isEmpty ? ( +
No Notification Available
+ ) : ( + children + )} +
+ ), +})); + +describe('VitalityNotificationView', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shows loading state when fetching Notification', () => { + (useClientQuery as Mock).mockReturnValue({ isLoading: true, data: undefined }); + + render(); + expect(screen.getByTestId('mock-loader')).toBeInTheDocument(); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('renders empty view when there are no Notification', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { getNotificationListByPageAndParams: [] }, + }); + + render(); + await waitFor(() => { + expect(screen.getByTestId('empty-view')).toBeInTheDocument(); + expect(screen.getByText('No Data Available')).toBeInTheDocument(); + }); + }); + + it('renders Notification list when data is available', async () => { + (useClientQuery as Mock).mockReturnValue({ + isLoading: false, + data: { + getNotificationListByPageAndParams: [ + { title: 'How to use Vitality?', description: 'Step-by-step guide', links: [] }, + { + title: 'How to reset my password?', + description: 'Go to settings', + links: [], + }, + ], + }, + }); + + render(); + + await waitFor(() => { + expect(screen.getByText('Notifications')).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('How to use Vitality?')), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('Step-by-step guide')), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('How to reset my password?')), + ).toBeInTheDocument(); + expect( + screen.getByText((content) => content.includes('Go to settings')), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/v6y-apps/front/src/commons/components/VitalitySelectGrouperView.tsx b/v6y-apps/front/src/commons/components/VitalitySelectGrouperView.tsx index 29e3142e..4bc20dc9 100644 --- a/v6y-apps/front/src/commons/components/VitalitySelectGrouperView.tsx +++ b/v6y-apps/front/src/commons/components/VitalitySelectGrouperView.tsx @@ -48,6 +48,7 @@ const VitalitySelectGrouperView = ({
- + {criteriaGroups?.length > 0 && ( { - const actual = await importOriginal(); - return { - ...actual, - useNavigationAdapter: vi.fn(), - useThemeConfigProvider: vi.fn(), - }; -}); - -describe('VitalityAppInfos', () => { - const mockApp = { - _id: 1, - name: 'Test App', - description: 'Test description', - contactMail: 'test@example.com', - repo: { - organization: 'test-org', - webUrl: 'https://github.com/test-org/test-repo', - allBranches: ['main', 'develop'], - }, - links: [ - { - label: 'Production', - value: 'https://test-app.com', - }, - ], - }; - - beforeEach(() => { - (useNavigationAdapter as Mock).mockReturnValue({ - createUrlQueryParam: vi.fn((key, value) => `${key}=${value}`), - }); - (useThemeConfigProvider as Mock).mockReturnValue({ - currentConfig: {}, - }); - }); - - it('should render the component with app details', () => { - render(); - - expect(screen.getByText('Test App')).toBeInTheDocument(); - expect(screen.getByText('Test description')).toBeInTheDocument(); - expect(screen.getByText('Number of opened branches: 2')).toBeInTheDocument(); - expect(screen.getByText('Contact The Team')).toBeInTheDocument(); // Assuming VitalityTerms.VITALITY_APP_LIST_CONTACT_EMAIL is "Contact email" - expect(screen.getByText('test-org')).toBeInTheDocument(); - expect(screen.getByText('Production')).toBeInTheDocument(); - expect(screen.getByText('See Details')).toBeInTheDocument(); // Assuming VitalityTerms.VITALITY_APP_LIST_OPEN_DETAILS_LABEL is "Open details" - }); - - it('should not render the details link if canOpenDetails is false', () => { - render(); - expect(screen.queryByText('Open details')).not.toBeInTheDocument(); - }); - - it('should render without contact email', () => { - const appWithoutEmail = { ...mockApp, contactMail: '' }; - render(); - expect(screen.queryByText('Contact The Team')).not.toBeInTheDocument(); - }); - - it('should render with more than 10 branches', () => { - const appWithManyBranches = { - ...mockApp, - repo: { ...mockApp.repo, allBranches: new Array(11).fill('branch') }, - }; - render(); - expect(screen.getByText('Number of opened branches: 11')).toBeInTheDocument(); - }); - - it('should render with no branches', () => { - const appWithNoBranches = { ...mockApp, repo: { ...mockApp.repo, allBranches: [] } }; - render(); - expect(screen.getByText('Number of opened branches: 0')).toBeInTheDocument(); - }); - - it('should render with no links', () => { - const appWithNoLinks = { ...mockApp, links: [] }; - render(); - expect(screen.queryByText('Production')).not.toBeInTheDocument(); - }); - - it('should render with no repository', () => { - const appWithNoRepo = { ...mockApp, repo: null }; - render(); - expect(screen.queryByText('test-org')).not.toBeInTheDocument(); - }); - - it('should render with no description', () => { - const appWithNoDescription = { ...mockApp, description: '' }; - render(); - expect(screen.queryByText('Test description')).not.toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/commons/components/__tests__/VitalityHelpView-test.tsx b/v6y-apps/front/src/commons/components/__tests__/VitalityHelpView-test.tsx deleted file mode 100644 index 371f5ea7..00000000 --- a/v6y-apps/front/src/commons/components/__tests__/VitalityHelpView-test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -// VitalityHelpView.test.tsx -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { describe, expect, it } from 'vitest'; - -import VitalityTerms from '../../config/VitalityTerms'; -import VitalityHelpView from '../help/VitalityHelpView'; - -describe('VitalityHelpView', () => { - const mockModule = { - branch: '1-poc-auth-obtenir-un-jwt', - path: '/Users/hela.ben-khalfallah/Desktop/github_workspace/v6y/src/code-analysis-workspace/mfs/1-poc-auth-obtenir-un-jwt/apps/front/package.json', - name: '', - label: '', - type: 'Code-Complexity', - category: 'maintainability-index-project-average', - score: 76.76, - scoreUnit: '%', - status: 'warning', - auditHelp: { - category: 'Code-Complexity-maintainability-index-project-average', - title: 'Default Title', - description: 'Default Description', - explanation: 'Default Explanation', - }, - statusHelp: undefined, - evolutionHelp: undefined, - }; - - it('should render the component with module help details', () => { - render(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_CATEGORY_LABEL), - ).toBeInTheDocument(); - expect( - screen.getByText('Code-Complexity-maintainability-index-project-average'), - ).toBeInTheDocument(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_TITLE_LABEL), - ).toBeInTheDocument(); - expect(screen.getByText('Default Title')).toBeInTheDocument(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_DESCRIPTION_LABEL), - ).toBeInTheDocument(); - expect(screen.getByText('Default Description')).toBeInTheDocument(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_EXPLANATION_LABEL), - ).toBeInTheDocument(); - expect(screen.getByText('Default Explanation')).toBeInTheDocument(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_DETECT_ON_BRANCH_LABEL), - ).toBeInTheDocument(); - expect(screen.getByText('1-poc-auth-obtenir-un-jwt')).toBeInTheDocument(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_DETECT_ON_PATH_LABEL), - ).toBeInTheDocument(); - expect( - screen.getByText( - '/Users/hela.ben-khalfallah/Desktop/github_workspace/v6y/src/code-analysis-workspace/mfs/1-poc-auth-obtenir-un-jwt/apps/front/package.json', - ), - ).toBeInTheDocument(); - }); - - it('should not render missing help details', () => { - const mockModuleWithoutHelp = { - ...mockModule, - auditHelp: { - category: '', - title: '', - description: '', - explanation: '', - }, - branch: '', - path: '', - }; - - render(); - - expect( - screen.queryByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_CATEGORY_LABEL), - ).not.toBeInTheDocument(); - expect( - screen.queryByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_TITLE_LABEL), - ).not.toBeInTheDocument(); - expect( - screen.queryByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_DESCRIPTION_LABEL), - ).not.toBeInTheDocument(); - expect( - screen.queryByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_EXPLANATION_LABEL), - ).not.toBeInTheDocument(); - expect( - screen.queryByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_DETECT_ON_BRANCH_LABEL), - ).not.toBeInTheDocument(); - expect( - screen.queryByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_DETECT_ON_PATH_LABEL), - ).not.toBeInTheDocument(); - }); - - it('should render statusHelp if auditHelp is not available', () => { - const mockModuleWithStatusHelp = { - ...mockModule, - auditHelp: undefined, - statusHelp: { - category: 'Status Category', - title: 'Status Title', - description: 'Status Description', - explanation: 'Status Explanation', - }, - }; - - render(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_CATEGORY_LABEL), - ).toBeInTheDocument(); - expect(screen.getByText('Status Category')).toBeInTheDocument(); - }); - - it('should render evolutionHelp if auditHelp and statusHelp are not available', () => { - const mockModuleWithEvolutionHelp = { - ...mockModule, - auditHelp: undefined, - statusHelp: undefined, - evolutionHelp: { - category: 'Evolution Category', - title: 'Evolution Title', - description: 'Evolution Description', - explanation: 'Evolution Explanation', - }, - }; - - render(); - - expect( - screen.getByText(VitalityTerms.VITALITY_APP_DETAILS_AUDIT_HELP_CATEGORY_LABEL), - ).toBeInTheDocument(); - expect(screen.getByText('Evolution Category')).toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/commons/components/__tests__/VitalityLinks-test.tsx b/v6y-apps/front/src/commons/components/__tests__/VitalityLinks-test.tsx deleted file mode 100644 index c2b913ff..00000000 --- a/v6y-apps/front/src/commons/components/__tests__/VitalityLinks-test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// VitalityLinks.test.tsx -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/react'; -import { VitalityLinks } from '@v6y/shared-ui'; -import * as React from 'react'; -import { describe, expect, it } from 'vitest'; - -describe('VitalityLinks', () => { - const mockLinks = [ - { - label: 'Link 1', - value: 'https://link1.com', - }, - { - label: 'Link 2', - value: 'https://link2.com', - }, - ]; - - it('should render the component with links', () => { - render(); - - expect(screen.getByText('Link 1')).toBeInTheDocument(); - expect(screen.getByText('Link 2')).toBeInTheDocument(); - }); - - it('should render links with correct href and target', () => { - render(); - - const link1Element = screen.getByText('Link 1').closest('a'); - expect(link1Element).toHaveAttribute('href', 'https://link1.com'); - expect(link1Element).toHaveAttribute('target', '_blank'); - expect(link1Element).toHaveAttribute('rel', 'noopener noreferrer'); - - const link2Element = screen.getByText('Link 2').closest('a'); - expect(link2Element).toHaveAttribute('href', 'https://link2.com'); - expect(link2Element).toHaveAttribute('target', '_blank'); - expect(link2Element).toHaveAttribute('rel', 'noopener noreferrer'); - }); - - it('should not render links with missing label or value', () => { - const mockLinksWithMissingData = [ - { - label: '', - value: 'https://link1.com', - }, - { - label: 'Link 2', - value: '', - }, - ]; - - render(); - - expect(screen.queryByText('Link 1')).not.toBeInTheDocument(); - expect(screen.queryByText('Link 2')).not.toBeInTheDocument(); - }); - - it('should not render anything if links array is empty or null', () => { - render(); - render(); - - expect(screen.queryByRole('link')).not.toBeInTheDocument(); - }); - - it('should apply the correct alignment', () => { - render(); - expect(screen.getAllByRole('link')).toHaveLength(2); - }); -}); diff --git a/v6y-apps/front/src/commons/components/__tests__/VitalityModulesView-test.tsx b/v6y-apps/front/src/commons/components/__tests__/VitalityModulesView-test.tsx deleted file mode 100644 index 43ad5a3d..00000000 --- a/v6y-apps/front/src/commons/components/__tests__/VitalityModulesView-test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// VitalityModulesView.test.tsx -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { describe, expect, it, vi } from 'vitest'; - -import { VitalityModuleType } from '../../types/VitalityModulesProps'; -import VitalityModulesView from '../modules/VitalityModulesView'; - -// Mock dynamic import to return the actual VitalityHelpView component -vi.mock('next/dynamic', () => ({ - default: vi.fn((callback) => callback().default), -})); - -describe('VitalityModulesView', () => { - const mockModules: VitalityModuleType[] = [ - { - name: 'Module 1', - label: 'Module 1 Label', - type: 'type1', - category: 'category1', - score: 80, - scoreUnit: '%', - status: 'success', - auditHelp: { - category: 'Help Category 1', - title: 'Help Title 1', - description: 'Help Description 1', - explanation: 'Help Explanation 1', - }, - branch: 'main', - path: '/path/to/file1', - statusHelp: {}, - evolutionHelp: {}, - }, - { - name: 'Module 2', - label: 'Module 2 Label', - type: 'type2', - category: 'category2', - score: 60, - scoreUnit: 'points', - status: 'warning', - auditHelp: {}, - statusHelp: { - category: 'Help Category 2', - title: 'Help Title 2', - description: 'Help Description 2', - explanation: 'Help Explanation 2', - }, - branch: 'develop', - path: '/path/to/file2', - evolutionHelp: {}, - }, - ]; - - it('should render the component with modules', () => { - render(); - - expect(screen.getByText('Module 1')).toBeInTheDocument(); - expect(screen.getByText('Module 2')).toBeInTheDocument(); - }); - - it('should render module details correctly', () => { - render(); - - // Check Module 1 details - expect(screen.getByText('Module 1')).toBeInTheDocument(); - expect(screen.getByText('80')).toBeInTheDocument(); - expect(screen.getByText('%')).toBeInTheDocument(); - expect(screen.getByText('main')).toBeInTheDocument(); - expect(screen.getByText('/path/to/file1')).toBeInTheDocument(); - - // Check Module 2 details - expect(screen.getByText('60')).toBeInTheDocument(); - expect(screen.getByText('points')).toBeInTheDocument(); - expect(screen.getByText('develop')).toBeInTheDocument(); - expect(screen.getByText('/path/to/file2')).toBeInTheDocument(); - }); - - it('should handle modules with missing optional fields', () => { - const incompleteModules: VitalityModuleType[] = [ - { - name: 'Module 3', - label: '', - type: '', - category: '', - score: undefined, - scoreUnit: '', - status: '', - auditHelp: {}, - branch: '', - path: '', - statusHelp: {}, - evolutionHelp: {}, - }, - ]; - - render(); - - // Check if Module 3 is rendered without errors - expect(screen.getByText('Module 3')).toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/commons/components/__tests__/VitalitySelectGrouperView-test.tsx b/v6y-apps/front/src/commons/components/__tests__/VitalitySelectGrouperView-test.tsx deleted file mode 100644 index 263a5822..00000000 --- a/v6y-apps/front/src/commons/components/__tests__/VitalitySelectGrouperView-test.tsx +++ /dev/null @@ -1,150 +0,0 @@ -// VitalitySelectGrouperView.test.tsx -import '@testing-library/jest-dom/vitest'; -import { fireEvent, render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; - -import useDataGrouper from '../../hooks/useDataGrouper'; -import VitalitySelectGrouperView from '../VitalitySelectGrouperView'; - -// Mock useDataGrouper hook -vi.mock('../../hooks/useDataGrouper'); - -describe('VitalitySelectGrouperView', () => { - const mockDataSource = [ - { name: 'Item 1', group: 'Group A' }, - { name: 'Item 2', group: 'Group B' }, - { name: 'Item 3', group: 'Group A' }, - ]; - const mockCriteria = 'group'; - const mockPlaceholder = 'Select a group'; - const mockLabel = 'Group by'; - const mockHelper = 'Select a group to filter items'; - const mockOnRenderChildren = vi.fn((group, items) => ( -
-

{group}

-
    - {items.map((item) => ( -
  • {item.name}
  • - ))} -
-
- )); - - beforeEach(() => { - (useDataGrouper as Mock).mockReturnValue({ - groupedDataSource: { - 'Group A': [ - { name: 'Item 1', group: 'Group A' }, - { name: 'Item 3', group: 'Group A' }, - ], - 'Group B': [{ name: 'Item 2', group: 'Group B' }], - }, - selectedCriteria: { key: '', label: undefined, value: 'All' }, - criteriaGroups: [ - { value: 'All', label: 'All' }, - { value: 'Group A', label: 'Group A' }, - { value: 'Group B', label: 'Group B' }, - ], - setSelectedCriteria: vi.fn(), - }); - }); - - it('should render the component with data', () => { - render( - , - ); - - expect(screen.getByRole('form')).toBeInTheDocument(); - expect(screen.getByText(mockLabel)).toBeInTheDocument(); - expect(screen.getByText(mockHelper)).toBeInTheDocument(); - expect(screen.getByRole('combobox')).toBeInTheDocument(); - }); - - it('should render an empty view if dataSource or criteria is empty', () => { - render(); - expect(screen.getAllByText('No data')?.[0]).toBeInTheDocument(); - }); - - it('should render an empty view if groupedDataSource is empty', () => { - (useDataGrouper as Mock).mockReturnValue({ - groupedDataSource: {}, - selectedCriteria: { key: '', label: undefined, value: 'All' }, - criteriaGroups: [], - setSelectedCriteria: vi.fn(), - }); - - render( - , - ); - expect(screen.getAllByText('No data')?.[0]).toBeInTheDocument(); - }); - - it('should update selectedCriteria when the select value changes', async () => { - render( - , - ); - - const selectElement = screen.getByRole('combobox'); - fireEvent.change(selectElement, { target: { value: 'Group A' } }); - - expect(useDataGrouper).toHaveBeenLastCalledWith({ - dataSource: mockDataSource, - criteria: mockCriteria, - hasAllGroup: undefined, - }); - }); - - it('should call onRenderChildren with correct parameters', () => { - render( - , - ); - - expect(mockOnRenderChildren).toHaveBeenCalledWith('All', mockDataSource); - }); - - it('should render correct items based on selected criteria', async () => { - render( - , - ); - - const selectElement = screen.getByRole('combobox'); - fireEvent.change(selectElement, { target: { value: 'Group A' } }); - - expect(screen.getAllByText('Group A')?.[0]).toBeInTheDocument(); - expect(screen.getByText('Item 1')).toBeInTheDocument(); - expect(screen.getByText('Item 3')).toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/commons/components/__tests__/VitalityTabGrouperView-test.tsx b/v6y-apps/front/src/commons/components/__tests__/VitalityTabGrouperView-test.tsx deleted file mode 100644 index a458b38e..00000000 --- a/v6y-apps/front/src/commons/components/__tests__/VitalityTabGrouperView-test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -// VitalityTabGrouperView.test.tsx -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; - -import useDataGrouper from '../../hooks/useDataGrouper'; -import VitalityTabGrouperView from '../VitalityTabGrouperView'; - -// Mock useDataGrouper hook -vi.mock('../../hooks/useDataGrouper'); - -describe('VitalityTabGrouperView', () => { - const mockDataSource = [ - { name: 'Item 1', group: 'Group A' }, - { name: 'Item 2', group: 'Group B' }, - { name: 'Item 3', group: 'Group A' }, - ]; - const mockCriteria = 'group'; - const mockPlaceholder = 'Tab a group'; - const mockLabel = 'Group by'; - const mockHelper = 'Tab a group to filter items'; - const mockOnRenderChildren = vi.fn((group, items) => ( -
-

{group}

-
    - {items.map((item) => ( -
  • {item.name}
  • - ))} -
-
- )); - - beforeEach(() => { - (useDataGrouper as Mock).mockReturnValue({ - groupedDataSource: { - 'Group A': [ - { name: 'Item 1', group: 'Group A' }, - { name: 'Item 3', group: 'Group A' }, - ], - 'Group B': [{ name: 'Item 2', group: 'Group B' }], - }, - TabedCriteria: { key: '', label: undefined, value: 'All' }, - criteriaGroups: [ - { value: 'All', label: 'All' }, - { value: 'Group A', label: 'Group A' }, - { value: 'Group B', label: 'Group B' }, - ], - setTabedCriteria: vi.fn(), - }); - }); - - it('should render the component with data', () => { - render( - , - ); - - expect(screen.getAllByText('No data')?.[0]).toBeInTheDocument(); - }); - - it('should render an empty view if groupedDataSource is empty', () => { - (useDataGrouper as Mock).mockReturnValue({ - groupedDataSource: {}, - TabedCriteria: { key: '', label: undefined, value: 'All' }, - criteriaGroups: [], - setTabedCriteria: vi.fn(), - }); - - render( - , - ); - expect(screen.getAllByText('No data')?.[0]).toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/commons/components/application-info/VitalityAppInfos.tsx b/v6y-apps/front/src/commons/components/application-info/VitalityAppInfos.tsx index 43ce4499..087855c9 100644 --- a/v6y-apps/front/src/commons/components/application-info/VitalityAppInfos.tsx +++ b/v6y-apps/front/src/commons/components/application-info/VitalityAppInfos.tsx @@ -1,7 +1,8 @@ import { Col, Divider, - List, + ListItem, + ListItemMeta, Row, Tag, VitalityLinks, @@ -31,8 +32,8 @@ const VitalityAppInfos = ({ app, source, canOpenDetails = true, style }: Vitalit const qualityMetricStatus = currentConfig?.status || {}; return ( - - + @@ -95,7 +96,7 @@ const VitalityAppInfos = ({ app, source, canOpenDetails = true, style }: Vitalit } /> - + ); }; diff --git a/v6y-apps/front/src/commons/components/help/VitalityHelpView.tsx b/v6y-apps/front/src/commons/components/help/VitalityHelpView.tsx index 66797c99..329ea806 100644 --- a/v6y-apps/front/src/commons/components/help/VitalityHelpView.tsx +++ b/v6y-apps/front/src/commons/components/help/VitalityHelpView.tsx @@ -1,3 +1,4 @@ +import { Matcher } from '@v6y/core-logic/src/utils'; import { Descriptions } from '@v6y/shared-ui'; import * as React from 'react'; @@ -5,7 +6,21 @@ import VitalityTerms from '../../config/VitalityTerms'; import { VitalityModuleType } from '../../types/VitalityModulesProps'; const VitalityHelpView = ({ module }: { module: VitalityModuleType }) => { - const moduleHelp = module?.auditHelp || module?.statusHelp || module?.evolutionHelp; + const moduleHelp = Matcher() + .on( + () => module?.auditHelp && Object.keys(module?.auditHelp).length > 0, + () => module?.auditHelp, + ) + .on( + () => module?.statusHelp && Object.keys(module?.statusHelp).length > 0, + () => module?.statusHelp, + ) + .on( + () => module?.evolutionHelp && Object.keys(module?.evolutionHelp).length > 0, + () => module?.evolutionHelp, + ) + .otherwise(() => ({})) as Record; + return ( import('../help/VitalityHelpView')); + +const VitalityModuleList = ({ modules }: VitalityModulesProps) => { + const [isHelpModalOpen, setIsHelpModalOpen] = useState(false); + const [helpDetails, setHelpDetails] = useState(); + + useEffect(() => { + if (Object.keys(helpDetails || {})?.length > 0) { + setIsHelpModalOpen(true); + } else { + setIsHelpModalOpen(false); + } + }, [helpDetails]); + + return ( + <> + ( + + )} + /> + + } + onCloseModal={() => setHelpDetails(undefined)} + isOpen={isHelpModalOpen} + > + {helpDetails && } + + + ); +}; + +export default VitalityModuleList; diff --git a/v6y-apps/front/src/commons/components/modules/VitalityModuleListItem.tsx b/v6y-apps/front/src/commons/components/modules/VitalityModuleListItem.tsx new file mode 100644 index 00000000..3b10a709 --- /dev/null +++ b/v6y-apps/front/src/commons/components/modules/VitalityModuleListItem.tsx @@ -0,0 +1,122 @@ +import { + Avatar, + Button, + Card, + Divider, + InfoCircleOutlined, + ListItem, + ListItemMeta, + PushpinOutlined, + Space, + Statistic, + VitalityText, + useThemeConfigProvider, +} from '@v6y/shared-ui'; +import * as React from 'react'; + +import VitalityTerms from '../../config/VitalityTerms'; +import { VitalityModuleType } from '../../types/VitalityModulesProps'; + +type VitalityModuleItemProps = { + module: VitalityModuleType; + onModuleClicked: (module: VitalityModuleType) => void; +}; + +const VitalityModuleListItem = ({ module, onModuleClicked }: VitalityModuleItemProps) => { + const { currentConfig } = useThemeConfigProvider(); + const patternName = + module.name || module.label || `${module.type ? `${module.type}-` : ''}${module.category}`; + const moduleScore = module.score ? `${module.score || 0} ${module.scoreUnit || ''}` : ''; + const hasAuditHelp = Object.keys(module.auditHelp || {}).length > 0; + const hasDependencyStatusHelp = Object.keys(module.statusHelp || {}).length > 0; + const hasEvolutionHelp = Object.keys(module.evolutionHelp || {}).length > 0; + const modulePath = module?.path?.replaceAll(' -> []', ''); + const qualityMetricStatus = currentConfig?.status || {}; + const qualityMetricStatusIcons = currentConfig?.statusIcons || {}; + + return ( + + + {patternName} + {(hasAuditHelp || hasDependencyStatusHelp || hasEvolutionHelp) && ( +
)} /> diff --git a/v6y-apps/front/src/features/app-details/components/dependencies/VitalityDependenciesView.tsx b/v6y-apps/front/src/features/app-details/components/dependencies/VitalityDependenciesView.tsx index 388259ea..ccbb7cdf 100644 --- a/v6y-apps/front/src/features/app-details/components/dependencies/VitalityDependenciesView.tsx +++ b/v6y-apps/front/src/features/app-details/components/dependencies/VitalityDependenciesView.tsx @@ -1,8 +1,7 @@ import { DependencyType } from '@v6y/core-logic/src/types'; -import { ProductOutlined, useNavigationAdapter } from '@v6y/shared-ui'; +import { ProductOutlined, VitalityDynamicLoader, useNavigationAdapter } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalitySectionView from '../../../../commons/components/VitalitySectionView'; import VitalityApiConfig from '../../../../commons/config/VitalityApiConfig'; import VitalityTerms from '../../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionBranchGrouper.tsx b/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionBranchGrouper.tsx index 1476286d..c28df51f 100644 --- a/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionBranchGrouper.tsx +++ b/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionBranchGrouper.tsx @@ -1,7 +1,7 @@ import { EvolutionType } from '@v6y/core-logic/src/types'; +import { VitalityDynamicLoader } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalitySelectGrouperView from '../../../../commons/components/VitalitySelectGrouperView'; import VitalityTerms from '../../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionStatusGrouper.tsx b/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionStatusGrouper.tsx index c20c170f..444cb08c 100644 --- a/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionStatusGrouper.tsx +++ b/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionStatusGrouper.tsx @@ -1,12 +1,12 @@ import { EvolutionType } from '@v6y/core-logic/src/types'; +import { VitalityDynamicLoader } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalityTabGrouperView from '../../../../commons/components/VitalityTabGrouperView'; import { VitalityModuleType } from '../../../../commons/types/VitalityModulesProps'; -const VitalityModulesView = VitalityDynamicLoader( - () => import('../../../../commons/components/modules/VitalityModulesView'), +const VitalityModuleList = VitalityDynamicLoader( + () => import('../../../../commons/components/modules/VitalityModuleList'), ); const VitalityEvolutionStatusGrouper = ({ evolutions }: { evolutions: EvolutionType[] }) => { @@ -20,11 +20,7 @@ const VitalityEvolutionStatusGrouper = ({ evolutions }: { evolutions: EvolutionT dataSource={evolutions} onRenderChildren={(status, data) => (
- +
)} /> diff --git a/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionsView.tsx b/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionsView.tsx index e9a67060..726f26cd 100644 --- a/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionsView.tsx +++ b/v6y-apps/front/src/features/app-details/components/evolutions/VitalityEvolutionsView.tsx @@ -1,8 +1,7 @@ import { EvolutionType } from '@v6y/core-logic/src/types'; -import { BulbOutlined, useNavigationAdapter } from '@v6y/shared-ui'; +import { BulbOutlined, VitalityDynamicLoader, useNavigationAdapter } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalitySectionView from '../../../../commons/components/VitalitySectionView'; import VitalityApiConfig from '../../../../commons/config/VitalityApiConfig'; import VitalityTerms from '../../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx b/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx index fcb23cdc..4fd722c1 100644 --- a/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx +++ b/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx @@ -1,8 +1,7 @@ import { ApplicationType } from '@v6y/core-logic/src/types'; -import { InfoOutlined, useNavigationAdapter } from '@v6y/shared-ui'; +import { InfoOutlined, VitalityDynamicLoader, useNavigationAdapter } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalitySectionView from '../../../../commons/components/VitalitySectionView'; import VitalityApiConfig from '../../../../commons/config/VitalityApiConfig'; import VitalityTerms from '../../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorBranchGrouper.tsx b/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorBranchGrouper.tsx index 5f077b04..43776cb8 100644 --- a/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorBranchGrouper.tsx +++ b/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorBranchGrouper.tsx @@ -1,7 +1,7 @@ import { KeywordType } from '@v6y/core-logic/src/types'; +import { VitalityDynamicLoader } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalitySelectGrouperView from '../../../../commons/components/VitalitySelectGrouperView'; import VitalityTerms from '../../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorStatusGrouper.tsx b/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorStatusGrouper.tsx index 68266231..a91711d0 100644 --- a/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorStatusGrouper.tsx +++ b/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorStatusGrouper.tsx @@ -1,12 +1,12 @@ import { KeywordType } from '@v6y/core-logic/src/types'; +import { VitalityDynamicLoader } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalityTabGrouperView from '../../../../commons/components/VitalityTabGrouperView'; import { VitalityModuleType } from '../../../../commons/types/VitalityModulesProps'; -const VitalityModulesView = VitalityDynamicLoader( - () => import('../../../../commons/components/modules/VitalityModulesView'), +const VitalityModuleList = VitalityDynamicLoader( + () => import('../../../../commons/components/modules/VitalityModuleList'), ); const VitalityQualityIndicatorStatusGrouper = ({ indicators }: { indicators: KeywordType[] }) => { @@ -25,15 +25,13 @@ const VitalityQualityIndicatorStatusGrouper = ({ indicators }: { indicators: Key ...indicators?.filter((indicator) => indicator.status === 'warning'), ...indicators?.filter((indicator) => indicator.status === 'success'), ]} - onRenderChildren={(status, data) => ( -
- -
- )} + onRenderChildren={(status, data) => { + return ( +
+ +
+ ); + }} /> ); }; diff --git a/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorsView.tsx b/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorsView.tsx index f008a164..74c9ea68 100644 --- a/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorsView.tsx +++ b/v6y-apps/front/src/features/app-details/components/quality-indicators/VitalityQualityIndicatorsView.tsx @@ -1,8 +1,7 @@ import { KeywordType } from '@v6y/core-logic/src/types'; -import { CompassOutlined, useNavigationAdapter } from '@v6y/shared-ui'; +import { CompassOutlined, VitalityDynamicLoader, useNavigationAdapter } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../../commons/components/VitalityDynamicLoader'; import VitalitySectionView from '../../../../commons/components/VitalitySectionView'; import VitalityApiConfig from '../../../../commons/config/VitalityApiConfig'; import VitalityTerms from '../../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-list/components/VitalityAppList.tsx b/v6y-apps/front/src/features/app-list/components/VitalityAppList.tsx index 53f8478c..a10e27aa 100644 --- a/v6y-apps/front/src/features/app-list/components/VitalityAppList.tsx +++ b/v6y-apps/front/src/features/app-list/components/VitalityAppList.tsx @@ -1,15 +1,21 @@ 'use client'; import { ApplicationType } from '@v6y/core-logic/src/types'; -import { Col, Row, useNavigationAdapter } from '@v6y/shared-ui'; +import { + Col, + Row, + VitalityDynamicLoader, + VitalityEmptyView, + VitalityLoadMoreList, + useNavigationAdapter, +} from '@v6y/shared-ui'; import * as React from 'react'; import { useEffect, useState } from 'react'; -import VitalityDynamicLoader from '../../../commons/components/VitalityDynamicLoader'; -import VitalityLoadMoreList from '../../../commons/components/VitalityLoadMoreList'; import VitalityAppInfos from '../../../commons/components/application-info/VitalityAppInfos'; import VitalityApiConfig from '../../../commons/config/VitalityApiConfig'; import { formatApplicationDataSource } from '../../../commons/config/VitalityCommonConfig'; +import VitalityTerms from '../../../commons/config/VitalityTerms'; import { exportAppListDataToCSV } from '../../../commons/utils/VitalityDataExportUtils'; import { buildClientQuery, @@ -90,17 +96,22 @@ const VitalityAppList = ({ source }: { source?: string }) => { - - { - const app = item as ApplicationType; - return ; - }} - onLoadMore={onLoadMore} - /> - + {appList?.length === 0 ? ( + + ) : ( + + { + const app = item as ApplicationType; + return ; + }} + onLoadMore={onLoadMore} + /> + + )} ); }; diff --git a/v6y-apps/front/src/features/app-list/components/VitalityAppListView.tsx b/v6y-apps/front/src/features/app-list/components/VitalityAppListView.tsx index 443a1fbb..54ed7bec 100644 --- a/v6y-apps/front/src/features/app-list/components/VitalityAppListView.tsx +++ b/v6y-apps/front/src/features/app-list/components/VitalityAppListView.tsx @@ -1,9 +1,8 @@ 'use client'; -import { Col, Row } from '@v6y/shared-ui'; +import { Col, Row, VitalityDynamicLoader } from '@v6y/shared-ui'; import * as React from 'react'; -import VitalityDynamicLoader from '../../../commons/components/VitalityDynamicLoader'; import VitalitySearchBar from '../../../commons/components/VitalitySearchBar'; import VitalityTerms from '../../../commons/config/VitalityTerms'; diff --git a/v6y-apps/front/src/features/app-list/components/__tests__/VitalityAppListHeader-test.tsx b/v6y-apps/front/src/features/app-list/components/__tests__/VitalityAppListHeader-test.tsx deleted file mode 100644 index ff617436..00000000 --- a/v6y-apps/front/src/features/app-list/components/__tests__/VitalityAppListHeader-test.tsx +++ /dev/null @@ -1,131 +0,0 @@ -// VitalityAppListHeader.test.tsx -import '@testing-library/jest-dom/vitest'; -import { fireEvent, render, screen } from '@testing-library/react'; -import { useNavigationAdapter } from '@v6y/shared-ui'; -import * as React from 'react'; -import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { useClientQuery } from '../../../../infrastructure/adapters/api/useQueryAdapter'; -import VitalityAppListHeader from '../VitalityAppListHeader'; - -// Mock useNavigationAdapter -vi.mock(import('@v6y/shared-ui'), async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - useNavigationAdapter: vi.fn(), - }; -}); - -// Mock useClientQuery -vi.mock('../../../../infrastructure/adapters/api/useQueryAdapter'); - -describe('VitalityAppListHeader', () => { - const mockOnExportApplicationsClicked = vi.fn(); - - beforeEach(() => { - (useNavigationAdapter as Mock).mockReturnValue({ - getUrlParams: vi.fn(() => [[], '']), - }); - - (useClientQuery as Mock).mockReturnValue({ - isLoading: false, - data: { getApplicationTotalByParams: 10 }, - refetch: vi.fn(), - }); - }); - - it('should render the component', () => { - render( - , - ); - - expect(screen.getByRole('heading').textContent).toEqual(`TOTAL Applications: 10`); - expect(screen.getByText(`Export Applications`)).toBeInTheDocument(); - expect( - screen.getByRole('button', { - name: 'export Export Applications', - }), - ).toBeInTheDocument(); - }); - - it('should call onExportApplicationsClicked when the export button is clicked', () => { - render( - , - ); - - const exportButton = screen.getByRole('button', { - name: 'export Export Applications', - }); - fireEvent.click(exportButton); - - expect(mockOnExportApplicationsClicked).toHaveBeenCalled(); - }); - - it('should fetch app total on mount', () => { - render( - , - ); - - expect(useClientQuery).toHaveBeenCalled(); - expect((useClientQuery as Mock).mock.results[0].value.refetch).toHaveBeenCalled(); - }); - - it('should update app total when data changes', () => { - (useClientQuery as Mock).mockReturnValue({ - isLoading: false, - data: { getApplicationTotalByParams: 5 }, - refetch: vi.fn(), - }); - - render( - , - ); - - expect(screen.getByRole('heading').textContent).toEqual(`TOTAL Applications: 5`); - }); - - it('should handle missing data', () => { - (useClientQuery as Mock).mockReturnValue({ - isLoading: false, - data: undefined, - refetch: vi.fn(), - }); - - render( - , - ); - - // Expecting no title to be rendered if appsTotal is 0 or undefined - expect(screen.queryByRole('heading', { level: 3 })).not.toBeInTheDocument(); - }); - - it('should refetch data when keywords or searchText change', () => { - const refetchMock = vi.fn(); - (useNavigationAdapter as Mock).mockReturnValue({ - getUrlParams: vi.fn(() => [['keyword1'], 'test']), - }); - (useClientQuery as Mock).mockReturnValue({ - isLoading: false, - data: { getApplicationTotalByParams: 10 }, - refetch: refetchMock, - }); - - render( - , - ); - - expect(refetchMock).toHaveBeenCalledTimes(2); // Initial refetch - - (useNavigationAdapter as Mock).mockReturnValue({ - getUrlParams: vi.fn(() => [['keyword2'], 'test2']), - }); - - // Trigger a re-render with updated keywords and searchText - render( - , - ); - - expect(refetchMock).toHaveBeenCalledTimes(4); // Refetch after props change - }); -}); diff --git a/v6y-apps/front/src/features/auth/components/VitalityLoginForm.tsx b/v6y-apps/front/src/features/auth/components/VitalityLoginForm.tsx index 22449022..99cd4451 100644 --- a/v6y-apps/front/src/features/auth/components/VitalityLoginForm.tsx +++ b/v6y-apps/front/src/features/auth/components/VitalityLoginForm.tsx @@ -2,7 +2,6 @@ import { Button, Form, Message, VitalityCheckbox, VitalityInput, useForm } from '@v6y/shared-ui'; import { useEffect } from 'react'; -import { z } from 'zod'; import VitalityTerms from '../../../commons/config/VitalityTerms'; import { @@ -64,21 +63,20 @@ const VitalityLoginForm = () => { > { - try { - loginSchemaValidator.parse(value); - return true; - } catch (error) { - if (error instanceof z.ZodError) { - return error?.errors?.[0]?.message; - } - } + const result = loginSchemaValidator.safeParse({ + email: value, + password: '', + }); + return result?.success + ? true + : result?.error?.format?.()?.email?._errors?.[0]; }, }} - ariaLabel="Email" /> @@ -96,11 +94,20 @@ const VitalityLoginForm = () => { { + const result = loginSchemaValidator.safeParse({ + email: '', + password: value, + }); + return result?.success + ? true + : result?.error?.format?.()?.password?._errors?.[0]; + }, }} - ariaLabel={VitalityTerms.VITALITY_APP_LOGIN_FORM_PASSWORD_LABEL} - type="password" /> diff --git a/v6y-apps/front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx b/v6y-apps/front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx deleted file mode 100644 index 1ff99b05..00000000 --- a/v6y-apps/front/src/features/auth/components/__tests__/VitalityLoginForm-test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import '@testing-library/jest-dom'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { useNavigationAdapter } from '@v6y/shared-ui'; -import * as React from 'react'; -import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; - -import { buildClientQuery } from '../../../../infrastructure/adapters/api/useQueryAdapter'; -import VitalityLoginForm from '../VitalityLoginForm'; - -// Mock useNavigationAdapter -vi.mock(import('@v6y/shared-ui'), async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - useNavigationAdapter: vi.fn(), - }; -}); - -// Mock useClientQuery -vi.mock('../../../../infrastructure/adapters/api/useQueryAdapter'); - -describe('VitalityLoginForm', () => { - beforeEach(() => { - (useNavigationAdapter as Mock).mockReturnValue({ - getUrlParams: vi.fn(() => [[], '']), - }); - - (buildClientQuery as Mock).mockReturnValue({ - loginAccount: { - token: 'test', - _id: 'test', - role: 'test', - }, - }); - }); - - it('should render the component', () => { - render(); - expect(screen.getByText('Email')).toBeInTheDocument(); - expect(screen.getByText('Password')).toBeInTheDocument(); - expect(screen.getByText('Login')).toBeInTheDocument(); - }); - - it('should fail when submitting with incorrect credentials', async () => { - (buildClientQuery as Mock).mockReturnValue({ loginAccount: null }); - - render(); - fireEvent.change(screen.getByRole('textbox', { name: /email/i }), { - target: { value: 'test@test.test' }, - }); - fireEvent.change(screen.getByLabelText('Password', { selector: 'input' }), { - target: { value: 'wrongpassword' }, - }); - fireEvent.click(screen.getByText('Login')); - await waitFor(() => { - expect(screen.getByText('Incorrect credentials')).toBeInTheDocument(); - }); - }); - - it('should redirect when submitting with correct credentials', async () => { - render(); - fireEvent.change(screen.getByRole('textbox', { name: /email/i }), { - target: { value: 'test@test.test' }, - }); - fireEvent.change(screen.getByLabelText('Password', { selector: 'input' }), { - target: { value: 'test' }, - }); - fireEvent.click(screen.getByText('Login')); - await waitFor(() => { - expect(screen.getByText('Successful connection')).toBeInTheDocument(); - }); - }); - - it('should call onFinishFailed when submitting the form with empty fields', async () => { - render(); - fireEvent.click(screen.getByText('Login')); - await waitFor(() => { - expect(screen.getByText('Please enter a valid email address')).toBeInTheDocument(); - expect(screen.getByText('Please enter a password')).toBeInTheDocument(); - }); - }); -}); diff --git a/v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenu-test.tsx b/v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenu-test.tsx deleted file mode 100644 index f94c8c43..00000000 --- a/v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenu-test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// VitalityDashboardMenu.test.tsx -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { describe, expect, it, vi } from 'vitest'; - -import { VITALITY_DASHBOARD_DATASOURCE } from '../../../../commons/config/VitalityCommonConfig'; -import VitalityDashboardMenu from '../VitalityDashboardMenu'; - -// Mock VitalityDashboardMenuItem -vi.mock('./VitalityDashboardMenuItem'); - -describe('VitalityDashboardMenu', () => { - it('should render the component with menu items', () => { - render(); - - expect(screen.getByText('React')).toBeInTheDocument(); - expect( - screen.getByText('Choose this option to view React applications.'), - ).toBeInTheDocument(); - - VITALITY_DASHBOARD_DATASOURCE.forEach((option) => { - expect(screen.getByText(option.title)).toBeInTheDocument(); - }); - }); - - it('should not render anything if options array is empty', () => { - render(); - - expect(screen.queryByText('React')).not.toBeInTheDocument(); - expect(screen.queryByText('Angular')).not.toBeInTheDocument(); - expect(screen.queryByText('React Legacy')).not.toBeInTheDocument(); - expect(screen.queryByText('Angular Legacy')).not.toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenuItem-test.tsx b/v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenuItem-test.tsx deleted file mode 100644 index 3244913a..00000000 --- a/v6y-apps/front/src/features/dashboard/components/__tests__/VitalityDashboardMenuItem-test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// VitalityDashboardMenuItem.test.tsx -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import { describe, expect, it } from 'vitest'; - -import { DashboardItemType } from '../../../../commons/config/VitalityCommonConfig'; -import VitalityDashboardMenuItem from '../VitalityDashboardMenuItem'; - -describe('VitalityDashboardMenuItem', () => { - const mockOption: DashboardItemType = { - autoFocus: true, - defaultChecked: false, - title: 'Test Item', - description: 'Test description', - url: '/test-url', - avatar:
Test Avatar
, - avatarColor: 'red', - }; - - it('should render the component with menu item details', () => { - render(); - - expect(screen.getByRole('link')).toHaveAttribute('href', '/test-url'); - - expect(screen.getByText('Test Item')).toBeInTheDocument(); - expect(screen.getByText('Test description')).toBeInTheDocument(); - }); - - it('should not render anything if option is null or undefined', () => { - render(); - render(); - - expect(screen.queryByRole('link')).not.toBeInTheDocument(); - }); -}); diff --git a/v6y-apps/front/src/features/faq/components/VitalityFaqView.tsx b/v6y-apps/front/src/features/faq/components/VitalityFaqView.tsx index 8b4172a9..8214b420 100644 --- a/v6y-apps/front/src/features/faq/components/VitalityFaqView.tsx +++ b/v6y-apps/front/src/features/faq/components/VitalityFaqView.tsx @@ -1,7 +1,7 @@ 'use client'; import { FaqType } from '@v6y/core-logic/src/types'; -import { QuestionOutlined, VitalityEmptyView } from '@v6y/shared-ui'; +import { QuestionOutlined } from '@v6y/shared-ui'; import * as React from 'react'; import VitalitySectionView from '../../../commons/components/VitalitySectionView'; @@ -31,9 +31,6 @@ const VitalityFaqView = () => { }); const dataSource = data?.getFaqListByPageAndParams; - if (!dataSource) { - return ; - } return ( { - const mockDataSource: FaqType[] = [ - { - title: 'Question 1', - description: 'Answer 1', - links: [ - { - label: 'Link 1', - value: 'https://link1.com', - }, - ], - }, - { - title: 'Question 2', - description: 'Answer 2', - }, - { - // This FAQ item should be filtered out because it has no title - description: 'Answer 3', - links: [ - { - label: 'Link 2', - value: 'https://link2.com', - }, - ], - }, - ]; - - it('should render the component with FAQ items', () => { - render(); - - expect(VitalityCollapse).toHaveBeenCalledWith( - expect.objectContaining({ - accordion: true, - bordered: true, - dataSource: [ - { - key: 'Question 1', - label: 'Question 1', - children: expect.anything(), - showArrow: true, - }, - { - key: 'Question 2', - label: 'Question 2', - children: expect.anything(), - showArrow: true, - }, - ], - }), - undefined, - ); - }); - - it('should filter out FAQ items without a title', () => { - render(); - - // Check that the FAQ item without a title is not rendered - expect(screen.queryByText('Answer 3')).not.toBeInTheDocument(); - expect(VitalityLinks).not.toHaveBeenCalledWith( - expect.objectContaining({ links: mockDataSource[2].links }), - expect.anything(), - ); - }); -}); diff --git a/v6y-apps/front/src/features/notifications/components/VitalityNotificationView.tsx b/v6y-apps/front/src/features/notifications/components/VitalityNotificationView.tsx index 0420b122..99228b7b 100644 --- a/v6y-apps/front/src/features/notifications/components/VitalityNotificationView.tsx +++ b/v6y-apps/front/src/features/notifications/components/VitalityNotificationView.tsx @@ -1,7 +1,7 @@ 'use client'; import { NotificationType } from '@v6y/core-logic/src/types'; -import { NotificationOutlined, VitalityEmptyView } from '@v6y/shared-ui'; +import { NotificationOutlined } from '@v6y/shared-ui'; import VitalitySectionView from '../../../commons/components/VitalitySectionView'; import VitalityApiConfig from '../../../commons/config/VitalityApiConfig'; @@ -30,9 +30,6 @@ const VitalityNotificationView = () => { }); const dataSource = data?.getNotificationListByPageAndParams; - if (!dataSource) { - return ; - } return ( { - const mockDataSource: NotificationType[] = [ - { - title: 'Question 1', - description: 'Answer 1', - links: [ - { - label: 'Link 1', - value: 'https://link1.com', - }, - ], - }, - { - title: 'Question 2', - description: 'Answer 2', - }, - { - // This Notification item should be filtered out because it has no title - description: 'Answer 3', - links: [ - { - label: 'Link 2', - value: 'https://link2.com', - }, - ], - }, - ]; - - it('should render the component with Notification items', () => { - render(); - - expect(VitalityCollapse).toHaveBeenCalledWith( - expect.objectContaining({ - accordion: true, - bordered: true, - dataSource: [ - { - key: 'Question 1', - label: 'Question 1', - children: expect.anything(), - showArrow: true, - }, - { - key: 'Question 2', - label: 'Question 2', - children: expect.anything(), - showArrow: true, - }, - ], - }), - undefined, - ); - }); - - it('should filter out Notification items without a title', () => { - render(); - - // Check that the Notification item without a title is not rendered - expect(screen.queryByText('Answer 3')).not.toBeInTheDocument(); - expect(VitalityLinks).not.toHaveBeenCalledWith( - expect.objectContaining({ links: mockDataSource[2].links }), - expect.anything(), - ); - }); -}); diff --git a/v6y-apps/front/vite.config.ts b/v6y-apps/front/vite.config.ts index 6ab0d4e1..336f40ad 100644 --- a/v6y-apps/front/vite.config.ts +++ b/v6y-apps/front/vite.config.ts @@ -6,8 +6,6 @@ import { configDefaults, defineConfig } from 'vitest/config'; * https://vitest.dev/config/#configuration */ export default defineConfig({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error plugins: [react()], resolve: { alias: { @@ -29,7 +27,18 @@ export default defineConfig({ coverage: { provider: 'v8', include: ['src/**'], - exclude: [...configDefaults.coverage.exclude, '**/types/**', '**/app/**', '**/api/**'], + exclude: [ + ...configDefaults.coverage.exclude, + '**/types/**', + '**/app/**', + '**/api/**', + '**/chatbot/**', + '**/ProtectedRoute.tsx', + '**/test-utils/**', + '**/apps-stats/**', + '**/pages/**', + '**/infrastructure/providers/**', + ], }, }, }); diff --git a/v6y-apps/front/vitest.setup.ts b/v6y-apps/front/vitest.setup.ts index 61a96d42..7425993a 100644 --- a/v6y-apps/front/vitest.setup.ts +++ b/v6y-apps/front/vitest.setup.ts @@ -1,13 +1,7 @@ -import { cleanup } from '@testing-library/react'; import { vi } from 'vitest'; -import { afterEach } from 'vitest'; import './setupTests'; -afterEach(() => { - cleanup(); -}); - Object.defineProperty(window, 'getComputedStyle', { value: () => ({ getPropertyValue: () => { diff --git a/v6y-libs/core-logic/src/__tests__/.gitkeep b/v6y-libs/core-logic/src/__tests__/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/v6y-libs/core-logic/src/core/__tests__/AuditUtils-test.ts b/v6y-libs/core-logic/src/__tests__/AuditUtils-test.ts similarity index 98% rename from v6y-libs/core-logic/src/core/__tests__/AuditUtils-test.ts rename to v6y-libs/core-logic/src/__tests__/AuditUtils-test.ts index a4db367f..45849e7e 100644 --- a/v6y-libs/core-logic/src/core/__tests__/AuditUtils-test.ts +++ b/v6y-libs/core-logic/src/__tests__/AuditUtils-test.ts @@ -1,7 +1,7 @@ // AuditUtils.test.ts import { describe, expect, it, vi } from 'vitest'; -import AuditUtils from '../AuditUtils.ts'; +import AuditUtils from '../core/AuditUtils.ts'; // Mock fs-extra vi.mock('fs-extra', async () => { diff --git a/v6y-libs/core-logic/src/core/__tests__/AuthenticationHelper-test.ts b/v6y-libs/core-logic/src/__tests__/AuthenticationHelper-test.ts similarity index 97% rename from v6y-libs/core-logic/src/core/__tests__/AuthenticationHelper-test.ts rename to v6y-libs/core-logic/src/__tests__/AuthenticationHelper-test.ts index e6854cfb..44b74c3a 100644 --- a/v6y-libs/core-logic/src/core/__tests__/AuthenticationHelper-test.ts +++ b/v6y-libs/core-logic/src/__tests__/AuthenticationHelper-test.ts @@ -1,9 +1,7 @@ import passport from 'passport'; import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; -import AccountProvider from '../../database/AccountProvider.ts'; -import { AccountType } from '../../types/AccountType.ts'; -import AppLogger from '../AppLogger.ts'; +import AppLogger from '../core/AppLogger.ts'; import { configureAuthenticationStrategy, createJwtStrategyVerify, @@ -11,7 +9,9 @@ import { isAdmin, isSuperAdmin, validateCredentials, -} from '../AuthenticationHelper.ts'; +} from '../core/AuthenticationHelper.ts'; +import AccountProvider from '../database/AccountProvider.ts'; +import { AccountType } from '../types/AccountType.ts'; vi.mock('jsonwebtoken'); vi.mock('passport'); diff --git a/v6y-libs/core-logic/src/core/__tests__/SemverUtils-test.ts b/v6y-libs/core-logic/src/__tests__/SemverUtils-test.ts similarity index 97% rename from v6y-libs/core-logic/src/core/__tests__/SemverUtils-test.ts rename to v6y-libs/core-logic/src/__tests__/SemverUtils-test.ts index 9f1abb6f..712efed2 100644 --- a/v6y-libs/core-logic/src/core/__tests__/SemverUtils-test.ts +++ b/v6y-libs/core-logic/src/__tests__/SemverUtils-test.ts @@ -1,7 +1,7 @@ // SemverUtils.test.ts import { describe, expect, it } from 'vitest'; -import SemverUtils from '../SemverUtils.ts'; +import SemverUtils from '../core/SemverUtils.ts'; describe('SemverUtils', () => { it('should compare versions correctly', () => { diff --git a/v6y-libs/core-logic/src/core/__tests__/ServerUtils-test.ts b/v6y-libs/core-logic/src/__tests__/ServerUtils-test.ts similarity index 97% rename from v6y-libs/core-logic/src/core/__tests__/ServerUtils-test.ts rename to v6y-libs/core-logic/src/__tests__/ServerUtils-test.ts index 84c1c2a8..da8067a8 100644 --- a/v6y-libs/core-logic/src/core/__tests__/ServerUtils-test.ts +++ b/v6y-libs/core-logic/src/__tests__/ServerUtils-test.ts @@ -4,7 +4,7 @@ import HttpsClient from 'https'; import HttpStaticClient from 'spdy'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import ServerUtils from '../ServerUtils.ts'; +import ServerUtils from '../core/ServerUtils.ts'; vi.mock('http'); vi.mock('https'); diff --git a/v6y-libs/core-logic/src/core/__tests__/StringUtils-test.ts b/v6y-libs/core-logic/src/__tests__/StringUtils-test.ts similarity index 97% rename from v6y-libs/core-logic/src/core/__tests__/StringUtils-test.ts rename to v6y-libs/core-logic/src/__tests__/StringUtils-test.ts index bcacf413..91a43762 100644 --- a/v6y-libs/core-logic/src/core/__tests__/StringUtils-test.ts +++ b/v6y-libs/core-logic/src/__tests__/StringUtils-test.ts @@ -1,7 +1,7 @@ // StringUtils.test.ts import { describe, expect, it, vi } from 'vitest'; -import StringUtils from '../StringUtils.ts'; +import StringUtils from '../core/StringUtils.ts'; // Mock AppLogger vi.mock('./AppLogger', async () => { diff --git a/v6y-libs/core-logic/src/types/index.ts b/v6y-libs/core-logic/src/types/index.ts index abf2ebf0..7d9db683 100644 --- a/v6y-libs/core-logic/src/types/index.ts +++ b/v6y-libs/core-logic/src/types/index.ts @@ -15,3 +15,4 @@ export * from './RepositoryType.ts'; export * from './SearchQueryType.ts'; export * from './LinkType.ts'; export * from './ServerConfigType.ts'; +export * from './ModuleType.ts'; diff --git a/v6y-libs/shared-ui/src/__tests__/.gitkeep b/v6y-libs/shared-ui/src/__tests__/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/v6y-libs/shared-ui/src/components/atoms/commons/Collapse.tsx b/v6y-libs/shared-ui/src/components/atoms/commons/Collapse.tsx new file mode 100644 index 00000000..b961df62 --- /dev/null +++ b/v6y-libs/shared-ui/src/components/atoms/commons/Collapse.tsx @@ -0,0 +1,3 @@ +import { Collapse } from 'antd'; + +export default Collapse; diff --git a/v6y-libs/shared-ui/src/components/atoms/commons/List.tsx b/v6y-libs/shared-ui/src/components/atoms/commons/List.tsx index 6d432020..010a8089 100644 --- a/v6y-libs/shared-ui/src/components/atoms/commons/List.tsx +++ b/v6y-libs/shared-ui/src/components/atoms/commons/List.tsx @@ -1,3 +1,6 @@ import { List } from 'antd'; -export default List; +const { Item: ListItem } = List; +const { Meta: ListItemMeta } = ListItem; + +export { List, ListItem, ListItemMeta }; diff --git a/v6y-libs/shared-ui/src/components/atoms/index.ts b/v6y-libs/shared-ui/src/components/atoms/index.ts index 59fa48b3..01130c6a 100644 --- a/v6y-libs/shared-ui/src/components/atoms/index.ts +++ b/v6y-libs/shared-ui/src/components/atoms/index.ts @@ -17,7 +17,6 @@ export { default as Card } from './commons/Card.tsx'; export { default as Divider } from './commons/Divider.tsx'; export { default as Dropdown } from './commons/Dropdown.tsx'; export { default as Flex } from './commons/Flex.tsx'; -export { default as List } from './commons/List.tsx'; export { default as Menu } from './commons/Menu.tsx'; export { default as Statistic } from './commons/Statistic.tsx'; export { default as Switch } from './commons/Switch.tsx'; @@ -27,5 +26,7 @@ export { default as Tabs } from './commons/Tabs.tsx'; export { default as Tag } from './commons/Tag.tsx'; export { default as Charts } from './app/Charts.tsx'; export { default as Message } from './commons/Message.tsx'; +export { default as Collapse } from './commons/Collapse.tsx'; export * from './commons/Typography.tsx'; export * from './commons/Grid.tsx'; +export * from './commons/List.tsx'; diff --git a/v6y-libs/shared-ui/src/components/organisms/app/VitalityCollapse.tsx b/v6y-libs/shared-ui/src/components/organisms/app/VitalityCollapse.tsx index 7f22735e..b26cdd4a 100644 --- a/v6y-libs/shared-ui/src/components/organisms/app/VitalityCollapse.tsx +++ b/v6y-libs/shared-ui/src/components/organisms/app/VitalityCollapse.tsx @@ -1,7 +1,7 @@ -import { Collapse } from 'antd'; import * as React from 'react'; -import { CollapseProps } from '../../types/CollapseProps.ts'; +import { Collapse } from '../../atoms'; +import { CollapseProps } from '../../types'; import VitalityEmptyView from './VitalityEmptyView'; const VitalityCollapse = ({ bordered, accordion, dataSource }: CollapseProps) => { diff --git a/v6y-apps/front/src/commons/components/VitalityDynamicLoader.tsx b/v6y-libs/shared-ui/src/components/organisms/app/VitalityDynamicLoader.tsx similarity index 90% rename from v6y-apps/front/src/commons/components/VitalityDynamicLoader.tsx rename to v6y-libs/shared-ui/src/components/organisms/app/VitalityDynamicLoader.tsx index dd0c8654..3e9d3861 100644 --- a/v6y-apps/front/src/commons/components/VitalityDynamicLoader.tsx +++ b/v6y-libs/shared-ui/src/components/organisms/app/VitalityDynamicLoader.tsx @@ -1,7 +1,8 @@ -import { VitalityLoader } from '@v6y/shared-ui'; import dynamic, { DynamicOptions } from 'next/dynamic'; import * as React from 'react'; +import VitalityLoader from './VitalityLoader'; + interface DynamicComponentProps { [key: string]: unknown; } diff --git a/v6y-libs/shared-ui/src/components/organisms/app/VitalityLinks.tsx b/v6y-libs/shared-ui/src/components/organisms/app/VitalityLinks.tsx index 62250256..cf6db02b 100644 --- a/v6y-libs/shared-ui/src/components/organisms/app/VitalityLinks.tsx +++ b/v6y-libs/shared-ui/src/components/organisms/app/VitalityLinks.tsx @@ -3,7 +3,7 @@ import { Col, Row } from 'antd'; import Link from 'next/link'; import * as React from 'react'; -import { LinksProps } from '../../types/LinksProps.ts'; +import { LinksProps } from '../../types'; import VitalityText from './VitalityText'; const VitalityLinks = ({ links, align }: LinksProps) => { diff --git a/v6y-apps/front/src/commons/components/VitalityLoadMoreList.tsx b/v6y-libs/shared-ui/src/components/organisms/app/VitalityLoadMoreList.tsx similarity index 76% rename from v6y-apps/front/src/commons/components/VitalityLoadMoreList.tsx rename to v6y-libs/shared-ui/src/components/organisms/app/VitalityLoadMoreList.tsx index 72b25f20..c0d9e80a 100644 --- a/v6y-apps/front/src/commons/components/VitalityLoadMoreList.tsx +++ b/v6y-libs/shared-ui/src/components/organisms/app/VitalityLoadMoreList.tsx @@ -1,15 +1,14 @@ -import { Button, Flex, List, SyncOutlined } from '@v6y/shared-ui'; - -import VitalityTerms from '../config/VitalityTerms'; -import { VitalityListProps } from '../types/VitalityListProps'; +import { Button, Flex, List, SyncOutlined } from '../../atoms'; +import { PaginatedListType } from '../../types'; const VitalityLoadMoreList = ({ isDataSourceLoading, + loadMoreLabel, bordered = true, dataSource, renderItem, onLoadMore, -}: VitalityListProps) => ( +}: PaginatedListType) => ( - {VitalityTerms.VITALITY_APP_LIST_LOAD_MORE_LABEL} + {loadMoreLabel} ) : null diff --git a/v6y-apps/front/src/commons/components/VitalityPaginatedList.tsx b/v6y-libs/shared-ui/src/components/organisms/app/VitalityPaginatedList.tsx similarity index 83% rename from v6y-apps/front/src/commons/components/VitalityPaginatedList.tsx rename to v6y-libs/shared-ui/src/components/organisms/app/VitalityPaginatedList.tsx index 0b2a0dd7..f4e1fb81 100644 --- a/v6y-apps/front/src/commons/components/VitalityPaginatedList.tsx +++ b/v6y-libs/shared-ui/src/components/organisms/app/VitalityPaginatedList.tsx @@ -1,7 +1,8 @@ -import { List, VitalityText } from '@v6y/shared-ui'; import * as React from 'react'; -import { VitalityListProps } from '../types/VitalityListProps'; +import { List } from '../../atoms'; +import { PaginatedListType } from '../../types'; +import VitalityText from './VitalityText'; const VitalityPaginatedList = ({ dataSource, @@ -12,7 +13,7 @@ const VitalityPaginatedList = ({ renderItem, showFooter = true, style, -}: VitalityListProps) => ( +}: PaginatedListType) => ( ReactNode; + showFooter?: boolean; + style?: CSSProperties; + onLoadMore?: () => void; + isDataSourceLoading?: boolean; +} diff --git a/v6y-libs/shared-ui/src/components/types/index.ts b/v6y-libs/shared-ui/src/components/types/index.ts index 80ed8169..00e2f148 100644 --- a/v6y-libs/shared-ui/src/components/types/index.ts +++ b/v6y-libs/shared-ui/src/components/types/index.ts @@ -8,3 +8,4 @@ export * from './TableType'; export * from './CheckboxOptionType.ts'; export * from './BreadcrumbType.ts'; export * from './ListType.ts'; +export * from './PaginatedListType.ts';