Spread Operator Applied to Strings in JavaScript

The spread operator () can be used to get an array of characters easily from a string in ES6 and higher JavaScript.

The following code shows how this works:

const str = "hello"

const chars = [...str]

console.log(chars)

The result is:

[ 'h', 'e', 'l', 'l', 'o' ]

 

Interestingly, we can also use the spread the inside an object instead of an array, in which case the operator will assign the characters of the string at each index from 0 to the length of the string minus one, using the indexes as keys in the object. The example below shows this.

const str = "hello"

const arr = { ...str }

console.log(arr)

Result:

{ 
  '0': 'h', 
  '1': 'e', 
  '2': 'l', 
  '3': 'l', 
  '4': 'o' 
}

 

Organize Test Cases using test.each in Jest

There is a better way to organize repetitive or similar test cases in Jest in a table to avoid writing out very similar tests where only specific input parameters change, and avoid duplicating code.

The simple math functions below are the functions to test in this example.

math-functions.js:

const isPerfectSquare = (x) => {
  return Number.isInteger(Math.sqrt(x))
}
 
const isProductPositive = (a, b) => {
  return a * b > 0
}
 
const realSqrt = (x) => {
  if (x < 0) throw new Error('Not defined for negatives')
  return Math.sqrt(x)
}
 
module.exports = { isPerfectSquare, isProductPositive, realSqrt }

For the first kind of test case layout,  we need to define a templated string in a format similar to the one below, listing the input and output for our test cases.

const { isPerfectSquare } = require('./math-functions.js')

const testCases = test.each`
 input     | expectedOutput
 ${'25'}   | ${true}
 ${'144'}  | ${true}
 ${'1024'} | ${true}
 ${'55'}   | ${false}
 ${'0'}    | ${true}
`

describe('perfect square tests', () => {
  testCases('it should return $expectedOutput when input is: $input',
    ({ input, expectedOutput }) => {
      expect(isPerfectSquare(input)).toBe(expectedOutput)
    })
})

Using test.each with Multiple Arguments per Test Case

The template below is used when we need multiple arguments passed into our function for each test case.

const { isProductPositive } = require('./math-functions.js')

const testCases = test.each`
 a       | b       | expectedOutput
 ${'5'}  | ${'20'} | ${true}
 ${'-1'} | ${'7'}  | ${false}
 ${'-4'} | ${'-4'} | ${true}
 `

describe('positive product tests', () => {
  testCases('it should return $expectedOutput for inputs: $a, $b',
    ({ a, b, expectedOutput }) => {
      expect(isProductPositive(a, b)).toBe(expectedOutput)
    })
})

Using a 2D Array of Inputs for Test Cases with test.each

We can use a two-dimensional array to define the test cases instead of the templated string if the cases are true/false or pass/fail; i.e. if the output should be the same for a varying set of inputs.

const { isProductPositive } = require('./math-functions.js')

const trueCases = [
  [5, 20],
  [-4, -4]
]

const falseCases = [
  [-1, 7]
]

describe('positive product tests: true cases', () => {
  test.each(trueCases)(
    'should return true for inputs: %s, %s',
    ( a, b ) => {
      expect(isProductPositive(a, b)).toBe(true)
    })
})

describe('positive product tests: false cases', () => {
  test.each(falseCases)(
    'should return false for inputs: %s, %s',
    ( a, b ) => {
      expect(isProductPositive(a, b)).toBe(false)
    })
})

Grouping Passing and Failing Test Cases

If we have a set of test cases which should pass and those which should throw an error, we can also use a nice clean layout for them.

const { realSqrt } = require('./math-functions.js')

const passingCases = [ 25, 3.141 ]
const errorCases = [ -1, -4.5 ]

describe('passing test cases', () => {
  test.each(passingCases)(
    'should return successfully for input: %s',
    (x) => {
      try {
        realSqrt(x)
      } catch (error) {
        fail('Should not reach here')
      }
    })
})

