I was writing a test that would click on a checkbox element
describe(`checkbox unchecked`, () => {
it(`should remove the answer if the checkbox is unchecked`, () => {
var form = angular.element(`<form name="form">
<div gi-checkbox-optional="answer"
gi-form-model="form">
</div>
</form>`);
checkboxOptionalScope.answer = 2;
$compile(form)(checkboxOptionalScope);
checkboxOptionalScope.$digest();
checkboxOptionalElement = form.children().eq(0);
var giCheckboxElement = checkboxOptionalElement.children()[0];
giCheckboxElement.click();
expect(checkboxOptionalScope.answer).toBe(null);
});
});
gi-checkbox-optional
is anapp.brief
directive.gi-checkbox
is anapp.common
directive that is prepended as the first child ofgi-checkbox-optional
when it is compiled.- For more information on the
gi-checkbox-optional
directive, please click here
The reason this was happening is because the app.brief
module was not including
app.common
as a dependency explicitly. That's not to say that gi-checkbox
was
the first app.common
directive to be used by app.brief
, but it does mean this
was the first time a test was written to expose the missing connection.
To understand this better we need to look at how our whole app is set up. We have
many different modules––app
, app.common
, app.brief
, etc.––each with their
own custom directives, filters, and services. Upon creation, each module does not
list every other module as dependency. Rather, the app
module includes all the
other modules as dependencies and everything is set up and injected at runtime.
This is what allows for, say, an app.common
directive to be used by an app.brief
directive without having to explicitly inject the app.common
module.
However, all of this falls to pieces when you are unit testing a specific module. Since you aren't loading up your entire app for a unit test (at least you shouldn't be)––you are only loading the module you are testing––it exposes any missing links. I take that back, it silently fails because of the missing links, leaving you banging your head against the wall.
In retrospect, it makes sense why it fails silently for a directive that's defined in another module. In the Angular 1.x world, there are no explicit definitions for what directives or services or filters a file is using. This makes it impossible for the Angular compiler to know what parts of the DOM should be treated and watched as directives, and which parts are just part of the HTML. The way that the compiler does know what to treat as a directive is based on what is defined within a module. Therefore, if you are only loading one module, the compiler will only know about the directives and services that are defined by that included module; it will simply skip over and not add watchers to the elements and attributes that are not defined in the included module. (As an aside, much of this ambiguity has been taken care of with imports in Angular 2, allowing you to definitively state what things that file will be using, but that is beyond the scope of this post.)
All of this goes to say that the fix was to add the app.common
module as a
dependency for app.brief
(source).
You do not need to change the module defined in your test file though. My first
(failed) attempt to solve this issue was to just add the app.common
module in
the beforeEach
function alongside app.brief
. I figured that I just needed to
specify all the modules I wanted to work with and the compiler would take both of
those into account when running through the code. While this did include both
the modules in the test file, the reason this doesn't work is because it didn't
allow the modules to talk to each other. The gi-checkbox
directive (defined in
app.common
) was dependent on the gi-checkbox-optional
directive (defined in
app.brief
). Including the two modules in the beforeEach
function gave the
compiler acknowledgment of them, but it did not let them depend on each other.
One thing that would be nice (and perhaps this is possible with mocks in Angular
2) is to simply include the pieces you wish to test at the top of the file.
app.common
contains––by definition––functionality that is “commonly” shared
among other modules. This makes it quite large, and therefore including the whole
module in the test file when I all need is a single directive seems like overkill.
This problem was far easier to understand and fix. It is still a bit unclear to me, but––based on some posts that I could find and what Jeff and I had talked about––the way I understand it is that when you call compile on some element with some scope, ...
Angular's implicitly defined module system strikes again...