TWNTY-3316 - Add tests for modules/spreadsheet-import (#4219)

Add tests for `modules/spreadsheet-import`

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: RubensRafael <rubensrafael2@live.com>
This commit is contained in:
gitstart-app[bot] 2024-02-29 14:01:41 +01:00 committed by GitHub
parent bc11cf80fa
commit 68a8502920
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1236 additions and 0 deletions

View File

@ -0,0 +1,67 @@
import { act, renderHook } from '@testing-library/react';
import { RecoilRoot, useRecoilState } from 'recoil';
import { useSpreadsheetImport } from '@/spreadsheet-import/hooks/useSpreadsheetImport';
import { spreadsheetImportState } from '@/spreadsheet-import/states/spreadsheetImportState';
import { StepType } from '@/spreadsheet-import/steps/components/UploadFlow';
import { RawData, SpreadsheetOptions } from '@/spreadsheet-import/types';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>
);
type SpreadsheetKey = 'spreadsheet_key';
export const mockedSpreadsheetOptions: SpreadsheetOptions<SpreadsheetKey> = {
isOpen: true,
onClose: () => {},
fields: [],
uploadStepHook: async () => [],
selectHeaderStepHook: async (headerValues: RawData, data: RawData[]) => ({
headerValues,
data,
}),
matchColumnsStepHook: async () => [],
rowHook: () => ({ spreadsheet_key: 'rowHook' }),
tableHook: () => [{ spreadsheet_key: 'tableHook' }],
onSubmit: async () => {},
allowInvalidSubmit: false,
customTheme: {},
maxRecords: 10,
maxFileSize: 50,
autoMapHeaders: true,
autoMapDistance: 1,
initialStepState: {
type: StepType.upload,
},
dateFormat: 'MM/DD/YY',
parseRaw: true,
rtl: false,
selectHeader: true,
};
describe('useSpreadsheetImport', () => {
it('should set isOpen to true, and update the options in the Recoil state', async () => {
const { result } = renderHook(
() => ({
useSpreadsheetImport: useSpreadsheetImport<SpreadsheetKey>(),
spreadsheetImportState: useRecoilState(spreadsheetImportState)[0],
}),
{
wrapper: Wrapper,
},
);
expect(result.current.spreadsheetImportState).toStrictEqual({
isOpen: false,
options: null,
});
act(() => {
result.current.useSpreadsheetImport.openSpreadsheetImport(
mockedSpreadsheetOptions,
);
});
expect(result.current.spreadsheetImportState).toStrictEqual({
isOpen: true,
options: mockedSpreadsheetOptions,
});
});
});

View File

@ -0,0 +1,47 @@
import { useState } from 'react';
import { act, renderHook } from '@testing-library/react';
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
import { StepType } from '@/spreadsheet-import/steps/components/UploadFlow';
describe('useSpreadsheetImportInitialStep', () => {
it('should return correct number for each step type', async () => {
const { result } = renderHook(() => {
const [step, setStep] = useState<StepType | undefined>();
const { initialStep } = useSpreadsheetImportInitialStep(step);
return { initialStep, setStep };
});
expect(result.current.initialStep).toBe(-1);
act(() => {
result.current.setStep(StepType.upload);
});
expect(result.current.initialStep).toBe(0);
act(() => {
result.current.setStep(StepType.selectSheet);
});
expect(result.current.initialStep).toBe(0);
act(() => {
result.current.setStep(StepType.selectHeader);
});
expect(result.current.initialStep).toBe(0);
act(() => {
result.current.setStep(StepType.matchColumns);
});
expect(result.current.initialStep).toBe(2);
act(() => {
result.current.setStep(StepType.validateData);
});
expect(result.current.initialStep).toBe(3);
});
});

View File

@ -0,0 +1,19 @@
import { renderHook } from '@testing-library/react';
import { Providers } from '@/spreadsheet-import/components/Providers';
import { mockedSpreadsheetOptions } from '@/spreadsheet-import/hooks/__tests__/useSpreadsheetImport.test';
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<Providers values={mockedSpreadsheetOptions}>{children}</Providers>
);
describe('useSpreadsheetImportInternal', () => {
it('should return the value provided by provider component', async () => {
const { result } = renderHook(() => useSpreadsheetImportInternal(), {
wrapper: Wrapper,
});
expect(result.current).toBe(mockedSpreadsheetOptions);
});
});

View File