describe('expected error test cases', () => {
  test.each(errorCases)(
    'should throw error for input: %s',
    (x) => {
      try {
        realSqrt(x)
        fail('Error was not thrown')
      } catch(error) {
        expect(error.message).toBe('Not defined for negatives')
      }
    })
})

Note the fail() function to explicitly fail the test cases for undesired behaviour.

JavaScript Object Destructuring Assignment in Different Contexts

Destructuring Assignment can be used in different contexts, some of which require subtle variations.

The most common context is assignment to a constant:

const myObject = {
  a: 5,
  b: 10
}

const { b } = myObject

// b is 10

Destructuring With A Value Returned from a Function

const { c } = f()

This requires knowledge of the object returned from f, and will only work if the function returns a field named c.

Destructuring Assignment Statements with the let Keyword

Destructuring assignment can be used with let, but we must use parentheses to give the interpreter some help so the syntax is not ambiguous.

const myObject = {
  a: 5,
  b: 10,
  c: 15
}

let b

({ b } = myObject)

console.log(b); // Required semicolon.

({ c } = myObject)

console.log(c)

This prints:

10
15

Without the parentheses the interpreter gets confused and we get an error:

SyntaxError: Unexpected token =

Parentheses are required to force the interpreter to treat the braces as destructuring assignment instead of assignment to an object literal.

Destructuring in Objects Passed in as Function Arguments

This is a way of requiring a specific object structure only as an argument.
If we define our function as follows:

const f = ( {x, y} ) => {
  console.log("x is: " + x)
  console.log("y is: " + y)
}

Then attempt to use it as below:

const a = 5
const b = 10
f({a, b})

The function prints:

x is: undefined
y is: undefined

The destructuring inside the argument object forces only the specific object structure {a,b} to be a valid input for the function.
Only the code below behaves as desired:

const x = 5
const y = 10

f({x, y})

This prints:

x is: 5
y is: 10

Destructuring Syntax not to be Confused with Returning Literal Objects

Note that a return statement with a literal object is not destructuring:

return { x, y }

This simply returns an object with fields x and y.

Destructuring and Renaming Fields

We can add a colon (:) and a new name after a named field in destructuring assignment in order to rename a field.
Example:

const myObject = {
  r: 10,
  s: 20,
}

const { r: newName } = myObject

console.log(newName) // Will print 10.

Destructure an Object with Remaining Leftover Fields

We can destructure an object taking specific fields we want, and leaving the rest inside the rest variable.

const obj = {
  a: 1,
  b: 2,
  c: 3,
  d: 4
}

const { a, ...rest } = obj

console.log(rest)

This outputs:

{ b: 2, c: 3, d: 4 }

Note that this does not have to be in any specific order.
If we choose to extract c instead as below:

const { c, ...rest } = obj

Our result rest object will be equal to:

{ a: 1, b: 2, d: 4 }

REST API Design for Usability: Human-Readable Date Fields

An API with great Developer Experience (DX) returns responses as immediately understandable as possible, and one common and easily achieved usability feature is to include human-readable or “pretty” date or timestamp fields in JSON representations, in addition to the Unix Epoch timestamps preferred for machine consumption.

In a software project an API can be used by many kinds of technical people, from developers to QA to Product Owners, to test functionality or examine data. In many cases the timestamps on the data are valuable information, and reading them quickly can be very handy and boost productivity, for example to see how recent data is, if it is valid and sorted correctly, and so on.

The following example shows an updated-at field on a response with an added pretty version of the field:

GET /resources/1

{
  "id": 12345,
  "updatedAt": 1580778703,
  "updatedAtPretty": "Tue, 04 Feb 2020 01:11:43 GMT"
}

If using a specific format, we can also include ISO or something similar in the name, referring to the format:

{
  "id": 12345,
  "updatedAt": 1580778877,
  "updatedAtIso": "2020-02-04T01:14:37Z"
}

