@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
508 lines (417 loc) • 11.8 kB
JavaScript
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
};