@ -0,0 +1,209 @@
import {
Data,
Field,
Info,
RowHook,
TableHook,
} from '@/spreadsheet-import/types';
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
describe('addErrorsAndRunHooks', () => {
type FullData = Data<'name' | 'age' | 'country'>;
const requiredField: Field<'name'> = {
key: 'name',
label: 'Name',
validations: [{ rule: 'required' }],
icon: null,
fieldType: { type: 'input' },
};
const regexField: Field<'age'> = {
key: 'age',
label: 'Age',
validations: [
{ rule: 'regex', value: '\\d+', errorMessage: 'Regex error' },
],
icon: null,
fieldType: { type: 'input' },
};
const uniqueField: Field<'country'> = {
key: 'country',
label: 'Country',
validations: [{ rule: 'unique' }],
icon: null,
fieldType: { type: 'input' },
};
const functionValidationFieldTrue: Field<'email'> = {
key: 'email',
label: 'Email',
validations: [
{
rule: 'function',
isValid: () => true,
errorMessage: 'Field is invalid',
},
],
icon: null,
fieldType: { type: 'input' },
};
const functionValidationFieldFalse: Field<'email'> = {
key: 'email',
label: 'Email',
validations: [
{
rule: 'function',
isValid: () => false,
errorMessage: 'Field is invalid',
},
],
icon: null,
fieldType: { type: 'input' },
};
const validData: Data<'name' | 'age'> = { name: 'John', age: '30' };
const dataWithoutNameAndInvalidAge: Data<'name' | 'age'> = {
name: '',
age: 'Invalid',
};
const dataWithDuplicatedValue: FullData = {
name: 'Alice',
age: '40',
country: 'Brazil',
};
const data: Data<'name' | 'age'>[] = [
validData,
dataWithoutNameAndInvalidAge,
];
const basicError: Info = { message: 'Field is invalid', level: 'error' };
const nameError: Info = { message: 'Name Error', level: 'error' };
const ageError: Info = { message: 'Age Error', level: 'error' };
const regexError: Info = { message: 'Regex error', level: 'error' };
const requiredError: Info = { message: 'Field is required', level: 'error' };
const duplicatedError: Info = {
message: 'Field must be unique',
level: 'error',
};
const rowHook: RowHook<'name' | 'age'> = jest.fn((row, addError) => {
addError('name', nameError);
return row;
});
const tableHook: TableHook<'name' | 'age'> = jest.fn((table, addError) => {
addError(0, 'age', ageError);
return table;
});
it('should correctly call rowHook and tableHook and add errors', () => {
const result = addErrorsAndRunHooks(
data,
[requiredField, regexField],
rowHook,
tableHook,
);
expect(rowHook).toHaveBeenCalled();
expect(tableHook).toHaveBeenCalled();
expect(result[0].__errors).toStrictEqual({
name: nameError,
age: ageError,
});
});
it('should overwrite hook errors with validation errors', () => {
const result = addErrorsAndRunHooks(
data,
[requiredField, regexField],
rowHook,
tableHook,
);
expect(rowHook).toHaveBeenCalled();
expect(tableHook).toHaveBeenCalled();
expect(result[1].__errors).toStrictEqual({
name: requiredError,
age: regexError,
});
});
it('should add errors for required field', () => {
const result = addErrorsAndRunHooks(data, [requiredField]);
expect(result[1].__errors).toStrictEqual({
name: requiredError,
});
});
it('should add errors for regex field', () => {
const result = addErrorsAndRunHooks(data, [regexField]);
expect(result[1].__errors).toStrictEqual({
age: regexError,
});
});
it('should add errors for unique field', () => {
const result = addErrorsAndRunHooks(
[
dataWithDuplicatedValue,
dataWithDuplicatedValue,
] as unknown as FullData[],
[uniqueField],
);
expect(result[0].__errors).toStrictEqual({
country: duplicatedError,
});
expect(result[1].__errors).toStrictEqual({
country: duplicatedError,
});
});
it('should add errors for unique field with empty values', () => {
const result = addErrorsAndRunHooks(
[{ country: '' }, { country: '' }],
[uniqueField],
);
expect(result[0].__errors).toStrictEqual({
country: duplicatedError,
});
expect(result[1].__errors).toStrictEqual({
country: duplicatedError,
});
});
it('should not add errors for unique field with empty values if allowEmpty is true', () => {
const result = addErrorsAndRunHooks(
[{ country: '' }, { country: '' }],
[{ ...uniqueField, validations: [{ rule: 'unique', allowEmpty: true }] }],
);
expect(result[0].__errors).toBeUndefined();
expect(result[1].__errors).toBeUndefined();
});
it('should add errors for function validation if result is false', () => {
const result = addErrorsAndRunHooks(
[{ email: 'email' }],
[functionValidationFieldFalse],
);
expect(result[0].__errors).toStrictEqual({
email: basicError,
});
});
it('should not add errors for function validation if result is true', () => {
const result = addErrorsAndRunHooks(
[{ email: 'email' }],
[functionValidationFieldTrue],
);
expect(result[0].__errors).toBeUndefined();
});
});

