From 515240016c5bc78ee36e1f1cbaca1f66d9a8bb96 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Thu, 31 Jul 2025 13:58:20 +0900 Subject: [PATCH] [feat] Add dual mode support to NodeConflictDialogContent and fix TypeScript errors - Add dual mode support to NodeConflictDialogContent: - Support props (conflictedPackages/conflicts) for specific conflicts - Fallback to global composable data for SidebarHelpCenterIcon usage - Import failed extensions section shows at top with package names - Conditional description display for post-What's-New scenarios - Fix TypeScript errors: - PackEnableToggle: Add missing parameter to showConflictModal call - InfoTabs: Update conflictResult prop to allow undefined - WarningTabPanel: Update conflictResult prop to accept undefined - Update tests for comprehensive dual mode functionality coverage --- .../manager/NodeConflictDialogContent.vue | 23 ++++++- .../manager/button/PackEnableToggle.vue | 2 +- .../content/manager/infoPanel/InfoTabs.vue | 2 +- .../infoPanel/tabs/WarningTabPanel.vue | 2 +- .../sidebar/SidebarHelpCenterIcon.vue | 6 +- src/services/dialogService.ts | 15 ++--- .../manager/NodeConflictDialogContent.test.ts | 66 ++++++++++++++++++- 7 files changed, 97 insertions(+), 19 deletions(-) diff --git a/src/components/dialog/content/manager/NodeConflictDialogContent.vue b/src/components/dialog/content/manager/NodeConflictDialogContent.vue index ae4f08c70..5ddd14a2e 100644 --- a/src/components/dialog/content/manager/NodeConflictDialogContent.vue +++ b/src/components/dialog/content/manager/NodeConflictDialogContent.vue @@ -169,24 +169,41 @@ import { useI18n } from 'vue-i18n' import ContentDivider from '@/components/common/ContentDivider.vue' import { useConflictDetection } from '@/composables/useConflictDetection' +import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes' import { getConflictMessage } from '@/utils/conflictMessageUtil' interface Props { + conflicts?: ConflictDetectionResult[] + conflictedPackages?: ConflictDetectionResult[] showAfterWhatsNew?: boolean } -const { showAfterWhatsNew } = withDefaults(defineProps(), { +const props = withDefaults(defineProps(), { + conflicts: () => [], + conflictedPackages: () => [], showAfterWhatsNew: false }) const { t } = useI18n() -const { conflictedPackages } = useConflictDetection() +const { conflictedPackages: globalConflictedPackages } = useConflictDetection() const conflictsExpanded = ref(false) const extensionsExpanded = ref(false) const importFailedExpanded = ref(false) -const conflictData = computed(() => conflictedPackages.value) +// Use props if provided, otherwise use composable data +const conflictData = computed(() => { + // If props have conflictedPackages, prioritize them + if (props.conflictedPackages.length > 0) { + return props.conflictedPackages + } + // If props have conflicts, use them + if (props.conflicts.length > 0) { + return props.conflicts + } + // Otherwise, use global conflicted packages from composable + return globalConflictedPackages.value +}) const allConflictDetails = computed(() => { const allConflicts = flatMap(conflictData.value, (result) => result.conflicts) diff --git a/src/components/dialog/content/manager/button/PackEnableToggle.vue b/src/components/dialog/content/manager/button/PackEnableToggle.vue index 6c57d9fa4..230fb9ae2 100644 --- a/src/components/dialog/content/manager/button/PackEnableToggle.vue +++ b/src/components/dialog/content/manager/button/PackEnableToggle.vue @@ -147,7 +147,7 @@ const onToggle = debounce( const handleToggleInteraction = async (event: Event) => { if (!canToggleDirectly.value) { event.preventDefault() - showConflictModal() + showConflictModal(false) } } diff --git a/src/components/dialog/content/manager/infoPanel/InfoTabs.vue b/src/components/dialog/content/manager/infoPanel/InfoTabs.vue index 42851ad28..225aacb42 100644 --- a/src/components/dialog/content/manager/infoPanel/InfoTabs.vue +++ b/src/components/dialog/content/manager/infoPanel/InfoTabs.vue @@ -55,7 +55,7 @@ import { ImportFailedKey } from '@/types/importFailedTypes' const { nodePack, hasCompatibilityIssues, conflictResult } = defineProps<{ nodePack: components['schemas']['Node'] hasCompatibilityIssues?: boolean - conflictResult?: ConflictDetectionResult | null + conflictResult?: ConflictDetectionResult | null | undefined }>() // Inject import failed context from parent diff --git a/src/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue b/src/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue index 2449b5fea..8d4a6e224 100644 --- a/src/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue +++ b/src/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue @@ -35,7 +35,7 @@ import { getConflictMessage } from '@/utils/conflictMessageUtil' const { nodePack, conflictResult } = defineProps<{ nodePack: components['schemas']['Node'] - conflictResult: ConflictDetectionResult | null + conflictResult: ConflictDetectionResult | null | undefined }>() const packageId = computed(() => nodePack?.id || '') diff --git a/src/components/sidebar/SidebarHelpCenterIcon.vue b/src/components/sidebar/SidebarHelpCenterIcon.vue index 44c92051e..c2f835c0f 100644 --- a/src/components/sidebar/SidebarHelpCenterIcon.vue +++ b/src/components/sidebar/SidebarHelpCenterIcon.vue @@ -124,8 +124,12 @@ const handleWhatsNewDismissed = async () => { * Show the node conflict dialog with current conflict data */ const showConflictModal = () => { + const conflictData = { + conflictedPackages: conflictDetection.conflictedPackages.value, + showAfterWhatsNew: true + } showNodeConflictDialog({ - showAfterWhatsNew: true, + ...conflictData, dialogComponentProps: { onClose: () => { markConflictsAsSeen() diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index 6510ae7ec..457fecc89 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -432,19 +432,14 @@ export const useDialogService = () => { } function showNodeConflictDialog( - options: { - showAfterWhatsNew?: boolean + options: InstanceType['$props'] & { dialogComponentProps?: DialogComponentProps buttonText?: string onButtonClick?: () => void } = {} ) { - const { - dialogComponentProps, - buttonText, - onButtonClick, - showAfterWhatsNew - } = options + const { dialogComponentProps, buttonText, onButtonClick, ...props } = + options return dialogStore.showDialog({ key: 'global-node-conflict', @@ -466,9 +461,7 @@ export const useDialogService = () => { }, ...dialogComponentProps }, - props: { - showAfterWhatsNew - }, + props, footerProps: { buttonText, onButtonClick diff --git a/tests-ui/tests/components/dialog/content/manager/NodeConflictDialogContent.test.ts b/tests-ui/tests/components/dialog/content/manager/NodeConflictDialogContent.test.ts index 324f42770..dcc9c2016 100644 --- a/tests-ui/tests/components/dialog/content/manager/NodeConflictDialogContent.test.ts +++ b/tests-ui/tests/components/dialog/content/manager/NodeConflictDialogContent.test.ts @@ -9,7 +9,7 @@ import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes' // Mock getConflictMessage utility vi.mock('@/utils/conflictMessageUtil', () => ({ - getConflictMessage: vi.fn((conflict, t) => { + getConflictMessage: vi.fn((conflict) => { return `${conflict.type}: ${conflict.current_value} vs ${conflict.required_value}` }) })) @@ -195,6 +195,70 @@ describe('NodeConflictDialogContent', () => { expect(conflictsSection.text()).toContain('3') expect(conflictsSection.text()).toContain('Conflicts') }) + + it('should render with conflict data from props (conflictedPackages)', () => { + // Clear composable data to ensure props are used + mockConflictData.value = [] + + const wrapper = createWrapper({ + conflictedPackages: mockConflictResults + }) + + // Should show 3 total conflicts (2 from Package1 + 1 from Package2, excluding import_failed) + expect(wrapper.text()).toContain('3') + expect(wrapper.text()).toContain('Conflicts') + // Should show 3 extensions at risk (all packages) + expect(wrapper.text()).toContain('Extensions at Risk') + // Should show import failed section + expect(wrapper.text()).toContain('Import Failed Extensions') + expect(wrapper.text()).toContain('1') // 1 import failed package + }) + + it('should render with conflict data from props (conflicts)', () => { + // Clear composable data to ensure props are used + mockConflictData.value = [] + + const wrapper = createWrapper({ + conflicts: mockConflictResults + }) + + // Should show 3 total conflicts (excluding import_failed) + expect(wrapper.text()).toContain('3') + expect(wrapper.text()).toContain('Conflicts') + expect(wrapper.text()).toContain('Extensions at Risk') + expect(wrapper.text()).toContain('Import Failed Extensions') + }) + + it('should prioritize conflictedPackages over conflicts prop', () => { + const singleConflict: ConflictDetectionResult[] = [ + { + package_id: 'SinglePackage', + package_name: 'Single Package', + has_conflict: true, + is_compatible: false, + conflicts: [ + { + type: 'os', + current_value: 'macOS', + required_value: 'Windows' + } + ] + } + ] + + // Clear composable data + mockConflictData.value = [] + + const wrapper = createWrapper({ + conflicts: mockConflictResults, // 4 conflicts total + conflictedPackages: singleConflict // 1 conflict + }) + + // Should use conflictedPackages (1 conflict) instead of conflicts (4 conflicts) + expect(wrapper.text()).toContain('1') + expect(wrapper.text()).toContain('Conflicts') + expect(wrapper.text()).toContain('Extensions at Risk') + }) }) describe('panel interactions', () => {