ok6

1.5.2 • Public • Published

ok6

Minimal JavaScript library for writing tests and assertions:

Version: v. 1.5.2

6 + 1 Functions:

  • ok ()
  • not ()
  • x ()
  • fails ()
  • type ()
  • eq ()
  • e ()

6 + 1 Type-Aliases:

  • N === Number
  • S === String
  • B === Boolean
  • O === Object
  • A === Array
  • F === Function
  • E === Error

INSTALLATION:

npm install ok6;

IMPORT / REQUIRE:

For Node.js:

const 
{ ok, not, x, type, eq, fails
, N, S, B, O, A, F, E
} = require ("ok6");

For browsers:

const ok6 = import ("./ok6_es6.js");
const 
{ ok, not, x, type, eq, fails
, N, S, B, O, A, F, E
} = ok6;

Functions:

ok (b, msg="ok() failed"):

If the 1st argument is NOT truthy throw an error whose message is the 2nd argument. Else returns the function ok() itself.


not (b, msg="not() failed"):

If the 1st argument IS truthy throw an error whose message is the 2nd argument. Else returns the function not() itself.


type (v, typeSpec, neverThrow):

If neverThrow is truthy type() behaves like a predicate. If its 1st argument 'v' is of type specified by its 2nd argument 'typeSpec' it returns itself (which is a truthy value). If not it returns false.

If neverThrow is falsy then type() either returns itself or throws an error. In other words it can be used as an assertion.

type(v, typeSpec) returns itself if one of the following holds:

a) v === null && typeSpec === null
b) typeof v      === typeSpec
c) v.constructor === typeSpec
d) v instanceof typeSpec
 
e) TYPED ARRAYS:
   v instanceof Array           &&
   typeSpec instanceof Array    &&
   typeSpec.length === 1        &&
	 ( v.slice(1,5) .reduce 
	   ( (accu, e)  => bool && type (e, typeSpec[0])
	   , type(v[0], typeSpec[0])
	   )
	 )

f) SUM TYPES:
   typeSpec instanceof Array    &&
   typeSpec.length > 1          &&
	 ( type (v, typeSpec[0])    ||
	   type (v, typeSpec[1])    ||
	   ...
	   type (v, typeSpec[typeSpec.length-1])
	 )

Can type (v) ever return true? Yes: type(v) returns itself if v was created with v = Object.create(null) because: ok (Object.create(null).constructor === undefined);


x ():

Calls type() passing its arguments to that.

If the result of calling type() with the arguments of x() is true then x() returns its 1st argument.

If the result of calling type() with the arguments of x() is false then x() throws an error.


eq (x, y, neverThrow):

If neverThrow is truthy behaves as a predicate: If first 2 arguments are recursively equal returns itself, else returns false.

If 3rd argument is NOT truthy it returns itself if equality is true, else throws an error.


fails (aFunction):

Calls argument-function without arguments. If that throws an error returns the error. If that does not throw an error, fails() throws an error.


e (msg, ErrorClass=Error)

Throws an Error with error-message 'msg' unconditionally. Just a practical shorthand for:

throw new ErrorClass ('some error message`).

Most programs are likely to have many locations where they throw errors. If you alwats retype the expression "throw new Error (...)" it means you have redundant code in many places in your program.


Type-Aliases:

When you import ok6.js you can choose to import it all or just the parts you need. If you want them all you write:

{ ok, not, x type, eq, fails
, N, S, B, O, A, F, E
} = require ("ok6");

The values of N, S, B, O, A, F are aliases for the JavaScript built-in constructors Number, String, Boolean, Object, Array, Function, Error.

If you use a lot of built-in types then importing N, S, B, O, A, F, E makes sense. They make ok6-assertions shorter:

ok (1      , N);
x  ([1,2,3], A);
... etc.

TESTS

The file ok6.js includes the function ok6_test() which runs the tests when you load the module. Here is its source:

function ok6_test ()
{ let testValue;

const
{ ok, not, x, fails, type, eq, e
, N, S, B, O, A, F, E
} = ok6 ();

// INTRO:
ok (ok);
ok (not);
not ( 1 instanceof Number);

ok    (     x (123, Number) === 123);
fails ( _=> x (123, String)        );

ok  ( eq ({x:[1, [ 2 ]]}, {x:[1, [ 2 ]]})  );
not ( eq ({x:[1, [ 2 ]]}, {x:[1, [ 7 ]]},  true)  ) ;  // 3rd arg true means work as predicate



// ------- OK: ------------------------------

ok    (1);
fails ( _=> ok  (0));

// ok() returns itself if it does not crash:
ok (ok (1)  === ok);
ok (1)
   (5 < 7)
   (23 * 46 === 1058);

testValue = -1;
 
fails     ( () =>  ok ( testValue > 0, "testValue > 0" )
          );
      
ok (fails ( () =>  ok ( testValue > 0, "testValue > 0" )
					) .message  === ("NOT ok: testValue > 0\n")
   );
		 


// -------- NOT: -----------------------

not (0);

fails ( _=> not (1));
not ([] === []);         // Shows why we need eq
not ({} === {});

// not() returns itself if it does not crash:
ok (not(0) === not);
not (1 > 2)
    (23 * 46 === 0)
		(0)
		(false)
		("");


testValue = -1;
 
fails     ( () =>  not ( testValue < 0, "testValue < 0" )
          );
      
ok (fails ( () =>  not ( testValue < 0, "testValue < 0" )
					) .message  === ("NOT not: testValue < 0\n")
   );
		
 
// ---------  TYPE: -----------------------------

let n = 3;
ok (type (n, Number) === type);
// therefore we can write multiple calls
// to it like this:
type (n  , Number)
     (n  , "number")
     ("n", String);



ok (type (null, null) );                 // null is special, supports "nullable" (sum-) types

ok (type   (123  , [String, Number] ));  // SUMTYPE
ok (type   ("abc", [String, Number] ));  // SUMTYPE

fails (()=> type  ("abc", [Number] ));
fails (()=> type  ({} , [String, Number] ));

type  (123, Number);
fails (()=> type(123, String));

ok (type  (null, [String, null] ) );

fails (()=> type  (123, [String, null]       )) ;
not   ( type  (123, [String, null], true )) ;


//  Possibly null,
ok  ( type (null, [String, null] )   );
not ( type (null, String, 1)         );

// Basics:
ok (type (123, "number") 	  === type);
ok (type (123, Number)   	  === type);
ok (type (123, String, 1)   === false);

not (type (123, undefined, 1));
not ( type (undefined, undefined, 1))


ok (type (undefined, "undefined") === type);
ok (type (null     , "object")    === type);
ok (type (null     , null)        === type);

ok ( type (undefined, undefined, 1) === false);  // Why? Because:
ok ( typeof undefined !== undefined);

ok ( type (undefined, null , 1) === false);      // Why? Because:
ok ( typeof undefined !== null);

ok ( type (null, undefined , 1    ) === false);  // Why? Because:
ok (typeof null !== undefined)

ok ( type (null, null  , 1        ) === type);

ok ( type (Object.create (null),  undefined ));
ok ( type (Object.create (null)             )); // Why? Because:
ok ( Object.create (null).constructor === undefined );


//  Typed Arrays:
ok  ( type (["abc"], [String])    );
ok  ( type ([ ],     [String])    );
not ( type ([5],     [String], 1)    );

ok  ( type ([]   , [String])         );
ok  ( type (["a"], [String])         );
ok  ( type (["a", "b"], [String])    );
not ( type (["a",   5], [String], 1)    );


// ----------- X: -------------------------

ok ( x (null, null)   === null );
ok ( x (123, Number)   === 123);
ok ( x (123, "number") === 123);

fails ( _=> x (123, String)); // why because:
not (type (123, String, 1));

fails ( _=> x (123));         // why because:
not (type(123, undefined, 1)) ;

fails ( _=> x (null));
fails ( _=> x (undefined));

//  Possibly null,
x ( null, [String, null]  );
fails  ( _=> x (null, String)       );

//  Typed Arrays:
x ( ["abc"], [String]  );
x ( [ ],     [String]  );
fails (_=>  x ([5],      [String]) );


// ---------- EQ: -------------------------------

eq (1,1)
   ([], [])
   ({}, {}) ;  // No need to repeat the 'eq'

ok  (eq (1, 1)) ;
ok  (eq ([[1]], [[1]]));
ok  (eq ({x:1}, {x:1}));
ok  (eq ({x:[1, [2]]}, {x:[1, [2]]}));

not   (     eq (1, 2,  1 ) );   //  Truthy 3rd arg measn use it as a PREDICATE.
fails ( e=> eq (1, 2     ) );   //  Falsy  3rd arg means use it as am ASSERTION.

not ( eq ([[1]], [[2]]  , "truthy" ));
not ( eq ({x:1}, {x:2}  , "truthy" ));
not ( eq ([[1]], [[2]]  , "truthy" ));
not ( eq ({x:1}, {x:[2]}, "truthy" ));

fails ( e => eq (1, 2 ) );  // 3rd arg falsy means throw error if  not recursively equal


// ------------  FAILS: --------------------------

fails (_=> ok.noSuchMethod() );
ok    ( fails (_=> "abc".noSuchMethod() )  instanceof Error );

let e2 = fails ( e => eq ({x:[1, [ 2 ]]}, {x:[1, [ 7 ]]})  ) ;
ok (e2.message === "not (eq (2, 7))" );

fails (_=>  fails ( ()=>1 ) );
fails ( () => e ('some error-message') );

x ( fails ( () => e ('some error-message') )
  , Error
  );

// ---------------- E: --------------------------

fails ( () => e ('some error-message') );

let h = 123;

// ------------ TYPE-SHORTHANDS: --------------------

type 	(1					, N)
			("s"				, S)
			(true				, B)
			({}					, O)
			([]					, A)
			(ok   			, F)
      (new Error(), E);
			
/* The type-shorthands N, S, B, O, A, F, E
are visible because on the top of this
function we said:
  
  const
   { ok, not, x, fails, type, eq
   , N, S, B, O, A, F, E
   } = ok6 ();
 */
 
 
console.log(`ok6 tests done`);
}

FAQ:

1. What does "ok6" mean?

Answer:

It means there are six (+ 1) "ok functions" in the library. The latest addition 'e()' is actually the SEVENTH function in the API. So in fact there are 6 + 1 functions. Names can be deceiving.

Note it is our firm goal to NOT add any more functions to the API.

By "ok function" we mean a function whose purpose is to tell whether some argument or result or assignment is "ok", or not.

All functions can be used as ASSERTIONS by default: They throw an error if the assertion represented by their arguments is not true. Except e() which always throws an error.

eq() and type() can also be used as PREDICATES by adding a 3rd argument that is truthy. That means that if called this way they will either return true or false, but will not throw an error just because the statement represented by their arguments is not true:

if (eq (arg, [2], "use as predicate"))
{ ...
}

if (type (arg, Number,  "use as predicate"))
{ ...
}

2. Do we need both x() and type() ?

x() and type() are called with the same arguments. Both cause an error if called with two arguments and first is not of the type specified by the second. So why not just use x() always, it is shorter to type?

Answer:

type () returns a truthy value (itself) if the assertion is true, and false otherwise if it is used as a predicate. That means you can NEGATE type() -assertions:

not (type (someValue, Number, 1))

Note above you must pass in a truthy 3rd argument, else type() will throw an error.

If x() does not crash it returns its first argument. That makes it convenient to use it as part of expressions which check the type of a value and then return it if it is of correct type.

This makes it easy to assert the type of your return value for instance:

function xExample (n)
{ let n2 = x (n, Number) + 1;
  ...
  return x (result, Number)
}

LICENSE

Copyright 2021 Class Cloud LLC SPDX-License-Identifier: Apache-2.0

RELEASE-ANNOUNCEMENTS:

https://twitter.com/ClassCloudLLC


Readme

Keywords

Package Sidebar

Install

npm i ok6

Weekly Downloads

0

Version

1.5.2

License

Apache 2.0

Unpacked Size

57.1 kB

Total Files

7

Last publish

Collaborators

  • classcloud