View File

@ -0,0 +1,45 @@
import { WorkSheet } from 'xlsx-ugnis';
import { exceedsMaxRecords } from '@/spreadsheet-import/utils/exceedsMaxRecords';
describe('exceedsMaxRecords', () => {
const maxRecords = 5;
it('should return true if the number of records exceeds the maximum limit', () => {
const workSheet: WorkSheet = {
'!ref': 'A1:A10',
};
const result = exceedsMaxRecords(workSheet, maxRecords);
expect(result).toBe(true);
});
it('should return false if the number of records does not exceed the maximum limit', () => {
const workSheet: WorkSheet = {
'!ref': 'A1:A4',
};
const result = exceedsMaxRecords(workSheet, maxRecords);
expect(result).toBe(false);
});
it('should return false if the number of records is equal to the maximum limit', () => {
const workSheet: WorkSheet = {
'!ref': 'A1:A5',
};
const result = exceedsMaxRecords(workSheet, maxRecords);
expect(result).toBe(false);
});
it('should return false if the worksheet does not have a defined range', () => {
const workSheet: WorkSheet = {};
const result = exceedsMaxRecords(workSheet, maxRecords);
expect(result).toBe(false);
});
});

View File

@ -0,0 +1,90 @@
import { Field } from '@/spreadsheet-import/types';
import { findMatch } from '@/spreadsheet-import/utils/findMatch';
describe('findMatch', () => {
const defaultField: Field<'defaultField'> = {
key: 'defaultField',
icon: null,
label: 'label',
fieldType: {
type: 'input',
},
alternateMatches: ['Full Name', 'First Name'],
};
const secondaryField: Field<'secondaryField'> = {
key: 'secondaryField',
icon: null,
label: 'label',
fieldType: {
type: 'input',
},
};
const fields = [defaultField, secondaryField];
it('should return the matching field if the header matches exactly with the key', () => {
const autoMapDistance = 0;
const result = findMatch(defaultField.key, fields, autoMapDistance);
expect(result).toBe(defaultField.key);
});
it('should return the matching field if the header matches exactly one of the alternate matches', () => {
const autoMapDistance = 0;
const result = findMatch(
defaultField.alternateMatches?.[0] ?? '',
fields,
autoMapDistance,
);
expect(result).toBe(defaultField.key);
});
it('should return the matching field if the header matches partially one of the alternate matches', () => {
const header = 'First';
const autoMapDistance = 5;
const result = findMatch(header, fields, autoMapDistance);
expect(result).toBe(defaultField.key);
});
it('should return the matching field if the header matches partially both of the alternate matches', () => {
const header = 'Name';
const autoMapDistance = 5;
const result = findMatch(header, fields, autoMapDistance);
expect(result).toBe(defaultField.key);
});
it('should return undefined if no exact match or alternate match is found within the auto map distance', () => {
const header = 'Header';
const autoMapDistance = 2;
const result = findMatch(header, fields, autoMapDistance);
expect(result).toBeUndefined();
});
it('should return the matching field with the smallest Levenshtein distance if within auto map distance', () => {
const header = 'Name'.split('').reverse().join('');
const autoMapDistance = 100;
const result = findMatch(header, fields, autoMapDistance);
expect(result).toBe(defaultField.key);
});
it('should return undefined if no match is found within auto map distance', () => {
const header = 'Name'.split('').reverse().join('');
const autoMapDistance = 1;
const result = findMatch(header, fields, autoMapDistance);
expect(result).toBeUndefined();
});
});

View File

