Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return the row number on not unique value #54

Merged
merged 17 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions demo/dist/bundle.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions demo/dist/bundle.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* @license
Papa Parse
v5.3.0
https://github.com/mholt/PapaParse
License: MIT
*/
46 changes: 23 additions & 23 deletions demo/index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import CSVFileValidator from '../src/csv-file-validator'

const requiredError = (headerName, rowNumber, columnNumber) => {
return `<div class="red">${headerName} is required in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
return `<div class="red">${headerName} is required in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
}
const validateError = (headerName, rowNumber, columnNumber) => {
return `<div class="red">${headerName} is not valid in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
return `<div class="red">${headerName} is not valid in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
}
const uniqueError = (headerName) => {
return `<div class="red">${headerName} is not unique</div>`
const uniqueError = (headerName, rowNumber) => {
return `<div class="red">${headerName} is not unique at the <strong>${rowNumber} row</strong></div>`
}
const isEmailValid = function (email) {
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
return reqExp.test(email)
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
return reqExp.test(email)
}
const isPasswordValid = function (password) {
return password.length >= 4
return password.length >= 4
}

const CSVConfig = {
headers: [
{ name: 'First Name', inputName: 'firstName', required: true, requiredError },
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError },
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true }
]
headers: [
{ name: 'First Name', inputName: 'firstName', required: true, requiredError },
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError },
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true }
]
}

document.getElementById('file').onchange = function(event) {
CSVFileValidator(event.target.files[0], CSVConfig)
.then(csvData => {
csvData.inValidMessages.forEach(message => {
document.getElementById('invalidMessages').insertAdjacentHTML('beforeend', message)
})
console.log(csvData.inValidMessages)
console.log(csvData.data)
})
document.getElementById('file').onchange = function (event) {
CSVFileValidator(event.target.files[0], CSVConfig)
.then(csvData => {
csvData.inValidMessages.forEach(message => {
document.getElementById('invalidMessages').insertAdjacentHTML('beforeend', message)
})
console.log(csvData.inValidMessages)
console.log(csvData.data)
})
}
4,421 changes: 669 additions & 3,752 deletions demo/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@
"email": "v.stokol@gmail.com",
"url": "https://github.com/shystruk"
},
"license": "MIT"
"license": "MIT",
"devDependencies": {
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "csv-file-validator",
"version": "1.10.1",
"version": "1.10.2",
"description": "Validation of CSV file against user defined schema (returns back object with data and invalid messages)",
"main": "./src/csv-file-validator.js",
"repository": "https://github.com/shystruk/csv-file-validator.git",
Expand Down
26 changes: 19 additions & 7 deletions src/csv-file-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,30 @@

config.headers
.filter(function (header) {
return header.unique
return header.unique;
})
.forEach(function (header) {
if (!isValuesUnique(file.data, header.inputName)) {
file.inValidMessages.push(
_isFunction(header.uniqueError)
? header.uniqueError(header.name)
: String(header.name + ' is not unique')
);
const duplicates = [];

file.data.forEach((row, rowIndex) => {
var value = row[header.inputName];

if (duplicates.indexOf(value) >= 0) {
file.inValidMessages.push(
_isFunction(header.uniqueError)
? header.uniqueError(header.name, rowIndex + 1)
: String(
header.name + " is not unique at the " + (rowIndex + 1) + "row"
)
);
} else {
duplicates.push(value);
}
});
}
});
};
}

return CSVFileValidator;
})));
26 changes: 20 additions & 6 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const isEmailValid = (email) => {
}

const isPasswordValid = (password) => (password.length >= 4)
const uniqueError = (headerName) => (`<div class="red">${headerName} is not unique</div>`)
const uniqueError = (headerName, rowNumber) => (`<div class="red">${headerName} is not unique at the <strong>${rowNumber} row</strong></div>`)

