sam-core v2.0.1
Sam-Core v1.2
sam-core is a small set of javascript (or typescript) extension methods for the built-in array
and string
types. It also contains a runTests
method which can be used in conjuction with jest to both execute tests and get a consistent description of the tests. As part of the pretty-printing of runTests
, two additional methods are exposed: trace
and getMethodBody
which are used to compute the test output.
In version 1.1.0, sam-core adds two new functions, arr
and str
which allow you to apply the new array and string methods without modifying the underlying types.
Table of Contents
Installation
yarn add -D sam-core
or
npm install -D sam-core
Release Notes
Version 1.2.12
The first
and last
array methods were rewritten so that they return undefined
instead of throwing a RangeError
, by default, when the array is empty or when no items in the array match the given predicate. This is more in keeping with how most node / javascript / typescript code is written. The second, optional parameter to both of these methods is now a boolean parameter which, if set to true
, will cause a RangeError
to be thrown if no items in the array satisfy the given predicate.
These changes also apply to the Arr
class returned from the arr
function.
See below for details.
Version 1.2.4
Added trace
and getMethodBody
methods.
Version 1.2.2
clear
method was added to array and thearr
wrapper method.satisfies
was added for the expected value of a test passed torunTests
. This lets you test that something is true after the test method completes.
Version 1.2.x
In this version, the runTests
method now allows the caller to specify that a test should throw an exception using the throws
method.
describe('Array.first', () => runTests(
[() => [].first(_, true), throws(RangeError)], // should throw range error
[() => [].first(), undefined] // should return undefined
))
Also in this release, two new methods were added to array: first
and last
. These get the first or last item in an array, if no predicate is supplied, or the first or last item in an array that matches the supplied predicate. These were also added to the arr
wrapper function.
Version 1.1.x
New to this version, sam-core now provides functions arr
and str
that give you access to the extension methods without modifying the built in class prototypes. To use them, import them from 'sam-core' and call the methods from the resulting object.
import { arr, str } from 'sam-core'
console.log(arr([0, 1, 2]).where(x => x > 0).valueOf()) // [1, 2]
console.log(str('JoeJoeBob').after('Joe').valueOf()) // 'JoeBob'
As long as you don't import 'sam-core'
directly, the built in array
and string
types will not be modified.
These methods can be chained as in
console.log(str('JoeBobSue').after('Joe').before('Sue').valueOf()) // 'Bob'
Usage
To use the extension methods in your code, simply import the module.
import 'sam-core'
To use runTests
, import it and use it conjuction with jest's describe
method:
import 'sam-core'
import { runTests } from 'sam-core'
describe('add', () => runTests(
[() => [1, 2, 3].add(5), [1, 2, 3, 5]],
[() => [0].add('Joe'), [0, 'Joe']]
))
To use the arr
or str
functions, import them and wrap your native types by calling them.
import { arr, str } from 'sam-core'
console.log(str('JoeBobSue').after('Bob').valueOf()) // 'Sue'
console.log(arr([1, 2, 3]).first()) // 1
Array Extensions
add(item: T): T[]
all(predicate: Predicate<T>): boolean
any(predicate?: Predicate<T>): boolean
clear() : T[]
first(predicate?: Predicate<T>, throwIfNotFound?: boolean): T
last(predicate?: Predicate<T>, throwIfNotFound?: boolean): T
remove(item: T | ((item: T)=>boolean)): T[]
removeAll(predicate: Predicate<T>): T[]
removeAt(index: number, count?: number): T[]
select<U>(convert: (item: T) => U): U[]
selectMany<U>(select: (item: T) => U[]): U[]
where(predicate: Predicate<T>): T[]
where Predicate
is defined as
type Predicate<T> = (item: T, index?: number) => boolean
You can import the Predicate
type with
import { Predicate } from 'sam-core'
add(item: T): T[]
Adds an item to the end of an array (in-place) and returns the result.
alias: push()
Examples
import 'sam-core'
[1, 2, 4].add(6) // [1, 2, 4, 6]
[].add('Joe') // ['Joe']
all(predicate: Predicate\): boolean
Returns true if and only if the given predicate returns true for all items in the array.
alias: every()
Examples
import 'sam-core'
[1, 2, 3].all(x => x < 4) // true
[1, 2, 3].all(x => x < 3) // false
any(predicate?: Predicate\): boolean
Returns true if any only if the given predicate returns true for at least one item in the array. If no predicate is provided, then returns true if and only if the array contains one or more items.
alias: some()
Examples
import 'sam-core'
[].any() // false
[1].any() // true
[1, 2, 3].any(x => x < 3) // true
[1, 2, 3].any(x => x > 3) // false
clear(): T[]
New in version 1.2.2
Removes all items from the array (in place) and returns the empty array.
Examples
import 'sam-core'
[].clear() // []
[1, 2, 3].clear() // []
first(predicate?: Predicate\, throwIfNotFound?: boolean): T
Changed in version 1.2.12 New in version 1.2.0
Returns the first item in the array that matches the optional predicate, if the predicate is given. If no matches are found, then either returns undefined
or throws a RangeError
exception, if the optional throwIfNotFound parameter is set to true
. If the predicate is not given, then returns the first item in the array. If the array is empty, then either returns undefined
or throws a RangeError
exception, if the optional throwIfNotFound parameter is set to true
.
Examples
import 'sam-core'
[].first() // undefined
[].first(_, true) // throws RangeError
[1, 2, 3].first(x => x > 1) // 2
[1, 2, 3].first(x => x > 3) // undefined
[1, 2, 3].first(x => x > 3, true) // throws RangeError
last(predicate?: Predicate\, defaultValue?: T): T
Changed in version 1.2.12 New in version 1.2.0
Returns the last item in the array that matches the optional predicate, if the predicate is given. If no matches are found, then either returns undefined
or throws a RangeError
exception, if the optional throwIfNotFound parameter is set to true
. If the predicate is not given, then returns the last item in the array. If the array is empty, then either returns undefined
or throws a RangeError
exception, if the optional throwIfNotFound parameter is set to true
.
Examples
import 'sam-core'
[].last() // undefined
[].last(_, true) // throws RangeError
[1, 2, 3].last(x => x > 1) // 3
[1, 2, 3].last(x => x > 3) // undefined
[1, 2, 3].last(x => x > 3, true) // throws RangeError
remove(item: T): T[]
Removes the given item from the array (in-place) and returns the result. The item is compared using reference equality. If the item is not found, the array remains unchanged.
Examples
import 'sam-core'
[].remove('Joe') // []
['Joe'].remove('Joe') // []
[1, 2, 4].remove(2) // [1, 4]
[1, 2, 4].remove(3) // [1, 2, 4]
remove(predicate: ((item: T) => boolean)): T[]
Removes the first item from the array (in-place) that matches the given predicate and returns the result. If no items satisfy the predicate, the array remains unchanged.
Examples
import 'sam-core'
[].remove(item => 'Joe') // []
['Joe'].remove(item => item == 'Joe') // []
[1, 2, 4].remove(item => item > 1) // [1, 4]
[1, 2, 4].remove(item => item == 3) // [1, 2, 4]
removeAll(predicate: Predicate\): T[]
Removes all items from the array (in place) for which the given predicate holds true.
alias: filter()
Examples
import 'sam-core'
[].removeAll(item => true) // []
[1, 2, 3].removeAll(Boolean) // []
[1, 2, 3].removeAll(item => item > 1) // [1]
[1, 2, 4].removeAll(item => item == 3) // [1, 2, 4]
removeAt(index: number, count?: number): T[]
Removes the item from the array (in place) at the given index and returns the result. If the given index is less than 0 or greater than or equal to the number of items in the array, the array remains unchanged. If the optional count argument is not provided, only one item will be removed. If the count argument is less than 1, no items will be removed. If count is greater than 0, that many items will be removed or as many as remains, starting from index, will be removed.
alias: splice()
Examples
import 'sam-core'
[].removeAt(0) // []
[1, 2, 3].removeAt(0) // [2, 3]
[1, 2, 3].removeAt(-1) // [1, 2, 3]
[1, 2, 3].removeAt(3) // [1, 2, 3]
[1, 2, 3].removeAt(0, -1) // [1, 2, 3]
[1, 2, 3].removeAt(0, 0) // [1, 2, 3]
[1, 2, 3].removeAt(0, 1) // [2, 3]
[1, 2, 3].removeAt(0, 2) // [3]
[1, 2, 3].removeAt(0, 3) // []
[1, 2, 3].removeAt(0, 4) // []
select\(converter: (item: T) => U): U[]
Converts the array from type T to type U via a provided converter function and returns the result.
alias: map()
Examples
import 'sam-core'
[].select(x => x.toString()) // []
[1, 2, 3].select(x => s.toString()) // ["1", "2", "3"]
[1, 2, 3].select(x => x + 1) // [2, 3, 4]
[0, 1, 2].select(Boolean) // [false, true, true]
selectMany\(select: (item: T) => U[]): U[]
Merges zero or more nested arrays into a single array of items.
Examples
import 'sam-core'
[].selectMany(x => x) // []
[[1, 2], [3, 4], [5, 6]].selectMany(x => x) // [1, 2, 3, 4, 5, 6]
[
{ name: 'Joe', things: [1, 2, 3] },
{ name: 'Bob', things: [4, 5, 6] }
].selectMany(x => x.things) // [1, 2, 3, 4, 5, 6]
where(predicate: Predicate\): T[]
Returns an array of the items for which the given predicate returns true.
alias: filter()
Examples
import 'sam-core'
[].where(x => x > 5) // []
[1, 2, 3].where(_ => true) // [1, 2, 3]
[1, 2, 3].where(_ => false) // []
[1, 2, 3].where(x => x < 3) // [1, 2]
[1, 2, 3].where(x => x > 3) // [],
[1, 2, 3].where(x => x != 2) // [1, 3]
String Extensions
after(mark: string | number): string
afterLast(mark: string): string
before(mark: string | number): string
beforeLast(mark: string | number): string
matches(expr: string | RegExp): boolean
toCamelCase(): string
toPascalCase(): string
after(index: number)
Returns the portion of the string that is after the given index. If the index is negative or greater than the length of the string, the original string is returned.
Examples
import 'sam-core'
'JoeBobAndSue'.after(3) // 'BobAndSue'
'Joe'.after(4) // 'Joe'
'Joe'.after(-1) // 'Joe'
after(s: string)
Returns the portion of the string that is after the given substring. If the string is empty or not found in the string, the original string is returned.
Examples
import 'sam-core'
'JoeBobAndSue'.after('o') // 'eBobAndSue'
'JoeBobAndSue'.after('Joe') // 'BobAndSue'
'Joe'.after('e') // ''
'Joe'.after('K') // 'Joe'
afterLast(s: string)
Returns the portion of the string that is after the last occurrence of the given substring. If the string is empty or not found in the string, the original string is returned.
Examples
import 'sam-core'
'JoeBobAndSue'.afterLast('o') // 'bAndSue'
'JoeBobAndSue'.afterLast('e') // ''
'123123123'.afterLast('1') // '23'
'Joe'.afterLast('K') // 'Joe'
before(index: number)
Returns the portion of the string that is before the given index. If the index is negative or greater than the length of the string, the original string is returned.
Examples
import 'sam-core'
'Sam'.before(-1) // 'Sam'
'Sam'.before(0) // ''
'Sam'.before(1) // 'S'
'Sam'.before(2) // 'Sa'
'Sam'.before(3) // 'Sam'
before(s: string)
Returns the portion of the string that is before the given substring. If the string is empty or not found in the string, the original string is returned.
Examples
import 'sam-core'
'JoeBobAndSue'.before('And') // 'JoeBob'
'JoeBobAndSue'.before('Joe') // ''
'Joe'.before('e') // 'Jo'
'Joe'.before('K') // 'Joe'
beforeLast(count: number)
Returns the left portion of the string that is the length of the string minus the given count. If the count is negative or zero, the original string is returned. If the count is greater than or equal to the length of the string, an empty string is returned.
Examples
import 'sam-core'
'JoeJoe'.beforeLast(-1) // 'JoeJoe'
'JoeJoe'.beforeLast(0) // 'JoeJoe'
'JoeJoe'.beforeLast(1) // 'JoeJo'
'JoeJoe'.beforeLast(2) // 'JoeJ'
'JoeJoe'.beforeLast(5) // 'J'
'JoeJoe'.beforeLast(6) // ''
'JoeJoe'.beforeLast(7) // ''
beforeLast(s: string)
Returns the portion of the string that is before the last occurrence of the given substring. If the string is empty or not found in the string, the original string is returned.
Examples
import 'sam-core'
'JoeBobAndSue'.beforeLast('o') // 'JoeB'
'JoeBobAndSue'.beforeLast('e') // 'JoeBobAndSu'
'123123123'.beforeLast('1') // '123123'
'Joe'.beforeLast('K') // 'Joe'
matches(expr: string | RegExp): boolean
Returns true if and only if the entire string is matched by the given expression. If the given expression does not start with the match beggining character (^) or does not end with the match end character ($), then these characters will be add to the expression.
Examples
import 'sam-core'
'abc.'.matches(/abc/) // true
'abcd'.matches(/abc/) // false
''.matches(/\s*/) // true
'\t'.matches(/\s*/) // true
'Joe'.matches('.*') // true
'Joe'.matches('^.*') // true
'Joe'.matches('.*$') // true
'Joe'.matches('o') // false
toCamelCase(): string
Returns the given string with the first character in the string converted to lower case.
Examples
import 'sam-core'
''.toCamelCase() // ''
'JoeJoe'.toCamelCase() // 'joeJoe'
'joeJoe'.toCamelCase() // 'joeJoe'
toPascalCase(): string
Returns the given string with the first character in the string converted to upper case.
Examples
import 'sam-core'
''.toPascalCase() // ''
'JoeJoe'.toPascalCase() // 'JoeJoe'
'joeJoe'.toPascalCase() // 'JoeJoe'
runTests\(...tests: () => T, T)
Runs each test in the the given sequence by invoking jest's test
method. Each item in the tests
array is a pair: a method that can produce the actual result (the actual method), and the expected result. Each invocation of jest's test
method is given a description based on the test being performed, and an invocation of jest's expect
method followed by jest's toEqual
method. If t
is a test, then t[0]
is the actual method and t[1]
is the expected value and jest is invoked like this:
test(
computeDescriptionOf(t[0], t[1]),
expect(t[0]()).toEqual(t[1]))
Use runTests
in conjuction with jest's describe
method.
New in version 1.2.0
The test can expect an exception will be thrown via the throws
method. See the Example below.
New in version 1.2.2
The test can require that something is true after the test is run using the satisfies
method. See the Example below.
Example
import 'sam-core'
import { runTests, throws, satisfies } from 'sam-core'
describe('Array.removeAll, with instance = [1, 2, 3]', () => {
const instance = [1, 2, 3]
runTests(
[() => [1, 2, 3].removeAll(_ => false), [1, 2, 3]],
[() => [1, 2, 3].removeAll(x => x > 3), [1, 2, 3]],
[() => [1, 2, 3].removeAll(x => x < 3), [3]],
// the 'instance' array should have been modified in place
[() => instance.removeAll(x => x >= 1),
satisfies(() => instance.length == 0)])
})
describe('Array.first', () => runTests(
// the test should throw a RangeError
[() => [].first(), throws(RangeError)]
))
The above would produce output like the following.
PASS src/array/tests/removeAll.test.ts
Array.removeAll, with instance = [1, 2, 3]
√ [1, 2, 3].removeAll(_ => false) == [1, 2, 3] (5ms)
√ [1, 2, 3].removeAll(x => x > 3) == [1, 2, 3] (8ms)
√ [1, 2, 3].removeAll(x => x < 3) == [3]
√ instance.removeAll(_ => true) satisfies instance.length == 0 (1ms)
PASS src/array/tests/first.test.ts
Array.first
√ [].first() throws RangeError(...) (13ms)
The file tools\logs\tests.log
at sam-core's git repository, has several examples of this type of output as most of sam-core was tested using runTests
.
arr\(value: T[]): Arr\
New in version 1.1.0
Takes a given array and returns a wrapper class of type Arr<T>
that has all of the array extension methods.
The Arr<T>
wrapper class has the following methods on it:
toString(): string
valueOf(): T[]
add(item: T): Arr<T>
all(predicate: Predicate<T>): boolean
any(predicate?: Predicate<T>): boolean
clear(): Arr<T>
first(predicate?: Predicate<T>, throwIfNotFound?: boolean): T
last(predicate?: Predicate<T>, throwIfNotFound?: boolean): T
remove(item: T | ((value: T) => boolean)): Arr<T>
removeAll(predicate: Predicate<T>): Arr<T>
removeAt(index: number, count?: number): Arr<T>
select<U>(convert: (item: T) => U): Arr<U>
selectMany<U>(select: (item: T) => U[]): Arr<U>
where(predicate: Predicate<T>): Arr<T>
Call the valueOf
method to get the underlying array.
str(value: T[]): Str
New in version 1.1.0
Takes a given string and returns a wrapper class of type Str
that has all of the string extension methods.
The Str
wrapper class has the following methods on it:
toString(): string
valueOf(): string
after(mark: string | number): Str
afterLast(mark: string): Str
before(mark: string | number): Str
beforeLast(mark: string | number): Str
matches(expr: string | RegExp): boolean
toCamelCase(): Str
toPascalCase(): Str
Call the valueOf
method to get the underlying string.
trace(x: any): string
New in version 1.2.4
Returns a human readable representation of the given argument.
Examples
import { trace } from 'sam-core'
const testDate = new Date('2010-11-26T04:26:33.000Z')
class Joe {
constructor(
public name: string = 'Joe',
public age: number = 32,
public alive: boolean = true) { }
}
trace(undefined) // <undefined>
trace(null) // <null>
trace(true) // true
trace(false) // false
trace(10) // 10
trace(10.99) // 10.99
trace("Joe") // 'Joe'
trace(/aregexp/) // /aregexp/
trace(['a', "Joe", 10, false]) // ['a', 'Joe', 10, false]
trace(x => x > 10) // x => x > 10
trace(new Boolean(true)) // true
trace(new Number(20)) // 20
trace(new String('Joe')) // 'Joe'
trace(new RegExp('Joe')) // /Joe/
trace(testDate) // new Date('2010-11-26T04:26:33.000Z')
trace({ name: "Joe", age: 32, alive: true }) // {name: 'Joe', age: 32, alive: true}
trace(new Joe()) // Joe {name: 'Joe', age: 32, alive: true}
getMethodBody\(method: (...args: any[]) => T): string
New in version 1.2.4
Returns the body portion of an inline method (not tested with function
style functions).
Note that
getMethodBody
(and thereforerunTests
) is very opinionated about the formatting of the result and is intended for clean test result output. It may or not be in line with everyone's taste or sense of style.
Example
import 'sam-core'
import { trace, getMethodBody } from 'sam-core'
getMethodBody(() => trace('Joe')) // trace('Joe')
getMethodBody(() => [1, 2, 3].add(5)) // [1, 2, 3].add(5)
2 years ago
2 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago