NewFiveFour | Blog | Portfolio


JS: Simple dependency injection for unit testing with destructing assignment

With destruction object assignment and default values you can easily implement dependency injection.
 
We can use default values easily in ES2015:

var func = function(a, b = "second parameter") {
   console.log(a, b)
}
func("first parameter")

This would print first parameter second parameter. And we could use this default parameter to pass in our dependency:

var getFromServer = function(url, _di_fetch = fetch) {
  _di_fetch(url)
}
getFromServer("https://newfivefour.com:3000/id?id=E14000874")

Then we could test the function by passing in a fake fetch as the second parameter. But we would have to know the order of parameters to use dependency injection.
 
We can use destructing object parameters to get around this:

var getFromServer = function({url, _di_fetch = fetch}) {
  _di_fetch(url)
}
getFromServer({url: "https://newfivefour.com:3000/id?id=E14000874"})

We now name the parameters and simply don't name the dependency injection parameter to get the default value.
 
We can test it as thus:

let fakeFetch = url => console.log(`The fake fetch has been passed ${url}`)
getFromServer({url: "www.google.com", _di_fetch: fakeFetch})

And now it will print The fake fetch has been passed www.google.com.
 
You can also place all your depenencies in one place if you do:

import di_container from './di_container.mjs'
...
var getFromServer = function({url, _di_fetch = di_container.fetch}) {
  _di_fetch(url)
}

This is a dependency injection solution rather than a full testing solution but it can be integrated into such with ease.

javascript dependency-injection


Javascript: Extending our markup language parser, part seven


This details the development of the package wildlife-analysis which is available on npm.
 

