[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
This commit is contained in:
Jin Yi
2025-07-31 13:58:20 +09:00
parent 6aa8d7e234
commit 515240016c
7 changed files with 97 additions and 19 deletions

View File

@@ -169,24 +169,41 @@ import { useI18n } from 'vue-i18n'
import ContentDivider from '@/components/common/ContentDivider.vue' import ContentDivider from '@/components/common/ContentDivider.vue'
import { useConflictDetection } from '@/composables/useConflictDetection' import { useConflictDetection } from '@/composables/useConflictDetection'
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import { getConflictMessage } from '@/utils/conflictMessageUtil' import { getConflictMessage } from '@/utils/conflictMessageUtil'
interface Props { interface Props {
conflicts?: ConflictDetectionResult[]
conflictedPackages?: ConflictDetectionResult[]
showAfterWhatsNew?: boolean showAfterWhatsNew?: boolean
} }
const { showAfterWhatsNew } = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
conflicts: () => [],
conflictedPackages: () => [],
showAfterWhatsNew: false showAfterWhatsNew: false
}) })
const { t } = useI18n() const { t } = useI18n()
const { conflictedPackages } = useConflictDetection() const { conflictedPackages: globalConflictedPackages } = useConflictDetection()
const conflictsExpanded = ref<boolean>(false) const conflictsExpanded = ref<boolean>(false)
const extensionsExpanded = ref<boolean>(false) const extensionsExpanded = ref<boolean>(false)
const importFailedExpanded = ref<boolean>(false) const importFailedExpanded = ref<boolean>(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 allConflictDetails = computed(() => {
const allConflicts = flatMap(conflictData.value, (result) => result.conflicts) const allConflicts = flatMap(conflictData.value, (result) => result.conflicts)

View File

@@ -147,7 +147,7 @@ const onToggle = debounce(
const handleToggleInteraction = async (event: Event) => { const handleToggleInteraction = async (event: Event) => {
if (!canToggleDirectly.value) { if (!canToggleDirectly.value) {
event.preventDefault() event.preventDefault()
showConflictModal() showConflictModal(false)
} }
} }
</script> </script>

View File

@@ -55,7 +55,7 @@ import { ImportFailedKey } from '@/types/importFailedTypes'
const { nodePack, hasCompatibilityIssues, conflictResult } = defineProps<{ const { nodePack, hasCompatibilityIssues, conflictResult } = defineProps<{
nodePack: components['schemas']['Node'] nodePack: components['schemas']['Node']
hasCompatibilityIssues?: boolean hasCompatibilityIssues?: boolean
conflictResult?: ConflictDetectionResult | null conflictResult?: ConflictDetectionResult | null | undefined
}>() }>()
// Inject import failed context from parent // Inject import failed context from parent

View File

@@ -35,7 +35,7 @@ import { getConflictMessage } from '@/utils/conflictMessageUtil'
const { nodePack, conflictResult } = defineProps<{ const { nodePack, conflictResult } = defineProps<{
nodePack: components['schemas']['Node'] nodePack: components['schemas']['Node']
conflictResult: ConflictDetectionResult | null conflictResult: ConflictDetectionResult | null | undefined
}>() }>()
const packageId = computed(() => nodePack?.id || '') const packageId = computed(() => nodePack?.id || '')

View File

@@ -124,8 +124,12 @@ const handleWhatsNewDismissed = async () => {
* Show the node conflict dialog with current conflict data * Show the node conflict dialog with current conflict data
*/ */
const showConflictModal = () => { const showConflictModal = () => {
const conflictData = {
conflictedPackages: conflictDetection.conflictedPackages.value,
showAfterWhatsNew: true
}
showNodeConflictDialog({ showNodeConflictDialog({
showAfterWhatsNew: true, ...conflictData,
dialogComponentProps: { dialogComponentProps: {
onClose: () => { onClose: () => {
markConflictsAsSeen() markConflictsAsSeen()

View File

@@ -432,19 +432,14 @@ export const useDialogService = () => {
} }
function showNodeConflictDialog( function showNodeConflictDialog(
options: { options: InstanceType<typeof NodeConflictDialogContent>['$props'] & {
showAfterWhatsNew?: boolean
dialogComponentProps?: DialogComponentProps dialogComponentProps?: DialogComponentProps
buttonText?: string buttonText?: string
onButtonClick?: () => void onButtonClick?: () => void
} = {} } = {}
) { ) {
const { const { dialogComponentProps, buttonText, onButtonClick, ...props } =
dialogComponentProps, options
buttonText,
onButtonClick,
showAfterWhatsNew
} = options
return dialogStore.showDialog({ return dialogStore.showDialog({
key: 'global-node-conflict', key: 'global-node-conflict',
@@ -466,9 +461,7 @@ export const useDialogService = () => {
}, },
...dialogComponentProps ...dialogComponentProps
}, },
props: { props,
showAfterWhatsNew
},
footerProps: { footerProps: {
buttonText, buttonText,
onButtonClick onButtonClick

View File

@@ -9,7 +9,7 @@ import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
// Mock getConflictMessage utility // Mock getConflictMessage utility
vi.mock('@/utils/conflictMessageUtil', () => ({ vi.mock('@/utils/conflictMessageUtil', () => ({
getConflictMessage: vi.fn((conflict, t) => { getConflictMessage: vi.fn((conflict) => {
return `${conflict.type}: ${conflict.current_value} vs ${conflict.required_value}` 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('3')
expect(conflictsSection.text()).toContain('Conflicts') 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', () => { describe('panel interactions', () => {