[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 { 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<Props>(), {
const props = withDefaults(defineProps<Props>(), {
conflicts: () => [],
conflictedPackages: () => [],
showAfterWhatsNew: false
})
const { t } = useI18n()
const { conflictedPackages } = useConflictDetection()
const { conflictedPackages: globalConflictedPackages } = useConflictDetection()
const conflictsExpanded = ref<boolean>(false)
const extensionsExpanded = 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 allConflicts = flatMap(conflictData.value, (result) => result.conflicts)

View File

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

View File

@@ -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

View File

@@ -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 || '')

View File

@@ -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()

View File

@@ -432,19 +432,14 @@ export const useDialogService = () => {
}
function showNodeConflictDialog(
options: {
showAfterWhatsNew?: boolean
options: InstanceType<typeof NodeConflictDialogContent>['$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

View File

@@ -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', () => {