You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
13 KiB
433 lines
13 KiB
import test from 'ava'
|
|
import Chance from '../chance.js'
|
|
import _ from 'lodash'
|
|
|
|
const chance = new Chance()
|
|
|
|
// chance.capitalize()
|
|
test('capitalize() works as expected', t => {
|
|
_.times(1000, () => {
|
|
let word = chance.capitalize(chance.word())
|
|
t.true(_.isString(word))
|
|
t.true(/[A-Z]/.test(word))
|
|
})
|
|
})
|
|
|
|
// chance.n()
|
|
test('n() gives an array of n terms for the given function', t => {
|
|
let arr = chance.n(chance.email, 25, { domain: 'example.com' })
|
|
t.true(_.isArray(arr))
|
|
t.is(arr.length, 25)
|
|
arr.map((email) => {
|
|
t.true(/example\.com$/.test(email))
|
|
})
|
|
})
|
|
|
|
test('n() gives an array of 1 when n not given', t => {
|
|
let arr = chance.n(chance.email)
|
|
t.true(_.isArray(arr))
|
|
t.is(arr.length, 1)
|
|
})
|
|
|
|
test('n() throws when first argument is not a function', t => {
|
|
let testFns = [
|
|
() => chance.n(chance.character({ pool: 'abcde' }), 10),
|
|
() => chance.n('foo', 10),
|
|
() => chance.n({}, 10),
|
|
() => chance.n(null, 10),
|
|
() => chance.n(undefined, 10),
|
|
() => chance.n(21, 10),
|
|
]
|
|
testFns.map((fn) => {
|
|
t.throws(fn, 'Chance: The first argument must be a function.')
|
|
})
|
|
})
|
|
|
|
test('n() gives an empty array when n is set to 0', t => {
|
|
let arr = chance.n(chance.email, 0)
|
|
t.true(_.isArray(arr))
|
|
t.is(arr.length, 0)
|
|
})
|
|
|
|
// chance.pick()
|
|
test('pick() returns a single element when called without a count argument', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pick(arr)
|
|
t.is(picked.length, 1)
|
|
})
|
|
})
|
|
|
|
test('pick() returns a multiple elements when called with a count argument', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pick(arr, 3)
|
|
t.is(picked.length, 3)
|
|
})
|
|
})
|
|
|
|
test('pick() does not destroy the original array', t => {
|
|
let arr = ['a', 'b', 'c', 'd', 'e', 'f'];
|
|
_.times(1000, () => {
|
|
let cloned = _.clone(arr)
|
|
let picked = chance.pick(cloned, 3)
|
|
t.is(cloned.length, 6)
|
|
t.deepEqual(arr, cloned)
|
|
})
|
|
})
|
|
|
|
test('pick() throws if zero elements in array', t => {
|
|
const fn = () => chance.pick([])
|
|
t.throws(fn, 'Chance: Cannot pick() from an empty array')
|
|
})
|
|
|
|
// chance.pickone()
|
|
test('pickone() returns a single element', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickone(arr)
|
|
t.is(picked.length, 1)
|
|
t.false(_.isArray(picked))
|
|
})
|
|
})
|
|
|
|
test('pickone() throws if zero elements in array', t => {
|
|
const fn = () => chance.pickone([])
|
|
t.throws(fn, 'Chance: Cannot pickone() from an empty array')
|
|
})
|
|
|
|
// chance.pickset()
|
|
test('pickset() returns empty array when count 0', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickset(arr, 0)
|
|
t.is(picked.length, 0)
|
|
t.true(_.isArray(picked))
|
|
})
|
|
})
|
|
|
|
test('pickset() throws if zero elements in array', t => {
|
|
const fn = () => chance.pickset([])
|
|
t.throws(fn, 'Chance: Cannot pickset() from an empty array')
|
|
})
|
|
|
|
test('pickset() returns single element array if no count provided', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickset(arr)
|
|
t.is(picked.length, 1)
|
|
t.true(_.isArray(picked))
|
|
})
|
|
})
|
|
|
|
test('pickset() throws if count is not positive number', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
const fn = () => chance.pickset(arr, -1)
|
|
t.throws(fn, 'Chance: Count must be a positive number')
|
|
})
|
|
|
|
test('pickset() returns single element array when called with count of 1', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickset(arr, 1)
|
|
t.is(picked.length, 1)
|
|
t.true(_.isArray(picked))
|
|
})
|
|
})
|
|
|
|
test('pickset() returns multiple elements when called with count > 1', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickset(arr, 3)
|
|
t.is(picked.length, 3)
|
|
t.true(_.isArray(picked))
|
|
})
|
|
})
|
|
|
|
test('pickset() returns no more values than the size of the array', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickset(arr, 5)
|
|
t.is(picked.length, 4)
|
|
})
|
|
})
|
|
|
|
test('pickset() does not destroy the original array', t => {
|
|
let arr = ['a', 'b', 'c', 'd', 'e', 'f'];
|
|
_.times(1000, () => {
|
|
let cloned = _.clone(arr)
|
|
let picked = chance.pickset(cloned, 3)
|
|
t.is(cloned.length, 6)
|
|
t.deepEqual(arr, cloned)
|
|
})
|
|
})
|
|
|
|
test('pickset() returns unique values', t => {
|
|
let arr = ['a', 'b', 'c', 'd']
|
|
_.times(1000, () => {
|
|
let picked = chance.pickset(arr, 4)
|
|
t.not(picked.indexOf('a'), -1)
|
|
t.not(picked.indexOf('b'), -1)
|
|
t.not(picked.indexOf('c'), -1)
|
|
t.not(picked.indexOf('d'), -1)
|
|
})
|
|
})
|
|
|
|
// chance.shuffle()
|
|
test('shuffle() returns an array of the same size', t => {
|
|
let arr = ['a', 'b', 'c', 'd', 'e']
|
|
_.times(1000, () => {
|
|
let shuffled = chance.shuffle(_.clone(arr))
|
|
t.is(shuffled.length, 5)
|
|
t.not(shuffled.indexOf('a'), -1)
|
|
})
|
|
})
|
|
|
|
test('shuffle() returns a well shuffled array', t => {
|
|
// See http://vq.io/1lEhbim checking it isn't doing that!
|
|
let arr = ['a', 'b', 'c', 'd', 'e'];
|
|
let positions = {
|
|
a: [0, 0, 0, 0, 0],
|
|
b: [0, 0, 0, 0, 0],
|
|
c: [0, 0, 0, 0, 0],
|
|
d: [0, 0, 0, 0, 0],
|
|
e: [0, 0, 0, 0, 0]
|
|
};
|
|
|
|
let shuffled = _.clone(arr)
|
|
_.times(10000, () => {
|
|
shuffled = chance.shuffle(shuffled)
|
|
shuffled.map((item, index) => {
|
|
// Accumulate the position of the item each time
|
|
positions[item][index]++
|
|
})
|
|
})
|
|
|
|
Object.keys(positions).map((index) => {
|
|
let position = positions[index]
|
|
position.map((item) => {
|
|
// This should be around 20% give or take a bit since there are
|
|
// 5 elements and they should be evenly distributed
|
|
t.true(item >= 1800)
|
|
t.true(item <= 2200)
|
|
})
|
|
})
|
|
})
|
|
|
|
test('shuffle() does not destroy original array', t => {
|
|
let arr = ['a', 'b', 'c', 'd', 'e']
|
|
_.times(1000, () => {
|
|
let cloned = _.clone(arr)
|
|
let shuffled = chance.shuffle(cloned)
|
|
t.is(shuffled.length, 5)
|
|
t.deepEqual(arr, cloned)
|
|
})
|
|
})
|
|
|
|
// chance.unique()
|
|
test('unique() gives a unique array of the selected function', t => {
|
|
_.times(500, () => {
|
|
let arr = chance.unique(chance.character, 25, { pool: "abcdefghijklmnopqrstuvwxyz" })
|
|
t.true(_.isArray(arr))
|
|
t.is(_.uniq(arr).length, 25)
|
|
})
|
|
})
|
|
|
|
test('unique() works properly with options', t => {
|
|
_.times(500, () => {
|
|
let arr = chance.unique(chance.date, 20, { year: 2016 })
|
|
t.true(_.isArray(arr))
|
|
t.is(_.uniq(arr).length, 20)
|
|
})
|
|
})
|
|
|
|
test('unique() throws when num is likely out of range', t => {
|
|
const fn = () => chance.unique(chance.character, 10, { pool: 'abcde' })
|
|
t.throws(fn, 'Chance: num is likely too large for sample set')
|
|
})
|
|
|
|
test('unique() throws when first argument is not a function', t => {
|
|
const fn = () => chance.unique(chance.character({ pool: 'abcde' }), 10)
|
|
t.throws(fn, 'Chance: The first argument must be a function.')
|
|
})
|
|
|
|
test('unique() will take a custom comparator for comparing complex objects', t => {
|
|
const comparator = (arr, val) => {
|
|
// If this is the first element, we know it doesn't exist
|
|
if (arr.length === 0) {
|
|
return false
|
|
} else {
|
|
// If a match has been found, short circuit check and just return
|
|
return arr.reduce((acc, item) => acc ? acc : item.name === val.name, false)
|
|
}
|
|
}
|
|
let arr = chance.unique(chance.currency, 25, { comparator: comparator })
|
|
t.is(_.uniq(arr).length, 25)
|
|
})
|
|
|
|
test('unique() works without a third argument', t => {
|
|
_.times(200, () => {
|
|
t.true(_.isArray(chance.unique(chance.character, 10)))
|
|
})
|
|
})
|
|
|
|
// chance.weighted()
|
|
test('weighted() returns an element', t => {
|
|
_.times(1000, () => {
|
|
let picked = chance.weighted(['a', 'b', 'c', 'd'], [1, 1, 1, 1])
|
|
t.true(_.isString(picked))
|
|
t.is(picked.length, 1)
|
|
})
|
|
})
|
|
|
|
test('weighted() works with just 2 items', t => {
|
|
// Use Math.random as the random function rather than our Mersenne twister
|
|
// just tospeed things up here because this test takes awhile to gather
|
|
// enough data to have a large enough sample size to adequately test. This
|
|
// increases speed by a few orders of magnitude at the cost of
|
|
// repeatability (which we aren't using here)
|
|
let chance = new Chance(Math.random)
|
|
var picked = { a: 0, b: 0 }
|
|
|
|
// This makes it a tad slow, but we need a large enough sample size to
|
|
// adequately test
|
|
_.times(50000, () => {
|
|
picked[chance.weighted(['a', 'b'], [1, 100])]++
|
|
})
|
|
|
|
// This range is somewhat arbitrary, but good enough to test our constraints
|
|
let ratio = picked.b / picked.a
|
|
t.true(ratio > 80)
|
|
t.true(ratio < 120)
|
|
})
|
|
|
|
test('weighted() works with trim', t => {
|
|
_.times(1000, () => {
|
|
let picked = chance.weighted(['a', 'b', 'c', 'd'], [1, 1, 1, 1], true)
|
|
t.true(_.isString(picked))
|
|
t.is(picked.length, 1)
|
|
})
|
|
})
|
|
|
|
test('weighted() throws error if called with an array of weights different from options', t => {
|
|
const fn = () => chance.weighted(['a', 'b', 'c', 'd'], [1, 2, 3])
|
|
t.throws(fn, 'Chance: Length of array and weights must match')
|
|
})
|
|
|
|
test('weighted() does not throw error if called with good weights', t => {
|
|
const fn = () => chance.weighted(['a', 'b', 'c', 'd'], [1, 2, 3, 4])
|
|
t.notThrows(fn)
|
|
})
|
|
|
|
test('weighted() throws error if weights invalid', t => {
|
|
const fn = () => chance.weighted(['a', 'b', 'c', 'd'], [0, 0, 0, 0])
|
|
t.throws(fn, 'Chance: No valid entries in array weights')
|
|
})
|
|
|
|
test('weighted() throws error if called with an array of weights different from options 2', t => {
|
|
const fn = () => chance.weighted(['a', 'b', 'c', 'd'], [1, 2, 3, 4, 5])
|
|
t.throws(fn, 'Chance: Length of array and weights must match')
|
|
})
|
|
|
|
test('weighted() throws error if weights contains NaN', t => {
|
|
const fn = () => chance.weighted(['a', 'b', 'c', 'd'], [1, NaN, 1, 1])
|
|
t.throws(fn, 'Chance: All weights must be numbers')
|
|
})
|
|
|
|
test('weighted() returns results properly weighted', t => {
|
|
// Use Math.random as the random function rather than our Mersenne twister
|
|
// just tospeed things up here because this test takes awhile to gather
|
|
// enough data to have a large enough sample size to adequately test. This
|
|
// increases speed by a few orders of magnitude at the cost of
|
|
// repeatability (which we aren't using here)
|
|
let chance = new Chance(Math.random)
|
|
let picked = { a: 0, b: 0, c: 0, d: 0 }
|
|
_.times(50000, () => {
|
|
picked[chance.weighted(['a', 'b', 'c', 'd'], [1, 100, 100, 1])]++
|
|
})
|
|
|
|
// This range is somewhat arbitrary, but good enough to test our constraints
|
|
let baRatio = picked.b / picked.a
|
|
t.true(baRatio > 60)
|
|
t.true(baRatio < 140)
|
|
|
|
let cdRatio = picked.c / picked.d
|
|
t.true(cdRatio > 60)
|
|
t.true(cdRatio < 140)
|
|
|
|
let cbRatio = (picked.c / picked.b) * 100
|
|
t.true(cbRatio > 50)
|
|
t.true(cbRatio < 150)
|
|
})
|
|
|
|
test('weighted() works with fractional weights', t => {
|
|
// Use Math.random as the random function rather than our Mersenne twister
|
|
// just tospeed things up here because this test takes awhile to gather
|
|
// enough data to have a large enough sample size to adequately test. This
|
|
// increases speed by a few orders of magnitude at the cost of
|
|
// repeatability (which we aren't using here)
|
|
let chance = new Chance(Math.random)
|
|
let picked = { a: 0, b: 0, c: 0, d: 0 }
|
|
_.times(50000, () => {
|
|
picked[chance.weighted(['a', 'b', 'c', 'd'], [0.001, 0.1, 0.1, 0.001])]++
|
|
})
|
|
|
|
// This range is somewhat arbitrary, but good enough to test our constraints
|
|
let baRatio = picked.b / picked.a
|
|
t.true(baRatio > 60)
|
|
t.true(baRatio < 140)
|
|
|
|
let cdRatio = picked.c / picked.d
|
|
t.true(cdRatio > 60)
|
|
t.true(cdRatio < 140)
|
|
|
|
let cbRatio = (picked.c / picked.b) * 100
|
|
t.true(cbRatio > 50)
|
|
t.true(cbRatio < 150)
|
|
})
|
|
|
|
test('weighted() works with weight of 0', t => {
|
|
_.times(1000, () => {
|
|
let picked = chance.weighted(['a', 'b', 'c'], [1, 0, 1])
|
|
t.true(_.isString(picked))
|
|
t.true(picked !== 'b')
|
|
})
|
|
})
|
|
|
|
test('weighted() works with negative weight', t => {
|
|
_.times(1000, () => {
|
|
let picked = chance.weighted(['a', 'b', 'c'], [1, -2, 1])
|
|
t.true(_.isString(picked))
|
|
t.true(picked !== 'b')
|
|
})
|
|
})
|
|
|
|
// chance.pad()
|
|
test('pad() always returns same number when width same as length of number', t => {
|
|
_.times(1000, () => {
|
|
let num = chance.natural({ min: 10000, max: 99999 })
|
|
let padded = chance.pad(num, 5)
|
|
t.true(_.isString(padded))
|
|
t.is(padded.length, 5)
|
|
})
|
|
})
|
|
|
|
test('pad() will pad smaller number to the right width', t => {
|
|
_.times(1000, () => {
|
|
let num = chance.natural({ max: 99999 })
|
|
let padded = chance.pad(num, 10)
|
|
t.true(_.isString(padded))
|
|
t.is(padded.length, 10)
|
|
t.not(padded.indexOf('00000'), -1)
|
|
})
|
|
})
|
|
|
|
test('pad() can take a pad e.lement', t => {
|
|
_.times(1000, () => {
|
|
let num = chance.natural({ max: 99999 })
|
|
let padded = chance.pad(num, 10, 'V')
|
|
t.true(_.isString(padded))
|
|
t.is(padded.length, 10)
|
|
t.not(padded.indexOf('VVVVV'), -1)
|
|
})
|
|
})
|
|
|