@ -0,0 +1,89 @@
import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Field, Validation } from '@/spreadsheet-import/types';
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
const nameField: Field<'Name'> = {
key: 'Name',
label: 'Name',
icon: null,
fieldType: {
type: 'input',
},
};
const ageField: Field<'Age'> = {
key: 'Age',
label: 'Age',
icon: null,
fieldType: {
type: 'input',
},
};
const validations: Validation[] = [{ rule: 'required' }];
const nameFieldWithValidations: Field<'Name'> = { ...nameField, validations };
const ageFieldWithValidations: Field<'Age'> = { ...ageField, validations };
type ColumnValues = 'Name' | 'Age';
const nameColumn: Column<ColumnValues> = {
type: ColumnType.matched,
index: 0,
header: '',
value: 'Name',
};
const ageColumn: Column<ColumnValues> = {
type: ColumnType.matched,
index: 0,
header: '',
value: 'Age',
};
const extraColumn: Column<ColumnValues> = {
type: ColumnType.matched,
index: 0,
header: '',
value: 'Age',
};
describe('findUnmatchedRequiredFields', () => {
it('should return an empty array if all required fields are matched', () => {
const fields = [nameFieldWithValidations, ageFieldWithValidations];
const columns = [nameColumn, ageColumn];
const result = findUnmatchedRequiredFields(fields, columns);
expect(result).toStrictEqual([]);
});
it('should return an array of labels for required fields that are not matched', () => {
const fields = [nameFieldWithValidations, ageFieldWithValidations];
const columns = [nameColumn];
const result = findUnmatchedRequiredFields(fields, columns);
expect(result).toStrictEqual(['Age']);
});
it('should return an empty array if there are no required fields', () => {
const fields = [nameField, ageField];
const columns = [nameColumn];
const result = findUnmatchedRequiredFields(fields, columns);
expect(result).toStrictEqual([]);
});
it('should return an empty array if all required fields are matched even if there are extra columns', () => {
const fields = [nameFieldWithValidations, ageFieldWithValidations];
const columns = [nameColumn, ageColumn, extraColumn];
const result = findUnmatchedRequiredFields(fields, columns);
expect(result).toStrictEqual([]);
});
});

View File

@ -0,0 +1,60 @@
import { Field } from '@/spreadsheet-import/types';
import { generateExampleRow } from '@/spreadsheet-import/utils/generateExampleRow';
describe('generateExampleRow', () => {
const defaultField: Field<'defaultField'> = {
key: 'defaultField',
icon: null,
label: 'label',
fieldType: {
type: 'input',
},
};
it('should generate an example row from input field type', () => {
const fields: Field<'defaultField'>[] = [defaultField];
const result = generateExampleRow(fields);
expect(result).toStrictEqual([{ defaultField: 'Text' }]);
});
it('should generate an example row from checkbox field type', () => {
const fields: Field<'defaultField'>[] = [
{
...defaultField,
fieldType: { type: 'checkbox' },
},
];
const result = generateExampleRow(fields);
expect(result).toStrictEqual([{ defaultField: 'Boolean' }]);
});
it('should generate an example row from select field type', () => {
const fields: Field<'defaultField'>[] = [
{
...defaultField,
fieldType: { type: 'select', options: [] },
},
];
const result = generateExampleRow(fields);
expect(result).toStrictEqual([{ defaultField: 'Options' }]);
});
it('should generate an example row with provided example values for fields', () => {
const fields: Field<'defaultField'>[] = [
{
...defaultField,
example: 'Example',
},
];
const result = generateExampleRow(fields);
expect(result).toStrictEqual([{ defaultField: 'Example' }]);
});
});

View File

@ -0,0 +1,62 @@
import { Field } from '@/spreadsheet-import/types';
import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions';
describe('getFieldOptions', () => {
const optionsArray = [
{
label: 'one',
value: 'One',
},
{
label: 'two',
value: 'Two',
},
{
label: 'three',
value: 'Three',
},
];
const fields: Field<'Options' | 'Name'>[] = [
{
key: 'Options',
icon: null,
label: 'options',
fieldType: {
type: 'select',
options: optionsArray,
},
},
{
key: 'Name',
icon: null,
label: 'name',
fieldType: {
type: 'input',
},
},
];
it('should return an empty array if the field does not exist in the fields list', () => {
const fieldKey = 'NonExistingField';
const result = getFieldOptions(fields, fieldKey);
expect(result).toEqual([]);
});
it('should return an empty array if the field is not of type select', () => {
const fieldKey = 'Name';
const result = getFieldOptions(fields, fieldKey);
expect(result).toEqual([]);
});
it('should return an array of options if the field is of type select', () => {
const fieldKey = 'Options';
const result = getFieldOptions(fields, fieldKey);
expect(result).toEqual(optionsArray);
});
});