const CSVConfig = {
headers: [
Expand Down Expand Up @@ -47,6 +47,13 @@ const CSVValidFileWithoutHeaders = [
'Vasyl;Stokolosa;fake@test.com;123123123;user;Ukraine',
].join('\n');

const CSVInvalidFileWithDuplicates = [
CSVHeader,
'Vasyl;Stokolosa;fake@test.com;123123;admin,manager',
'Vasyl;Stokolosa;fake@test.com;123123123;user;Ukraine',
'Vasyl;Stokolosa;fake@test.com;123123123;user;Ukraine',
].join('\n');

test('module should be a function', t => {
t.is(typeof CSVFileValidator, 'function');
});
Expand All @@ -59,35 +66,35 @@ test('should return an object with empty inValidMessages/data keys', async t =>
t.deepEqual(csvData.data, []);
});

test('should validate .csv file and return invalid messages with data', async t => {
test('should return invalid messages with data', async t => {
const csvData = await CSVFileValidator(CSVInvalidFile, CSVConfig);

t.is(csvData.inValidMessages.length, 3);
t.is(csvData.data.length, 2);
});

test('should validate .csv file and return data, file is valid', async t => {
test('should return data, file is valid', async t => {
const csvData = await CSVFileValidator(CSVValidFile, CSVConfig);

t.is(csvData.inValidMessages.length, 0);
t.is(csvData.data.length, 2);
});

test('should validate .csv file without headers and return data, file is valid and headers are optional', async t => {
test('file without headers, the file is valid and headers are optional', async t => {
const csvData = await CSVFileValidator(CSVValidFileWithoutHeaders, { ...CSVConfig, isHeaderNameOptional: true });

t.is(csvData.inValidMessages.length, 0);
t.is(csvData.data.length, 2);
});

test('should validate .csv file with headers and return data, file is valid and headers are optional', async t => {
test('file with headers, the file is valid and headers are optional', async t => {
const csvData = await CSVFileValidator(CSVValidFile, { ...CSVConfig, isHeaderNameOptional: true });

t.is(csvData.inValidMessages.length, 0);
t.is(csvData.data.length, 2);
});

test('should validate .csv file without headers and return invalid messages with data, file is valid and headers are missed', async t => {
test('file is valid and headers are missed', async t => {
const csvData = await CSVFileValidator(CSVValidFileWithoutHeaders, CSVConfig);

t.is(csvData.inValidMessages.length, 5);
Expand All @@ -99,3 +106,10 @@ test('should return optional column', async t => {

t.is(csvData.data[1].country, 'Ukraine');
});

test('file is valid and Email is not unique at the ... row', async t => {
const csvData = await CSVFileValidator(CSVInvalidFileWithDuplicates, CSVConfig);

t.is(csvData.inValidMessages.length, 2);
t.is(csvData.data.length, 3);
});
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1694,9 +1694,9 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2:
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==

handlebars@^4.0.3:
version "4.7.6"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
dependencies:
minimist "^1.2.5"
neo-async "^2.6.0"
Expand Down Expand Up @@ -1804,9 +1804,9 @@ home-or-tmp@^2.0.0:
os-tmpdir "^1.0.1"

hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==

http-signature@~0.10.0:
version "0.10.1"
Expand Down Expand Up @@ -2451,9 +2451,9 @@ lodash.merge@^4.6.0:
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==

lodash@^4.17.20, lodash@^4.17.4:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==

loose-envify@^1.0.0:
version "1.4.0"
Expand Down Expand Up @@ -3940,9 +3940,9 @@ tunnel-agent@~0.4.0:
integrity sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=

uglify-js@^3.1.4:
version "3.12.1"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.1.tgz#78307f539f7b9ca5557babb186ea78ad30cc0375"
integrity sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==
version "3.13.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.5.tgz#5d71d6dbba64cf441f32929b1efce7365bb4f113"
integrity sha512-xtB8yEqIkn7zmOyS2zUNBsYCBRhDkvlNxMMY2smuJ/qA8NCHeQvKCF3i9Z4k8FJH4+PJvZRtMrPynfZ75+CSZw==

uid2@0.0.3:
version "0.0.3"
Expand Down Expand Up @@ -4137,9 +4137,9 @@ xtend@^4.0.0, xtend@~4.0.1:
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==

y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
version "3.2.2"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==

yallist@^2.1.2:
version "2.1.2"
Expand Down