Today I Learned

hashrocket A Hashrocket project

23 posts about #emberjs surprise

Ember nested routes and it's active class

Ember adds automagically a css class active to all link-to elements under the current route.

So let's say you have this in your router:

// app/router.js
Router.map(function() {
  this.route('blog', function() {
    this.route('post', { path: ':slug' });
  });
});

If you access a page like http://localhost:4200/blog/last-post, all the following links will have the active css class.

{{#link-to 'blog'}}Blog{{/link-to}}
{{#link-to 'blog' post}}{{post.name}}{{/link-to}}
<a id="ember661" href="/blog" class="ember-view active">Blog</a>
<a id="ember663" href="/blog/last-post" class="ember-view active">Last Post</a>

So far so good, but now, if by any reason you just want either child link or parent link with the active class you can change the parent link-to to use .index.

{{#link-to 'blog.index'}}Blog{{/link-to}}

So both blog and blog.index links to the same url, but they act differently with active class.

Testem supports parallel execution!

It actually has for a while but it had some limitations. It's recently been updated and can speed up your tests greatly! LinkedIn used this feature to take their test suite from 40 minutes to 2 minutes! 🚀😆❤️

You can define multiple test pages to split up the tests by filters:

{
   "test_page": [
     "tests/index.html?filter=acceptance",
     "tests/index.html?filter=integration",
     "tests/index.html?filter=unit"
   ]
 }

You can even set the number of parallel workers manually or set it to -1 to have testem max out whatever your CI server can handle:

{
  "parallel": "-1"
 }

If you want to do this in Ember you're in luck! ember-exam makes this super easy! Thanks Trent Willis!

$ ember exam --split=<num> --parallel

Ember queryParams default value has to be static

Today I learned that Ember queryParams do not work with computed properties. They intend to be static values and here is an issue discussion about that.

So this code will not work properly:

export default Ember.Controller.extend({
  queryParams: ['start'],
  start: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate();
});
export default Ember.Route.extend({
  queryParams: {
    start: { refreshModel: true }
  },
  model(params) {
    console.log('start', params.start);
  }
});

In this example the default value is cached and if you try to change it manually, or use a computed property with volatile, you'll see a delegate is not a function error message.

Custom QUnit assertions

QUnit is the test framework on Ember land. It's really good, but there are just 14 assertions to be used. We have ok() and notOk() so it seems that we can do whatever we want, but when some test fails the message are not good enough.

A simple string includes example:

// tests/acceptance/home-test.js
assert.ok(homePage.title.includes('Welcome to TIL'));
// => failed, expected argument to be truthy, was: false

To improve that I created this new custom assertion:

// tests/helpers/custom-asserts.js
import QUnit from 'qunit';

QUnit.assert.includes = function(text, expected) {
  let message = 'expected: "' + expected + '" to be included on: "' + text + '"';
  this.ok(text.includes(expected), message);
};

And imported in:

// tests/helpers/module-for-acceptance.js
import '../helpers/custom-asserts';

And here it is the new message on error:

// tests/acceptance/home-test.js
assert.includes(homePage.title, 'Welcome to TIL')
// => expected: "Welcome to TIL" to be included on: "Welcome to Today I Learned!"

Ember prevent nested actions to be called

If you have an element that is affected by multiple actions you can use bubbles=false to prevent that action to bubble up to the next on the hierarchy.

<div class="container-fluid" {{action 'hideNav'}}>
  <div class="navbar-header">
    <button {{action 'toggleNav' bubbles=false}} type="button" class="navbar-toggle collapsed" aria-expanded="false">
      <span class="sr-only">Toggle navigation</span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </button>
    {{link-to name 'index' class="navbar-brand"}}
  </div>

  <div class="collapse navbar-collapse">
    {{yield}}
  </div>
</div>

How to test Facebook login with Ember + Torii

Configuring Torii is super simple. The only trick is that the name of the session conflicts with ember-simple-auth. I renamed mine to torii-session.

Configure

Calling this.get('torii').open('facebook-oauth2') triggers a Facbook dialog to show in another window. There may be a better way to get the token into simple auth but I wrote a synchronous method that calls some private APIs... don't judge me.

User Login Service

Logging into Facebook triggers a postMessage to notify Torii of success or failure. Torii then POSTs the code to your backend using the adapter.

Torii Adapter

The adapter is simple enough to mock with ember-cli-mirage. I just return a token if the parameters exist.

API stub

The more challenging part (usually) is stubbing the Facebook authentication. Here is what I wanted to write:

Acceptance Test

Notice how ember-cli-page-object make the tests read so pretty!

Not surprisingly, Ember's dependency injection and some great patterns in Torii make swapping the original facebook-oauth2 service for our stubbed service straight forward.

Stubbed OAuth2 Provider

There were only slight changes needed to test Facebook auth failure. Similarly, adding Google or Twitter authentication won't require many changes.

Can't find variable: Symbol

Have you ever had an Ember CLI test that passes in Chrome but not in Phantom? Of course you have.

Did it have an error that looks something like this?

✘ ReferenceError: Can't find variable: Symbol

ReferenceError: Can't find variable: Symbol

Don't (╯°□°)╯︵ ʇno dıןɟ ! You just have to enable the Babel Symbol polyfill.

// ember-cli-build.js
...
var app = new EmberApp(defaults, {
  babel: {
    optional: ['es6.spec.symbols'],
    includePolyfill: true
  }
});

Lemme fix that for you (ヘ・_・)ヘ┳━┳

All green!

[Ember Integration tests] `this.inject` rocks!

When I first saw the inject helper in Ember Integration tests I had assumed that I would use these to stub services for testing purposes.

@rwjblue quickly pointed out to me that this is/was not their intent. And that for that I should use register for that purpose.

Instead what they are useful for is to utilize functions (helpers) found on services. Something like i18n.t. For our example I'll just assume we have a service named foo with a function upcase. And our component takes a string and presents it upcased in a span.

With that in mind we can do something like this in our integration test:

moduleForComponent('some-thing', {
  integration: true
});

test('does a thing', function(assert) {
  this.inject.service('foo');
  this.render(hbs`{{some-thing textToRender='foo' }}`);
  let result = this.foo.upcase('foo'); // 'FOO'
  
  assert.equal(this.$('span').text(), result, 'should upcase textToRender');
});

Alright, I know what you are thinking. This is a little contrived. And it is, but let's examine what this is doing a little more closely.

We injected foo to make it available in the testing context with this.foo or this.get('foo'). That's super sweet!

Need a service helper function you can do it with this.inject!

Learn more here

Cheers

[Ember Integration tests] `this.register` rocks!

In Ember Integration tests it sometimes becomes necessary to stub / mock services. Luckily, we have a few options open to us.

Let's assume that we have a service called foo that implements an API method called bar. We'll also assume that when you click on our components' a tag you trigger an action that calls the foo service's API.

Whoa, still with me?

keanu

Alright. Let's dive in!

import FooService from '<app-name>/services/foo';

moduleForComponent('some-thing',  {
  integration: true,
  beforeEach(){ 
    // extending in case the foo service has other responsibilities
    this.register('foo', FooService.extend({
      bar() { assert.ok(true); } 
    }));
  }
});

test('blah', function(assert) {
  assert.expect(1);
  this.render(hbs`{{some-thing}}`);
  this.$('a').click();
});

That wasn't so bad. We're simply ensuring that the FooService's bar API function gets called. I think this is a wonderful way to handle this.

You can find more information about this.register (and how to stub/mock services) here:

Stubbing Services Ember.js Guides

The pitfalls of `rootURL` and `baseURL`

rootURL

Notice that the rootURL parameter has a trailing /. This is required and will raise an error otherwise. There is also an internal assert in Ember that raises if any URL does not start with the rootURL. For our example, if a user tries to navigate to /app/foo the app will throw an error.

fix

Sadly, the fix for this issue must to be implemented server-side.

baseURL

The problems with baseURL are subtle but have caused the core team many issues with testing, styles, history, etc.

fix

Most of the problems are actually related to the HTML spec and are unrelated to Ember.js such as certain SVG issues. As such, the only real fix for the <base> tag is to kill it and use broccoli-asset-rev to actually change the URLs at build time.

The difference between `rootURL` and `baseURL`

These properties do very different things yet they can be used to accomplish very similar goals. For this reason they are confusing and oftentimes used interchangeably. But beware, they also have very distinct pitfalls.

baseURL

//config/environment.js
 ENV.baseURL='/app/foo';

Results in:

<!--index.html-->

<base href="/app/foo">

So when the index.html loads an asset, the browser will internally prepend baseURL:

<script src="assets/vendor.js"></script>

is treated like:

<script src="app/foo/assets/vendor.js"></script>

rootURL

//app/router.js
var Router = Ember.Router.extend({
  rootURL: '/app/foo/'
});

Unlike baseURL this will not affect the asset URLs. This parameter tells Ember that the root index route starts at /app/foo/. So even if you define a route that has a path of '/' such as:

this.route('dashboard', { path: '/' });

Ember will recognize /app/foo/ as the dashboard route.

Would you like to know more?

Read about the pitfalls of rootURL and baseURL.

Fastboot proofing your Ember addons...

I've been working a bit on trying to get Ember Weekend working with fastboot (again). The process was halted when I ran into complications around Liquid Fire and its animation dependency lib Velocity.js, which requires that a document be present.

Fastboot uses Simple DOM as its DOM and does not call it document on self (or window). Server side rendering with Fastboot has some limitations around what you are able to do as a result.

In most cases your addons should guard against any document usage with the following guard:

if (typeof document !== 'undefined') {
  // something with document here
}

Yesterday, I put a PR in (which is very much still a WIP) to help make this easier for addon authors who use EmberApp#import to get their dependencies into vendor.js. The idea is to automatically wrap all dependencies imported this way with the guard above. And allow opting-out by specifying preventDOMGuard in your app.import

app.import(this.bowerDirector + '/foo.js', { preventDOMGuard: true });

We'll see how it goes.

Module imports in EmberCLI

If you've worked with EmberCLI a bit, you've probably run across a few import lines that look like:

import Ember from 'ember';

This actually gives you the Ember global (eventually the Ember module), but silences your jshinter which is warning you that you shouldn't really use globals. This prepares you for some long term changes to EmberCLI.

Sometimes, however, you might want to be more specific.

You can do this in a couple of ways, the last of which is what I'd recommend.

First, utilizing ember-cli-shims:

import computed from 'ember-computed';
// or 
import { alias }  from 'ember-computed';

This is great. You can look at the ember-cli-shims repo to see what shims have been made available. However, I'd caution you here, it is likely that there will be changes to these shims. For example, ember-computed could change to @ember/computed. So, I'd actually reccomend...

Second, import and immediate destructure:

import Ember from 'ember';

const { computed } = Ember;

This leverages es6 style destructuring. This way your code doesn't have to change when the module import changes are complete, you'll just have to remove the destructuring assignment and update your import statements.

Cheers

Using the `wait` helper in Ember integration tests

Component integration tests are a powerful way to test your Ember components in relative isolation. They look something like this:

test('it renders', function(assert) {
  assert.expect(2);

  this.render(hbs`{{x-foo}}`);

  assert.equal(this.$().text().trim(), '');
});

Sometimes, however, you'll want to handle a bit of asynchrony. Thanks to rwjblue that is now not too big of a deal with the wait helper:

import { moduleForComponent, test } from 'ember-test-helpers';
import wait from 'ember-test-helpers/wait';

test('it works when async exists in an event/action', function(assert) {
  this.render(hbs`{{my-foo}}`);

  this.$('button').click();

  return wait()
    .then(() => {
      // assertions for after async behavior
    });
});

And that's about it! You'll need to update ember-qunit to at least: v0.4.15

Cheers!

Force Ember Data to skip cache with reload: true

In Ember Data 1.13 many of the store methods for retrieving data changed to become more constent. However I missed a key aspect of those changes and somehow it has not effected my everyday Ember until now.

The way findAll and findRecord behave in regards to caching was changed so that the behavior would facilitate the most common use case. The behavior that the Ember Data team has implemented is:

  • First time store.find is called, fetch new data
  • Next time return cached data
  • Fetch new data in the background and update

This means that your app might seem to behave fine when you migrate to Ember[-Data] 1.13 or 2.* but there could be subtle places where hard refreshes on certain show pages makes navigating back to an index page render without all the other records loaded. This just happened with the EmberWeekend site.

The solution was to simply add { reload: true } to the options of your findAll queries.

store.findAll('user', { reload: true }); //enforces getting fresh data

Inheriting from LinkComponent in Ember is amazing!

LinkComponent is the module backing the famous Ember {{ link-to }} helper. The LinkComponent will automatically add an active class to components that inherit from it as well as override their click handler to direct to a specified route.

Until quite recently in (Ember 2.1) it wasn't really possible to utilize this module without {{ link-to }}.

No longer!

My use case was simple, I wanted to have a {{ link-to tagName="li" }} with some draggable behavior. This proved to be quite difficult. Once I got our app up to 2.1 I decided to use LinkComponent

To do so is quite simple:

// app/components/x-foo.js
export default Ember.LinkComponent.extend({
  tagName: 'li'
  // your handlers here
});

Then you invoke it just as you would with a {{ link-to }}

{{# x-foo 
    pathToRoute
    model 
    myComponentAttribute1='foo' 
}}
{{/ x-foo }}

And that is it. You get for free the active class behavior and your component's click handler will have knowledge of Ember's router to send you to the route you specified at invocation.

I had a great experience pulling this in (got to remove 20 or so LoC), have fun!

Cheers,

Jonathan

Use array.[] instead of array.@each

In Ember, I knew array.@each, array.length, and array.[] would all work in my computed properties, but it turns out array.[] and array.length are much more performant, and should be used over array.@each in almost every case.

Use array.@each when you need to know about a property/properties on a member of the array changing, for example: array.@each.{foo,bar,baz} will watch for changes to the foo, bar, and baz properties.

https://blog.eladshahar.com/performant-array-computed-properties.html

Ember Truth Helpers and HTMLBars Sub Expressions

Sub expressions. Sounds kinda ominous right? Well, let me show you a few ways you can leverage ember-truth-helpers to clean up your HTMLBars templates.

Given something like:

{{# if hasComments }}
  Yay comments!
{{/ if }}

You need to have a CP to handle this:

hasComments: Ember.computed.gt('comments', 0)

Which is fine, but this is such a simple operation isn't there an easier way?

Sub Expressions to the rescue!

{{# if (gt comments.length 0) }}
  Yay comments!
{{/ if }}

Here we are using the truth helper gt and a subexpression so we don't have to use our context at all.

This reads easily and won't be subject error when some poor soul chooses a confusing variable name (as would be the case in the former example).

But that isn't all, Sub Expressions are composable.

Imagine that we need to also ensure that there is an sandBox is not specified. We could simply leverage SE's again:

{{# if (and (gt comments.length 0) (not sandBox) ) }}
  Yay comments!
{{/ if }}

Okay, so sandBox is a little bit contrived, but you see the power (I hope).

Cheers,

Jonathan

Let's talk about Ember's {{ mut }} HTMLBars helper

You've likely seen code that looks like the below since Ember 1.13 was released:

{{ my-component updateAction=(action (mut model.value)) }}

And been confused as to what this means. Luckily, @rwjblue explained in the EmberJS Community slack that:

Which kinda clears things up. It'd be a little easier if we could correlate the mut helper with something a bit more familiar. The above my-component snippet could be rewritten like so:

export default Ember.Controller.extend({
  actions: {
    updateValue(newValue) {
      this.set('model.value', newValue);
    }
  }
});

and

{{my-component updateAction=(action 'updateValue')}}

To restate the exposition from @rwjblue above... The mut helper returns a mutable object which is an object that responds to update. The action helper knows about these objects and knows to call update (passing in the new value) when given a mutable object .

This is a great shorthand that enables us to write a LOT less boilerplate code, but does require a little bit of understanding so that we don't confuse each other.


The above information was shamelessly stolen from the EmberJS Community Slack. If you'd like to participate join by going here: Ember Community Slack Invite Page to get your invite.

Cheers!

Always triggering the model hook in a route

In a link-to helper, many times you already have the model you're linking to in the Ember Data Store, so you pass in the model directly to the link-to. This will cause the route to skip the model hook because Ember Data already knows what the model is. If for some reason you want to ensure the model hook is always called, pass in model.id instead of model.

Just remember that this is done intentionally for performance, link-to model is always going to be faster than link-to model.id, because the model is already loaded.

Yielded closure actions as an API for a Component

From within x-parent component that has an action update that (for example) returns a promise:

{{ yield (action 'update') }}

You can then tap into that update action from within the block section like so:

{{ x-parent as |parentUpdateAction| }}
  {{ x-child update=parentUpdateAction }}
{{/ x-parent }}

Then from within the click handler on x-child you could utilize the promise returned from the closure action:

click() {
  this.attrs.update().then(function(){
    // do something like transition away
  }).catch(function(){
    // update ui to handle error case
  });
}

Yielding closure actions like this is powerful. Being able to utilize the return value from a closure action is great for ensuring that the right section of your application is responsible for the things that it cares about.

I've recently talked about this on Ember Weekend.

Specifically I think that you can utilize this feature to create Ember components that yield their API. Treating these yielded actions as the component's API can allow you to create template DSL's that are really something special.

Cheers, Jonathan