View File

@ -0,0 +1,132 @@
import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Field } from '@/spreadsheet-import/types';
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
describe('getMatchedColumns', () => {
const columns: Column<string>[] = [
{ index: 0, header: 'Name', type: ColumnType.matched, value: 'Name' },
{
index: 1,
header: 'Location',
type: ColumnType.matched,
value: 'Location',
},
{
index: 2,
header: 'Age',
type: ColumnType.matched,
value: 'Age',
},
];
const fields: Field<string>[] = [
{
key: 'Name',
label: 'Name',
fieldType: { type: 'input' },
icon: null,
},
{
key: 'Location',
label: 'Location',
fieldType: { type: 'select', options: [] },
icon: null,
},
{ key: 'Age', label: 'Age', fieldType: { type: 'input' }, icon: null },
];
const data = [
['John', 'New York'],
['Alice', 'Los Angeles'],
];
const autoMapDistance = 2;
it('should return matched columns for each field', () => {
const result = getMatchedColumns(columns, fields, data, autoMapDistance);
expect(result).toEqual([
{ index: 0, header: 'Name', type: ColumnType.matched, value: 'Name' },
{
index: 1,
header: 'Location',
type: ColumnType.matchedSelect,
value: 'Location',
matchedOptions: [
{
entry: 'New York',
},
{
entry: 'Los Angeles',
},
],
},
{ index: 2, header: 'Age', type: ColumnType.matched, value: 'Age' },
]);
});
it('should handle columns with duplicate values by choosing the closest match', () => {
const columnsWithDuplicates: Column<string>[] = [
{ index: 0, header: 'Name', type: ColumnType.matched, value: 'Name' },
{ index: 1, header: 'Name', type: ColumnType.matched, value: 'Name' },
{
index: 2,
header: 'Location',
type: ColumnType.matched,
value: 'Location',
},
];
const result = getMatchedColumns(
columnsWithDuplicates,
fields,
data,
autoMapDistance,
);
expect(result[0]).toEqual({
index: 0,
header: 'Name',
type: ColumnType.empty,
});
expect(result[1]).toEqual({
index: 1,
header: 'Name',
type: ColumnType.matched,
value: 'Name',
});
});
it('should return initial columns when no auto match is found', () => {
const unmatchedColumnsData: string[][] = [
['John', 'New York', '30'],
['Alice', 'Los Angeles', '25'],
];
const unmatchedFields: Field<string>[] = [
{
key: 'Hobby',
label: 'Hobby',
fieldType: { type: 'input' },
icon: null,
},
{
key: 'Interest',
label: 'Interest',
fieldType: { type: 'input' },
icon: null,
},
];
const result = getMatchedColumns(
columns,
unmatchedFields,
unmatchedColumnsData,
autoMapDistance,
);
expect(result).toEqual(columns);
});
});

View File

@ -0,0 +1,46 @@
import * as XLSX from 'xlsx-ugnis';
import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
describe('mapWorkbook', () => {
it('should map the workbook to a 2D array of strings', () => {
const inputWorkbook = XLSX.utils.book_new();
const inputSheetData = [
['Name', 'Age'],
['John', '30'],
['Alice', '25'],
];
const expectedOutput = inputSheetData;
const worksheet = XLSX.utils.aoa_to_sheet(inputSheetData);
XLSX.utils.book_append_sheet(inputWorkbook, worksheet, 'Sheet1');
const result = mapWorkbook(inputWorkbook);
expect(result).toEqual(expectedOutput);
});
it('should map the specified sheet of the workbook to a 2D array of strings', () => {
const inputWorkbook = XLSX.utils.book_new();
const inputSheet1Data = [
['Name', 'Age'],
['John', '30'],
['Alice', '25'],
];
const inputSheet2Data = [
['City', 'Population'],
['New York', '8500000'],
['Los Angeles', '4000000'],
];
const expectedOutput = inputSheet2Data;
const worksheet1 = XLSX.utils.aoa_to_sheet(inputSheet1Data);
const worksheet2 = XLSX.utils.aoa_to_sheet(inputSheet2Data);
XLSX.utils.book_append_sheet(inputWorkbook, worksheet1, 'Sheet1');
XLSX.utils.book_append_sheet(inputWorkbook, worksheet2, 'Sheet2');
const result = mapWorkbook(inputWorkbook, 'Sheet2');
expect(result).toEqual(expectedOutput);
});
});

