diff --git a/frontend/src/composables/__tests__/useColumnDataTypeIndicators.test.ts b/frontend/src/composables/__tests__/useColumnDataTypeIndicators.test.ts index 50f27a1..16cd1c4 100644 --- a/frontend/src/composables/__tests__/useColumnDataTypeIndicators.test.ts +++ b/frontend/src/composables/__tests__/useColumnDataTypeIndicators.test.ts @@ -41,14 +41,14 @@ describe('useColumnDataTypeIndicators', () => { describe('generateDataTypeTooltip', () => { test('should generate tooltip for basic column', () => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: 'test_column', dataType: 'VARCHAR', sampleValues: ['sample1', 'sample2'], nullable: false, } - const tooltip = generateDataTypeTooltip(column) + const tooltip = generateDataTypeTooltip(columnInfo) expect(tooltip).toContain('Data Type: Text (VARCHAR)') expect(tooltip).toContain('Wikibase Compatible: string, url, external-id, monolingualtext') @@ -56,7 +56,7 @@ describe('useColumnDataTypeIndicators', () => { }) test('should include nullable information when applicable', () => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: 'nullable_column', dataType: 'INTEGER', sampleValues: ['1', '2'], @@ -64,7 +64,7 @@ describe('useColumnDataTypeIndicators', () => { uniqueCount: 100, } - const tooltip = generateDataTypeTooltip(column) + const tooltip = generateDataTypeTooltip(columnInfo) expect(tooltip).toContain('Data Type: Number (INTEGER)') expect(tooltip).toContain('Nullable: Yes') @@ -73,21 +73,21 @@ describe('useColumnDataTypeIndicators', () => { }) test('should handle incompatible data types', () => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: 'boolean_column', dataType: 'BOOLEAN', sampleValues: ['true', 'false'], nullable: false, } - const tooltip = generateDataTypeTooltip(column) + const tooltip = generateDataTypeTooltip(columnInfo) expect(tooltip).toContain('Data Type: Boolean (BOOLEAN)') expect(tooltip).toContain('Wikibase Compatible: None (requires transformation)') }) test('should format unique count with locale formatting', () => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: 'large_column', dataType: 'VARCHAR', sampleValues: ['test'], @@ -95,7 +95,7 @@ describe('useColumnDataTypeIndicators', () => { uniqueCount: 1234567, } - const tooltip = generateDataTypeTooltip(column) + const tooltip = generateDataTypeTooltip(columnInfo) expect(tooltip).toContain('Unique Values: 1,234,567') }) @@ -142,7 +142,7 @@ describe('useColumnDataTypeIndicators', () => { describe('generateColumnTooltip', () => { test('should combine data type and sample information', () => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: 'test_column', dataType: 'VARCHAR', sampleValues: ['sample1', 'sample2'], @@ -150,7 +150,7 @@ describe('useColumnDataTypeIndicators', () => { uniqueCount: 50, } - const tooltip = generateColumnTooltip(column) + const tooltip = generateColumnTooltip(columnInfo) expect(tooltip).toContain('Data Type: Text (VARCHAR)') expect(tooltip).toContain('Nullable: Yes') @@ -160,14 +160,14 @@ describe('useColumnDataTypeIndicators', () => { }) test('should handle columns with no sample data', () => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: 'empty_column', dataType: 'INTEGER', sampleValues: [], nullable: false, } - const tooltip = generateColumnTooltip(column) + const tooltip = generateColumnTooltip(columnInfo) expect(tooltip).toContain('Data Type: Number (INTEGER)') expect(tooltip).toContain('Sample Values:') diff --git a/frontend/src/composables/__tests__/useDragDropContext.test.ts b/frontend/src/composables/__tests__/useDragDropContext.test.ts index d21970d..2fcd9e3 100644 --- a/frontend/src/composables/__tests__/useDragDropContext.test.ts +++ b/frontend/src/composables/__tests__/useDragDropContext.test.ts @@ -344,9 +344,9 @@ describe('createDropZoneConfig', () => { const config = context.createDropZoneConfig( 'item.terms.labels.en', ['string'], - (column: ColumnInfo, target: DropTarget) => { + (columnInfo: ColumnInfo, target: DropTarget) => { dropCallbackCalled = true - droppedColumn = column + droppedColumn = columnInfo }, ) diff --git a/frontend/src/composables/__tests__/useStatementConfig.test.ts b/frontend/src/composables/__tests__/useStatementConfig.test.ts index 500ef1e..170dcc7 100644 --- a/frontend/src/composables/__tests__/useStatementConfig.test.ts +++ b/frontend/src/composables/__tests__/useStatementConfig.test.ts @@ -166,7 +166,7 @@ describe('useStatementConfig', () => { expect(store.statements[0]?.rank).toBe('preferred') }) - it('should not auto-save when statement is invalid', async () => { + it('should not auto-save when statement is invalid', () => { const store = useSchemaStore() const { currentStatement, canSaveStatement, resetStatement } = useStatementConfig() @@ -177,10 +177,6 @@ describe('useStatementConfig', () => { expect(canSaveStatement.value).toBe(false) expect(store.statements).toHaveLength(0) - // Wait to ensure no auto-save happens - await new Promise((resolve) => setTimeout(resolve, 0)) - expect(store.statements).toHaveLength(0) - // Set only property (still incomplete without source) currentStatement.value.property = { id: 'P123' as PropertyId, @@ -188,9 +184,6 @@ describe('useStatementConfig', () => { dataType: 'string', } expect(canSaveStatement.value).toBe(false) - - // Wait to ensure no auto-save happens - await new Promise((resolve) => setTimeout(resolve, 0)) expect(store.statements).toHaveLength(0) }) diff --git a/frontend/src/composables/__tests__/useStatementDropZone.test.ts b/frontend/src/composables/__tests__/useStatementDropZone.test.ts index b30aa2c..bf4d056 100644 --- a/frontend/src/composables/__tests__/useStatementDropZone.test.ts +++ b/frontend/src/composables/__tests__/useStatementDropZone.test.ts @@ -117,8 +117,8 @@ describe('useStatementDropZone', () => { test('should call column drop callback with valid column data', () => { const { handleDrop, setOnColumnDrop } = useStatementDropZone() - setOnColumnDrop((column) => { - droppedColumn = column + setOnColumnDrop((columnInfo) => { + droppedColumn = columnInfo }) const mockEvent = { @@ -141,8 +141,8 @@ describe('useStatementDropZone', () => { test('should not call callback when no column data is provided', () => { const { handleDrop, setOnColumnDrop } = useStatementDropZone() - setOnColumnDrop((column) => { - droppedColumn = column + setOnColumnDrop((columnInfo) => { + droppedColumn = columnInfo }) const mockEvent = { diff --git a/frontend/src/composables/useColumnDataTypeIndicators.ts b/frontend/src/composables/useColumnDataTypeIndicators.ts index 0b13ba4..4547735 100644 --- a/frontend/src/composables/useColumnDataTypeIndicators.ts +++ b/frontend/src/composables/useColumnDataTypeIndicators.ts @@ -108,18 +108,18 @@ export const useColumnDataTypeIndicators = () => { /** * Generate tooltip content for column data type information */ - const generateDataTypeTooltip = (_column: ColumnInfo): string => { - const compatibleTypes = getCompatibleWikibaseTypes(_column.dataType) - const displayName = formatDataTypeDisplayName(_column.dataType) + const generateDataTypeTooltip = (columnInfo: ColumnInfo): string => { + const compatibleTypes = getCompatibleWikibaseTypes(columnInfo.dataType) + const displayName = formatDataTypeDisplayName(columnInfo.dataType) - let tooltip = `Data Type: ${displayName} (${_column.dataType})` + let tooltip = `Data Type: ${displayName} (${columnInfo.dataType})` - if (_column.nullable) { + if (columnInfo.nullable) { tooltip += '\nNullable: Yes' } - if (_column.uniqueCount !== undefined) { - tooltip += `\nUnique Values: ${_column.uniqueCount.toLocaleString()}` + if (columnInfo.uniqueCount !== undefined) { + tooltip += `\nUnique Values: ${columnInfo.uniqueCount.toLocaleString()}` } if (compatibleTypes.length > 0) { @@ -157,9 +157,9 @@ export const useColumnDataTypeIndicators = () => { /** * Generate comprehensive tooltip content for a column */ - const generateColumnTooltip = (_column: ColumnInfo): string => { - const dataTypeInfo = generateDataTypeTooltip(_column) - const sampleInfo = formatSampleValuesForTooltip(_column.sampleValues) + const generateColumnTooltip = (columnInfo: ColumnInfo): string => { + const dataTypeInfo = generateDataTypeTooltip(columnInfo) + const sampleInfo = formatSampleValuesForTooltip(columnInfo.sampleValues) return `${dataTypeInfo}\n\nSample Values:\n${sampleInfo}` } diff --git a/frontend/src/composables/useStatementDropZone.ts b/frontend/src/composables/useStatementDropZone.ts index b7110d1..900deda 100644 --- a/frontend/src/composables/useStatementDropZone.ts +++ b/frontend/src/composables/useStatementDropZone.ts @@ -27,7 +27,7 @@ export const useStatementDropZone = () => { // Reactive state const isOverDropZone = ref(false) - const onColumnDropCallback = ref<((column: ColumnInfo) => void) | null>(null) + const onColumnDropCallback = ref<((columnInfo: ColumnInfo) => void) | null>(null) // Direct reactive references to drag store const draggedColumn = computed(() => dragDropStore.draggedColumn) @@ -71,7 +71,7 @@ export const useStatementDropZone = () => { ) // Method to set the callback - const setOnColumnDrop = (callback: (column: ColumnInfo) => void) => { + const setOnColumnDrop = (callback: (columnInfo: ColumnInfo) => void) => { onColumnDropCallback.value = callback } diff --git a/frontend/src/composables/useStatementEditor.ts b/frontend/src/composables/useStatementEditor.ts index 2c14518..503aa75 100644 --- a/frontend/src/composables/useStatementEditor.ts +++ b/frontend/src/composables/useStatementEditor.ts @@ -198,9 +198,9 @@ export const useStatementEditor = () => { localStatement.value.rank = newRank } - const handleColumnDrop = (_column: ColumnInfo) => { + const handleColumnDrop = (columnInfo: ColumnInfo) => { // Auto-suggest compatible data type - const compatibleTypes = getCompatibleWikibaseTypes(_column.dataType) + const compatibleTypes = getCompatibleWikibaseTypes(columnInfo.dataType) const suggestedDataType: WikibaseDataType = compatibleTypes[0] ?? (localStatement.value.value.dataType || 'string') @@ -208,8 +208,8 @@ export const useStatementEditor = () => { localStatement.value.value = { type: 'column', source: { - columnName: _column.name, - dataType: _column.dataType, + columnName: columnInfo.name, + dataType: columnInfo.dataType, }, dataType: suggestedDataType, } diff --git a/frontend/src/composables/useValueMapping.ts b/frontend/src/composables/useValueMapping.ts index 7a38021..f94f78a 100644 --- a/frontend/src/composables/useValueMapping.ts +++ b/frontend/src/composables/useValueMapping.ts @@ -81,10 +81,10 @@ export const useValueMapping = () => { // Methods const createValueMappingFromColumn = ( - column: ColumnInfo, + columnInfo: ColumnInfo, targetDataType?: WikibaseDataType, ): ValueMapping => { - const compatibleTypes = getCompatibleWikibaseTypes(column.dataType) + const compatibleTypes = getCompatibleWikibaseTypes(columnInfo.dataType) let dataType: WikibaseDataType if (targetDataType) { @@ -103,8 +103,8 @@ export const useValueMapping = () => { return { type: 'column', source: { - columnName: column.name, - dataType: column.dataType, + columnName: columnInfo.name, + dataType: columnInfo.dataType, }, dataType, } @@ -209,11 +209,11 @@ export const useValueMapping = () => { const autoSuggestDataType = ( property: PropertyReference, - column?: ColumnInfo, + columnInfo?: ColumnInfo, ): WikibaseDataType => { - if (!column) return property.dataType as WikibaseDataType + if (!columnInfo) return property.dataType as WikibaseDataType - const compatibleTypes = getCompatibleWikibaseTypes(column.dataType) + const compatibleTypes = getCompatibleWikibaseTypes(columnInfo.dataType) // If property data type is compatible with column, use it if (compatibleTypes.includes(property.dataType as WikibaseDataType)) { diff --git a/frontend/src/stores/__tests__/drag-drop.store.test.ts b/frontend/src/stores/__tests__/drag-drop.store.test.ts index ca824e4..97ecaf4 100644 --- a/frontend/src/stores/__tests__/drag-drop.store.test.ts +++ b/frontend/src/stores/__tests__/drag-drop.store.test.ts @@ -314,7 +314,7 @@ describe('useDragDropStore', () => { ] testCases.forEach(({ dataType, expectedTargets }) => { - const column: ColumnInfo = { + const columnInfo: ColumnInfo = { name: `test_${dataType.toLowerCase()}`, dataType, sampleValues: ['test'], @@ -329,7 +329,7 @@ describe('useDragDropStore', () => { })) store.setAvailableTargets(targets) - store.startDrag(column) + store.startDrag(columnInfo) expect(store.validDropTargets.length).toBe(expectedTargets.length) store.endDrag() diff --git a/frontend/src/stores/drag-drop.store.ts b/frontend/src/stores/drag-drop.store.ts index 9fc4808..ba91442 100644 --- a/frontend/src/stores/drag-drop.store.ts +++ b/frontend/src/stores/drag-drop.store.ts @@ -66,11 +66,11 @@ export const useDragDropStore = defineStore('dragDrop', () => { }) // Actions - const startDrag = (_column: ColumnInfo) => { - draggedColumn.value = _column + const startDrag = (columnInfo: ColumnInfo) => { + draggedColumn.value = columnInfo dragState.value = 'dragging' // Calculate valid drop targets based on column data type - validDropTargets.value = calculateValidTargets(_column) + validDropTargets.value = calculateValidTargets(columnInfo) } const endDrag = () => { @@ -93,10 +93,10 @@ export const useDragDropStore = defineStore('dragDrop', () => { } // Helper function to calculate valid drop targets for a column - const calculateValidTargets = (_column: ColumnInfo): string[] => { - if (!_column) return [] + const calculateValidTargets = (columnInfo: ColumnInfo): string[] => { + if (!columnInfo) return [] - const compatibleTypes = getCompatibleWikibaseTypes(_column.dataType) + const compatibleTypes = getCompatibleWikibaseTypes(columnInfo.dataType) return availableTargets.value .filter((target) => { @@ -105,7 +105,7 @@ export const useDragDropStore = defineStore('dragDrop', () => { if (!isCompatible) return false // Check nullable constraints - if (target.isRequired && _column.nullable) return false + if (target.isRequired && columnInfo.nullable) return false // Additional validation based on target type switch (target.type) { @@ -113,7 +113,7 @@ export const useDragDropStore = defineStore('dragDrop', () => { case 'alias': { // Check for reasonable length constraints const maxLength = target.type === 'label' ? 250 : 100 - const hasLongValues = _column.sampleValues?.some((val) => val.length > maxLength) + const hasLongValues = columnInfo.sampleValues?.some((val) => val.length > maxLength) if (hasLongValues) return false break } @@ -142,8 +142,8 @@ export const useDragDropStore = defineStore('dragDrop', () => { } // Method to get valid targets for a specific column - const getValidTargetsForColumn = (_column: ColumnInfo): DropTarget[] => { - const validPaths = calculateValidTargets(_column) + const getValidTargetsForColumn = (columnInfo: ColumnInfo): DropTarget[] => { + const validPaths = calculateValidTargets(columnInfo) return availableTargets.value.filter((target) => validPaths.includes(target.path)) } diff --git a/frontend/src/types/__tests__/drag-drop-context.test.ts b/frontend/src/types/__tests__/drag-drop-context.test.ts deleted file mode 100644 index 1b4ea83..0000000 --- a/frontend/src/types/__tests__/drag-drop-context.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { describe, it, expect, beforeEach } from 'bun:test' -import { ref } from 'vue' -import type { - SchemaDragDropContext, - DropTarget, - DropFeedback, - DropValidation, - DragEventData, - DropEventData, - DragVisualState, -} from '@frontend/types/drag-drop' -import type { ColumnInfo } from '@frontend/types/wikibase-schema' - -describe('Drag and Drop Context Types', () => { - let mockColumnInfo: ColumnInfo - let mockDropTarget: DropTarget - - beforeEach(() => { - mockColumnInfo = { - name: 'test_column', - dataType: 'VARCHAR', - sampleValues: ['value1', 'value2'], - nullable: false, - uniqueCount: 100, - } - - mockDropTarget = { - type: 'label', - path: 'item.terms.labels.en', - acceptedTypes: ['string'], - language: 'en', - isRequired: false, - } - }) - - describe('Core Drag Drop Context', () => { - it('should create and manage SchemaDragDropContext state', () => { - const isOverDropZone = ref(false) - const dropFeedback = ref(null) - - const context: SchemaDragDropContext = { - isOverDropZone, - dropFeedback, - } - - // Test initial state - expect(context.isOverDropZone.value).toBe(false) - expect(context.dropFeedback.value).toBeNull() - - // Test drop zone hover states - context.isOverDropZone.value = true - expect(context.isOverDropZone.value).toBe(true) - - // Test validation feedback - const feedback: DropFeedback = { - type: 'success', - message: 'Valid mapping for labels', - } - context.dropFeedback.value = feedback - expect(context.dropFeedback.value).toEqual(feedback) - - // Test cleanup - context.isOverDropZone.value = false - context.dropFeedback.value = null - expect(context.isOverDropZone.value).toBe(false) - expect(context.dropFeedback.value).toBeNull() - }) - }) - - describe('Event and Validation Types', () => { - it('should create DropValidation results and event data', () => { - // Valid drop validation - const validValidation: DropValidation = { - isValid: true, - reason: 'Compatible data types', - } - - // Invalid drop validation - const invalidValidation: DropValidation = { - isValid: false, - reason: 'Incompatible data types', - } - - // Drag event data - const mockElement = {} as HTMLElement - const timestamp = Date.now() - const dragEvent: DragEventData = { - column: mockColumnInfo, - sourceElement: mockElement, - timestamp, - } - - // Drop event data - const position = { x: 100, y: 200 } - const dropEvent: DropEventData = { - column: mockColumnInfo, - target: mockDropTarget, - position, - timestamp, - } - - expect(validValidation.isValid).toBe(true) - expect(invalidValidation.isValid).toBe(false) - expect(dragEvent.column).toEqual(mockColumnInfo) - expect(dragEvent.sourceElement).toBe(mockElement) - expect(dropEvent.target).toEqual(mockDropTarget) - expect(dropEvent.position).toEqual(position) - }) - }) - - describe('Visual State and Feedback Types', () => { - it('should create DragVisualState for active and idle states', () => { - // Active drag visual state - const activeDragState: DragVisualState = { - isDragging: true, - draggedColumn: mockColumnInfo, - validDropTargets: ['item.terms.labels.en', 'item.terms.descriptions.en'], - invalidDropTargets: ['item.statements[0].value'], - hoveredTarget: 'item.terms.labels.en', - } - - // Idle visual state - const idleDragState: DragVisualState = { - isDragging: false, - draggedColumn: null, - validDropTargets: [], - invalidDropTargets: [], - hoveredTarget: null, - } - - expect(activeDragState.isDragging).toBe(true) - expect(activeDragState.draggedColumn).toEqual(mockColumnInfo) - expect(activeDragState.validDropTargets).toHaveLength(2) - expect(idleDragState.isDragging).toBe(false) - expect(idleDragState.draggedColumn).toBeNull() - expect(idleDragState.validDropTargets).toHaveLength(0) - }) - - it('should create DropTarget types and DropFeedback messages', () => { - // Different drop target types - const labelTarget: DropTarget = { - type: 'label', - path: 'item.terms.labels.en', - acceptedTypes: ['string'], - language: 'en', - isRequired: false, - } - - const statementTarget: DropTarget = { - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: ['string', 'wikibase-item'], - propertyId: 'P31', - isRequired: true, - } - - // Different feedback types - const successFeedback: DropFeedback = { - type: 'success', - message: 'Column successfully mapped to label', - } - - const errorFeedback: DropFeedback = { - type: 'error', - message: 'Invalid data type for this target', - } - - const warningFeedback: DropFeedback = { - type: 'warning', - message: 'This mapping may result in data loss', - } - - expect(labelTarget.type).toBe('label') - expect(labelTarget.language).toBe('en') - expect(statementTarget.type).toBe('statement') - expect(statementTarget.propertyId).toBe('P31') - expect(successFeedback.type).toBe('success') - expect(errorFeedback.type).toBe('error') - expect(warningFeedback.type).toBe('warning') - }) - }) -}) diff --git a/frontend/src/types/__tests__/drag-drop.test.ts b/frontend/src/types/__tests__/drag-drop.test.ts deleted file mode 100644 index b02ebf7..0000000 --- a/frontend/src/types/__tests__/drag-drop.test.ts +++ /dev/null @@ -1,511 +0,0 @@ -import { describe, it, expect } from 'bun:test' -import type { - SchemaDragDropContext, - DropZoneConfig, - DropFeedback, - DragDropContext, - DropTarget, - DropTargetType, - DragState, - DropValidation, - DragEventData, - DropEventData, - DragVisualState, - DragDropConfig, -} from '@frontend/types/drag-drop' -import type { ColumnInfo, WikibaseDataType } from '@frontend/types/wikibase-schema' - -describe('Drag Drop Types', () => { - describe('SchemaDragDropContext', () => { - it('should handle null/idle state', () => { - const mockRef = (value: T) => ({ value }) as Ref - - const context: SchemaDragDropContext = { - isOverDropZone: mockRef(false), - dropFeedback: mockRef(null), - } - - expect(context.isOverDropZone.value).toBe(false) - expect(context.dropFeedback.value).toBeNull() - }) - }) - - describe('DropZoneConfig', () => { - it('should create complete drop zone configuration', () => { - const mockDragEvent = {} as DragEvent - let dropCalled = false - let dragEnterCalled = false - let dragLeaveCalled = false - let dragOverCalled = false - - const config: DropZoneConfig = { - onDrop: (event: DragEvent) => { - dropCalled = true - expect(event).toBe(mockDragEvent) - }, - onDragEnter: (event: DragEvent) => { - dragEnterCalled = true - expect(event).toBe(mockDragEvent) - }, - onDragLeave: (event: DragEvent) => { - dragLeaveCalled = true - expect(event).toBe(mockDragEvent) - }, - onDragOver: (event: DragEvent) => { - dragOverCalled = true - expect(event).toBe(mockDragEvent) - }, - acceptedDataTypes: ['application/x-column-data', 'text/plain'], - validateDrop: (data: string) => { - return data.includes('test_column') - }, - } - - // Test event handlers - config.onDrop(mockDragEvent) - config.onDragEnter?.(mockDragEvent) - config.onDragLeave?.(mockDragEvent) - config.onDragOver?.(mockDragEvent) - - expect(dropCalled).toBe(true) - expect(dragEnterCalled).toBe(true) - expect(dragLeaveCalled).toBe(true) - expect(dragOverCalled).toBe(true) - expect(config.acceptedDataTypes).toContain('application/x-column-data') - expect(config.validateDrop?.('test_column_data')).toBe(true) - expect(config.validateDrop?.('invalid_data')).toBe(false) - }) - - it('should create minimal drop zone configuration', () => { - let dropCalled = false - - const config: DropZoneConfig = { - onDrop: () => { - dropCalled = true - }, - acceptedDataTypes: ['text/plain'], - } - - config.onDrop({} as DragEvent) - - expect(dropCalled).toBe(true) - expect(config.acceptedDataTypes).toHaveLength(1) - expect(config.onDragEnter).toBeUndefined() - expect(config.onDragLeave).toBeUndefined() - expect(config.onDragOver).toBeUndefined() - expect(config.validateDrop).toBeUndefined() - }) - }) - - describe('DropFeedback', () => { - it('should create success feedback', () => { - const feedback: DropFeedback = { - type: 'success', - message: 'Column successfully mapped to label', - } - - expect(feedback).toEqual({ - type: 'success', - message: 'Column successfully mapped to label', - }) - }) - - it('should create error feedback', () => { - const feedback: DropFeedback = { - type: 'error', - message: 'Invalid data type for this target', - } - - expect(feedback).toEqual({ - type: 'error', - message: 'Invalid data type for this target', - }) - }) - - it('should create warning feedback', () => { - const feedback: DropFeedback = { - type: 'warning', - message: 'This mapping may cause data loss', - } - - expect(feedback).toEqual({ - type: 'warning', - message: 'This mapping may cause data loss', - }) - }) - }) - - describe('DragDropContext', () => { - it('should create basic drag drop context', () => { - const columnInfo: ColumnInfo = { - name: 'test_column', - dataType: 'VARCHAR', - sampleValues: ['sample1', 'sample2'], - nullable: true, - } - - const dropTarget: DropTarget = { - type: 'label', - path: 'item.terms.labels.en', - acceptedTypes: ['string'], - language: 'en', - } - - const context: DragDropContext = { - draggedColumn: columnInfo, - dropTarget: dropTarget, - isValidDrop: true, - dragStartPosition: { x: 100, y: 200 }, - } - - expect(context).toEqual({ - draggedColumn: columnInfo, - dropTarget: dropTarget, - isValidDrop: true, - dragStartPosition: { x: 100, y: 200 }, - }) - }) - - it('should handle null values', () => { - const context: DragDropContext = { - draggedColumn: null, - dropTarget: null, - isValidDrop: false, - } - - expect(context).toEqual({ - draggedColumn: null, - dropTarget: null, - isValidDrop: false, - }) - expect(context.dragStartPosition).toBeUndefined() - }) - }) - - describe('DropTarget', () => { - it('should create label drop target', () => { - const target: DropTarget = { - type: 'label', - path: 'item.terms.labels.en', - acceptedTypes: ['string', 'monolingualtext'], - language: 'en', - isRequired: true, - } - - expect(target).toEqual({ - type: 'label', - path: 'item.terms.labels.en', - acceptedTypes: ['string', 'monolingualtext'], - language: 'en', - isRequired: true, - }) - }) - - it('should create statement drop target', () => { - const target: DropTarget = { - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: ['wikibase-item', 'string'], - propertyId: 'P123', - } - - expect(target).toEqual({ - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: ['wikibase-item', 'string'], - propertyId: 'P123', - }) - expect(target.language).toBeUndefined() - }) - - it('should create qualifier drop target', () => { - const target: DropTarget = { - type: 'qualifier', - path: 'item.statements[0].qualifiers[0].value', - acceptedTypes: ['time', 'quantity'], - propertyId: 'P585', - } - - expect(target).toEqual({ - type: 'qualifier', - path: 'item.statements[0].qualifiers[0].value', - acceptedTypes: ['time', 'quantity'], - propertyId: 'P585', - }) - }) - - it('should create reference drop target', () => { - const target: DropTarget = { - type: 'reference', - path: 'item.statements[0].references[0].value', - acceptedTypes: ['url', 'external-id'], - propertyId: 'P854', - } - - expect(target).toEqual({ - type: 'reference', - path: 'item.statements[0].references[0].value', - acceptedTypes: ['url', 'external-id'], - propertyId: 'P854', - }) - }) - }) - - describe('DropTargetType', () => { - it('should accept all valid drop target types', () => { - const validTypes: DropTargetType[] = [ - 'label', - 'description', - 'alias', - 'statement', - 'qualifier', - 'reference', - ] - - validTypes.forEach((type) => { - const target: DropTarget = { - type, - path: `test.path.${type}`, - acceptedTypes: ['string'], - } - - expect(target.type).toBe(type) - }) - }) - }) - - describe('DragState', () => { - it('should handle all drag states', () => { - const states: DragState[] = ['idle', 'dragging', 'dropping', 'invalid'] - - states.forEach((state) => { - // Just verify the type can be assigned - const currentState: DragState = state - expect(currentState).toBe(state) - }) - }) - }) - - describe('DropValidation', () => { - it('should create valid drop validation', () => { - const validation: DropValidation = { - isValid: true, - } - - expect(validation).toEqual({ - isValid: true, - }) - expect(validation.reason).toBeUndefined() - }) - - it('should create invalid drop validation with reason', () => { - const validation: DropValidation = { - isValid: false, - reason: 'Data type mismatch: VARCHAR cannot be mapped to wikibase-item', - } - - expect(validation).toEqual({ - isValid: false, - reason: 'Data type mismatch: VARCHAR cannot be mapped to wikibase-item', - }) - }) - }) - - describe('DragEventData', () => { - it('should create drag event data', () => { - const mockElement = {} as HTMLElement - const columnInfo: ColumnInfo = { - name: 'drag_column', - dataType: 'INTEGER', - sampleValues: ['1', '2', '3'], - nullable: false, - } - - const eventData: DragEventData = { - column: columnInfo, - sourceElement: mockElement, - timestamp: Date.now(), - } - - expect(eventData).toEqual({ - column: columnInfo, - sourceElement: mockElement, - timestamp: eventData.timestamp, - }) - expect(typeof eventData.timestamp).toBe('number') - expect(eventData.timestamp).toBeGreaterThan(0) - }) - }) - - describe('DropEventData', () => { - it('should create drop event data', () => { - const columnInfo: ColumnInfo = { - name: 'drop_column', - dataType: 'VARCHAR', - sampleValues: ['a', 'b', 'c'], - nullable: true, - } - - const dropTarget: DropTarget = { - type: 'description', - path: 'item.terms.descriptions.fr', - acceptedTypes: ['string'], - language: 'fr', - } - - const eventData: DropEventData = { - column: columnInfo, - target: dropTarget, - position: { x: 250, y: 300 }, - timestamp: Date.now(), - } - - expect(eventData).toEqual({ - column: columnInfo, - target: dropTarget, - position: { x: 250, y: 300 }, - timestamp: eventData.timestamp, - }) - expect(typeof eventData.timestamp).toBe('number') - }) - }) - - describe('DragVisualState', () => { - it('should create complete visual state', () => { - const columnInfo: ColumnInfo = { - name: 'visual_column', - dataType: 'TEXT', - sampleValues: ['text1', 'text2'], - nullable: false, - } - - const visualState: DragVisualState = { - isDragging: true, - draggedColumn: columnInfo, - validDropTargets: ['item.terms.labels.en', 'item.terms.descriptions.en'], - invalidDropTargets: ['item.statements[0].value'], - hoveredTarget: 'item.terms.labels.en', - } - - expect(visualState).toEqual({ - isDragging: true, - draggedColumn: columnInfo, - validDropTargets: ['item.terms.labels.en', 'item.terms.descriptions.en'], - invalidDropTargets: ['item.statements[0].value'], - hoveredTarget: 'item.terms.labels.en', - }) - }) - - it('should handle idle visual state', () => { - const visualState: DragVisualState = { - isDragging: false, - draggedColumn: null, - validDropTargets: [], - invalidDropTargets: [], - hoveredTarget: null, - } - - expect(visualState).toEqual({ - isDragging: false, - draggedColumn: null, - validDropTargets: [], - invalidDropTargets: [], - hoveredTarget: null, - }) - }) - }) - - describe('DragDropConfig', () => { - it('should create complete configuration', () => { - const config: DragDropConfig = { - enableVisualFeedback: true, - highlightValidTargets: true, - showInvalidTargetWarnings: false, - animationDuration: 300, - snapToTarget: true, - } - - expect(config).toEqual({ - enableVisualFeedback: true, - highlightValidTargets: true, - showInvalidTargetWarnings: false, - animationDuration: 300, - snapToTarget: true, - }) - }) - - it('should create minimal configuration', () => { - const config: DragDropConfig = { - enableVisualFeedback: false, - highlightValidTargets: false, - showInvalidTargetWarnings: false, - animationDuration: 0, - snapToTarget: false, - } - - expect(config).toEqual({ - enableVisualFeedback: false, - highlightValidTargets: false, - showInvalidTargetWarnings: false, - animationDuration: 0, - snapToTarget: false, - }) - }) - }) - - describe('Type Integration', () => { - it('should work with WikibaseDataType from schema-mapping', () => { - const wikibaseTypes: WikibaseDataType[] = [ - 'string', - 'wikibase-item', - 'quantity', - 'time', - 'url', - ] - - const target: DropTarget = { - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: wikibaseTypes, - } - - expect(target.acceptedTypes).toHaveLength(5) - expect(target.acceptedTypes).toContain('string') - expect(target.acceptedTypes).toContain('wikibase-item') - expect(target.acceptedTypes).toContain('quantity') - }) - - it('should integrate ColumnInfo with drag events', () => { - const columnInfo: ColumnInfo = { - name: 'integration_test', - dataType: 'DECIMAL', - sampleValues: ['1.5', '2.7', '3.14'], - nullable: true, - uniqueCount: 42, - } - - const dragEvent: DragEventData = { - column: columnInfo, - sourceElement: {} as HTMLElement, - timestamp: Date.now(), - } - - const dropTarget: DropTarget = { - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: ['quantity'], - propertyId: 'P2067', - } - - const dropEvent: DropEventData = { - column: columnInfo, - target: dropTarget, - position: { x: 0, y: 0 }, - timestamp: Date.now(), - } - - expect(dragEvent.column.uniqueCount).toBe(42) - expect(dropEvent.target.acceptedTypes).toContain('quantity') - }) - }) -}) diff --git a/frontend/src/types/__tests__/drop-target-validation.test.ts b/frontend/src/types/__tests__/drop-target-validation.test.ts deleted file mode 100644 index 754f74c..0000000 --- a/frontend/src/types/__tests__/drop-target-validation.test.ts +++ /dev/null @@ -1,393 +0,0 @@ -import { describe, it, expect, beforeEach } from 'bun:test' -import { useDataTypeCompatibility } from '@frontend/composables/useDataTypeCompatibility' -import type { DropTarget, DropZoneConfig, DropTargetType } from '@frontend/types/drag-drop' -import type { ColumnInfo, WikibaseDataType } from '@frontend/types/wikibase-schema' - -describe('Drop Target Validation Logic', () => { - const { isDataTypeCompatible } = useDataTypeCompatibility() - let mockColumnInfo: ColumnInfo - let mockDropTargets: Record - - beforeEach(() => { - mockColumnInfo = { - name: 'test_column', - dataType: 'VARCHAR', - sampleValues: ['value1', 'value2'], - nullable: false, - uniqueCount: 100, - } - - mockDropTargets = { - label: { - type: 'label', - path: 'item.terms.labels.en', - acceptedTypes: ['string'], - language: 'en', - isRequired: false, - }, - description: { - type: 'description', - path: 'item.terms.descriptions.en', - acceptedTypes: ['string'], - language: 'en', - isRequired: false, - }, - alias: { - type: 'alias', - path: 'item.terms.aliases.en[0]', - acceptedTypes: ['string'], - language: 'en', - isRequired: false, - }, - statement: { - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: ['string', 'wikibase-item'], - propertyId: 'P31', - isRequired: true, - }, - qualifier: { - type: 'qualifier', - path: 'item.statements[0].qualifiers[0].value', - acceptedTypes: ['time'], - propertyId: 'P585', - isRequired: false, - }, - reference: { - type: 'reference', - path: 'item.statements[0].references[0].value', - acceptedTypes: ['url'], - propertyId: 'P854', - isRequired: false, - }, - } - }) - - describe('Data Type Compatibility Validation', () => { - it('should validate string column for label target', () => { - const stringColumn: ColumnInfo = { - name: 'name_column', - dataType: 'VARCHAR', - sampleValues: ['John Doe', 'Jane Smith'], - nullable: false, - } - - const isValid = isDataTypeCompatible( - stringColumn.dataType, - mockDropTargets.label.acceptedTypes, - ) - expect(isValid).toBe(true) - }) - - it('should validate text column for description target', () => { - const textColumn: ColumnInfo = { - name: 'description_column', - dataType: 'TEXT', - sampleValues: ['A detailed description', 'Another description'], - nullable: true, - } - - const isValid = isDataTypeCompatible( - textColumn.dataType, - mockDropTargets.description.acceptedTypes, - ) - expect(isValid).toBe(true) - }) - - it('should reject numeric column for string target', () => { - const numericColumn: ColumnInfo = { - name: 'age_column', - dataType: 'INTEGER', - sampleValues: ['25', '30', '35'], - nullable: false, - } - - const isValid = isDataTypeCompatible( - numericColumn.dataType, - mockDropTargets.label.acceptedTypes, - ) - expect(isValid).toBe(false) - }) - - it('should validate date column for time qualifier', () => { - const dateColumn: ColumnInfo = { - name: 'date_column', - dataType: 'DATE', - sampleValues: ['2023-01-01', '2023-02-01'], - nullable: false, - } - - const timeTarget: DropTarget = { - type: 'qualifier', - path: 'item.statements[0].qualifiers[0].value', - acceptedTypes: ['time'], - propertyId: 'P585', - } - - const isValid = isDataTypeCompatible(dateColumn.dataType, timeTarget.acceptedTypes) - expect(isValid).toBe(true) - }) - - it('should validate URL column for reference target', () => { - const urlColumn: ColumnInfo = { - name: 'source_url', - dataType: 'VARCHAR', - sampleValues: ['https://example.com', 'https://source.org'], - nullable: true, - } - - const isValid = isDataTypeCompatible( - urlColumn.dataType, - mockDropTargets.reference.acceptedTypes, - ) - expect(isValid).toBe(true) - }) - }) - - describe('Drop Zone Configuration Validation', () => { - it('should create valid drop zone config for labels', () => { - const onDrop = (event: DragEvent) => { - const data = event.dataTransfer?.getData('application/x-column-data') - expect(data).toBeDefined() - } - - const config: DropZoneConfig = { - onDrop, - acceptedDataTypes: ['application/x-column-data'], - validateDrop: (data: string) => { - try { - const column = JSON.parse(data) as ColumnInfo - return ['VARCHAR', 'TEXT', 'STRING'].includes(column.dataType.toUpperCase()) - } catch { - return false - } - }, - } - - expect(config.acceptedDataTypes).toContain('application/x-column-data') - expect(config.validateDrop).toBeDefined() - - // Test validation function - const validData = JSON.stringify(mockColumnInfo) - expect(config.validateDrop!(validData)).toBe(true) - - const invalidData = JSON.stringify({ ...mockColumnInfo, dataType: 'INTEGER' }) - expect(config.validateDrop!(invalidData)).toBe(false) - }) - - it('should handle drag events properly', () => { - let dragEnterCalled = false - let dragLeaveCalled = false - let dragOverCalled = false - - const config: DropZoneConfig = { - onDrop: () => {}, - onDragEnter: () => { - dragEnterCalled = true - }, - onDragLeave: () => { - dragLeaveCalled = true - }, - onDragOver: (event) => { - dragOverCalled = true - event.preventDefault() - }, - acceptedDataTypes: ['application/x-column-data'], - } - - // Simulate drag events with mock objects - const mockEvent = { type: 'dragenter' } as DragEvent - config.onDragEnter?.(mockEvent) - expect(dragEnterCalled).toBe(true) - - const mockLeaveEvent = { type: 'dragleave' } as DragEvent - config.onDragLeave?.(mockLeaveEvent) - expect(dragLeaveCalled).toBe(true) - - const mockOverEvent = { - type: 'dragover', - preventDefault: () => {}, - } as DragEvent - config.onDragOver?.(mockOverEvent) - expect(dragOverCalled).toBe(true) - }) - }) - - describe('Multi-language Target Validation', () => { - it('should validate language-specific targets', () => { - const targets = [ - { - type: 'label' as DropTargetType, - path: 'item.terms.labels.en', - acceptedTypes: ['string'] as WikibaseDataType[], - language: 'en', - }, - { - type: 'label' as DropTargetType, - path: 'item.terms.labels.fr', - acceptedTypes: ['string'] as WikibaseDataType[], - language: 'fr', - }, - { - type: 'label' as DropTargetType, - path: 'item.terms.labels.de', - acceptedTypes: ['string'] as WikibaseDataType[], - language: 'de', - }, - ] - - targets.forEach((target) => { - expect(target.language).toBeDefined() - expect(target.acceptedTypes).toContain('string') - expect(target.path).toContain(target.language) - }) - }) - - it('should handle alias arrays for languages', () => { - const aliasTargets = [ - { - type: 'alias' as DropTargetType, - path: 'item.terms.aliases.en[0]', - acceptedTypes: ['string'] as WikibaseDataType[], - language: 'en', - }, - { - type: 'alias' as DropTargetType, - path: 'item.terms.aliases.en[1]', - acceptedTypes: ['string'] as WikibaseDataType[], - language: 'en', - }, - ] - - aliasTargets.forEach((target, index) => { - expect(target.path).toContain(`[${index}]`) - expect(target.language).toBe('en') - }) - }) - }) - - describe('Property-based Target Validation', () => { - it('should validate statement targets with property IDs', () => { - const statementTargets = [ - { - type: 'statement' as DropTargetType, - path: 'item.statements[0].value', - acceptedTypes: ['wikibase-item'] as WikibaseDataType[], - propertyId: 'P31', // instance of - }, - { - type: 'statement' as DropTargetType, - path: 'item.statements[1].value', - acceptedTypes: ['time'] as WikibaseDataType[], - propertyId: 'P569', // date of birth - }, - { - type: 'statement' as DropTargetType, - path: 'item.statements[2].value', - acceptedTypes: ['string'] as WikibaseDataType[], - propertyId: 'P1476', // title - }, - ] - - statementTargets.forEach((target) => { - expect(target.propertyId).toMatch(/^P\d+$/) - expect(target.acceptedTypes.length).toBeGreaterThan(0) - }) - }) - - it('should validate qualifier targets with property IDs', () => { - const qualifierTarget: DropTarget = { - type: 'qualifier', - path: 'item.statements[0].qualifiers[0].value', - acceptedTypes: ['time'], - propertyId: 'P585', // point in time - } - - expect(qualifierTarget).toEqual({ - type: 'qualifier', - path: 'item.statements[0].qualifiers[0].value', - acceptedTypes: ['time'], - propertyId: 'P585', - }) - }) - - it('should validate reference targets with property IDs', () => { - const referenceTargets = [ - { - type: 'reference' as DropTargetType, - path: 'item.statements[0].references[0].value', - acceptedTypes: ['wikibase-item'] as WikibaseDataType[], - propertyId: 'P248', // stated in - }, - { - type: 'reference' as DropTargetType, - path: 'item.statements[0].references[1].value', - acceptedTypes: ['url'] as WikibaseDataType[], - propertyId: 'P854', // reference URL - }, - ] - - referenceTargets.forEach((target) => { - expect(target.propertyId).toMatch(/^P\d+$/) - expect(['P248', 'P854']).toContain(target.propertyId) - }) - }) - }) - - describe('Required Field Validation', () => { - it('should identify required targets', () => { - const requiredTarget: DropTarget = { - type: 'statement', - path: 'item.statements[0].value', - acceptedTypes: ['wikibase-item'], - propertyId: 'P31', - isRequired: true, - } - - const optionalTarget: DropTarget = { - type: 'description', - path: 'item.terms.descriptions.en', - acceptedTypes: ['string'], - language: 'en', - isRequired: false, - } - - expect(requiredTarget.isRequired).toBe(true) - expect(optionalTarget.isRequired).toBe(false) - }) - }) - - describe('Path Validation', () => { - it('should validate JSON paths for different target types', () => { - const pathTests = [ - { path: 'item.terms.labels.en', valid: true }, - { path: 'item.terms.descriptions.fr', valid: true }, - { path: 'item.terms.aliases.de[0]', valid: true }, - { path: 'item.statements[0].value', valid: true }, - { path: 'item.statements[1].qualifiers[0].value', valid: true }, - { path: 'item.statements[2].references[0].value', valid: true }, - { path: 'invalid.path', valid: false }, - { path: '', valid: false }, - ] - - pathTests.forEach((test) => { - const isValid = validateTargetPath(test.path) - expect(isValid).toBe(test.valid) - }) - }) - }) -}) - -const validateTargetPath = (path: string): boolean => { - if (!path || path.trim() === '') return false - - const validPatterns = [ - /^item\.terms\.(labels|descriptions)\.[a-z]{2,3}$/, - /^item\.terms\.aliases\.[a-z]{2,3}\[\d+\]$/, - /^item\.statements\[\d+\]\.value$/, - /^item\.statements\[\d+\]\.qualifiers\[\d+\]\.value$/, - /^item\.statements\[\d+\]\.references\[\d+\]\.value$/, - ] - - return validPatterns.some((pattern) => pattern.test(path)) -} diff --git a/frontend/src/types/__tests__/schema-mapping.test.ts b/frontend/src/types/__tests__/schema-mapping.test.ts deleted file mode 100644 index d15dee4..0000000 --- a/frontend/src/types/__tests__/schema-mapping.test.ts +++ /dev/null @@ -1,591 +0,0 @@ -import { describe, it, expect } from 'bun:test' -import type { UUID } from 'crypto' -import type { - WikibaseSchemaMapping, - TermsSchemaMapping, - ColumnMapping, - StatementSchemaMapping, - PropertyReference, - ValueMapping, - ColumnInfo, - WikibaseDataType, - StatementRank, - TransformationRule, - TransformationFunction, - TransformationParameter, - ValidationRule, - SchemaMapping, - ColumnReference, - ValueSchemaMapping, - ValidatedSchemaMapping, - ValidationError, - ValidationResult, - PropertyValueMap, - ReferenceSchemaMapping, -} from '@frontend/types/wikibase-schema' - -// Test UUIDs for consistent testing -const TEST_SCHEMA_ID = Bun.randomUUIDv7() as UUID -const TEST_PROJECT_ID = Bun.randomUUIDv7() as UUID -const TEST_STATEMENT_ID = Bun.randomUUIDv7() as UUID -const TEST_VALIDATED_SCHEMA_ID = Bun.randomUUIDv7() as UUID -const TEST_PROJECT_123_ID = Bun.randomUUIDv7() as UUID -const TEST_STMT_PREFERRED_ID = Bun.randomUUIDv7() as UUID -const TEST_STMT_NORMAL_ID = Bun.randomUUIDv7() as UUID -const TEST_STMT_DEPRECATED_ID = Bun.randomUUIDv7() as UUID - -describe('Schema Mapping Types', () => { - describe('Core Schema Types', () => { - it('should create WikibaseSchemaMapping with and without optional fields', () => { - const baseSchema = { - id: TEST_SCHEMA_ID, - projectId: TEST_PROJECT_ID, - name: 'Test Schema', - wikibase: 'https://test.wikibase.org', - item: { - terms: { labels: {}, descriptions: {}, aliases: {} }, - statements: [], - }, - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z', - } - - // With item ID - const schemaWithId: WikibaseSchemaMapping = { - ...baseSchema, - item: { ...baseSchema.item, id: 'Q123' }, - } - - // Without item ID - const schemaWithoutId: WikibaseSchemaMapping = baseSchema - - expect(schemaWithId.item.id).toBe('Q123') - expect(schemaWithoutId.item.id).toBeUndefined() - expect(schemaWithId.id).toBe(TEST_SCHEMA_ID) - expect(schemaWithoutId.projectId).toBe(TEST_PROJECT_ID) - }) - }) - - describe('Mapping Types', () => { - it('should create TermsSchemaMapping with multiple languages and empty state', () => { - // Full terms mapping - const fullTermsMapping: TermsSchemaMapping = { - labels: { - en: { columnName: 'name_en', dataType: 'VARCHAR' }, - fr: { columnName: 'name_fr', dataType: 'VARCHAR' }, - }, - descriptions: { - en: { columnName: 'desc_en', dataType: 'TEXT' }, - }, - aliases: { - en: [ - { columnName: 'alias1_en', dataType: 'VARCHAR' }, - { columnName: 'alias2_en', dataType: 'VARCHAR' }, - ], - }, - } - - // Empty terms mapping - const emptyTermsMapping: TermsSchemaMapping = { - labels: {}, - descriptions: {}, - aliases: {}, - } - - expect(fullTermsMapping.labels.en?.columnName).toBe('name_en') - expect(fullTermsMapping.aliases.en).toHaveLength(2) - expect(Object.keys(emptyTermsMapping.labels)).toHaveLength(0) - }) - - it('should create ColumnMapping with and without transformation', () => { - // Basic column mapping - const basicMapping: ColumnMapping = { - columnName: 'test_column', - dataType: 'VARCHAR', - } - - // Column mapping with transformation - const transformationRule: TransformationRule = { - type: 'expression', - value: 'UPPER(${column})', - parameters: { format: 'uppercase' }, - } - - const mappingWithTransform: ColumnMapping = { - columnName: 'test_column', - dataType: 'VARCHAR', - transformation: transformationRule, - } - - expect(basicMapping.transformation).toBeUndefined() - expect(mappingWithTransform.transformation?.type).toBe('expression') - expect(mappingWithTransform.transformation?.value).toBe('UPPER(${column})') - }) - }) - - describe('StatementSchemaMapping', () => { - it('should create complete statement mapping', () => { - const propertyRef: PropertyReference = { - id: 'P123', - label: 'Test Property', - dataType: 'string', - } - - const valueMapping: ValueMapping = { - type: 'column', - source: { columnName: 'value_col', dataType: 'VARCHAR' }, - dataType: 'string', - } - - const qualifier: PropertyValueMap = { - property: { id: 'P456', dataType: 'string' }, - value: { - type: 'column', - source: { columnName: 'qualifier_col', dataType: 'VARCHAR' }, - dataType: 'string', - }, - } - - const reference: ReferenceSchemaMapping = { - id: Bun.randomUUIDv7() as UUID, - snaks: [ - { - property: { id: 'P854', label: 'reference URL', dataType: 'url' }, - value: { - type: 'column', - source: { columnName: 'ref_url', dataType: 'VARCHAR' }, - dataType: 'url', - }, - }, - ], - } - - const statementMapping: StatementSchemaMapping = { - id: TEST_STATEMENT_ID, - property: propertyRef, - value: valueMapping, - rank: 'normal', - qualifiers: [qualifier], - references: [reference], - } - - expect(statementMapping).toEqual({ - id: TEST_STATEMENT_ID, - property: { - id: 'P123', - label: 'Test Property', - dataType: 'string', - }, - value: { - type: 'column', - source: { columnName: 'value_col', dataType: 'VARCHAR' }, - dataType: 'string', - }, - rank: 'normal', - qualifiers: [ - { - property: { id: 'P456', dataType: 'string' }, - value: { - type: 'column', - source: { columnName: 'qualifier_col', dataType: 'VARCHAR' }, - dataType: 'string', - }, - }, - ], - references: [ - { - id: expect.any(String), - snaks: [ - { - property: { id: 'P854', label: 'reference URL', dataType: 'url' }, - value: { - type: 'column', - source: { columnName: 'ref_url', dataType: 'VARCHAR' }, - dataType: 'url', - }, - }, - ], - }, - ], - }) - }) - - it('should handle different statement ranks', () => { - const testCases = [ - { rank: 'preferred' as StatementRank, id: TEST_STMT_PREFERRED_ID }, - { rank: 'normal' as StatementRank, id: TEST_STMT_NORMAL_ID }, - { rank: 'deprecated' as StatementRank, id: TEST_STMT_DEPRECATED_ID }, - ] - - testCases.forEach(({ rank, id }) => { - const statementMapping: StatementSchemaMapping = { - id, - property: { id: 'P123', dataType: 'string' }, - value: { - type: 'constant', - source: 'test value', - dataType: 'string', - }, - rank, - qualifiers: [], - references: [], - } - - expect(statementMapping.rank).toBe(rank) - }) - }) - }) - - describe('Value and Data Types', () => { - it('should create all ValueMapping types and handle WikibaseDataType', () => { - // Column-based value mapping - const columnMapping: ColumnMapping = { - columnName: 'test_col', - dataType: 'VARCHAR', - } - - const columnValueMapping: ValueMapping = { - type: 'column', - source: columnMapping, - dataType: 'string', - } - - // Constant value mapping - const constantValueMapping: ValueMapping = { - type: 'constant', - source: 'fixed value', - dataType: 'string', - } - - // Expression value mapping - const expressionValueMapping: ValueMapping = { - type: 'expression', - source: 'CONCAT(${col1}, " - ", ${col2})', - dataType: 'string', - } - - // Test all valid Wikibase data types - const validTypes: WikibaseDataType[] = [ - 'string', - 'wikibase-item', - 'wikibase-property', - 'quantity', - 'time', - 'globe-coordinate', - 'url', - 'external-id', - 'monolingualtext', - 'commonsMedia', - ] - - expect(columnValueMapping.type).toBe('column') - expect(constantValueMapping.type).toBe('constant') - expect(expressionValueMapping.type).toBe('expression') - expect(validTypes).toHaveLength(10) - - // Verify data type validation works - validTypes.forEach((dataType) => { - const testMapping: ValueMapping = { - type: 'constant', - source: 'test', - dataType, - } - expect(testMapping.dataType).toBe(dataType) - }) - }) - - it('should create ColumnInfo with complete and minimal data', () => { - // Complete column info - const completeColumnInfo: ColumnInfo = { - name: 'test_column', - dataType: 'VARCHAR', - sampleValues: ['value1', 'value2', 'value3'], - nullable: true, - uniqueCount: 150, - } - - // Minimal column info - const minimalColumnInfo: ColumnInfo = { - name: 'simple_column', - dataType: 'INTEGER', - sampleValues: [], - nullable: false, - } - - expect(completeColumnInfo.uniqueCount).toBe(150) - expect(completeColumnInfo.sampleValues).toHaveLength(3) - expect(minimalColumnInfo.uniqueCount).toBeUndefined() - expect(minimalColumnInfo.nullable).toBe(false) - }) - }) - - describe('Validation Types', () => { - it('should create ValidationError and ValidationResult types', () => { - // Create validation error - const error: ValidationError = { - type: 'error', - code: 'MISSING_REQUIRED_MAPPING', - message: 'Required mapping is missing for labels', - path: 'item.terms.labels', - } - - // Create validation warning - const warning: ValidationError = { - type: 'warning', - code: 'MISSING_REQUIRED_MAPPING', - message: 'Some optional fields are not mapped', - path: 'item.terms.descriptions', - } - - // Create validation result with errors and warnings - const invalidResult: ValidationResult = { - isValid: false, - errors: [error], - warnings: [warning], - } - - // Create valid result with no errors - const validResult: ValidationResult = { - isValid: true, - errors: [], - warnings: [], - } - - expect(error.type).toBe('error') - expect(warning.type).toBe('warning') - expect(invalidResult.isValid).toBe(false) - expect(invalidResult.errors).toHaveLength(1) - expect(invalidResult.warnings).toHaveLength(1) - expect(validResult.isValid).toBe(true) - expect(validResult.errors).toHaveLength(0) - }) - }) - - describe('Transformation Types', () => { - it('should create and execute TransformationFunction with TransformationParameter', () => { - // Create transformation parameters - const parameters: TransformationParameter[] = [ - { - name: 'format', - type: 'string', - required: true, - description: 'Output format pattern', - }, - { - name: 'defaultValue', - type: 'string', - required: false, - defaultValue: 'N/A', - description: 'Default value when input is null', - }, - { - name: 'maxLength', - type: 'number', - required: false, - defaultValue: 100, - }, - ] - - // Create transformation function - const transformationFunction: TransformationFunction = { - name: 'formatString', - description: 'Formats string values according to pattern', - parameters, - execute: (input: unknown, params: Record) => { - return `${params.format}: ${input}` - }, - } - - // Test all parameter types - const parameterTypes: Array = [ - 'string', - 'number', - 'boolean', - 'array', - 'object', - ] - - expect(transformationFunction.name).toBe('formatString') - expect(transformationFunction.parameters).toHaveLength(3) - expect(transformationFunction.parameters[0]?.required).toBe(true) - expect(transformationFunction.parameters[1]?.defaultValue).toBe('N/A') - expect(parameterTypes).toHaveLength(5) - - // Test function execution - const uppercaseFunction: TransformationFunction = { - name: 'uppercase', - description: 'Converts input to uppercase', - parameters: [], - execute: (input: unknown) => { - return typeof input === 'string' ? input.toUpperCase() : input - }, - } - - const result = uppercaseFunction.execute('hello world', {}) - expect(result).toBe('HELLO WORLD') - }) - }) - - describe('Advanced Schema Types', () => { - it('should create and execute ValidationRule with SchemaMapping', () => { - // Create validation rule - const rule: ValidationRule = { - id: 'required-labels', - name: 'Required Labels Validation', - description: 'Ensures that at least one label mapping exists', - validate: (schema: WikibaseSchemaMapping) => { - const errors: ValidationError[] = [] - if (Object.keys(schema.item.terms.labels).length === 0) { - errors.push({ - type: 'error', - code: 'MISSING_REQUIRED_MAPPING', - message: 'At least one label mapping is required', - path: 'item.terms.labels', - }) - } - return errors - }, - } - - // Create column mappings and schema mapping - const columnMappings: Record = { - name_en: { columnName: 'name_en', dataType: 'VARCHAR' }, - description_en: { columnName: 'description_en', dataType: 'TEXT' }, - } - - const schemaMapping: SchemaMapping = { - item: { - terms: { - labels: { en: columnMappings['name_en']! }, - descriptions: { en: columnMappings['description_en']! }, - aliases: {}, - }, - statements: [], - }, - columnMappings, - validationRules: [rule], - } - - // Test schemas for validation - const schemaWithLabels: WikibaseSchemaMapping = { - id: TEST_SCHEMA_ID, - projectId: TEST_PROJECT_ID, - name: 'test', - wikibase: 'test', - item: { - terms: { - labels: { en: { columnName: 'name', dataType: 'VARCHAR' } }, - descriptions: {}, - aliases: {}, - }, - statements: [], - }, - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z', - } - - const schemaWithoutLabels: WikibaseSchemaMapping = { - ...schemaWithLabels, - item: { - ...schemaWithLabels.item, - terms: { labels: {}, descriptions: {}, aliases: {} }, - }, - } - - // Test validation rule execution - const validResult = rule.validate(schemaWithLabels) - const invalidResult = rule.validate(schemaWithoutLabels) - - expect(rule.id).toBe('required-labels') - expect(schemaMapping.validationRules).toHaveLength(1) - expect(schemaMapping.item.terms.labels['en']?.columnName).toBe('name_en') - expect(validResult).toHaveLength(0) - expect(invalidResult).toHaveLength(1) - expect(invalidResult[0]?.code).toBe('MISSING_REQUIRED_MAPPING') - }) - - it('should create ColumnReference and ValueSchemaMapping with optional features', () => { - // Required column reference - const requiredColumnRef: ColumnReference = { - columnName: 'test_column', - dataType: 'INTEGER', - required: true, - } - - // Optional column reference - const optionalColumnRef: ColumnReference = { - columnName: 'optional_column', - dataType: 'VARCHAR', - required: false, - } - - // Value schema mapping with transformation - const transformation: TransformationFunction = { - name: 'trim', - description: 'Trims whitespace', - parameters: [], - execute: (input: unknown) => (typeof input === 'string' ? input.trim() : input), - } - - const valueMappingWithTransform: ValueSchemaMapping = { - columnReference: requiredColumnRef, - dataType: 'string', - transformation, - } - - // Value schema mapping without transformation - const valueMappingSimple: ValueSchemaMapping = { - columnReference: optionalColumnRef, - dataType: 'quantity', - } - - expect(requiredColumnRef.required).toBe(true) - expect(optionalColumnRef.required).toBe(false) - expect(valueMappingWithTransform.transformation?.name).toBe('trim') - expect(valueMappingSimple.transformation).toBeUndefined() - }) - - it('should create ValidatedSchemaMapping with validation results', () => { - const baseSchema: WikibaseSchemaMapping = { - id: TEST_VALIDATED_SCHEMA_ID, - projectId: TEST_PROJECT_123_ID, - name: 'Validated Schema', - wikibase: 'https://test.wikibase.org', - item: { - terms: { - labels: { en: { columnName: 'name', dataType: 'VARCHAR' } }, - descriptions: {}, - aliases: {}, - }, - statements: [], - }, - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z', - } - - const validation: ValidationResult = { - isValid: true, - errors: [], - warnings: [ - { - type: 'warning', - code: 'MISSING_REQUIRED_MAPPING', - message: 'No description provided', - path: 'item.terms.descriptions', - }, - ], - } - - const validatedSchema: ValidatedSchemaMapping = { - ...baseSchema, - validation, - completeness: 75, - } - - expect(validatedSchema.validation.isValid).toBe(true) - expect(validatedSchema.validation.warnings).toHaveLength(1) - expect(validatedSchema.completeness).toBe(75) - expect(validatedSchema.id).toBe(TEST_VALIDATED_SCHEMA_ID) - }) - }) -}) diff --git a/frontend/src/types/__tests__/validation-errors.test.ts b/frontend/src/types/__tests__/validation-errors.test.ts deleted file mode 100644 index de9b202..0000000 --- a/frontend/src/types/__tests__/validation-errors.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { describe, it, expect } from 'bun:test' -import type { - ValidationError, - ValidationResult, - ValidationContext, - ValidationErrorCode, - ValidationErrorType, -} from '@frontend/types/wikibase-schema' -import { ValidationMessages } from '@frontend/types/wikibase-schema' - -describe('ValidationError Types', () => { - it('should create ValidationError, ValidationResult, and ValidationContext types', () => { - // Complete ValidationError with optional fields - const completeError: ValidationError = { - type: 'error', - code: 'MISSING_REQUIRED_MAPPING', - message: 'Required mapping is missing', - path: 'item.terms.labels.en', - field: 'label', - context: { columnName: 'title' }, - } - - // Minimal ValidationError with required fields only - const minimalError: ValidationError = { - type: 'error', - code: 'MISSING_REQUIRED_MAPPING', - message: 'Required mapping is missing', - path: 'item.terms.labels.en', - } - - // ValidationResult with errors and warnings - const result: ValidationResult = { - isValid: false, - errors: [minimalError], - warnings: [ - { - type: 'warning', - code: 'INCOMPATIBLE_DATA_TYPE', - message: 'Data type may not be optimal', - path: 'item.statements[0].value', - }, - ], - } - - // ValidationContext with optional fields - const context: ValidationContext = { - columnName: 'title', - propertyId: 'P31', - languageCode: 'en', - dataType: 'VARCHAR', - targetType: 'label', - schemaPath: 'item.terms.labels.en', - } - - expect(completeError.field).toBe('label') - expect(completeError.context?.columnName).toBe('title') - expect(minimalError.field).toBeUndefined() - expect(result.isValid).toBe(false) - expect(result.errors).toHaveLength(1) - expect(result.warnings).toHaveLength(1) - expect(context.propertyId).toBe('P31') - }) - - it('should validate error codes and types', () => { - const errorCodes: ValidationErrorCode[] = [ - 'MISSING_REQUIRED_MAPPING', - 'INCOMPATIBLE_DATA_TYPE', - 'DUPLICATE_LANGUAGE_MAPPING', - 'INVALID_PROPERTY_ID', - 'MISSING_STATEMENT_VALUE', - 'INVALID_LANGUAGE_CODE', - 'MISSING_ITEM_CONFIGURATION', - 'DUPLICATE_PROPERTY_MAPPING', - ] - - const errorTypes: ValidationErrorType[] = ['error', 'warning'] - - // Test all error codes have messages - errorCodes.forEach((code) => { - expect(ValidationMessages[code]).toBeDefined() - expect(typeof ValidationMessages[code]).toBe('string') - expect(ValidationMessages[code].length).toBeGreaterThan(0) - }) - - // Test error type discrimination - errorTypes.forEach((type) => { - expect(['error', 'warning']).toContain(type) - }) - - expect(errorCodes).toHaveLength(8) - expect(errorTypes).toHaveLength(2) - }) -}) diff --git a/frontend/src/types/drag-drop.ts b/frontend/src/types/drag-drop.ts index 097d3b6..d7dab35 100644 --- a/frontend/src/types/drag-drop.ts +++ b/frontend/src/types/drag-drop.ts @@ -60,14 +60,14 @@ export interface DropValidation { // Drag event data export interface DragEventData { - column: ColumnInfo + columnInfo: ColumnInfo sourceElement: HTMLElement timestamp: number } // Drop event data export interface DropEventData { - column: ColumnInfo + columnInfo: ColumnInfo target: DropTarget position: { x: number; y: number } timestamp: number