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-coreor
npm install -D sam-coreRelease 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
clearmethod was added to array and thearrwrapper method.satisfieswas 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()) // 1Array 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) => booleanYou 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) // falseany(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) // falseclear(): 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 RangeErrorlast(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 RangeErrorremove(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(): stringafter(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') // falsetoCamelCase(): 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(): StrCall 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)3 years ago
3 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago