html-table-to-dataframe
Version:
Convert HTML tables to data-frames
455 lines (454 loc) • 17.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.toHaveColumnValuesInSet = exports.toHaveTableToMatch = exports.toHaveTableToNotMatch = exports.toHaveColumnGroupToBeValues = exports.toHaveColumnGroupToBeValue = exports.toHaveColumnToBeValue = exports.toHaveTableRowCount = exports.toHaveColumnToNotMatch = exports.toHaveColumnToMatchGroupWhenFilteredBy = exports.toHaveColumnToMatchWhenFilteredBy = exports.toHaveColumnValuesToBeNumbers = exports.toHaveColumnValuesToBeInRange = exports.toHaveColumnsValuesToMatchRegex = exports.toHaveColumnValuesToMatchRegex = exports.toHaveTableRowCountEqualTo = exports.toHaveTableRowCountLessThan = exports.toHaveTableRowCountGreaterThan = void 0;
/**
* toHaveTableRowCountGreaterThan expects a tableData as processed by toDataFrame.
* The assertion checks that the total row count of the table is greater than the expected value.
*
* @example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 100 |
*
* toHaveTableRowCountGreaterThan(dt, 3) will fail because the row count is 2.
*/
const toHaveTableRowCountGreaterThan = (tableData, expectedLength) => {
if (tableData.length <= expectedLength) {
throw new Error(`Expected row count to be greater than ${expectedLength}, but it was ${tableData.length}.`);
}
};
exports.toHaveTableRowCountGreaterThan = toHaveTableRowCountGreaterThan;
/**
* toHaveTableRowCountLessThan expects a tableData as processed by toDataFrame.
* The assertion checks that the total row count of the table is less than than the expected value.
*
* @example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 100 |
*
* toHaveTableRowCountLessThan(dt, 1) will fail because the row count is 2.
*/
const toHaveTableRowCountLessThan = (tableData, expectedLength) => {
if (tableData.length >= expectedLength) {
throw new Error(`Expected row count to be less than ${expectedLength}, but it was ${tableData.length}.`);
}
};
exports.toHaveTableRowCountLessThan = toHaveTableRowCountLessThan;
/**
* toHaveTableRowCountEqualTo expects a tableData as processed by convertHTMLTable.
* The assertion checks that the total row count of the table equal to the the expected value.
*
* Example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 100 |
*
* toHaveTableRowCountEqualTo(dt, 3) will fail because the row count is 2.
*/
const toHaveTableRowCountEqualTo = (tableData, expectedLength) => {
if (tableData.length !== expectedLength) {
throw new Error(`Expected row count should ${expectedLength}, but it was ${tableData.length}.`);
}
};
exports.toHaveTableRowCountEqualTo = toHaveTableRowCountEqualTo;
/**
* toHaveColumnValuesToMatchRegex expects tableData as processed by convertHTMLTable.
* The assertion will check that the values in the specified column match the given regular expression pattern.
*
* @example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 100 |
*
* toHaveColumnValuesToMatchRegex(dt, "two", "\\d\\d") will fail because {"two":"100"} has 3 digits.
*
* "Write Once, Never Read" - Use sparingly, add expectations where RegExs can be replaced.
*/
const toHaveColumnValuesToMatchRegex = (tableData, columnHeader, regexPattern) => {
if (!tableData || tableData.length === 0) {
throw new Error('TableData cannot be empty');
}
// Create a regular expression object from the regexPattern string
const regex = new RegExp(regexPattern);
for (const row of tableData) {
const actualValue = row[columnHeader];
if (!regex.test(actualValue)) {
throw new Error(`Column header "${columnHeader}" with value "${actualValue}" does not match the pattern "${regexPattern}".`);
}
}
};
exports.toHaveColumnValuesToMatchRegex = toHaveColumnValuesToMatchRegex;
/**
* toHaveColumnsValuesToMatchRegex expects tableData as processed by convertHTMLTable.
* The assertion will check each column that the values in the specified column match the
* given regular expression pattern.
*
* @example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 100 |
*
* toHaveColumnsValuesToMatchRegex(dt, ["one", "two"], "\\d\\d") will fail because {"two":"100"} has 3 digits.
*
* "Write Once, Never Read" - Use sparingly, add expectations where RegExs can be replaced.
*/
const toHaveColumnsValuesToMatchRegex = (tableData, columnHeaders, regexPattern) => {
if (columnHeaders.length === 0) {
throw new Error('Columns cannot be empty');
}
columnHeaders.forEach((column) => {
(0, exports.toHaveColumnValuesToMatchRegex)(tableData, column, regexPattern);
});
};
exports.toHaveColumnsValuesToMatchRegex = toHaveColumnsValuesToMatchRegex;
/**
* toHaveColumnValuesToBeInRange expects tableData as processed by convertHTMLTable.
* The assertion checks that the values in the specified column fall within the given range.
*
* @example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 100 |
*
* toHaveColumnValuesToBeInRange(dt, "two", 0, 4) will fail because {"two":"100"} is greater than 4.
*/
const toHaveColumnValuesToBeInRange = (tableData, columnHeader, minValue, maxValue) => {
for (const row of tableData) {
const actualValue = parseFloat(row[columnHeader]); // Convert the value to a number
if (isNaN(actualValue) || actualValue < minValue || actualValue > maxValue) {
throw new Error(`Column header "${columnHeader}" with value "${row[columnHeader]}" is not within the range [${minValue}, ${maxValue}].`);
}
}
};
exports.toHaveColumnValuesToBeInRange = toHaveColumnValuesToBeInRange;
/**
* toHaveColumnValuesToBeNumbers expects tableData as processed by convertHTMLTable.
* The assertion will check that the values in the specified column are numbers.
*
* Example:
*
* | one | two |
* | 1 | 3 |
* | 2 | 1e |
*
* toHaveColumnValuesToBeNumbers(dt, "two") will fail because {"two":"1e"} is not a number.
*/
const toHaveColumnValuesToBeNumbers = (tableData, columnHeader) => {
for (const row of tableData) {
const parsedValue = parseFloat(row[columnHeader]);
// Check conversion, check type and check against
// original, for example 1e will be converted to 1.
if (isNaN(parsedValue) || parsedValue.toString() !== row[columnHeader]) {
throw new Error(`Column header "${columnHeader}" with value "${row[columnHeader]}" is not a number.`);
}
}
};
exports.toHaveColumnValuesToBeNumbers = toHaveColumnValuesToBeNumbers;
/**
* toHaveColumnToMatchWhenFilteredBy expects tableData as processed by convertHTMLTable().
* This assertion checks whether a target column/value pair exists when filtered by a specified column/value.
* It also does not validate the correctness of column names.
*
* @example:
* | col_1 | col_2 |
* | ------| ----- |
* | 1 | 3 |
* | 2 | 1e |
*
* toHaveColumnToMatchWhenFilteredBy(dt, "col_1", "2", "col_2", "xyz");
*
* This will fail as {"col_1":"2"} is located; however, {"col_2":"xyz"} is not found.
*/
const toHaveColumnToMatchWhenFilteredBy = (tableData, targetColumn, targetValue, filterColumn, filterValue) => {
if (targetValue === undefined || targetValue === null) {
throw new Error('Target value cannot be null or undefined!');
}
if (filterValue === undefined || filterValue === null) {
throw new Error('Filter value cannot be null or undefined!');
}
if (!tableData) {
throw new Error('Table data cannot be null or undefined!');
}
const targetCheck = tableData.filter((item) => item[filterColumn] === filterValue);
if (targetCheck.length === 0) {
throw new Error(`Column header "${filterColumn}" with value "${filterValue}" was not found! For ${JSON.stringify(tableData)}.`);
}
const filteredTarget = targetCheck.filter((item) => item[targetColumn] === targetValue);
if (filteredTarget.length === 0) {
const mappedItems = tableData.map((i) => i[targetColumn]).join(', ');
throw new Error(`Column header "${targetColumn}" with value "${targetValue}" does not match any items (${mappedItems}).`);
}
};
exports.toHaveColumnToMatchWhenFilteredBy = toHaveColumnToMatchWhenFilteredBy;
/**
* toHaveColumnToMatchGroupWhenFilteredBy uses an array of types GroupType
* to cycle through and pass the items to toHaveColumnToMatchWhenFilteredBy.
*
* @example:
*
* | col_1 | col_2 | col_3 |
* | ------| ----- | ----- |
* | 1 | a | b |
*
* const group: GroupType[] = [
* { filterColumn: "col_2", filterValue: "a" },
* { filterColumn: "col_3", filterValue: "a" },
* ];
*
* toHaveColumnToMatchGroupWhenFilteredBy(dt, "col_1", "1", group);
*
* This will fail as {"col_2": "a"} is OK; however, {"col_3": "a"} should be "b".
*/
const toHaveColumnToMatchGroupWhenFilteredBy = (tableData, targetColumn, targetValue, filterGroup) => {
filterGroup.forEach((item) => {
if (item.filterValue === undefined || item.filterValue === null) {
throw new Error('Filter value cannot be null or undefined!');
}
(0, exports.toHaveColumnToMatchWhenFilteredBy)(tableData, targetColumn, targetValue, item.filterColumn, item.filterValue);
});
};
exports.toHaveColumnToMatchGroupWhenFilteredBy = toHaveColumnToMatchGroupWhenFilteredBy;
/**
* toHaveColumnToNotMatch expects a tableData generated by convertHTMLTable().
* This expectation confirms that a column no longer contains a match. This
* expectation is designed around checking a row is no longer available. Such
* as when a record has been deleted or archived.
*
* @example:
* | col_1 | col_2 |
* | ------| ----- |
* | 1 | 3 |
* | 2 | 1e |
*
* toHaveColumnToNotMatch(dt, "col_1", "2");
*
* This will fail as {"col_1":"2"} is found and should not be.
*/
const toHaveColumnToNotMatch = (tableData, targetColumn, targetValue) => {
if (!tableData) {
throw new Error('DataTable cannot be undefined!');
}
if (!targetValue) {
throw new Error('Target Value cannot be undefined!');
}
const targetCheck = tableData.find((item) => item[targetColumn] === targetValue);
if (targetCheck !== undefined) {
throw new Error(`Column header "${targetColumn}" with value "${targetValue}" should not be found!`);
}
};
exports.toHaveColumnToNotMatch = toHaveColumnToNotMatch;
/**
* toHaveTableRowCount expects a tableData as processed by convertHTMLTable().
* The expectation will match the row count.
*
* @example:
* | col_1 | col_2 |
* | ------| ----- |
* | 1 | 3 |
* | 2 | 1e |
*
* toHaveTableRowCount(dt, 3);
*
* This will fail as row count should be 2.
*/
const toHaveTableRowCount = (tableData, expectedLength) => {
var _a;
if (tableData === null || tableData.length !== expectedLength) {
throw new Error(`Expected row count to be ${expectedLength}, but it was ${(_a = tableData === null || tableData === void 0 ? void 0 : tableData.length) !== null && _a !== void 0 ? _a : 0}`);
}
};
exports.toHaveTableRowCount = toHaveTableRowCount;
/**
* toHaveColumnToBeValue expects only 1 table row and
* the column value should match the provided value.
*
* @example:
* | col_1 | col_2 |
* | ------| ----- |
* | 1 | 3 |
*
* toHaveColumnToBeValue(dt, "col_2", "3");
*
* This will fail as the column value should be 3.
*/
const toHaveColumnToBeValue = (tableData, column, value) => {
if (tableData === null || tableData.length !== 1) {
throw new Error(`Expected row count to be 1`);
}
const firstRow = tableData[0];
if (firstRow[column] !== value) {
throw new Error(`Expected column "${column}" to have value "${value}", but it was "${firstRow[column]}"`);
}
};
exports.toHaveColumnToBeValue = toHaveColumnToBeValue;
/**
* toHaveColumnGroupToBeValue expects only 1 table row and
* the column value in a group should match the provided.
* Exceptions are made when the filterValue is null/undefined.
*
* @example:
* | col_1 | col_2 |
* | ------| ----- |
* | 1 | 3 |
*
* toHaveColumnGroupToBeValue(dt, [{ filterColumn: "col_2", filterValue: "3" }]);
*
* This will fail as the column value should be 3.
*/
const toHaveColumnGroupToBeValue = (tableData, filterGroup) => {
if (tableData === null || tableData.length !== 1) {
throw new Error(`Expected row count to be 1`);
}
filterGroup.forEach((item) => {
var _a;
if (item.filterValue === undefined || item.filterValue === null) {
throw new Error(`Value of ${(_a = item.filterValue) !== null && _a !== void 0 ? _a : ''} for ${item.filterColumn} cannot be null or undefined`);
}
(0, exports.toHaveColumnToBeValue)(tableData, item.filterColumn, item.filterValue);
});
};
exports.toHaveColumnGroupToBeValue = toHaveColumnGroupToBeValue;
/**
* toHaveColumnGroupToBeValues is the multiple grouped check,
* using toHaveColumnGroupToBeValue for each row. Table Data
* is the same and filterGroups is an array of groups. FilterGroups
* and the TableData must be the same length.
*
* Example:
* See toHaveColumnGroupToBeValue
*/
const toHaveColumnGroupToBeValues = (tableData, filterGroups) => {
if (tableData === null || tableData.length === 0 || tableData.length !== filterGroups.length) {
throw new Error('Table data and filterGroups must be equal and not empty');
}
filterGroups.forEach((filterGroup, index) => {
(0, exports.toHaveColumnGroupToBeValue)([tableData[index]], filterGroup);
});
};
exports.toHaveColumnGroupToBeValues = toHaveColumnGroupToBeValues;
/**
* toHaveTableToNotMatch will convert the tables key/values
* into a string and compares the two for equality.
*
* @param tableData1
* @param tableData2
*
* @example:
*
* table1: | col_1 | col_2 | table2: | col_1 | col_2 |
* | ------| ----- | | ------| ----- |
* | 1 | 3 | | 1 | 3 |
*
* toHaveTableToNotMatch(table1, table2);
*
* This will fail as the two tables are the same
*/
const toHaveTableToNotMatch = (tableData1, tableData2) => {
if (!tableData1 || !tableData2) {
throw new Error('Table data cannot be null');
}
if (tableData1.length !== tableData2.length) {
throw new Error(`Tables are not valid Table1/Size: ${tableData1.length} Vs Table2/Size: ${tableData2.length}`);
}
if (tableData1.length === 0 || tableData2.length === 0) {
throw new Error('Rows cannot be empty');
}
const tableAsString = (table) => {
return table
.map((obj) => Object.entries(obj)
.map(([key, value]) => `${key}: ${value}`)
.join(''))
.join('');
};
const stringTable1 = tableAsString(tableData1);
const stringTable2 = tableAsString(tableData2);
if (stringTable1 === stringTable2) {
throw new Error('Tables are identical');
}
};
exports.toHaveTableToNotMatch = toHaveTableToNotMatch;
/**
* toHaveTableToMatch will convert the tables key/values
* into a string and compares the two for equality.
*
* @param tableData1
* @param tableData2
*
* @example:
*
* table1: | col_1 | col_2 | table2: | col_1 | col_2 |
* | ------| ----- | | ------| ----- |
* | 1 | 3 | | 1 | 4 |
*
* toHaveTableToMatch(table1, table2);
*
* This will fail as the two tables are different
*/
const toHaveTableToMatch = (tableData1, tableData2) => {
if (!tableData1 || !tableData2) {
throw new Error('Table data cannot be null');
}
if (tableData1.length !== tableData2.length) {
throw new Error(`Tables are not valid Table1/Size: ${tableData1.length} Vs Table2/Size: ${tableData2.length}`);
}
if (tableData1.length === 0 || tableData2.length === 0) {
throw new Error('Rows cannot be empty');
}
const tableAsString = (table) => {
return table
.map((obj) => Object.entries(obj)
.map(([key, value]) => `${key}: ${value}`)
.join(''))
.join('');
};
const stringTable1 = tableAsString(tableData1);
const stringTable2 = tableAsString(tableData2);
if (stringTable1 !== stringTable2) {
throw new Error('Tables are different');
}
};
exports.toHaveTableToMatch = toHaveTableToMatch;
/**
* toHaveColumnValuesInSet
*
* Asserts that all values in a specified column of table data match a set of valid values.
* Throws an error if the column header does not exist in a table row.
* Throws an error if any value in the column is not in the provided set.
*
* @param tableData - Array of rows, each represented as an object with key-value pairs.
* @param columnHeader - The column header to check. Must be a key in `tableData`.
* @param targetSet - A Set of valid values for the column.
*
* @throws Error - If the column header is not in a `tableData` row.
* @throws Error - If any value in the column is not in `targetSet`.
*
* @example
*
* | col_1 | col_2 |
* | ------| ----- |
* | 1 | 3 |
* | 2 | 1e |
*
* const set: Set<string> = new Set(["1", "2", "3"]);
* toHaveColumnValuesInSet(dataFrame, "col_2", set);
* // Throws an error because "1e" is not in the set {"1", "2", "3"}
*/
const toHaveColumnValuesInSet = (tableData, columnHeader, targetSet) => {
tableData.forEach((row) => {
if (!(columnHeader in row)) {
throw new Error(`Column header "${columnHeader}" was not found in row ${JSON.stringify(row)}`);
}
const cellValue = row[columnHeader];
if (!targetSet.has(cellValue)) {
throw new Error(`Column "${columnHeader}" has a value "${cellValue}" which is not in the expected set.`);
}
});
};
exports.toHaveColumnValuesInSet = toHaveColumnValuesInSet;