Previously we refactored our markup parser to get rid of the regular expression and make it so the tags can have start and end tags ( https://newfivefour.com/javascript-extending-our-regex-less-markup-parser-part-six.html ).
 
But it still doesn't deal with ##, since each marker is a single character.
 
We were looking at a single character of the input string through theText.split("").forEach(function(letter, pos) {. We should refactor our code so it's in a for loop with an iterator.
 
This means we won't automatically be looking at a letter, but a position, which we can alter by increasing the position if we want to look at more than one letter. And we can increase the iterator when we want to skip letters.
 
We were using the extra argument in the forEach loop to define this which was an object that contained the markup language marker last position, all the tokens we match etc. That will now be in a separate object, parseState which the loop will alter.
 
Here's the code now we've refactored out the forEach loop in favour of a for loop.

let parseState = { 
    endtag: undefined, pos: -1, isGrabbingOther: false, 
    tokens: [["*", "*"], 
             ["/", "/"], 
             ["_", "_"], 
             ["`", "`"],
             ["[", "]"]]}
  for(var pos = 0; pos < txt.length; pos++) {
    let letter = txt[pos];
    if(hasOtherGrabEnded(letter, parseState)) {
      tokens.push(txt.substr(parseState.pos, pos - parseState.pos))
      parseState.isGrabbingOther = false
      startTag(letter, pos, parseState)
    } else if(hasTagEnded(letter, parseState) || endOfInput(txt, pos)) {
      tokens.push(txt.substr(parseState.pos, pos - parseState.pos + 1))
      endTag(parseState)
    } else if(shouldStartNewTag(parseState)) {
      if(hasStartTag(letter, parseState)) startTag(letter, pos, parseState)
      else startOtherGrab(pos, parseState)
    }
  }

( You can play with it here: https://codepen.io/newfivefour/pen/OBXXdp )
 
We can also change the helper functions. They close over the parseState so there's no need to pass it in. And we can make them deal not with letters but with positions in a string. We'll have a look at some of the functions:

  let parseState = { 
    endtag: undefined, pos: -1, isGrabbingOther: false, 
    tokens: [["*", "*"], 
             ["/", "/"], 
             ["_", "_"],
             ["`", "`"],
             ["[", "]"]]}
  let startTokens       = parseState.tokens.map(t => t[0])
  let endTokens         = parseState.tokens.map(t => t[1])
  let getStartTagOrNull = (pos) => startTokens.filter(t => t == txt.substr(pos, t.length))[0]
  let getEndTagOrNull   = (pos) => endTokens.filter(t => t == txt.substr(pos, t.length))[0]
  let endingMarkFor     = (start) => parseState.tokens.filter(t => t[0] == start)[0][1]

This defines our parse state with the starting and ending tokens, creates an array with all the start markers, all the end markers, find a start marker at a position in the txt string (by looking at txt at the passed point until the length of the suspected marker), find a end marker at a position in the txt string, and returns an ending markers if you pass it a starting marker.
 
The remaining functions alter the state of parseState during the loop.
 
Now we have these new functions that work, not on a lettter, but on a position in a string, our main logic changes a little in the last if else loop: we use getStartTagOrNull instead of passing in a letter. And we don't need the letter variable:

for(var pos = 0; pos < txt.length; pos++) {
  if(hasOtherGrabEnded(pos)) {
    tokens.push(txt.substr(parseState.pos, pos - parseState.pos))
    parseState.isGrabbingOther = false
    startTag(getStartTagOrNull(pos), pos)
  } else if(hasTagEnded(pos) || endOfInput(txt, pos)) {
    tokens.push(txt.substr(parseState.pos, pos - parseState.pos + 1))
    endTag()
  } else if(shouldStartNewTag()) {
    let knownTag = getStartTagOrNull(pos)
    if(knownTag) startTag(knownTag, pos)
    else startOtherGrab(pos)
  }
}

Here's the updated version: https://codepen.io/newfivefour/pen/YJWjwM
 
We're nearly done, but we can't just enter ["##", "##"] into our list of tokens just yet. This is because we'll match an end marker, ##, with the position at the first #, and then continue. This means we won't grab the entire ending marker.
 
Let's look at this code:

let extraEndTagMarkers = endMarkLengthMin1(pos)
tokens.push(txt.substr(parseState.pos, pos - parseState.pos + 1 + extraEndTagMarkers))
pos = pos + extraEndTagMarkers 
endTag(pos)

The first line gets the length of the end marker (minus 1), if if there's no ending mark returns 0.
 
Then we grab the text from position in parseState, adding on the extra ending marker if we need to.
 
Then we add that potential extra length on our loop iterator (so we skip the remainder of the tag in the loop)
 
Then end the tag as usual.
 
Here's the final example which deals with markers with multiple characters, # and ## in our case: https://codepen.io/newfivefour/pen/GYqXyv
 
It also deals with our bullet points * a point and . a point.
 
But there's a slight problem with that if we match until the end of the line, i.e. ["\n* ", "\n"]. It will grab the \n as part of the tag, and if the next line requires a \n at the start of it, which ["\n* ", "\n"] does, then it will not see it. In this case we need a newline between * points. (We'll see a way around this later)
 
Next we'll look at integrating our new parser into the old quickText function.

javascript


Javascript: Removing the regular expression from our markup language, part 5

Previously we made a markup language but noted the regular expressions were becoming unmaintainable ( https://newfivefour.com/javascript-extending-our-new-markup-language-part-4.html ).
 
We'll start making a function that parses our text into tokens. Our algorithm will go like this:
  1. Start a new tag when *, /, _, etc is encountered
  2. Continue until another *, /, _, etc tag is found
  3. Then save all the text inside that, including the markers, in an array
And
  1. If a tag ends, and the next character is not a markup tag, grab the text anyway
  2. Continue until a *, /, _, etc tag is found
  3. Then save all the text in an array, and start a new markup tag as above
The code that is below:

var strArray = s => s.split("")
var str = `hihi *hello*/italic/ ss_u_pp *ag/a/in* some \`pre\` text`
document.body.innerHTML = str + "<br><br>" + quickText(str).join("<br>")

function quickText(txt, ignoreNewlines) {
  let startTag          = (letter, pos, ob) => { ob.endtag = letter; ob.pos = pos }
  let endTag            = (ob) => ob.endtag = undefined
  let hasTagEnded       = (letter, ob) => ob.endtag && ob.endtag == letter
  let startOtherGrab    = (pos, ob) => { ob.isGrabbingOther = true; ob.pos = pos}
  let endOtherGrab      = (ob) => ob.endOther = false
  let isEndOfPrevTag    = (ob) => ob.endtag == undefined
  let hasTagsInLetter   = (letter, ob) => ob.tokens.indexOf(letter) != -1
  let endOfInput        = (txt, pos) => pos == txt.length -1
  let shouldStartNewTag = (ob) => isEndOfPrevTag(ob) && !ob.isGrabbingOther
  let hasOtherGrabEnded = (lett, ob) => ob.isGrabbingOther && hasTagsInLetter(lett, ob)
  let tokens = []
  strArray(txt).forEach(function(letter, pos) {
    if(hasOtherGrabEnded(letter, this)) {
      tokens.push(txt.substr(this.pos, pos - this.pos))
      this.isGrabbingOther = false
      startTag(letter, pos, this)
    } else if(hasTagEnded(letter, this) || endOfInput(txt, pos)) {
      tokens.push(txt.substr(this.pos, pos - this.pos + 1))
      endTag(this)
    } else if(shouldStartNewTag(this)) {
      if(hasTagsInLetter(letter, this)) startTag(letter, pos, this)
      else startOtherGrab(pos, this)
    }
  }, { endtag: undefined, pos: -1, tokens: ["*", "/", "_", "`"], isGrabbingOther: false })
  return tokens
}

You can play with it here: https://codepen.io/newfivefour/pen/MPyqNa?editors=0011
 
Note this parsing section can be plugged into the other sections you saw in previous posts.
 
But this function only deals with single character markups, and doesn't deal with markers with different start and end points ([ and ]). And we're going to deal with those in later posts.
 

javascript


Javascript: Removing the regular expression from our new markup language, part six

Previously we decided the regular expression for our markup language were unmaintainable. And we wrote a function that replaced the regular expressions. ( https://newfivefour.com/javascript-removing-regex-from-new-markup-language.html )
 
But that function didn't deal with markup tags with different start and end points, [hello there|https://url] was our example.
 
We can remedy that by specifying the start and end tags for our markup. i.e.

tokens: [["*", "*"], 
         ["/", "/"], 
         ["_", "_"], 
         ["`", "`"],
         ["[", "]"]]

And in our helper function we'll ensure we look for the starting tag and look for the ending tag. This is the function that gets for the ending tag for a certain letter:

let endingTagFor = (letter, ob) => ob.tokens.filter(t => t[0] == letter)[0][1]

And here's the function that checks if we're found a starting tag:

let hasStartTag = (letter, ob) => ob.tokens.map(t => t[0]).indexOf(letter) != -1

Our entire function is now here:

function quickText(txt, ignoreNewlines) {
  let startTag          = (lett, pos, ob) => { ob.endtag = endingTagFor(lett, ob); ob.pos = pos }
  let endTag            = (ob) => ob.endtag = undefined
  let hasTagEnded       = (letter, ob) => ob.endtag && ob.endtag == letter
  let startOtherGrab    = (pos, ob) => { ob.isGrabbingOther = true; ob.pos = pos}
  let endOtherGrab      = (ob) => ob.endOther = false
  let isEndOfPrevTag    = (ob) => ob.endtag == undefined
  let hasStartTag       = (letter, ob) => ob.tokens.map(t => t[0]).indexOf(letter) != -1
  let endingTagFor      = (letter, ob) => ob.tokens.filter(t => t[0] == letter)[0][1]
  let endOfInput        = (txt, pos) => pos == txt.length -1
  let shouldStartNewTag = (ob) => isEndOfPrevTag(ob) && !ob.isGrabbingOther
  let hasOtherGrabEnded = (lett, ob) => ob.isGrabbingOther && hasStartTag(lett, ob)
  let tokens = []; strArray(txt).forEach(function(letter, pos) {
    if(hasOtherGrabEnded(letter, this)) {
      tokens.push(txt.substr(this.pos, pos - this.pos))
      this.isGrabbingOther = false
      startTag(letter, pos, this)
    } else if(hasTagEnded(letter, this) || endOfInput(txt, pos)) {
      tokens.push(txt.substr(this.pos, pos - this.pos + 1))
      endTag(this)
    } else if(shouldStartNewTag(this)) {
      if(hasStartTag(letter, this)) startTag(letter, pos, this)
      else startOtherGrab(pos, this)
    }
  }, { endtag: undefined, pos: -1, isGrabbingOther: false, 
      tokens: [["*", "*"], 
               ["/", "/"], 
               ["_", "_"], 
               ["`", "`"],
               ["[", "]"]],  })
  return tokens
}

And you can play with it here: https://codepen.io/newfivefour/pen/bmepZV
 
We still don't deal with multiple letter tags though, ## for example, and we will next.
 

javascript


Javascript: Extending our new markup language part 4

Previously ( https://newfivefour.com/javascript-extending-our-new-markup-languge-part-3.html ) we said there was two annoying things about our new markup language: the regular expressions are getting out of hand, and two we can't do _hello *bold* hello_. We'll deal with the latter now.
 
In our last example we did <b>${replaceSpaceAndNL(token[1])}</b> when we found some bold text. That means everything within that bold text went unprocessed.
 
We can fix that easily by making our function recursive -- it will call itself on that text. So instead we will have: <b>${quickText(token[1])}</b>. We can use this recursive nature in all our if statements, for bold text, for italic, for list items, etc.
 
Eventually the text inside it will be normal text. And the normal text will be processed with replaceSpaceAndNL(token[1]), that is the spaces and newlines will be processed into HTML spaces and newlines.
 
But in the case of headings and bullet points we don't want the newlines to be processed. So instead we will pass our quickText function a boolean that says whether we should process newlines or not, and have the final map in our function look at that.
 
So our function will now look like this:

function quickText(txt, ignoreNewlines) {
  var notThisButThat = (dis, that, name) => dis != name && that == name
  var dot = "[\\s\\S]"
  var tokens = ["[^*_/\\^`\\[\\]#][^*_/\\^`\\[\\]#]*[^*_/\\^`\\[\\]#]", 
                `_${dot}*?_`,
                `[*][^ ]${dot}*?[*]`,
                `##.*?\\n`,`#[^#].*?\\n`,
                `[/]${dot}*?[/]`,
                `\\[${dot}*?\\]`,
                `^[*] .*\\n`,
                `^[\\^] .*\\n`,
                "[`][\\s\\S]*?[`]"]
  return txt.match(new RegExp(tokens.join("|"), "gm"))
    .map(t => {
      if(t.startsWith("/")) return ["italic", t.slice(1, -1)]
      else if (t.startsWith("_")) return ["underline", t.slice(1, -1)]
      else if (t.startsWith("* ")) return ["ulistitem", t.slice(1)]
      else if (t.startsWith("^ ")) return ["olistitem", t.slice(1)]
      else if (t.startsWith("##")) return ["heading2", t.slice(2)]
      else if (t.startsWith("#")) return ["heading1", t.slice(1)]
      else if (t.startsWith("*")) return ["bold", t.slice(1, -1)]
      else if (t.startsWith("`")) return ["pre", t.slice(1, -1)]
      else if (t.startsWith("[")) return ["link", t.slice(1, -1).split("|")]
      else return ["normal", t]
    })
    .map(function(token) {
      let retValue = ""
      if(notThisButThat(token[0], this.prev, "olistitem")) retValue += "</ol>"
      else if(notThisButThat(token[0], this.prev, "ulistitem")) retValue += "</ul>"
      if(notThisButThat(this.prev, token[0], "olistitem")) retValue += "<ol style='margin:0px'>"
      else if(notThisButThat(this.prev, token[0], "ulistitem")) retValue += "<ul style='margin:0px'>"      
      if(token[0] == "italic") retValue += `<i>${quickText(token[1])}</i>`
      else if (token[0] == "underline") retValue += `<u>${quickText(token[1])}</u>`
      else if (token[0] == "bold") retValue += `<b>${quickText(token[1])}</b>`
      else if (token[0] == "ulistitem") retValue += `<li>${quickText(token[1], true)}</li>`
      else if (token[0] == "olistitem") retValue += `<li>${quickText(token[1], true)}</li>`
      else if (token[0] == "heading1") retValue += `<h1>${quickText(token[1], true)}</h1>`
      else if (token[0] == "heading2") retValue += `<h2>${quickText(token[1], true)}</h2>`
      else if (token[0] == "pre") retValue += `<pre style="display: inline">${token[1]}</pre>`
      else if (token[0] == "link") retValue += `<a href="${token[1][1]}">${quickText(token[1][0])}</a>`
      else if(ignoreNewlines) retValue += token[1].replace(/  /g, "&nbsp;")
      else retValue += token[1].replace(/  /g, "&nbsp;").replace(/\n/g, '<br>')
      this.prev = token[0]
      return retValue
    }, { prev : ""})
    .join("")
}

You can play with it here: https://codepen.io/newfivefour/pen/XxdZEe
 
We still have the fairly unmaintainable regular expressions to remove. And we'll do that in a later post.

javascript


Page 2 of 89
prev next

Portfolio