How to test a function that yields a block with Minitest and Rspec

In my quest for learning a new language I’ve picked Elm and Elixir, I think both are great languages to learn but for this article, I will be focusing on Elm. To be more specific, I will guide you through the process of building a simple Google Chrome extension using Elm.

Here is what you should expect to get out of reading this article.

  • Why should you care about Elm?
  • Basic Elm, syntax & setup
  • How to build a Google Chrome extension, using Elm

What is Elm?

Elm is a functional programming language that compiles to Javascript. If you’ve played with React and Redux, you’ll find Elm a lot more fun and productive.

Elm project setup

I’m going to describe the setup process on OSX with npm but the process should be the same on Linux. Let us go ahead and create a directory for our example project; we will call it elm_chrome_ext.

$ mkdir elm_chrome_ext
$ cd elm_chrome_ext
$ npm install -g elm

You don’t need a project directory to install Elm, but I’ve created one because we will need it in just a second. You usually install Elm as a global package (that’s what npm -g does) so you can generate new projects quickly.

Basic Elm syntax

I won’t go much into the language, and how to use it in this article; I think the Elm docs and the guide are doing a great job on that. But if you get stuck, feel free to ask for help in the comments.

Inside the project folder, let’s create a sub-folder for the Elm source files so that we have everything organized. We will name it elmsrc.

$ mkdir elmsrc
$ touch elmsrc/Main.elm
$ cd elmsrc

Now that we have a folder for storing everything Elm related, we can start adding the necessary dependencies. First, let’s add the Elm core package.

$ elm package install
Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/core 4.0.1

Do you approve of this plan? (y/n) y
Downloading elm-lang/core
Packages configured successfully!

$ elm package install elm-lang/html
To install elm-lang/html I would like to add the following
dependency to elm-package.json:

    "elm-lang/html": "1.0.0 <= v < 2.0.0"

May I add that to elm-package.json for you? (y/n) y

Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/html 1.0.0
    elm-lang/virtual-dom 1.0.1

Do you approve of this plan? (y/n) y
Downloading elm-lang/html
Downloading elm-lang/virtual-dom
Packages configured successfully!

What we’ve done here is we’ve installed the core Elm package and the html package so we can get started.

Now to compile our code, we will use elm make elmsrc/Main.elm --warn --output="elm.js". This command tells Elm to compile the Main.elm file and put the generated Javascript code into a file called elm.js in the current folder. I like to use the --warn flag just because the compilation output is slightly more verbose and so it helps me with my learning process.

We need to add some code to the Main.elm file before we can compile it, otherwise we’ll get compilation errors and we surely don’t want that. I’ve added comments (the lines that begin with --) to describe what each function does.

-- elmsrc/Main.elm
module Main exposing (..)

-- This line imports functions that generate HTML
import Html exposing (Html, button, div, text)

-- Html.App is slightly more complicated but you can think of it as supervisor
for our little Elm app
import Html.App as Html

-- This will be the rendered HTML (an empty div)
view : a -> Html b
view model =
    div []
        []


-- The update function will be used to update the rendered HTML
update : a -> b -> number
update msg model =
    case msg of
        _ ->
            0

-- This is what puts everything together
main : Program Never
main =
    Html.beginnerProgram { model = 0, view = view, update = update }
$ elm make Main.elm --warn --output="../elm.js"
Success! Compiled 1 module.
Successfully generated ../elm.js

What is a Google Chrome extension?

Have you ever seen those icons on the right of your address bar? The ones you click to do various things? Those are Chrome extensions.

Google Chrome Extensions

They are like your personal admin dashboard for all the sites you visit since they can be activated almost anywhere. So if you ever find yourself needing more general functionality across many sites, a Chrome extension is what you need.

Basic Chrome extension setup

Creating a Chrome extension skeleton is easy. All you need is a manifest.json file and an HTML file. The manifest file should be in the root of your project, so in this case it’s elm_chrome_ext/manifest.json.

{
  "manifest_version": 2,

  "name": "Example extension with Elm",
  "description": "This is just a minimalistic example of how to build a Chrome extension with Elm",
  "version": "1.0",

  "browser_action": {
    "default_popup": "elmext.html"
  },

  "permissions": [
    "activeTab"
  ]
}

Here is a simple HTML file that will do for now.

<!-- elmext.html-->
<!DOCTYPE html>
<html>
  <head>
    <title>Chrome Extension</title>
  </head>
  <body>
    TEST
  </body>
</html>

Now, to load the code we have just created, we need to visit chrome://extensions in Google Chrome and click the Load unpacked extension… button.

Chrome extensions page

Chrome extension loaded

Chrome extension clicked

Security policy

One of the major pains I had with trying to build the Google Chrome extension and Elm was a violation of Chrome’s CSP (Content Security Policy). Just remember to come back to this section if you ever get a message (in Chrome’s Developer Tools console) that looks like the following.

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-eSfQRhteSlQwLyblZBiFx4pdpvFSsfB22IDT2h2qtWM='), or a nonce ('nonce-...') is required to enable inline execution.

To get around this issue, you need to defer the initialization of the Elm application to after the DOM gets loaded. So create a file called elm-import.js and add this code inside it.

// elm-import.js
document.addEventListener('DOMContentLoaded', function() {
  var div = document.getElementById('widget');
  Elm.Main.embed(div);
});

Then include load the elm-import.js file inside your HTML.

<!DOCTYPE html>
<html>
  <head>
    <title>UtmTag URL Builder</title>
    <link href="popup.css" rel="stylesheet" />
    <script type="text/javascript" src="elm.js"></script>
    <script type="text/javascript" src="elm-import.js"></script>
  </head>
  <body>
   <div id="widget" class="ut-widget"></div>
  </body>
</html>

Making use of Elm

We have our extension working and we have our Elm code that compiles but does nothing yet. So the extension would work without our Elm code. Let’s change that.

We will add a small interactive counter to our extenstion that will be powered by Elm.

But first, let’s also add a tiny bit of styling so can see the buttons a little better.

/* popup.css */
.ut-widget {
  width: 200px;
  font-size: 18px;
}

The Elm code won’t change too much either. The notable changes will be in the view and update functions where we need to add the buttons so we can interactively change the value of the counter and actually change the value (with the update function).

module Main exposing (..)

import Html exposing (Html, button, div, text)
import Html.App as Html

-- We need to handle click event
import Html.Events exposing (onClick)

-- Here we are defining two possible values
type Msg = Increment | Decrement

view : a -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

update : Msg -> number -> number
update msg model =
    case msg of
        Increment ->
            model + 1

        Decrement ->
            model - 1


main : Program Never
main =
    Html.beginnerProgram { model = 0, view = view, update = update }

Not that you might need to reload the extension by going to the extensions page and hitting reload.

Chrome extension buttons

Clicking any of the +/- buttons will increment and decrement the counter.

Chrome extension buttons

Conclusion

You know the basics of building Google Chrome extensions now so let your mind go wild and make something useful.

One last thing

If you know other people how would love to read this, please share it. Also make sure you leave a comment below with any questions you have.