View File

@ -0,0 +1,23 @@
import { normalizeCheckboxValue } from '@/spreadsheet-import/utils/normalizeCheckboxValue';
describe('normalizeCheckboxValue', () => {
const testCases = [
{ value: 'yes', expected: true },
{ value: 'Yes', expected: true },
{ value: 'no', expected: false },
{ value: 'No', expected: false },
{ value: 'true', expected: true },
{ value: 'True', expected: true },
{ value: 'false', expected: false },
{ value: 'False', expected: false },
{ value: undefined, expected: false },
{ value: 'invalid', expected: false },
];
testCases.forEach(({ value, expected }) => {
it(`should return ${expected} for value "${value}"`, () => {
const result = normalizeCheckboxValue(value);
expect(result).toBe(expected);
});
});
});

View File

@ -0,0 +1,147 @@
import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Field } from '@/spreadsheet-import/types';
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
describe('normalizeTableData', () => {
const columns: Column<string>[] = [
{ index: 0, header: 'Name', type: ColumnType.matched, value: 'name' },
{ index: 1, header: 'Age', type: ColumnType.matched, value: 'age' },
{
index: 2,
header: 'Active',
type: ColumnType.matchedCheckbox,
value: 'active',
},
];
const fields: Field<string>[] = [
{ key: 'name', label: 'Name', fieldType: { type: 'input' }, icon: null },
{ key: 'age', label: 'Age', fieldType: { type: 'input' }, icon: null },
{
key: 'active',
label: 'Active',
fieldType: {
type: 'checkbox',
},
icon: null,
},
];
const rawData = [
['John', '30', 'Yes'],
['Alice', '', 'No'],
['Bob', '25', 'Maybe'],
];
it('should normalize table data according to columns and fields', () => {
const result = normalizeTableData(columns, rawData, fields);
expect(result).toStrictEqual([
{ name: 'John', age: '30', active: true },
{ name: 'Alice', age: undefined, active: false },
{ name: 'Bob', age: '25', active: false },
]);
});
it('should normalize matchedCheckbox values and handle booleanMatches', () => {
const columns: Column<string>[] = [
{
index: 0,
header: 'Active',
type: ColumnType.matchedCheckbox,
value: 'active',
},
];
const fields: Field<string>[] = [
{
key: 'active',
label: 'Active',
fieldType: {
type: 'checkbox',
booleanMatches: { yes: true, no: false },
},
icon: null,
},
];
const rawData = [['Yes'], ['No'], ['OtherValue']];
const result = normalizeTableData(columns, rawData, fields);
expect(result).toStrictEqual([{ active: true }, { active: false }, {}]);
});
it('should map matchedSelect and matchedSelectOptions values correctly', () => {
const columns: Column<string>[] = [
{
index: 0,
header: 'Number',
type: ColumnType.matchedSelect,
value: 'number',
matchedOptions: [
{ entry: 'One', value: '1' },
{ entry: 'Two', value: '2' },
],
},
];
const fields: Field<string>[] = [
{
key: 'number',
label: 'Number',
fieldType: {
type: 'select',
options: [
{ label: 'One', value: '1' },
{ label: 'Two', value: '2' },
],
},
icon: null,
},
];
const rawData = [['One'], ['Two'], ['OtherValue']];
const result = normalizeTableData(columns, rawData, fields);
expect(result).toStrictEqual([
{ number: '1' },
{ number: '2' },
{ number: undefined },
]);
});
it('should handle empty and ignored columns', () => {
const columns: Column<string>[] = [
{ index: 0, header: 'Empty', type: ColumnType.empty },
{ index: 1, header: 'Ignored', type: ColumnType.ignored },
];
const rawData = [['Value1', 'Value2']];
const result = normalizeTableData(columns, rawData, []);
expect(result).toStrictEqual([{}]);
});
it('should handle unrecognized column types and return empty object', () => {
const columns: Column<string>[] = [
{
index: 0,
header: 'Unrecognized',
type: 'Unknown' as unknown as ColumnType.matched,
value: '',
},
];
const rawData = [['Value']];
const result = normalizeTableData(columns, rawData, []);
expect(result).toStrictEqual([{}]);
});
});

