Skip to content

Commit

Permalink
Create a function to check for invalid values (#38)
Browse files Browse the repository at this point in the history
Invent a recursive function that checks against NaN input in <Table> and <Cell> props
  • Loading branch information
jogawebb authored Feb 26, 2024
1 parent a7542b4 commit a8a46c9
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 1 deletion.
7 changes: 7 additions & 0 deletions src/components/Cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type TableCellProperties,
tableCellPropertiesToNode,
} from '../properties/table-cell-properties.ts';
import { checkForForbiddenParameters, isValidNumber } from '../utilities/parameter-checking.ts';
import { createChildComponentsFromNodes, registerComponent } from '../utilities/components.ts';
import { create } from '../utilities/dom.ts';
import { QNS } from '../utilities/namespaces.ts';
Expand Down Expand Up @@ -38,6 +39,12 @@ export class Cell extends Component<CellProps, CellChild> {
];
public static readonly mixed: boolean = false;

public constructor(cellProps: CellProps, ...cellChild: CellChild[]) {
// Ensure that properties of type `number` are not `NaN`.
checkForForbiddenParameters(cellProps, isValidNumber, true);
super(cellProps, ...cellChild);
}

/**
* Creates an XML DOM node for this component instance.
*/
Expand Down
9 changes: 8 additions & 1 deletion src/components/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import './Row.ts';
import './RowAddition.ts';
import './RowDeletion.ts';
import { checkForForbiddenParameters, isValidNumber } from '../utilities/parameter-checking.ts';

import { type ComponentAncestor, Component, ComponentContext } from '../classes/Component.ts';
import {
Expand Down Expand Up @@ -47,11 +48,16 @@ export class Table extends Component<TableProps, TableChild> {
*/
public readonly model = new TableGridModel(this);

public constructor(tableProps: TableProps, ...tableChildren: TableChild[]) {
checkForForbiddenParameters(tableProps, isValidNumber, true);
super(tableProps, ...tableChildren);
}

/**
* Creates an XML DOM node for this component instance.
*/
public async toNode(ancestry: ComponentAncestor[]): Promise<Node> {
return create(
const node = create(
`
element ${QNS.w}tbl {
$tablePropertiesNode,
Expand All @@ -71,6 +77,7 @@ export class Table extends Component<TableProps, TableChild> {
children: await this.childrenToNode(ancestry),
},
);
return node;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/utilities/length.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { checkForForbiddenParameters, isValidNumber } from '../utilities/parameter-checking.ts';

/**
* An object that describes a size or length in various cross-computable units. Useful for telling the
* library how centimeters you would like one thing to be, while the one thing is defined as twentieth-
Expand Down Expand Up @@ -51,6 +53,8 @@ export type Length = {
};

function _convert(points: number): Length {
// Ensure points is not NaN
checkForForbiddenParameters(points, isValidNumber, true);
return {
pt: points,
emu: points * 12700,
Expand Down
63 changes: 63 additions & 0 deletions src/utilities/parameter-checking.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, expect, it, run } from 'https://deno.land/x/tincan@1.0.1/mod.ts';

import { checkForForbiddenParameters, isValidNumber } from './parameter-checking.ts';

describe('Checking for bad object parameters', () => {
type fakeNestedType = {
first: string,
second: number,
third: boolean
};
type fakeType = {
first: number,
second: number,
third: fakeNestedType,
fourth: number
};

const passingInnerObject: fakeNestedType = {
first: 'darkness',
second: 4,
third: false,
};

const failingInnerObject: fakeNestedType = {
first: 'darkness',
second: NaN,
third: true,
};

const passingOuterObject: fakeType = {
first: 0xA4,
second: 123,
third: passingInnerObject,
fourth: 0b111,
};

const failingOuterObject: fakeType = {
first: 1,
second: 2,
third: failingInnerObject,
fourth: 3
}

it('ensure that NaN is caught when used as a parameter of type number', () => {
const objTest = checkForForbiddenParameters(
passingOuterObject,
isValidNumber,
true,
);
expect(objTest).toBe(true);


expect(
() => checkForForbiddenParameters(
failingOuterObject,
isValidNumber,
true
)
).toThrow()
});
});

run();
61 changes: 61 additions & 0 deletions src/utilities/parameter-checking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* A function that can be used to test that all the parameters of an object pass
* a particular test. This can be used to check that no values in an object are NaN,
* or could be used to check that values are inside a particular range.
*
* @param objectToCheck An object whose parameters we want to validate.
*
* @param callback A callback function that we use to check the values of our object.
* The callback will be used recursively if objectToCheck has nested values.
*
* @param callbackFailureValue The boolean value that will indicate a failure of the callback function.
*
* @returns Returns `true` if all the object's values pass the check between the callback function and
* the value that determines a failure. Otherwise, throws an error.
*
* @todo Add a better means of validating that the values used to generate ooxml are, in fact,
* constrained in the ways we think they should be.
*/

export function checkForForbiddenParameters<ObjectToCheck>(
objectToCheck: ObjectToCheck,
callback: (object: unknown) => boolean,
callbackFailureValue: boolean,
): true {
type propObject = { prop: string, value: unknown };
const values: propObject[] = [];
// Recurse through an object and flatten it to key-value pairs.
const flattenedObject = (deepObject: unknown, accumulator: propObject[]) => {
if (typeof deepObject === 'object') {
for (const key in deepObject) {
if (typeof deepObject[key as keyof unknown] === 'object') {
flattenedObject(deepObject[key as keyof unknown], accumulator);
} else {
accumulator.push({ prop: key, value: deepObject[key as keyof unknown] });
}
}
}
return accumulator;
};

// Iterate over an object's values until we hit one that causes the callback
// function to equal the failure value.
const flattenedObjectArray = flattenedObject(objectToCheck, values);
flattenedObjectArray.forEach(entry => {
const { prop, value } = entry;
if (callback(value) === callbackFailureValue) {
throw new Error(
`Error when checking parameters.\nCallback for { ${prop}: ${value} } returned ${callback(value)}.`,
);
}
});
return true;
}

/**
* A function to check if any arbitrary property of an object is `NaN`. Intended to be used as a
* possible callback for the `checkForForbiddenParameters` function in case we want to detect `NaN`.
*/
export function isValidNumber(objectProperty: unknown): boolean {
return typeof objectProperty === 'number' && Number.isNaN(objectProperty);
}

0 comments on commit a8a46c9

Please sign in to comment.