Skip to content

Commit

Permalink
Add tests for valdate options functions
Browse files Browse the repository at this point in the history
  • Loading branch information
AhmedElbohoty committed Sep 4, 2024
1 parent 03a63bf commit 735e4bb
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 72 deletions.
123 changes: 87 additions & 36 deletions src/lib/options/__tests__/validate-options.spec.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,6 @@
import { includes, is, validateOptions } from '../index';
import { validateOptions } from '../index';

test('Tests for "is" function', () => {
expect(is(undefined, 'string')).toBe(false);

expect(is(5, 'number')).toBe(true);
expect(is('5', 'number')).toBe(false);
expect(is(NaN, 'number')).toBe(false);

expect(is([], 'array')).toBe(true);
expect(is({}, 'array')).toBe(false);
expect(is([1, 2, 3], 'array', 'number')).toBe(true);
expect(is([1, '2', 3], 'array', 'number')).toBe(false);

expect(is(true, 'boolean')).toBe(true);
expect(is(false, 'boolean')).toBe(true);
expect(is('true', 'boolean')).toBe(false);

expect(is({}, 'object')).toBe(true);
expect(is([], 'object')).toBe(true);

expect(is('hello', 'string')).toBe(true);
expect(is(5, 'string')).toBe(false);

expect(is(() => 1, 'function')).toBe(true);
expect(is({}, 'function')).toBe(false);
});

test('Tests for "includes" function', () => {
const array = [1, 2, 3, 4, 5];

expect(includes(array, 3)).toBe(true);
expect(includes(array, 6)).toBe(false);
});

