1096 lines
39 KiB
TypeScript
1096 lines
39 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { compareApplicationFormValues, groupChangesBySection } from '../../../app/utils/formSnapshotComparison'
|
|
import type {
|
|
ApplicationFormDto,
|
|
ApplicationFormSnapshotDto,
|
|
FormElementDto,
|
|
FormElementSnapshotDto,
|
|
FormOptionDto,
|
|
FormElementSectionDto,
|
|
FormElementSubSectionDto,
|
|
FormElementSectionSnapshotDto,
|
|
FormElementSubSectionSnapshotDto,
|
|
FormElementType
|
|
} from '../../../.api-client'
|
|
|
|
// Helper to create a minimal FormOptionDto
|
|
function createOption(value: string, label: string): FormOptionDto {
|
|
return {
|
|
value,
|
|
label,
|
|
processingPurpose: 'NONE',
|
|
employeeDataCategory: 'NONE'
|
|
}
|
|
}
|
|
|
|
// Helper to create a FormElementDto
|
|
function createFormElement(
|
|
reference: string,
|
|
title: string,
|
|
type: FormElementType,
|
|
options: FormOptionDto[]
|
|
): FormElementDto {
|
|
return {
|
|
id: `id-${reference}`,
|
|
reference,
|
|
title,
|
|
type,
|
|
options
|
|
}
|
|
}
|
|
|
|
// Helper to create a FormElementSnapshotDto
|
|
function createSnapshotElement(
|
|
reference: string,
|
|
title: string,
|
|
type: FormElementType,
|
|
options: FormOptionDto[]
|
|
): FormElementSnapshotDto {
|
|
return {
|
|
reference,
|
|
title,
|
|
type,
|
|
options
|
|
}
|
|
}
|
|
|
|
// Helper to create an ApplicationFormDto with a single element
|
|
function createForm(elements: FormElementDto[], sectionTitle = 'Test Section'): ApplicationFormDto {
|
|
const subSection: FormElementSubSectionDto = {
|
|
id: 'subsection-1',
|
|
title: 'Subsection',
|
|
formElements: elements
|
|
}
|
|
|
|
const section: FormElementSectionDto = {
|
|
id: 'section-1',
|
|
title: sectionTitle,
|
|
formElementSubSections: [subSection]
|
|
}
|
|
|
|
return {
|
|
id: 'form-1',
|
|
name: 'Test Form',
|
|
isTemplate: false,
|
|
formElementSections: [section]
|
|
}
|
|
}
|
|
|
|
// Helper to create an ApplicationFormSnapshotDto with a single element
|
|
function createSnapshot(elements: FormElementSnapshotDto[], sectionTitle = 'Test Section'): ApplicationFormSnapshotDto {
|
|
const subSection: FormElementSubSectionSnapshotDto = {
|
|
title: 'Subsection',
|
|
elements
|
|
}
|
|
|
|
const section: FormElementSectionSnapshotDto = {
|
|
title: sectionTitle,
|
|
subsections: [subSection]
|
|
}
|
|
|
|
return {
|
|
name: 'Test Form',
|
|
status: 'DRAFT',
|
|
organizationId: 'org-1',
|
|
sections: [section]
|
|
}
|
|
}
|
|
|
|
describe('formSnapshotComparison', () => {
|
|
describe('compareApplicationFormValues', () => {
|
|
describe('TEXTFIELD element', () => {
|
|
it('should detect new answer (empty → filled)', () => {
|
|
const current = createForm([
|
|
createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.elementTitle).toBe('Name')
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('John Doe')
|
|
expect(diff.newAnswers[0]?.previousLabel).toBeNull()
|
|
expect(diff.changedAnswers).toHaveLength(0)
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
|
|
it('should detect changed answer', () => {
|
|
const current = createForm([
|
|
createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('Jane Doe', '')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('John Doe')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('Jane Doe')
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
|
|
it('should detect cleared answer (filled → empty)', () => {
|
|
const current = createForm([createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')])])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('John Doe')
|
|
expect(diff.clearedAnswers[0]?.currentLabel).toBeNull()
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
expect(diff.changedAnswers).toHaveLength(0)
|
|
})
|
|
|
|
it('should ignore unchanged values', () => {
|
|
const current = createForm([
|
|
createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
expect(diff.changedAnswers).toHaveLength(0)
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('TEXTAREA element', () => {
|
|
it('should detect new answer', () => {
|
|
const current = createForm([
|
|
createFormElement('textarea_1', 'Description', 'TEXTAREA', [
|
|
createOption('This is a long description text.', '')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textarea_1', 'Description', 'TEXTAREA', [createOption('', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('This is a long description text.')
|
|
})
|
|
|
|
it('should detect changed answer', () => {
|
|
const current = createForm([
|
|
createFormElement('textarea_1', 'Description', 'TEXTAREA', [createOption('Updated text', '')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textarea_1', 'Description', 'TEXTAREA', [createOption('Original text', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('Original text')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('Updated text')
|
|
})
|
|
|
|
it('should detect cleared answer', () => {
|
|
const current = createForm([createFormElement('textarea_1', 'Description', 'TEXTAREA', [createOption('', '')])])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textarea_1', 'Description', 'TEXTAREA', [createOption('Some text', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Some text')
|
|
})
|
|
})
|
|
|
|
describe('RICH_TEXT element', () => {
|
|
it('should detect new answer', () => {
|
|
const current = createForm([
|
|
createFormElement('richtext_1', 'Notes', 'RICH_TEXT', [
|
|
createOption('<p>Rich <strong>text</strong> content</p>', '')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('richtext_1', 'Notes', 'RICH_TEXT', [createOption('', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('<p>Rich <strong>text</strong> content</p>')
|
|
})
|
|
|
|
it('should detect changed answer', () => {
|
|
const current = createForm([
|
|
createFormElement('richtext_1', 'Notes', 'RICH_TEXT', [createOption('<p>Updated</p>', '')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('richtext_1', 'Notes', 'RICH_TEXT', [createOption('<p>Original</p>', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('<p>Original</p>')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('<p>Updated</p>')
|
|
})
|
|
|
|
it('should detect cleared answer', () => {
|
|
const current = createForm([createFormElement('richtext_1', 'Notes', 'RICH_TEXT', [createOption('', '')])])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('richtext_1', 'Notes', 'RICH_TEXT', [createOption('<p>Content</p>', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('DATE element', () => {
|
|
it('should detect new answer', () => {
|
|
const current = createForm([
|
|
createFormElement('date_1', 'Start Date', 'DATE', [createOption('2024-01-15', '')])
|
|
])
|
|
const version = createSnapshot([createSnapshotElement('date_1', 'Start Date', 'DATE', [createOption('', '')])])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('2024-01-15')
|
|
})
|
|
|
|
it('should detect changed answer', () => {
|
|
const current = createForm([
|
|
createFormElement('date_1', 'Start Date', 'DATE', [createOption('2024-02-20', '')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('date_1', 'Start Date', 'DATE', [createOption('2024-01-15', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('2024-01-15')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('2024-02-20')
|
|
})
|
|
|
|
it('should detect cleared answer', () => {
|
|
const current = createForm([createFormElement('date_1', 'Start Date', 'DATE', [createOption('', '')])])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('date_1', 'Start Date', 'DATE', [createOption('2024-01-15', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('SELECT element', () => {
|
|
it('should detect new answer (nothing selected → option selected)', () => {
|
|
const current = createForm([
|
|
createFormElement('select_1', 'Priority', 'SELECT', [
|
|
createOption('false', 'Low'),
|
|
createOption('true', 'Medium'),
|
|
createOption('false', 'High')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('select_1', 'Priority', 'SELECT', [
|
|
createOption('false', 'Low'),
|
|
createOption('false', 'Medium'),
|
|
createOption('false', 'High')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('Medium')
|
|
expect(diff.newAnswers[0]?.previousLabel).toBeNull()
|
|
})
|
|
|
|
it('should detect changed answer (different option selected)', () => {
|
|
const current = createForm([
|
|
createFormElement('select_1', 'Priority', 'SELECT', [
|
|
createOption('false', 'Low'),
|
|
createOption('false', 'Medium'),
|
|
createOption('true', 'High')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('select_1', 'Priority', 'SELECT', [
|
|
createOption('true', 'Low'),
|
|
createOption('false', 'Medium'),
|
|
createOption('false', 'High')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('Low')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('High')
|
|
})
|
|
|
|
it('should detect cleared answer (option selected → nothing selected)', () => {
|
|
const current = createForm([
|
|
createFormElement('select_1', 'Priority', 'SELECT', [
|
|
createOption('false', 'Low'),
|
|
createOption('false', 'Medium'),
|
|
createOption('false', 'High')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('select_1', 'Priority', 'SELECT', [
|
|
createOption('false', 'Low'),
|
|
createOption('true', 'Medium'),
|
|
createOption('false', 'High')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Medium')
|
|
expect(diff.clearedAnswers[0]?.currentLabel).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('RADIOBUTTON element', () => {
|
|
it('should detect new answer', () => {
|
|
const current = createForm([
|
|
createFormElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
|
createOption('true', 'Male'),
|
|
createOption('false', 'Female'),
|
|
createOption('false', 'Other')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
|
createOption('false', 'Male'),
|
|
createOption('false', 'Female'),
|
|
createOption('false', 'Other')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('Male')
|
|
})
|
|
|
|
it('should detect changed answer', () => {
|
|
const current = createForm([
|
|
createFormElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
|
createOption('false', 'Male'),
|
|
createOption('true', 'Female'),
|
|
createOption('false', 'Other')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
|
createOption('true', 'Male'),
|
|
createOption('false', 'Female'),
|
|
createOption('false', 'Other')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('Male')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('Female')
|
|
})
|
|
|
|
it('should detect cleared answer', () => {
|
|
const current = createForm([
|
|
createFormElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
|
createOption('false', 'Male'),
|
|
createOption('false', 'Female'),
|
|
createOption('false', 'Other')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('radio_1', 'Gender', 'RADIOBUTTON', [
|
|
createOption('false', 'Male'),
|
|
createOption('false', 'Female'),
|
|
createOption('true', 'Other')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Other')
|
|
})
|
|
})
|
|
|
|
describe('CHECKBOX element', () => {
|
|
it('should detect new answer (single checkbox selected)', () => {
|
|
const current = createForm([
|
|
createFormElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('true', 'Feature A'),
|
|
createOption('false', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('false', 'Feature A'),
|
|
createOption('false', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('Feature A')
|
|
})
|
|
|
|
it('should detect new answer (multiple checkboxes selected)', () => {
|
|
const current = createForm([
|
|
createFormElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('true', 'Feature A'),
|
|
createOption('true', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('false', 'Feature A'),
|
|
createOption('false', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('Feature A, Feature B')
|
|
})
|
|
|
|
it('should detect changed answer (different checkboxes selected)', () => {
|
|
const current = createForm([
|
|
createFormElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('false', 'Feature A'),
|
|
createOption('true', 'Feature B'),
|
|
createOption('true', 'Feature C')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('true', 'Feature A'),
|
|
createOption('false', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.previousLabel).toBe('Feature A')
|
|
expect(diff.changedAnswers[0]?.currentLabel).toBe('Feature B, Feature C')
|
|
})
|
|
|
|
it('should detect cleared answer (all checkboxes deselected)', () => {
|
|
const current = createForm([
|
|
createFormElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('false', 'Feature A'),
|
|
createOption('false', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('checkbox_1', 'Features', 'CHECKBOX', [
|
|
createOption('true', 'Feature A'),
|
|
createOption('true', 'Feature B'),
|
|
createOption('false', 'Feature C')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Feature A, Feature B')
|
|
})
|
|
})
|
|
|
|
describe('SWITCH element', () => {
|
|
it('should detect new answer (switch turned on)', () => {
|
|
const current = createForm([
|
|
createFormElement('switch_1', 'Enable Notifications', 'SWITCH', [createOption('true', 'Enabled')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('switch_1', 'Enable Notifications', 'SWITCH', [createOption('false', 'Enabled')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('Enabled')
|
|
})
|
|
|
|
it('should detect cleared answer (switch turned off)', () => {
|
|
const current = createForm([
|
|
createFormElement('switch_1', 'Enable Notifications', 'SWITCH', [createOption('false', 'Enabled')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('switch_1', 'Enable Notifications', 'SWITCH', [createOption('true', 'Enabled')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('Enabled')
|
|
})
|
|
})
|
|
|
|
describe('TABLE element', () => {
|
|
it('should detect new rows added', () => {
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Jane"]', 'Name'),
|
|
createOption('["Developer", "Designer"]', 'Role')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('[]', 'Name'),
|
|
createOption('[]', 'Role')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('2 Zeilen')
|
|
expect(diff.newAnswers[0]?.tableDiff).toBeDefined()
|
|
expect(diff.newAnswers[0]?.tableDiff!.addedCount).toBe(2)
|
|
expect(diff.newAnswers[0]?.tableDiff!.removedCount).toBe(0)
|
|
expect(diff.newAnswers[0]?.tableDiff!.modifiedCount).toBe(0)
|
|
})
|
|
|
|
it('should detect rows removed', () => {
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Employees', 'TABLE', [createOption('[]', 'Name'), createOption('[]', 'Role')])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Jane"]', 'Name'),
|
|
createOption('["Developer", "Designer"]', 'Role')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('2 Zeilen')
|
|
expect(diff.clearedAnswers[0]?.tableDiff).toBeDefined()
|
|
expect(diff.clearedAnswers[0]?.tableDiff!.removedCount).toBe(2)
|
|
expect(diff.clearedAnswers[0]?.tableDiff!.addedCount).toBe(0)
|
|
})
|
|
|
|
it('should detect modified rows when row count changes', () => {
|
|
// Note: The diff algorithm compares by label first ("N Zeilen").
|
|
// If row count is the same, changes are detected. If row count differs, it's a change.
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Janet", "Bob"]', 'Name'),
|
|
createOption('["Developer", "Designer", "Tester"]', 'Role')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Jane"]', 'Name'),
|
|
createOption('["Developer", "Designer"]', 'Role')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff).toBeDefined()
|
|
expect(diff.changedAnswers[0]?.tableDiff!.addedCount).toBe(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.modifiedCount).toBe(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.changeType).toBe('modified')
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.previousValues['Name']).toBe('Jane')
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.currentValues['Name']).toBe('Janet')
|
|
})
|
|
|
|
it('should detect modified rows when row count is the same', () => {
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Janet"]', 'Name'),
|
|
createOption('["Developer", "Designer"]', 'Role')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Jane"]', 'Name'),
|
|
createOption('["Developer", "Designer"]', 'Role')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff).toBeDefined()
|
|
expect(diff.changedAnswers[0]?.tableDiff!.addedCount).toBe(0)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.modifiedCount).toBe(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.changeType).toBe('modified')
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.previousValues['Name']).toBe('Jane')
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[1]?.currentValues['Name']).toBe('Janet')
|
|
})
|
|
|
|
it('should detect mixed changes (added, removed, modified)', () => {
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John Updated", "New Person"]', 'Name'),
|
|
createOption('["Senior Dev", "Manager"]', 'Role')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John", "Jane", "Bob"]', 'Name'),
|
|
createOption('["Developer", "Designer", "Tester"]', 'Role')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff).toBeDefined()
|
|
// Row 0: modified (John → John Updated, Developer → Senior Dev)
|
|
// Row 1: modified (Jane → New Person, Designer → Manager)
|
|
// Row 2: removed (Bob, Tester)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.modifiedCount).toBe(2)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.removedCount).toBe(1)
|
|
})
|
|
|
|
it('should handle single row correctly', () => {
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('["John"]', 'Name'),
|
|
createOption('["Developer"]', 'Role')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Employees', 'TABLE', [
|
|
createOption('[]', 'Name'),
|
|
createOption('[]', 'Role')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('1 Zeile')
|
|
})
|
|
|
|
it('should handle boolean values in table cells', () => {
|
|
// To detect boolean cell changes, we need a row count change too.
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Settings', 'TABLE', [
|
|
createOption('["Feature A", "Feature B"]', 'Name'),
|
|
createOption('[true, false]', 'Enabled')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Settings', 'TABLE', [
|
|
createOption('["Feature A"]', 'Name'),
|
|
createOption('[false]', 'Enabled')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.previousValues['Enabled']).toBe('Nein')
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.currentValues['Enabled']).toBe('Ja')
|
|
})
|
|
|
|
it('should handle array values in table cells', () => {
|
|
const current = createForm([
|
|
createFormElement('table_1', 'Projects', 'TABLE', [
|
|
createOption('["Project A", "Project B"]', 'Name'),
|
|
createOption('[["Tag1", "Tag2"], ["Tag3"]]', 'Tags')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('table_1', 'Projects', 'TABLE', [
|
|
createOption('["Project A"]', 'Name'),
|
|
createOption('[["Tag1"]]', 'Tags')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.previousValues['Tags']).toBe('Tag1')
|
|
expect(diff.changedAnswers[0]?.tableDiff!.rows[0]?.currentValues['Tags']).toBe('Tag1, Tag2')
|
|
})
|
|
})
|
|
|
|
describe('element removed from form', () => {
|
|
it('should detect when element is removed and had a value', () => {
|
|
const current = createForm([])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.elementTitle).toBe('Name')
|
|
expect(diff.clearedAnswers[0]?.previousLabel).toBe('John Doe')
|
|
})
|
|
|
|
it('should not report removed element if it had no value', () => {
|
|
const current = createForm([])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('element added to form', () => {
|
|
it('should detect new element with value as new answer', () => {
|
|
const current = createForm([
|
|
createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')])
|
|
])
|
|
const version = createSnapshot([])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.elementTitle).toBe('Name')
|
|
expect(diff.newAnswers[0]?.currentLabel).toBe('John Doe')
|
|
})
|
|
|
|
it('should not report new element if it has no value', () => {
|
|
const current = createForm([createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')])])
|
|
const version = createSnapshot([])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('multiple elements', () => {
|
|
it('should handle multiple elements with different change types', () => {
|
|
const current = createForm([
|
|
createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John Doe', '')]),
|
|
createFormElement('textfield_2', 'Email', 'TEXTFIELD', [createOption('new@email.com', '')]),
|
|
createFormElement('select_1', 'Status', 'SELECT', [
|
|
createOption('false', 'Active'),
|
|
createOption('false', 'Inactive')
|
|
])
|
|
])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')]),
|
|
createSnapshotElement('textfield_2', 'Email', 'TEXTFIELD', [createOption('old@email.com', '')]),
|
|
createSnapshotElement('select_1', 'Status', 'SELECT', [
|
|
createOption('true', 'Active'),
|
|
createOption('false', 'Inactive')
|
|
])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(1)
|
|
expect(diff.newAnswers[0]?.elementTitle).toBe('Name')
|
|
|
|
expect(diff.changedAnswers).toHaveLength(1)
|
|
expect(diff.changedAnswers[0]?.elementTitle).toBe('Email')
|
|
|
|
expect(diff.clearedAnswers).toHaveLength(1)
|
|
expect(diff.clearedAnswers[0]?.elementTitle).toBe('Status')
|
|
})
|
|
})
|
|
|
|
describe('multiple sections', () => {
|
|
it('should track section titles correctly', () => {
|
|
const section1: FormElementSectionDto = {
|
|
id: 'section-1',
|
|
title: 'Personal Info',
|
|
formElementSubSections: [
|
|
{
|
|
id: 'sub-1',
|
|
title: 'Basic',
|
|
formElements: [createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('John', '')])]
|
|
}
|
|
]
|
|
}
|
|
|
|
const section2: FormElementSectionDto = {
|
|
id: 'section-2',
|
|
title: 'Contact Info',
|
|
formElementSubSections: [
|
|
{
|
|
id: 'sub-2',
|
|
title: 'Email',
|
|
formElements: [
|
|
createFormElement('textfield_2', 'Email', 'TEXTFIELD', [createOption('john@example.com', '')])
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
const current: ApplicationFormDto = {
|
|
id: 'form-1',
|
|
name: 'Test Form',
|
|
isTemplate: false,
|
|
formElementSections: [section1, section2]
|
|
}
|
|
|
|
const versionSection1: FormElementSectionSnapshotDto = {
|
|
title: 'Personal Info',
|
|
subsections: [
|
|
{
|
|
title: 'Basic',
|
|
elements: [createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')])]
|
|
}
|
|
]
|
|
}
|
|
|
|
const versionSection2: FormElementSectionSnapshotDto = {
|
|
title: 'Contact Info',
|
|
subsections: [
|
|
{
|
|
title: 'Email',
|
|
elements: [createSnapshotElement('textfield_2', 'Email', 'TEXTFIELD', [createOption('', '')])]
|
|
}
|
|
]
|
|
}
|
|
|
|
const version: ApplicationFormSnapshotDto = {
|
|
name: 'Test Form',
|
|
status: 'DRAFT',
|
|
organizationId: 'org-1',
|
|
sections: [versionSection1, versionSection2]
|
|
}
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(2)
|
|
expect(diff.newAnswers.find((c) => c.sectionTitle === 'Personal Info')).toBeDefined()
|
|
expect(diff.newAnswers.find((c) => c.sectionTitle === 'Contact Info')).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle empty forms', () => {
|
|
const current = createForm([])
|
|
const version = createSnapshot([])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
expect(diff.changedAnswers).toHaveLength(0)
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
|
|
it('should handle elements without reference', () => {
|
|
const elementWithoutRef: FormElementDto = {
|
|
id: 'id-1',
|
|
title: 'No Reference',
|
|
type: 'TEXTFIELD',
|
|
options: [createOption('value', '')]
|
|
}
|
|
|
|
const current = createForm([elementWithoutRef])
|
|
const version = createSnapshot([])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
// Element without reference should be ignored
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
})
|
|
|
|
it('should handle whitespace-only text values as empty', () => {
|
|
const current = createForm([createFormElement('textfield_1', 'Name', 'TEXTFIELD', [createOption(' ', '')])])
|
|
const version = createSnapshot([
|
|
createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [createOption('', '')])
|
|
])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
// Whitespace-only should be treated as empty
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
expect(diff.changedAnswers).toHaveLength(0)
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
|
|
it('should handle null/undefined options gracefully', () => {
|
|
const current = createForm([createFormElement('textfield_1', 'Name', 'TEXTFIELD', [])])
|
|
const version = createSnapshot([createSnapshotElement('textfield_1', 'Name', 'TEXTFIELD', [])])
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
|
|
expect(diff.newAnswers).toHaveLength(0)
|
|
expect(diff.changedAnswers).toHaveLength(0)
|
|
expect(diff.clearedAnswers).toHaveLength(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('groupChangesBySection', () => {
|
|
it('should group changes by section title', () => {
|
|
const current: ApplicationFormDto = {
|
|
id: 'form-1',
|
|
name: 'Test Form',
|
|
isTemplate: false,
|
|
formElementSections: [
|
|
{
|
|
id: 'section-1',
|
|
title: 'Section A',
|
|
formElementSubSections: [
|
|
{
|
|
id: 'sub-1',
|
|
title: 'Sub',
|
|
formElements: [
|
|
createFormElement('text_1', 'Field 1', 'TEXTFIELD', [createOption('Value 1', '')]),
|
|
createFormElement('text_2', 'Field 2', 'TEXTFIELD', [createOption('Value 2', '')])
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'section-2',
|
|
title: 'Section B',
|
|
formElementSubSections: [
|
|
{
|
|
id: 'sub-2',
|
|
title: 'Sub',
|
|
formElements: [createFormElement('text_3', 'Field 3', 'TEXTFIELD', [createOption('Value 3', '')])]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
const version: ApplicationFormSnapshotDto = {
|
|
name: 'Test Form',
|
|
status: 'DRAFT',
|
|
organizationId: 'org-1',
|
|
sections: [
|
|
{
|
|
title: 'Section A',
|
|
subsections: [
|
|
{
|
|
title: 'Sub',
|
|
elements: [
|
|
createSnapshotElement('text_1', 'Field 1', 'TEXTFIELD', [createOption('', '')]),
|
|
createSnapshotElement('text_2', 'Field 2', 'TEXTFIELD', [createOption('', '')])
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
title: 'Section B',
|
|
subsections: [
|
|
{
|
|
title: 'Sub',
|
|
elements: [createSnapshotElement('text_3', 'Field 3', 'TEXTFIELD', [createOption('', '')])]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
const grouped = groupChangesBySection(diff)
|
|
|
|
expect(grouped).toHaveLength(2)
|
|
|
|
const sectionA = grouped.find((g) => g.sectionTitle === 'Section A')
|
|
expect(sectionA).toBeDefined()
|
|
expect(sectionA!.changes).toHaveLength(2)
|
|
|
|
const sectionB = grouped.find((g) => g.sectionTitle === 'Section B')
|
|
expect(sectionB).toBeDefined()
|
|
expect(sectionB!.changes).toHaveLength(1)
|
|
})
|
|
|
|
it('should return empty array when no changes', () => {
|
|
const diff = {
|
|
newAnswers: [],
|
|
changedAnswers: [],
|
|
clearedAnswers: []
|
|
}
|
|
|
|
const grouped = groupChangesBySection(diff)
|
|
|
|
expect(grouped).toHaveLength(0)
|
|
})
|
|
|
|
it('should combine all change types in the same section', () => {
|
|
const current: ApplicationFormDto = {
|
|
id: 'form-1',
|
|
name: 'Test Form',
|
|
isTemplate: false,
|
|
formElementSections: [
|
|
{
|
|
id: 'section-1',
|
|
title: 'Section A',
|
|
formElementSubSections: [
|
|
{
|
|
id: 'sub-1',
|
|
title: 'Sub',
|
|
formElements: [
|
|
createFormElement('text_1', 'New Field', 'TEXTFIELD', [createOption('New Value', '')]),
|
|
createFormElement('text_2', 'Changed Field', 'TEXTFIELD', [createOption('Updated', '')]),
|
|
createFormElement('text_3', 'Cleared Field', 'TEXTFIELD', [createOption('', '')])
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
const version: ApplicationFormSnapshotDto = {
|
|
name: 'Test Form',
|
|
status: 'DRAFT',
|
|
organizationId: 'org-1',
|
|
sections: [
|
|
{
|
|
title: 'Section A',
|
|
subsections: [
|
|
{
|
|
title: 'Sub',
|
|
elements: [
|
|
createSnapshotElement('text_1', 'New Field', 'TEXTFIELD', [createOption('', '')]),
|
|
createSnapshotElement('text_2', 'Changed Field', 'TEXTFIELD', [createOption('Original', '')]),
|
|
createSnapshotElement('text_3', 'Cleared Field', 'TEXTFIELD', [createOption('Was Here', '')])
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
const diff = compareApplicationFormValues(current, version)
|
|
const grouped = groupChangesBySection(diff)
|
|
|
|
expect(grouped).toHaveLength(1)
|
|
expect(grouped[0]?.sectionTitle).toBe('Section A')
|
|
expect(grouped[0]?.changes).toHaveLength(3)
|
|
})
|
|
})
|
|
})
|