View File

@ -0,0 +1,9 @@
import { readFileAsync } from '@/spreadsheet-import/utils/readFilesAsync';
describe('readFileAsync', () => {
it('should resolve with the file content as ArrayBuffer', async () => {
const file = new File(['Test content'], 'test.txt', { type: 'text/plain' });
const result = await readFileAsync(file);
expect(result).toBeInstanceOf(ArrayBuffer);
});
});

View File

@ -0,0 +1,94 @@
import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { Field } from '@/spreadsheet-import/types';
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
describe('setColumn', () => {
const defaultField: Field<'Name'> = {
icon: null,
label: 'label',
key: 'Name',
fieldType: { type: 'input' },
};
const oldColumn: Column<'oldValue'> = {
index: 0,
header: 'Name',
type: ColumnType.matched,
value: 'oldValue',
};
it('should return a matchedSelect column if field type is "select"', () => {
const field = {
...defaultField,
fieldType: { type: 'select' },
} as Field<'Name'>;
const data = [['John'], ['Alice']];
const result = setColumn(oldColumn, field, data);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.matchedSelect,
value: 'Name',
matchedOptions: [
{
entry: 'John',
},
{
entry: 'Alice',
},
],
});
});
it('should return a matchedCheckbox column if field type is "checkbox"', () => {
const field = {
...defaultField,
fieldType: { type: 'checkbox' },
} as Field<'Name'>;
const result = setColumn(oldColumn, field);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.matchedCheckbox,
value: 'Name',
});
});
it('should return a matched column if field type is "input"', () => {
const field = {
...defaultField,
fieldType: { type: 'input' },
} as Field<'Name'>;
const result = setColumn(oldColumn, field);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.matched,
value: 'Name',
});
});
it('should return an empty column if field type is not recognized', () => {
const field = {
...defaultField,
fieldType: { type: 'unknown' },
} as unknown as Field<'Name'>;
const result = setColumn(oldColumn, field);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.empty,
});
});
});

View File

@ -0,0 +1,22 @@
import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
describe('setIgnoreColumn', () => {
it('should return a column with type "ignored"', () => {
const column: Column<'John'> = {
index: 0,
header: 'Name',
type: ColumnType.matched,
value: 'John',
};
const result = setIgnoreColumn(column);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.ignored,
});
});
});

View File

@ -0,0 +1,63 @@
import {
Column,
ColumnType,
} from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
describe('setSubColumn', () => {
it('should return a matchedSelectColumn with updated matchedOptions', () => {
const oldColumn: Column<'John' | ''> = {
index: 0,
header: 'Name',
type: ColumnType.matchedSelect,
matchedOptions: [
{ entry: 'Name1', value: 'John' },
{ entry: 'Name2', value: '' },
],
value: 'John',
};
const entry = 'Name1';
const value = 'John Doe';
const result = setSubColumn(oldColumn, entry, value);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.matchedSelect,
matchedOptions: [
{ entry: 'Name1', value: 'John Doe' },
{ entry: 'Name2', value: '' },
],
value: 'John',
});
});
it('should return a matchedSelectOptionsColumn with updated matchedOptions', () => {
const oldColumn: Column<'John' | 'Jane'> = {
index: 0,
header: 'Name',
type: ColumnType.matchedSelectOptions,
matchedOptions: [
{ entry: 'Name1', value: 'John' },
{ entry: 'Name2', value: 'Jane' },
],
value: 'John',
};
const entry = 'Name1';
const value = 'John Doe';
const result = setSubColumn(oldColumn, entry, value);
expect(result).toEqual({
index: 0,
header: 'Name',
type: ColumnType.matchedSelectOptions,
matchedOptions: [
{ entry: 'Name1', value: 'John Doe' },
{ entry: 'Name2', value: 'Jane' },
],
value: 'John',
});
});
});

View File

@ -0,0 +1,12 @@
import { uniqueEntries } from '@/spreadsheet-import/utils/uniqueEntries';
describe('uniqueEntries', () => {
it('should return unique entries from the specified column index', () => {
const data = [['John'], ['Alice'], ['John']];
const columnIndex = 0;
const result = uniqueEntries(data, columnIndex);
expect(result).toStrictEqual([{ entry: 'John' }, { entry: 'Alice' }]);
});
});