-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from chalu/add-unit-tests
Add unit tests
- Loading branch information
Showing
16 changed files
with
5,617 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = tab | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[*.yml] | ||
indent_style = space | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
name: CI | ||
on: | ||
push: | ||
branches: | ||
- "!main" | ||
- "*" | ||
pull_request: | ||
branches: | ||
- "dev" | ||
- "main" | ||
|
||
jobs: | ||
build-and-test: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Install PNPM | ||
uses: pnpm/action-setup@v3 | ||
with: | ||
version: 8 | ||
run_install: false | ||
|
||
- name: Install Node 20.x | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: "20.x" | ||
cache: pnpm | ||
|
||
- name: Install Dependencies | ||
run: pnpm install | ||
|
||
- name: Check Code Style | ||
run: pnpm lint | ||
|
||
- name: Run Build | ||
run: pnpm build | ||
|
||
- name: Run Unit Tests | ||
run: pnpm test:ci | ||
|
||
- name: Summarise Tests | ||
uses: test-summary/action@v2 | ||
with: | ||
paths: "__tests__/summary.xml" | ||
show: "fail, skip" | ||
if: always() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pnpm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"rules": { | ||
"import/extensions": "off", | ||
"unicorn/prefer-module": "off" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import {simpleFaker as Faker} from '@faker-js/faker'; | ||
|
||
type Config = { | ||
howMany?: number; | ||
}; | ||
|
||
type MaybeNumber = number | undefined | unknown; | ||
|
||
const defaultDataSize = 100; | ||
|
||
const factory = <T>(howMany: number, generator: () => T) => Faker.helpers.multiple<T>(generator, {count: howMany}); | ||
|
||
export const uuids = (arguments_: Config = {}) => { | ||
const {howMany = defaultDataSize} = arguments_; | ||
return factory(howMany, Faker.string.uuid); | ||
}; | ||
|
||
export const dates = (arguments_: Config = {}) => { | ||
const {howMany = defaultDataSize} = arguments_; | ||
return factory(howMany, Faker.date.recent); | ||
}; | ||
|
||
export const numbers = (arguments_: Config = {}) => { | ||
const {howMany = defaultDataSize} = arguments_; | ||
return factory(howMany, Faker.string.numeric).map(n => Number.parseInt(n, 10)); | ||
}; | ||
|
||
export const hexadecimals = (arguments_: Config = {}) => { | ||
const {howMany = defaultDataSize} = arguments_; | ||
return factory(howMany, Faker.string.hexadecimal); | ||
}; | ||
|
||
const mulpleOf = (multiple: number, item: MaybeNumber) => { | ||
if ( | ||
!item | ||
|| typeof item !== 'number' | ||
|| item % multiple !== 0 | ||
) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
export const isEven = (number_: MaybeNumber) => mulpleOf(2, number_); | ||
export const isMultipleOfFive = (number_: MaybeNumber) => mulpleOf(5, number_); | ||
export const isDate = (date: unknown) => date instanceof Date && !Number.isNaN(date.getTime()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import * as _ from 'jest-extended'; | ||
import {tuplesFromArray} from '../src/index'; | ||
import { | ||
uuids, dates, hexadecimals, numbers, isEven, isDate, | ||
} from './data'; | ||
|
||
type ParameterDataset<D> = {data: D[]; size: number; type?: string}; | ||
|
||
describe('nTuple Array', () => { | ||
it('should use default maxItems value', () => { | ||
const tuples = tuplesFromArray({list: numbers()}); | ||
for (const tuple of tuples) { | ||
expect(tuple).toBeArrayOfSize(2); | ||
} | ||
}); | ||
|
||
it('should use provided maxItems value', () => { | ||
const tuples = tuplesFromArray({list: numbers(), maxItems: 5}); | ||
for (const tuple of tuples) { | ||
expect(tuple).toBeArrayOfSize(5); | ||
} | ||
}); | ||
|
||
it('should work without a match function', () => { | ||
const tuples = tuplesFromArray({list: uuids(), maxItems: 10}); | ||
for (const tuple of tuples) { | ||
expect(tuple).toBeArrayOfSize(10); | ||
} | ||
}); | ||
|
||
it('should use match function when provided', () => { | ||
const nums = Array.from({length: 100}, (_, i: number) => i + 1); | ||
const tuples = tuplesFromArray({list: nums, maxItems: 5, match: isEven}); | ||
for (const tuple of tuples) { | ||
expect(tuple).toBeArrayOfSize(5); | ||
expect(tuple).toSatisfyAll(isEven); | ||
} | ||
}); | ||
|
||
it.each<ParameterDataset<string>>([ | ||
{data: uuids({howMany: 50}), size: 2}, | ||
{data: uuids({howMany: 55}), size: 5}, | ||
{data: uuids({howMany: 200}), size: 10}, | ||
])('should produce correct quantities when sub-arrays have $size items', ({data, size}) => { | ||
const tuples = tuplesFromArray({list: data, maxItems: size}); | ||
const allItemsFlattened: Array<string | undefined > = []; | ||
for (const tuple of tuples) { | ||
expect(tuple).toBeArrayOfSize(size); | ||
allItemsFlattened.push(...tuple); | ||
} | ||
|
||
expect(allItemsFlattened).toBeArrayOfSize(data.length); | ||
}); | ||
|
||
it.each<ParameterDataset<string>>([ | ||
{data: hexadecimals({howMany: 5}), size: 3}, | ||
{data: hexadecimals({howMany: 59}), size: 5}, | ||
{data: hexadecimals({howMany: 108}), size: 10}, | ||
])('should produce correct remainder quantities when sub-arrays are not all maxItems size', ({data, size}) => { | ||
const tuples = tuplesFromArray({list: data, maxItems: size}); | ||
const allItemsFlattened: Array<string | undefined > = []; | ||
const localTuples: Array<Array<string | undefined >> = []; | ||
for (const tuple of tuples) { | ||
allItemsFlattened.push(...tuple); | ||
localTuples.push(tuple); | ||
} | ||
|
||
expect(allItemsFlattened).toBeArrayOfSize(data.length); | ||
|
||
for (const tuple of localTuples) { | ||
expect(tuple).toBeArray(); | ||
expect(tuple.length).toBeLessThanOrEqual(size); | ||
expect(tuple.length).toBeGreaterThanOrEqual(data.length % size); | ||
} | ||
|
||
const hasRemainder = localTuples.pop(); | ||
expect(hasRemainder).toBeArrayOfSize(data.length % size); | ||
}); | ||
|
||
it.each<ParameterDataset<number | Date>>([ | ||
{data: dates({howMany: 50}), size: 10, type: 'date'}, | ||
{data: Array.from({length: 50}, (_, i: number) => i + 1), size: 5, type: 'number'}, | ||
])('should produce sub-arrays with the correct data types', ({data, size, type}) => { | ||
const matcher = type === 'number' ? isEven : undefined; | ||
const tuples = tuplesFromArray({list: data, maxItems: size, match: matcher}); | ||
|
||
for (const tuple of tuples) { | ||
expect(tuple).toBeArrayOfSize(size); | ||
|
||
const predicate = type === 'number' | ||
? isEven | ||
: (type === 'date' ? isDate : () => false); | ||
|
||
expect(tuple).toSatisfyAll(predicate); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import {toBeArray, toBeArrayOfSize, toSatisfyAll} from 'jest-extended'; | ||
|
||
expect.extend({toBeArray, toBeArrayOfSize, toSatisfyAll}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
const {tuplesFromArray, InvalidInvocationParameterError} = require('../dist/index.js'); | ||
|
||
describe('Smoke Tests', () => { | ||
it('should throw if input array is not specified', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
|
||
it('should throw if input array is undefined', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({list: undefined}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
|
||
it('should throw if input array is not an array', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({list: 'some iterable object'}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
|
||
it('should throw if maxItems param not a number', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({list: [], maxItems: {}}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
|
||
it('should throw if maxItems param is 0', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({list: [], maxItems: 0}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
|
||
it('should throw if maxItems param is < 0', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({list: [], maxItems: -2}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
|
||
it('should throw if the match param not a function', () => { | ||
function functionInClientCode() { | ||
tuplesFromArray({list: [], match: {}}); | ||
} | ||
|
||
expect(functionInClientCode).toThrow(InvalidInvocationParameterError); | ||
}); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
type IterationResult<T> = { | ||
done: boolean; | ||
value: Array<T | undefined>; | ||
}; | ||
type Matcher<T> = (item: T | undefined) => boolean; | ||
export type TupleConfig<T> = { | ||
list: T[]; | ||
maxItems?: number; | ||
match?: Matcher<T>; | ||
}; | ||
export declare class InvalidInvocationParameterError extends Error { | ||
} | ||
export declare const tuplesFromArray: <T>(config: TupleConfig<T>) => { | ||
[Symbol.iterator](): any; | ||
next(): IterationResult<T>; | ||
}; | ||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.tuplesFromArray = exports.InvalidInvocationParameterError = void 0; | ||
class InvalidInvocationParameterError extends Error { | ||
} | ||
exports.InvalidInvocationParameterError = InvalidInvocationParameterError; | ||
const validateParametersOrThrow = (list, maxItems, match) => { | ||
if (!list || !Array.isArray(list)) { | ||
throw new InvalidInvocationParameterError('expected list to be an array'); | ||
} | ||
if (typeof maxItems !== 'number' | ||
|| (typeof maxItems === 'number' && maxItems <= 0)) { | ||
const message = 'expected maxItems (when provided) to be a positive integer (1 and above)'; | ||
throw new InvalidInvocationParameterError(message); | ||
} | ||
if (match !== undefined && typeof match !== 'function') { | ||
const message = 'expected match (when provided) to be a function'; | ||
throw new InvalidInvocationParameterError(message); | ||
} | ||
}; | ||
const tuplesFromArray = (config) => { | ||
const { list, match, maxItems = 2 } = config; | ||
validateParametersOrThrow(list, maxItems, match); | ||
let cursor = 0; | ||
const maxItemSize = Number.parseInt(`${maxItems}`, 10); | ||
const iterable = { | ||
[Symbol.iterator]() { | ||
return this; | ||
}, | ||
next() { | ||
if (cursor >= list.length) { | ||
return { done: true, value: [] }; | ||
} | ||
const items = []; | ||
const endIndex = match === undefined | ||
// A match funtion was provided. Okay to run to array end | ||
// or until we've matched maxItemSize elements | ||
? Math.min(cursor + maxItemSize, list.length) | ||
// No match function was provided. We should run till we are | ||
// out of items (list.length) or till we gotten the next set | ||
// of maxItemSize items | ||
: list.length; | ||
while (cursor < endIndex) { | ||
const item = list[cursor]; | ||
cursor += 1; | ||
if (match && !match(item)) { | ||
continue; | ||
} | ||
items.push(item); | ||
if (match && items.length === maxItemSize) { | ||
break; | ||
} | ||
} | ||
return { done: false, value: items }; | ||
}, | ||
}; | ||
return iterable; | ||
}; | ||
exports.tuplesFromArray = tuplesFromArray; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import type {JestConfigWithTsJest} from 'ts-jest'; | ||
|
||
const jestConfig: JestConfigWithTsJest = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
testMatch: [ | ||
'/**/*.test.*', | ||
], | ||
transformIgnorePatterns: [ | ||
'/node_modules/', | ||
'\\.pnp\\.[^\\/]+$', | ||
], | ||
reporters: [ | ||
'default', | ||
['jest-junit', { | ||
outputFile: '__tests__/summary.xml', | ||
}], | ||
], | ||
setupFilesAfterEnv: ['./__tests__/setup.ts'], | ||
}; | ||
|
||
export default jestConfig; |
Oops, something went wrong.