We could also use updatedAtIso8601 if using this specific readable format. Two other options for general human-readable date fields:

updatedAtReadable
updatedAtHuman

This extra field should not affect the response size much since all API responses should be gzipped.

However, if small size is very important we can always make the feature optional, so that the readable date is only added if a query string parameter is included. This post discusses this pattern in general. Such a request could look like the following:

GET /resources?prettyDates=true

Readable Versions of Other Types of API Fields

Dates and times are not the only kinds of data which could benefit from readable versions. If we have other kinds of human-readable fields, the parameter could control several fields in addition to just the readable date. Suppose we have a field called fileSizeReadable for example, to complement a field expressed in bytes. For a parameter controlling all human-readable fields we could use:

GET /resources?readable=true

GET /resources?human=true

To show a complete example, a default response:

GET /resources/1

{
  "id": 12345,
  "updatedAt": 1580778877,
  "fileSize": 95098036
}

The same call including the pretty fields:

GET /resources/1?human=true

{
  "id": 12345,
  "updatedAt": 1580778877,
  "updatedAtPretty": "2020-02-04T01:14:37Z",
  "fileSize": 95098036,
  "fileSizePretty": "138.3 MB"
}

 

Expect an Array to Contain an Object in Jest

To test if an object appears within an array, the natural first thought is to use toContain as below:

describe('contains test', () => {
  it('should return true', () => {
    const arr = [ { a: 1 }, { b: 2 }, { c: 3 } ]
    expect(arr).toContain( // This will fail.
      { b: 2 }
    )
  })
})

This will actually result in a failure. The matcher is comparing two different instances of the object {b: 2} and expecting two references to the exact same object in memory. Jest will even offer a helpful suggestion with the failure:

Looks like you wanted to test for object/array equality 
with the stricter toContain matcher. 
You probably need to use toContainEqual instead

To make this behave as we want, we need to use toContainEqual, which will compare only the values of the objects, and not their references. The following code works:

describe('contains test', () => {
  it('should return true', () => {
    const arr = [ { a: 1 }, { b: 2 }, { c: 3 } ]
    expect(arr).toContainEqual( // Compare values only.
      { b: 2 }
    )
  })
})

This will now succeed as desired.

 

Expect an Object to Contain Another Object

What if we want to test if an object contains { b: 2 } as one of its key-value pairs, i.e. to contain it as a sub-object?

The following will achieve this:

describe('object contains test', () => {
  it('should return true', () => {
    const myObject = { 
      a: 1, 
      b: 2, 
      c: 3 
    }
    expect(myObject).toEqual(expect.objectContaining(
      { b: 2 }
    ))
  })
})

This will succeed. Note that we used toEqual so that only object values are compared instead of identical object references.

 

Array Reduce Syntax in JavaScript

Reduce provides a clean mechanism to compute a single value from an array, i.e. to reduce the array to a single value.

Very generally, the form is:

result = arr.reduce(callbackFunction)

The idea is to move over each array element and invoke the callback function.

We need to give the callback two arguments to provide a mechanism of reduction for the array elements. We call these accumulator (acc) and element (elem).

The simplest useful reduce example is summing the array:

const arr = [1, 2, 3, 4, 5]
const sum = arr.reduce((acc, elem) => acc + elem) 
console.log(sum)

This outputs:

15

Image a sliding window in which acc stores a value accumulated in any way, and elem points to the 2nd, 3rd, 4th, etc element. The accumulator is updated and passed to the next iteration.

For demonstration, to see how reduce moves through the array:

const arr = [1, 2, 3, 4, 5]

arr.reduce((acc, elem) => {
   console.log(acc);
   console.log(elem);
})

This outputs:

1 // default first value of acc
2 // second element
undefined // acc is not being updated
3 // third element
undefined
4 // fourth element
undefined
5 // fifth element

Note that acc starts at the first element of the array by default.
We usually have to update acc to make reduce useful.

Reduce Starting from an Initial Value

