Skip to the content. Disable Animations :x: :movie_camera:

Empirical++: Power Tools Go Brrr...

jackhammer

Thanks to the software contributions of our WAVES participants and mentors, we’ve got an exciting slew of shiny new tools to show off. And you’ve got an exciting slew of shiny new things to learn about!

🔗 Prefab

Contributed by TODO.

🔗 D3

Contributed by TODO.

🔗 Interactive Tutorials

Contributed by TODO.

🔗 Installing Test Dependencies

Given the size and complexity of Empirical’s code base, how do we make sure that everything stays in working order? Tests! To get started working on an Empirical project, you’ll want to be familiar with how to run existing tests and how to write new ones.

First thing you’ve got to do is install a few dependencies Luckily for you, we’ve got Empirical’s dependency install process automated as a make rule in the base directory of the repo that should just work.

make install-test-dependencies

If that doesn’t just work, you can try hopping into our pre-built Docker container. You should have better luck with make install-test-dependencies in there.

sudo docker run -it devosoft/empirical:latest

🔗 Run All the Tests

To run our existing battery of native tests, you’ll just

cd tests
make

To run the tests for a particular source subdirectory, just cd into the corresponding subdirectory in tests and make. For example, to run the tests for base

cd tests/base
make

Those options probably take too long, though. You’re more likely to find running the tests for a particular source file more useful. To do that, cd into its subdirectory and make test-BasenameWithoutExtension. For example, to test base/vector.h you’d

cd tests/base
make test-vector

🔗 Anatomy of a Native Test

What’s actually going on inside these tests? Getting you to Catch the drift will REQUIRE popping one open for a closer look.

Hopefully, those puns will be very funny to you in a minute.

We use the Catch framework for our native tests.

#define CATCH_CONFIG_MAIN // Catch magic

#include "third-party/Catch/single_include/catch2/catch.hpp"

#include "tools/info_theory.h"

#include <sstream>
#include <string>

// Catch uses macro magic to string these TEST_CASE's together.
// You can provide several TEST_CASE's within the same file.
TEST_CASE("Test info_theory", "[tools]")
{

  emp::vector<int> weights = { 100, 100, 200 };
  // REQUIRE is a special Catch macro that must evaluate to true
  // or the executable exits with an error code
  REQUIRE( emp::Entropy(weights) == 1.5 );

}

🔗 Run the Web Tests

To run our existing battery of web tests, you’d

cd tests/web
make

To run the tests for a particular header, for example Widget, just

cd tests/web
make test-Widget

:bangbang: There’s also DOM tests for prefab in addition to web.

🔗 Anatomy of a Web Test

Includes… gotta have ‘em!

#include <functional>
#include <unordered_map>

#include "base/assert.h"
#include "web/_MochaTestRunner.h"
#include "web/Document.h"
#include "web/Element.h"
#include "web/web.h"

Today, we’re going to be painting coding a happy little tree struct. There’s nothing wrong with having a tree struct as a friend.

Bob Ross Matthew Andres Moreno

This struct will inherit from BaseTest, which handles much of the boilerplate for us behind the scenes.

struct Test_WidgetWrapWith : emp::web::BaseTest {

Step the first: set up some DOM state in our constructor.

  // Construct the following HTML structure:
  // <div id="wrapper2"> <!-- inserted with WrapWith -->
  //   <p id="parent">
  //     parent
  //     <div id="wrapper"> <!-- inserted with WrapWith -->
  //       wrapper
  //       <button id="child"></button>
  //     </div>
  //   </p>
  //   <br/><br/>
  // </div>
  Test_WidgetWrapWith()
  : BaseTest({"emp_test_container"})
  {

    emp::web::Element parent("p", "parent");
    parent << "parent";
    Doc("emp_test_container") << parent;

    emp::web::Button child(
      []() {
        EM_ASM({
          $("#child_button").attr("clicked", "yes");
        });
      },
      "child"
    );
    child.SetAttr("clicked", "no");
    child.SetAttr("id", "child_button");
    parent << child;

    emp::web::Div wrapper("wrapper");
    wrapper << "wrapper";
    child.WrapWith(wrapper);

    parent.WrapWith(
      emp::web::Div("wrapper2").SetCSS("background-color", "red")
    ).SetCSS("background-color", "blue");

    Doc("emp_test_container").Div("wrapper2") << "<br/><br/>";

  }

Step the second: make sure the DOM state ended up the way we expected it to.

:bangbang: Everything inside the EM_ASM is actually JavaScript, not C++. It’s built using the Mocha test framework and Chai BDD/TDD assertion library, so if you’re curious about the syntax kicking around or you want to learn even more fancy footwork take a peek over there.

  void Describe() override {

    // Test that the HTML components created in constructor are correct.
    EM_ASM({

      describe("Widget::WrapWith", function() {
        describe("#wrapper2", function() {
          it('should have parent #emp_test_container', function() {
            const parent_id = $("#wrapper2").parent().attr("id");
            chai.assert.equal(parent_id, "emp_test_container");
          });
          it('should have child #parent', function() {
            var children = $("#wrapper2").children();
            // Get ids of child
            var child_ids = [];
            for (i = 0; i < children.length; i++) {
              child_ids.push(children[i].getAttribute("id"));
            }
            chai.assert.include(child_ids, "parent");
            chai.assert.equal($("#wrapper2").children("#parent").length, 1);
          });
        });
        describe("#parent", function() {
          it('should have parent #wrapper2', function() {
            const parent_id = $("#parent").parent().attr("id");
            chai.assert.equal(parent_id, "wrapper2");
          });
          it('should have child #wrapper', function() {
            chai.assert.equal($("#parent").children("#wrapper").length, 1);
          });
        });
        describe("#wrapper", function() {
          it('should have parent #parent', function() {
            const parent_id = $("#wrapper").parent().attr("id");
            chai.assert.equal(parent_id, "parent");
          });
          it('should have child #child_button', function() {
            chai.assert.equal($("#wrapper").children("#child_button").length, 1);
          });
        });
        describe("#child_button", function() {
          it('should have parent #wrapper', function() {
            const parent_id = $("#child_button").parent().attr("id");
            chai.assert.equal(parent_id, "wrapper");
          });
        });
        describe("button#child_button", function() {
          it('should do stuff when clicked', function() {
            const before = $("#child_button").attr("clicked");
            chai.assert.equal(before, "no", "check initial clicked value");
            $("#child_button").trigger( "click" );
            const after = $("#child_button").attr("clicked");
            chai.assert.equal(after, "yes", "check post-click clicked value");
          });
        });
      });
    });
  }

Tie up our happy little struct.

};

All that’s left is to

  1. instantiate a emp::web::MochaTestRunner (ahoy, more boilerplate-hiding magics),
  2. give it a vector of div’s that need to be in our HTML document before our happy little struct runs,
  3. register our happy little struct with the test runner, and
  4. hit the go button.
emp::web::MochaTestRunner test_runner;
int main() {

  // put a div with id emp_test_container into our HTML document
  test_runner.Initialize({"emp_test_container"});

  test_runner.AddTest<Test_WidgetWrapWith>("Test Widget::WrapWith");
  test_runner.Run();
}

🔗 Layin’ Down the Law

Travis CI yells at us when tests don’t pass.

CodeCov yells at us when we don’t write tests. You can use their web interface to check which code lines of your pull request haven’t been tested yet.