UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

508 lines (417 loc) • 11.8 kB
import { isArrayEqualStrict } from "./collection/array/isArrayEqualStrict.js"; import { isArrayLike } from "./collection/array/isArrayLike.js"; import { InMemoryDescriptor } from "./debug/InMemoryDescriptor.js"; /** * @template T * @param {T} a * @param {T} b * @param {string} [m] */ function equal(a, b, m) { if (a !== b) { const details = `${a} !== ${b}`; const message = (m !== undefined && m !== "") ? `${m}. ${details}` : details; throw new Error(message); } } /** * @template T * @param {T} a * @param {T} b * @param {string} [m] */ function notEqual(a, b, m) { assert(a !== b, m) // eslint-disable-line eqeqeq } /** * * @param {boolean} t * @param {string} [m] */ function notOk(t, m) { assert(!t, m) } /** * * @param {object} a * @param {object} b * @param {string} [m] */ function logicalEquals(a, b, m) { assert.ok(a.equals(b), m); } /** * * @param {boolean} t * @param {string} [m] */ function assert(t, m) { if (!t) { throw new Error(m || 'AssertionError') } } /** * * @param {number} a * @param {number} b * @param {string} [m] */ function greaterThan(a, b, m) { assert.equal(typeof a, 'number'); assert.equal(typeof b, 'number'); if (!(a > b)) { let message = ''; if (m !== undefined) { message += m + '. '; } message += `Expected ${a} > ${b}.`; throw new Error(message); } } /** * * @param {number} a * @param {number} b * @param {string} [m] */ function lessThan(a, b, m) { assert.equal(typeof a, 'number'); assert.equal(typeof b, 'number'); if (!(a < b)) { let message = ''; if (m !== undefined) { message += m + '. '; } message += `Expected ${a} < ${b}.`; throw new Error(message); } } /** * * @param {number} a * @param {number} b * @param {string} [m] */ function greaterThanOrEqual(a, b, m) { assert.equal(typeof a, 'number'); assert.equal(typeof b, 'number'); if (!(a >= b)) { let message = ''; if (m !== undefined) { message += m + '. '; } message += `Expected ${a} >= ${b}.`; throw new Error(message); } } /** * * @param {number} a * @param {number} b * @param {string} [m] */ function lessThanOrEqual(a, b, m) { assert.equal(typeof a, 'number'); assert.equal(typeof b, 'number'); if (!(a <= b)) { let message = ''; if (m !== undefined) { message += m + '. '; } message += `Expected ${a} <= ${b}.`; throw new Error(message); } } /** * @template T * @param {T} value * @param {T[]} options must be string-serializable for useful error messages * @param {string} [name] optional name for a better error message */ function isOneOf(value, options,name = "value") { if(options.indexOf(value) === -1) { throw new Error(`${name} must be one of [${options.join(', ')}], instead was '${value}'`); } } const typeOfTypes = ['string', 'boolean', 'number', 'object', 'undefined', 'function', 'symbol']; /** * Perform a `value typeof === type` check and throw an error if the type does not match. * For static cases, prefer to use specific type-checking functions such as {@link assert.isString} or {@link assert.isNumber}. * @param {*} value * @param {string} type * @param {string} valueName */ function typeOf(value, type, valueName = 'value') { isOneOf(typeof value,typeOfTypes ); const typeofValueName = typeof valueName; assert.equal(typeofValueName, 'string', `valueName must be a string, instead was '${typeofValueName}'`); const typeofValue = typeof value; if (typeofValue !== type) { throw new Error(`expected ${valueName} to be ${type}, instead was '${typeofValue}'(=${value})`); } } /** * @template T * @param {T[]} haystack * @param {T} needle * @param {string} [message] */ function arrayHas(haystack, needle, message = 'Array does not contain the item') { assert.notEqual(haystack.indexOf(needle), -1, message); } /** * @template T * @param {T[]} haystack * @param {T} needle * @param {string} [message] */ function arrayHasNo(haystack, needle, message = 'Array contains the item') { assert.equal(haystack.indexOf(needle), -1, message); } /** * @template T * @param {T[]|ArrayLike<T>|Float32Array|Int32Array} a * @param {T[]|ArrayLike<T>|Float32Array|Int32Array} b * @param {string} [message] */ function arrayEqual(a, b, message = 'Arrays are not equal') { if (!isArrayEqualStrict(a, b)) { throw new Error(message); } } /** * @template T * @param {T} value * @param {Object<T>} enumerable * @param {string} [name] */ assert.enum = function (value, enumerable, name = 'value') { for (let n in enumerable) { if (enumerable[n] === value) { return; } } throw new Error(`${name}(=${value}) is not a valid enumerable value, valid values are: [${Object.values(enumerable).join(', ')}]`); }; assert.notEqual = notEqual; assert.notOk = notOk; assert.equal = equal; assert.logicalyEqual = logicalEquals; assert.ok = assert; assert.greaterThan = greaterThan; assert.greaterThanOrEqual = greaterThanOrEqual; assert.lessThan = lessThan; assert.lessThanOrEqual = lessThanOrEqual; assert.typeOf = typeOf; assert.arrayHas = arrayHas; assert.arrayHasNo = arrayHasNo; assert.arrayEqual = arrayEqual; assert.isOneOf = isOneOf; /** * * @param {*} value * @param {Class} klass * @param {string} [value_name] * @param {string} [class_name] */ assert.isInstanceOf = function ( value, klass, value_name = "value", class_name = klass.name) { assert.defined(klass, 'klass'); assert.ok(value instanceof klass, `expected ${value_name} to be instance of class '${class_name}'`); } /** * * @param {number|*} value * @param {string} [name] */ assert.isNumber = function (value, name = 'value') { const typeofValue = typeof value; if (typeofValue !== 'number') { throw new Error(`expected ${name} to be a number, instead was '${typeofValue}'(=${value})`); } }; /** * * @param {string|*} value * @param {string} [name] */ assert.isString = function (value, name = 'value') { const typeofValue = typeof value; if (typeofValue !== 'string') { throw new Error(`expected ${name} to be a string, instead was '${typeofValue}'(=${value})`); } }; /** * * @param {boolean|*} value * @param {string} [name] */ assert.isBoolean = function (value, name = 'value') { const typeofValue = typeof value; if (typeofValue !== 'boolean') { throw new Error(`expected ${name} to be a boolean, instead was '${typeofValue}'(=${value})`); } }; /** * * @param {function|*} value * @param {string} [name] */ assert.isFunction = function (value, name = 'value') { const typeofValue = typeof value; if (typeofValue !== 'function') { throw new Error(`expected ${name} to be a function, instead was '${typeofValue}'(=${value})`); } }; /** * * @param {Object|*} value * @param {string} [name] */ assert.isObject = function (value, name = 'value') { const typeofValue = typeof value; if (typeofValue !== 'object') { throw new Error(`expected ${name} to be an object, instead was '${typeofValue}'(=${value})`); } }; /** * * @param {number|*} value * @param {string} [name] */ assert.isInteger = function (value, name = 'value') { assert.isNumber(value, name); if (!Number.isInteger(value)) { throw new Error(`${name} must be an integer, instead was ${value}`); } } /** * * @param {number|*} value * @param {string} [name] */ assert.isNonNegativeInteger = function (value, name = 'value') { assert.isInteger(value, name); if (value < 0) { throw new Error(`${name} must be >= 0, instead was ${value}`); } } /** * @template T * @param {T[]} value * @param {string} name */ assert.isArray = function (value, name = 'value') { if (!Array.isArray(value)) { throw new Error(`expected ${name} to be an array, instead was something else (typeof ='${typeof value}')`); } } /** * @template T * @param {ArrayLike<T>|T[]|Uint32Array|Float32Array} value * @param {string} name */ assert.isArrayLike = function (value, name = 'value') { if (!isArrayLike(value)) { throw new Error(`expected ${name} to be an array-like structure, instead was something else (typeof ='${typeof value}')`); } } /** * Check for value !== undefined, throws exception if value is undefined * @param {*} value * @param {String} [name] */ assert.defined = function (value, name = "value") { if (value === undefined) { throw new Error(`${name} is undefined`); } }; /** * Checks for value === undefined, throws exception if value is NOT undefined * @param {*} value * @param {String} [name] */ assert.undefined = function (value, name = "value") { if (value !== undefined) { throw new Error(`${name} is not undefined`); } }; /** * * @param {*} value * @param {String} [name] */ assert.isNull = function (value, name) { if (value !== null) { throw new Error(`${name} is NOT null`); } } /** * * @param {*} value * @param {String} [name] */ assert.notNull = function (value, name = "value") { if (value === null) { throw new Error(`${name} is null`); } }; /** * * @param {number} value * @param {string} name */ assert.notNaN = function (value, name = "value") { if (Number.isNaN(value)) { throw new Error(`${name} must be a valid number, instead was NaN`); } }; /** * Is the given number finite? * Will trigger if the number is infinite, i.e. fails the `Number.isFinite` test * @param {number} value * @param {string} [name] optional name for the value being checked */ assert.isFinite = function (value, name = "value") { if (!Number.isFinite(value)) { throw new Error(`${name} must be a finite number, instead was ${value}`); } }; /** * Alias * @deprecated use {@link assert.isFinite} instead */ assert.isFiniteNumber = assert.isFinite; /** * More powerful and flexible assertion API based on matchers. * Allows complex matching criteria to be constructed. * * see {@link Matcher} * @example * assert.that(value,'value', anyOf(isNull(),isUndefined())); // will trigger iff !(value === undefined || value === null) * assert.that(value, 'value', isOneOf([3,7])); // will only trigger iff !(value === 3 || value === 7) * * @template T * @param {T} value * @param {string} name * @param {Matcher<T>} matcher */ assert.that = function (value, name, matcher) { assert.isString(name, 'name'); assert.defined(matcher, 'matcher'); if (matcher.matches(value)) { return; } // if we got here, our assertion has failed // construct failure message const mismatch_description = new InMemoryDescriptor(); mismatch_description.appendText(`Expected ${name} to be `); matcher.describeTo(mismatch_description); mismatch_description.appendText(" instead "); matcher.describeMismatch(value, mismatch_description); throw new Error(mismatch_description.value); } export { assert };