test('should validate boolean options', () => {
test('Tests to validate boolean options', () => {
const options: any = {
makeCumulative: true,
loop: false,
Expand Down Expand Up @@ -65,7 +32,7 @@ test('should validate boolean options', () => {
expect(validatedOptions.selectBars).toBe(undefined);
});

test('should validate number options', () => {
test('Tests to validate number options', () => {
const options: any = {
labelsWidth: 100,
tickDuration: 500,
Expand Down Expand Up @@ -138,3 +105,87 @@ test('Tests to validate function or string options', () => {
expect(validatedOpts.dateCounter).toBe(undefined);
expect(validatedOpts.caption).toBe(undefined);
});

test('Tests to validate dataShape option', () => {
// Valid options
const options: any = { dataShape: 'long' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.dataShape).toBe('long');

// Invalid options
options.dataShape = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.dataShape).toBeUndefined();
});

test('Tests to validate dataType option', () => {
// Valid options
const options: any = { dataType: 'json' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.dataType).toBe('json');

// Invalid options
options.dataType = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.dataType).toBeUndefined();
});

test('Tests to validate labelsPosition option', () => {
// Valid options
const options: any = { labelsPosition: 'inside' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.labelsPosition).toBe('inside');

// Invalid options
options.labelsPosition = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.labelsPosition).toBeUndefined();
});

test('Tests to validate controlButtons option', () => {
// Valid options
const options: any = { controlButtons: 'all' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.controlButtons).toBe('all');

// Invalid options
options.controlButtons = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.controlButtons).toBeUndefined();
});

test('Tests to validate overlays option', () => {
// Valid options
const options: any = { overlays: 'play' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.overlays).toBe('play');

// Invalid options
options.overlays = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.overlays).toBeUndefined();
});

test('Tests to validate fillDateGapsInterval option', () => {
// Valid options
const options: any = { fillDateGapsInterval: 'year' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.fillDateGapsInterval).toBe('year');

// Invalid options
options.fillDateGapsInterval = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.fillDateGapsInterval).toBeUndefined();
});

test('Tests to validate fillDateGapsValue option', () => {
// Valid options
const options: any = { fillDateGapsValue: 'last' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.fillDateGapsValue).toBe('last');

// Invalid options
options.fillDateGapsValue = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.fillDateGapsValue).toBeUndefined();
});
26 changes: 13 additions & 13 deletions src/lib/options/options.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ export interface OptionsAction extends Action {
payload: Partial<Options>;
}

type DataTransformType = null | ((data: Data[] | WideData[]) => Data[] | WideData[]);
type BooleanLike = boolean | 'true' | 'false';

export interface Options {
dataShape: 'long' | 'wide' | 'auto';
dataType: 'json' | 'csv' | 'tsv' | 'xml' | 'auto';
dataTransform: DataTransformType;
dataTransform: null | ((data: Data[] | WideData[]) => Data[] | WideData[]);
fillDateGapsInterval: null | 'year' | 'month' | 'day';
fillDateGapsValue: 'last' | 'interpolate';
labelsPosition: 'inside' | 'outside';
controlButtons: 'all' | 'play' | 'none';
overlays: 'all' | 'play' | 'repeat' | 'none';

makeCumulative: boolean;
loop: boolean;
showIcons: boolean;
showGroups: boolean;
mouseControls: boolean;
keyboardControls: boolean;
autorun: boolean;
injectStyles: boolean;
fixedScale: boolean;
highlightBars: boolean;
selectBars: boolean;
makeCumulative: BooleanLike;
loop: BooleanLike;
showIcons: BooleanLike;
showGroups: BooleanLike;
mouseControls: BooleanLike;
keyboardControls: BooleanLike;
autorun: BooleanLike;
injectStyles: BooleanLike;
fixedScale: BooleanLike;
highlightBars: BooleanLike;
selectBars: BooleanLike;

labelsWidth: number;
tickDuration: number;
Expand Down
38 changes: 19 additions & 19 deletions src/lib/options/validate-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ export function validateOptions(options: Partial<Options>): Partial<Options> {

// Validate boolean options
boolOpts.forEach((opt) => {
if (!is(options[opt], 'boolean')) return;
newOptions[opt] = options[opt];
if (is(options[opt], 'boolean')) {
newOptions[opt] = options[opt];
} else if (is(options[opt], 'string')) {
if (options[opt] === 'true' || options[opt] === 'false') {
newOptions[opt] = options[opt] === 'true';
}
}
});

// Validate number options
Expand Down Expand Up @@ -130,41 +135,36 @@ function validateColorMap(value: string[] | { [key: string]: string } | undefine
// Check if color map is array of string
if (is(value, 'array', 'string')) return true;

if (is(value, 'object')) {
for (const [k, v] of Object.entries(value)) {
if (!is(k, 'string')) return false;
if (!is(v, 'string')) return false;
}
}

return false;
// Check if color map is object and every value within that object is a string
return is(value, 'object') && Object.values(value).every((v) => typeof v === 'string');
}

type types = 'array' | 'boolean' | 'object' | 'number' | 'string' | 'undefined' | 'function';
export function is(value: any, type: types, arrayType?: types): boolean {
function is(value: any, type: types, arrayType?: types): boolean {
if (typeof value === 'undefined') return false;

if (type === 'number') {
return typeof value === 'number' && !Number.isNaN(Number(value));
return typeof value === 'number' && !isNaN(Number(value));
}

if (type === 'boolean') {
return typeof value === 'boolean' || value === 'true' || value === 'false';
}

if (type === 'array') {
if (!Array.isArray(value)) return false;
if (!arrayType) return true;

for (const val of value) if (!is(val, arrayType)) return false;

return true;
return value.every((val) => is(val, arrayType));
}

if (type === 'object') {
return value && typeof value === 'object';
return value !== null && typeof value === 'object';
}

return typeof value === type;
}

export function includes<T>(array: T[], value: T): boolean {
if (typeof value === 'undefined') return false;
return array.includes(value);
function includes(arr: any[], x: any) {
return x != null && arr.includes(x);
}
8 changes: 5 additions & 3 deletions src/lib/race.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ export async function race(
}
});

const validOptions = validateOptions(newOptions);

const dataOptions: Array<keyof Options> = [
'dataTransform',
'fillDateGapsInterval',
Expand All @@ -170,12 +172,12 @@ export async function race(
];
let dataOptionsChanged = false;
dataOptions.forEach((key) => {
if (newOptions[key] && newOptions[key] !== store.getState().options[key]) {
if (validOptions[key] && validOptions[key] !== store.getState().options[key]) {
dataOptionsChanged = true;
}
});

store.dispatch(actions.options.changeOptions(newOptions));
store.dispatch(actions.options.changeOptions(validOptions));
const { injectStyles, theme, autorun } = store.getState().options;

if (dataOptionsChanged) {
Expand All @@ -186,7 +188,7 @@ export async function race(
subscribeToStore(store, renderer, preparedData);
}

if ('injectStyles' in newOptions || 'theme' in newOptions) {
if ('injectStyles' in validOptions || 'theme' in validOptions) {
document.getElementById(stylesId)?.remove();
if (injectStyles) {
stylesId = styleInject(root, theme);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/worker/prepare-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function prepareData(
.then(processFixedOrder(options.fixedOrder))
.then(validateAndSort)
.then(fillDateGaps(options.fillDateGapsInterval, options.fillDateGapsValue, options.topN))
.then(calculateLastValues(options.makeCumulative));
.then(calculateLastValues(options.makeCumulative as boolean));
}

function fetchData(
Expand Down

0 comments on commit 735e4bb

Please sign in to comment.