The starting value of acc in reduce can be changed with an extra parameter. Generally:

const result = arr.reduce(callback, initialValue)

An example of an offset sum:

const arr = [1, 2, 3, 4, 5]
const result = arr.reduce((acc, elem) => {
  return acc + elem
}, 10) // *start the accumulator at 10 instead of 1*

console.log(result)

This outputs:

25

That is, 10 + (1+2+3+4+5)

Calling Reduce on an Array With a Single Element

Note that when we call reduce with a single element array without an initial value for acc, the acc value is undefined.
The callback will not be called; the only option reduce has in this case is to return the elem value.

const arr = [5]
const output = arr.reduce((acc, elem) => {
  console.log("Callback called") // will not print
  return acc + elem
})

console.log(output)

This will return 5, without calling the callback function.

However, if we add the initial value for acc, the callback will be called.

const arr = [5]
const output = arr.reduce((acc, obj) => {
  console.log("Callback called") // will print
  return acc + obj
}, 0) // initial value

console.log(output)

Using Reduce with an Object as Accumulator

We can also reduce an array to an object instead of a single scalar value.

This simple example reduces the array to an object with characters as keys, and a boolean true indicating that they appear inside the array.

const arr = ['a', 'c', 'b', 'a', 'c', 'e']
 
const result = arr.reduce((acc, elem) => {
  acc[elem] = true // build up acc
  return acc // send the partially built up acc into the next iteration
}, {} ) // initial value of acc is an empty object

console.log(result)

This will return:

{
   a: true,  
   c: true, 
   b: true, 
   e: true 
}

So we have reduced the array to a single object.

Using Reduce to Accumulate an Array

We can also use reduce to build up a list of values.
This example accumulates an array of names only from an array of objects with names and values.

const arr = [ 
  {name: 'A', val: 1},
  {name: 'B', val: 2},
  {name: 'C', val: 3}
]

const result = arr.reduce((acc, elem) => {
  acc.push(elem.name) // accumulate an array by pushing elements
  return acc
}, [] ) // acc is initially an empty array

console.log(result)

This returns:

[ 'A', 'B', 'C' ]

Index and Entire Array Parameters for Reduce

There are two more parameters available for reduce which can come in handy: the index of the current element and the entire array being reduced.

const arr = [1, 2, 3, 4, 5]

const sum = arr.reduce((acc, elem, index, wholeArray) => {
  console.log(`Index: ${index}`)
  console.log(`Whole array: ${wholeArray}`)
  return acc + elem
}, 0) // starting value of acc

console.log(`Sum: ${sum}`)

This returns:

Index: 0
Whole array: 1,2,3,4,5
Index: 1
Whole array: 1,2,3,4,5
Index: 2
Whole array: 1,2,3,4,5
Index: 3
Whole array: 1,2,3,4,5
Index: 4
Whole array: 1,2,3,4,5
Sum: 15

Find all instances of a matched pattern in a string in Python

To find those parts of a string which match a pattern specified using a Regular Expression, the following code is probably the simplest.

import re

source = "A string 123 with numbers 456 and letters 789"

pattern = "[0-9]+"

results = re.findall(pattern, source)

for match in results: 
   print(match)

Result:

123
456
789

All matches of the regex inside the source string will be stored in the results list.

 

Delete Element Inside an Array in JavaScript

To remove an element from the middle of an array at a given index, we use the splice function as below. For example, to delete at index 2:

const arr = [1, 2, 3, 4, 5]
arr.splice(2, 1)
console.log(arr)

Result:

[ 1, 2, 4, 5 ]

In general, to delete an element at a specific index:

const deleteAtIndex = (arr, index) => {
  arr.splice(index, 1)
}

arr = [1, 2, 3, 4, 5]
deleteAtIndex(arr, 2)
console.log(arr)

Result:

[ 1, 2, 4, 5 ]

 

The following are all options that one my think of that do not do what we want.

Using the delete operator leaves a gap inside the array:

const arr = [1, 2, 3, 4, 5]
delete arr[2]
console.log(arr)

Result:

[ 1, 2, <1 empty item>, 4, 5 ]

Attempting to set the element to null or undefined also does not remove an element; it merely sets the reference at that location to null or undefined:

const arr = [1, 2, 3, 4, 5]
arr[2] = undefined
console.log(arr)

Result:

[ 1, 2, undefined, 4, 5 ]

Prevent Committing Secrets with a Pre-Commit Hook

It is difficult to remove a secret once committed to a git repo; the following describes how to prevent a commit with a secret (password, key, etc) using a pre-commit hook.

Even when following good practices for managing secrets (such as Secrets-as-a-Service) there is still a risk of accidentally committing secrets to git repositories by mistake. For example, suppose we pull secrets to use locally from Vault or similar, write some code and run “git add –all”; a file not meant to be committed can end up in the commit. If that happens, and especially if the commit is pushed to a remote, we would need to take extensive measures to wipe the secret from git history (see references).

Therefore, it is ideal to prevent committing anything that looks like a secret or password to a repo in the first place. This is possible by looking for high Shannon entropy strings and other techniques.
The detect-secrets project from Yelp is good for this.
Note: we need Docker installed on the system to use this tool.

Install the Node wrapper for detect-secrets globally:

$ npm install -g detect-secrets

Suppose we have a simple JavaScript file like this in index.js:

const output = 'Hello world'

console.log(output)

Run the following to check files for potential secrets:

$ detect-secrets-launcher index.js

Checking file: index.js

No results show for this file.

Now we add a high entropy string that looks like a password or secret key:

const output = 'Hello world'
const pwd = 'i6nrcpoeqzo4vmd0wpsn7j1pdglwv56mxsjggqkczwa'

console.log(output)

Run the secret detection again; the report should show:

$ detect-secrets-launcher index.js

Checking file: index.js
Potential secrets about to be committed to git repo! Please rectify or
explicitly ignore with an inline `pragma: allowlist secret` comment.

Secret Type: Base64 High Entropy String
Location: index.js:2

This shows the tool detected the potential secret.

Note that the scanner needs the file to be known to git; the git add command made this true.

We can add the detect-secrets-launcher command to a git pre-commit hook to detect any attempt to commit secrets and fail the pre-commit hook if any secrets are detected. The command will set the exit status to 1 if any secrets are found.

See this post for instructions on adding a git hook with Husky.

This .huskyrc file will connect detect-secrets to the pre-commit hook:

{
  "hooks": {
    "pre-commit": "detect-secrets-launcher *.js"
  }
}

Now try to commit the index.js file with the secret included:

$ git add .
$ git commit -m "try to commit a secret"

husky > pre-commit (node v11.6.0)

Checking file: index.js
Potential secrets about to be committed to git repo! Please rectify or
explicitly ignore with an inline `pragma: allowlist secret` comment.

Secret Type: Base64 High Entropy String
Location: index.js:2

...

husky > pre-commit hook failed (add --no-verify to bypass)

For larger projects with  more files the command might be:

"pre-commit": "detect-secrets-launcher src/**/*.js"

This can be used in projects as a guard against developers committing secrets, by accident or otherwise.

References

https://github.com/Yelp/detect-secrets

https://www.npmjs.com/package/detect-secrets

https://help.github.com/en/github/authenticating-to-github/removing-sensitive-data-from-a-repository

Add Search to Hyper

Hyper is a nice terminal emulator for MacOS and others which does not have a search feature by default.

To add search to the Hyper terminal emulator app:

Open ~/.hyper.js (or click Hyper/Preferences… ) with a text editor.

Find the plugins value and add the following:

...

plugins: [
  'hyper-search'
],

...

Then install the hyper search plugin:

$ npm install hyper-search

Now restart Hyper, and search should work the usual way (e.g. Command+f on MacOS).

 

References

https://www.npmjs.com/package/hyper-search