<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Balint Erdi's tech blog</title>
  <subtitle>Tech articles, mostly about Ember.js</subtitle>
  <id>https://balinterdi.com/blog</id>
  <link href="https://balinterdi.com/blog"/>
  <link href="https://balinterdi.com/blog/feed.xml" rel="self"/>
  <updated>2024-11-15T10:16:00+00:00</updated>
  <author>
    <name>Balint Erdi</name>
  </author>
  <entry>
    <title>The Rock &amp; Roll with Ember band - Michal Bryxí</title>
    <link rel="alternate" href="https://balinterdi.com/blog/the-rock-roll-with-ember-band-michal-bryxi/"/>
    <id>https://balinterdi.com/blog/the-rock-roll-with-ember-band-michal-bryxi/</id>
    <published>2024-11-15T10:16:00+00:00</published>
    <updated>2026-03-25T08:14:13+00:00</updated>
    <author>
      <name>Balint Erdi</name>
    </author>
    <content type="html">&lt;p&gt;The &amp;quot;Rock &amp;amp; Roll with Ember band&amp;quot; interview series introduces readers of &lt;a href="https://balinterdi.com/rock-and-roll-with-emberjs"&gt;the similarly titled book&lt;/a&gt;. They tell us about how they got acquainted with the framework, how they learned it, what they use it for, and share a few more details about the non-tech related side of their lives. I hope you enjoy reading them as much as I did.&lt;/p&gt;

&lt;h4&gt;Could you introduce yourself in a few sentences?&lt;/h4&gt;

&lt;p&gt;&lt;img src="/images/blog/rr-ember-band/michal-bryxi.png" alt="Photo of Michal Bryxí"&gt;&lt;/p&gt;

&lt;p&gt;Hi, I&amp;#39;m Michal Bryxí, or Míša. You might know me from EmberJS Discord, conferences or just the internet space. I&amp;#39;m quite a keen advocate of good UX, not blaming users for the faults of the IT industry and spending my time in any other way than at the computer.&lt;/p&gt;

&lt;h4&gt;Which part of the world are you from?&lt;/h4&gt;

&lt;p&gt;Originally I come from Czechia, but I&amp;#39;ve been living in UK for ~7 years and now I&amp;#39;m based in Interlaken - Switzerland.&lt;/p&gt;

&lt;h4&gt;When and how did your Ember journey begin? How did you learn about the framework?&lt;/h4&gt;

&lt;p&gt;Because I used &lt;a href="https://www.puppet.com/why-puppet/use-cases/continuous-configuration-automation"&gt;Puppet&lt;/a&gt; (configuration management software) in my previous company, I thought it would be cool to start working for Puppet (the company). After I started they told me that they actually need me on some frontend project and I will be working with something called Ember.&lt;/p&gt;

&lt;p&gt;Right after I started, the company shipped me to Portland to attend EmberConf (2016). On the long way in the plane I was hacking away on what would become my biggest project using Ruby on Rails. But since it was a very long flight I thought I should give Ember a try to see if I can use it for fun projects &amp;amp; learn something for work.&lt;/p&gt;

&lt;p&gt;It was a perfect match and I&amp;#39;m happy to report that said app is still running without any major rewrite ever since.&lt;/p&gt;

&lt;h4&gt;How did reading the Rock &amp;amp; Roll with Ember book help you? Can you recall something that you learned from it?&lt;/h4&gt;

&lt;p&gt;The basics. I&amp;#39;m not very patient person and I tend to jump from thing to thing when building something, often times missing a lot of crucial details in doing so. Rock &amp;amp; Roll with Ember gives the end to end story and shows all the little pieces that come into play when building EmberJS applications.&lt;/p&gt;

&lt;h4&gt;Other than reading the book, how did you learn to “speak&amp;quot; Ember?&lt;/h4&gt;

&lt;p&gt;Projects, projects, projects. I maintain a few tiny addons which helped me understand the v2 addon evolution. I have tons of apps around that have their quirks and specifics. For myself, I deeply believe that I could not be where I am without working on &amp;quot;my&amp;quot; stuff outside the regular &amp;quot;work time&amp;quot;.&lt;/p&gt;

&lt;h4&gt;What Ember feature/RFC/etc. are you most excited about?&lt;/h4&gt;

&lt;p&gt;This changes all the time to be honest. But I&amp;#39;m super happy that ember-data is getting a big overhaul as I consider it the most important part for all my projects. Dropping custom build story and plugging into the vite world sounds super promising. And for the future I&amp;#39;m curious about dropping controllers and overhaul of the routing system.&lt;/p&gt;

&lt;h4&gt;Is there something you’d like to see covered or explained in more detail in the book?&lt;/h4&gt;

&lt;p&gt;Short answer: Ember Data. I don&amp;#39;t consider building an UI that displays stuff and reacts to user inputs that hard. As you can (mostly) dictate all the bits and pieces. But state mutation, persistence, updates ... that&amp;#39;s something where a lot of things can go sideways.&lt;/p&gt;

&lt;h4&gt;Are there any (side-)projects that you’ve built in Ember? What is it (are they) about?&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.beauty-pay.cz/"&gt;E2E Beauty Salon management solution&lt;/a&gt; (hello Phorest :)). I&amp;#39;ve been working on it on and off for over a decade and it&amp;#39;s still going strong.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://the-mountains-are-calling.netlify.app/?zoom=15"&gt;Private location tracking visualisation tool&lt;/a&gt;. This was built in ~two weekends.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://winds-mobi-client-web.netlify.app/map"&gt;POC frontend replacement for live winds tracking&lt;/a&gt;. I hope to replace the original Angular app; this also probably took me ~2 weekends.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Were there any challenges or stumbling blocks while you were building your app(s)?&lt;/h4&gt;

&lt;p&gt;The usual:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation

&lt;ul&gt;
&lt;li&gt;Sometimes things are missing&lt;/li&gt;
&lt;li&gt;Sometimes links within the docs are broken&lt;/li&gt;
&lt;li&gt;Sometimes search in the docs is not working&lt;/li&gt;
&lt;li&gt;Sometimes you stumble upon DenverCoder9 comment&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Addons

&lt;ul&gt;
&lt;li&gt;There are quite a few that used to work, but are deteoriating now&lt;/li&gt;
&lt;li&gt;Some addons have quite a list of PRs from folks, but nobody merges them for year(s)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;NullVoxPopulli is on vacation

&lt;ul&gt;
&lt;li&gt;Then, I think, we all feel like our productivity goes down&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;What do you like to do in your free time?&lt;/h4&gt;

&lt;p&gt;Trail running &amp;amp; paragliding&lt;/p&gt;

&lt;h4&gt;If people would like to follow you (or your project), where can they do so?&lt;/h4&gt;

&lt;p&gt;Right now I&amp;#39;m probably most active &lt;a href="https://veganism.social/@MichalBryxi"&gt;on my Mastodon&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;Is there something else you’d like to say?&lt;/h4&gt;

&lt;p&gt;I love folks from around EmberJS community. The love, patience, thoughtfulness and general community vibe. You are the best!&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;If you&amp;#39;ve read the book, and would like to give a similar interview, please drop me a line at &lt;a href="mailto:rarwe-book@balinterdi.com?Subject=Rock%20and%20Roll%20with%20Ember%20interview"&gt;rarwe-book@balinterdi.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you&amp;#39;d like to join the Rock &amp;amp; Roll with Ember band, &lt;a href="https://balinterdi.com/rock-and-roll-with-emberjs"&gt;check out the book of the same name&lt;/a&gt; to learn Ember (and be part of a band).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Testing async DOM functions in Ember.js - a custom waiter alternative</title>
    <link rel="alternate" href="https://balinterdi.com/blog/testing-async-dom-functions-in-ember-js-custom-waiter-alternatives/"/>
    <id>https://balinterdi.com/blog/testing-async-dom-functions-in-ember-js-custom-waiter-alternatives/</id>
    <published>2024-01-18T09:18:00+00:00</published>
    <updated>2026-03-25T08:14:13+00:00</updated>
    <author>
      <name>Balint Erdi</name>
    </author>
    <content type="html">&lt;p&gt;In &lt;a href="/blog/testing-async-dom-functions-in-ember-js/"&gt;my last blog post&lt;/a&gt;, we wrote a custom test waiter to reliably test a component that relied on a DOM callback that wasn&amp;#39;t integrated into Ember&amp;#39;s testing framework.&lt;/p&gt;

&lt;p&gt;In the end, we saw that the test also passed without the test waiter when we added an extra &lt;code&gt;await settled()&lt;/code&gt; after the component was rendered.&lt;/p&gt;

&lt;p&gt;This seemed counter-intuitive, and Ember even called us out on it via a lint error since &lt;code&gt;await render()&lt;/code&gt; already calls &lt;code&gt;settled&lt;/code&gt; internally.&lt;/p&gt;

&lt;p&gt;This post discovers why this passes and offers an alternative to implementing a custom test waiter.&lt;/p&gt;

&lt;p&gt;To harness the collective mind of the Ember ecosystem, &lt;a href="https://discuss.emberjs.com/t/why-does-settled-make-a-test-pass-after-render/20358/3"&gt;I asked the question on Discuss&lt;/a&gt;. The responses confirmed my suspicion about why it passes, and I also received a few great ideas about alternatives.&lt;/p&gt;

&lt;h2&gt;Buying just a little bit of time&lt;/h2&gt;

&lt;p&gt;The test looks like this:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Integration | Component | avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  setupRenderingTest(hooks);
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;It falls back to initials when the image cannot be loaded&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    await render(
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;      hbs&lt;span class="error"&gt;`&lt;/span&gt;&amp;lt;Avatar &lt;span class="error"&gt;@&lt;/span&gt;name=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Marquis de Carabas&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;url=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/images/non-existent.webp&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; /&amp;gt;&lt;span class="error"&gt;`&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    await settled();
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).hasText(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;MC&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The extra &lt;code&gt;settled&lt;/code&gt; call takes very little time, but seems enough for the DOM function to complete in almost all cases (I re-ran the test about 50 times, and it passed every time).&lt;/p&gt;

&lt;p&gt;So this fixes the test by a happy incident: Ember doesn&amp;#39;t wait for the callback to complete, but the few milliseconds added by the extra settled is just enough for this, so the assertion finds the component in the expected state.&lt;/p&gt;

&lt;p&gt;Short of implementing the waiter, can we do better?&lt;/p&gt;

&lt;h2&gt;Wait for it&lt;/h2&gt;

&lt;p&gt;One of the great things about Ember is that it takes testing seriously and thus allows us great tools to test our apps. Unsurprisingly, it has a test helper for this case called &lt;a href="https://github.com/emberjs/ember-test-helpers/blob/5dca0a8a41e57d47c48af30990a11d3b40b86870/addon/addon-test-support/%40ember/test-helpers/dom/wait-for.ts"&gt;&lt;code&gt;waitFor&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The docstring of waitFor tells a great deal about what we can use it for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Used to wait for a particular selector to appear in the DOM. Due to the fact
  that it does not wait for general settledness, this is quite useful for testing
  interim DOM states (e.g. loading states, pending promises, etc).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without the waiter, general settledness doesn&amp;#39;t help us, so &lt;code&gt;waitFor&lt;/code&gt; works great for our use case: we want to wait for the initials to appear so that we know that our fallback worked correctly:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Integration | Component | avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  setupRenderingTest(hooks);
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;It falls back to initials when the image cannot be loaded&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    await render(
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;      hbs&lt;span class="error"&gt;`&lt;/span&gt;&amp;lt;Avatar &lt;span class="error"&gt;@&lt;/span&gt;name=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Marquis de Carabas&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;url=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/images/non-existent.webp&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; /&amp;gt;&lt;span class="error"&gt;`&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    await waitFor(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).hasText(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;MC&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  });
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This works great! The default timeout, 1000ms, is more than enough for the callback passed to &lt;code&gt;onError&lt;/code&gt; to run and render the initials, so this makes the test pass (you can pass another timeout value if you specify a &lt;code&gt;timeout&lt;/code&gt; parameter).&lt;/p&gt;

&lt;p&gt;(To add another tool to your testing toolbelt: &lt;code&gt;waitFor&lt;/code&gt; calls another waiter function, &lt;a href="https://github.com/emberjs/ember-test-helpers/blob/5dca0a8a41e57d47c48af30990a11d3b40b86870/addon/addon-test-support/%40ember/test-helpers/wait-until.ts"&gt;&lt;code&gt;waitUntil&lt;/code&gt;&lt;/a&gt;, which is even more potent as you can wait for more things than the appearance of a selected DOM element)&lt;/p&gt;

&lt;h2&gt;Custom waiter vs. waitFor&lt;/h2&gt;

&lt;p&gt;It&amp;#39;s important to note that the mechanism by which the extra &lt;code&gt;settled()&lt;/code&gt; and &lt;code&gt;waitFor()&lt;/code&gt; make the test pass is similar.&lt;/p&gt;

&lt;p&gt;Although &lt;code&gt;waitFor&lt;/code&gt; is specific about what it wants to see in the DOM, it doesn&amp;#39;t guarantee that our implementation works: if it takes more than 1000 msecs for our &lt;code&gt;onError&lt;/code&gt; callback plus the re-render to run, it will fail.&lt;/p&gt;

&lt;p&gt;The custom waiter solution is more reliable: as Ember&amp;#39;s testing framework doesn&amp;#39;t execute the assertion before the waiter has finished, it guarantees there will be no false negatives (the test saying the implementation doesn&amp;#39;t work when it does).&lt;/p&gt;

&lt;p&gt;So, which one should you pick?&lt;/p&gt;

&lt;p&gt;You probably won&amp;#39;t like the answer, but I have to say: &amp;quot;it depends.&amp;quot; :)&lt;/p&gt;

&lt;p&gt;I like the actual guarantee that the test waiter brings, I think it&amp;#39;s the &amp;quot;real&amp;quot;, the most reliable solution.&lt;/p&gt;

&lt;p&gt;On the other hand, it&amp;#39;s certainly more ceremony than &lt;code&gt;waitFor&lt;/code&gt;, and you have to add a non-trivial amount of code to the component &amp;quot;just&amp;quot; to test it (see the &lt;a href="/blog/testing-async-dom-functions-in-ember-js/"&gt;&amp;quot;Adding code to make something testable&amp;quot;&lt;/a&gt; section in the last post).&lt;/p&gt;

&lt;p&gt;And if you don&amp;#39;t have control over the code you test, you can&amp;#39;t add a waiter to it, so your only option is &lt;code&gt;waitFor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;waitFor&lt;/code&gt; is also great for testing interim DOM state (as the code comment says), like verifying that a toast message is shown. In those cases, you can&amp;#39;t use a custom waiter because you don&amp;#39;t want to wait for everything to settle.&lt;/p&gt;

&lt;p&gt;So as not to avoid deciding on this one, I&amp;#39;d add a custom waiter, but I wouldn&amp;#39;t hesitate for a moment to approve a PR that uses a &lt;code&gt;waitFor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to &lt;a href="https://discuss.emberjs.com/u/dknutsen/summary"&gt;Dan&lt;/a&gt; and &lt;a href="https://discuss.emberjs.com/u/boris/summary"&gt;Boris&lt;/a&gt; for replying to my Discuss blog post. It served as the content for this blog post.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Testing async DOM functions in Ember.js</title>
    <link rel="alternate" href="https://balinterdi.com/blog/testing-async-dom-functions-in-ember-js/"/>
    <id>https://balinterdi.com/blog/testing-async-dom-functions-in-ember-js/</id>
    <published>2023-12-18T10:41:00+00:00</published>
    <updated>2026-03-25T08:14:13+00:00</updated>
    <author>
      <name>Balint Erdi</name>
    </author>
    <content type="html">&lt;p&gt;&amp;quot;Just use the platform&amp;quot; - they say, referring to not reinventing the wheel for a browser feature in our favorite JavaScript framework if the browser already provides it.&lt;/p&gt;

&lt;p&gt;And they are right! If a browser spec is implemented by the browsers we care about, it makes perfect sense to use it. We have to consider a few other things, though, one of which is whether the feature can be properly tested.&lt;/p&gt;

&lt;p&gt;In this post, we&amp;#39;ll see a specific example of using a browser feature, and making it testable in Ember.js.&lt;/p&gt;

&lt;h2&gt;Showing an avatar – or the initials&lt;/h2&gt;

&lt;p&gt;An avatar component is a recurring element in web applications. We want to show a user&amp;#39;s face and name together, in a specific layout (most often, next to each other). Such a component should take a name and a URL to the image:&lt;/p&gt;
&lt;div class="highlight handlebars  one-line"&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;Avatar&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;name&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Marquis de Carabas&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;url&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/photos/carabas.png&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Simple enough, but what happens if the image can&amp;#39;t be loaded? We could rely on the browser showing the &amp;quot;broken image&amp;quot; symbol but I think in this case we can do better than just &amp;quot;using the platform&amp;quot;: we can show the user&amp;#39;s initials instead:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/blog/testing-async-dom-functions/broken-image-symbol.png" alt="Image without fallback"&gt;
&lt;img src="/images/blog/testing-async-dom-functions/image-with-initials-fallback.png" alt="Image with initials fallback"&gt;&lt;/p&gt;

&lt;p&gt;Right, but how do we know that the image cannot be loaded? Turns out there is an &lt;code&gt;onError&lt;/code&gt; callback on &lt;code&gt;img&lt;/code&gt; tags which is only called in this exact case. Based on that, the implmentation of our &lt;code&gt;Avatar&lt;/code&gt; component can look something like this:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; AvatarComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked isShowingInitials = &lt;span class="predefined-constant"&gt;false&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  get initials() {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    const [first, ...rest] = &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.name.split(&lt;span class="regexp"&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="char"&gt;\s&lt;/span&gt;&lt;span class="content"&gt;+&lt;/span&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    const last = rest.pop();
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; [first, last].map((name) =&amp;gt; name[&lt;span class="integer"&gt;0&lt;/span&gt;]).join(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  showInitials() {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.isShowingInitials = &lt;span class="predefined-constant"&gt;true&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;onError&lt;/code&gt; callback can be hooked up to the &lt;code&gt;showInitials&lt;/code&gt; action. We only want to show the initials if the image is not available:&lt;/p&gt;
&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;{{!-- avatar.hbs --}}&lt;/span&gt; 
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;avatar&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;if&lt;/span&gt; &lt;span class="attribute-name"&gt;this.isShowingInitials&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;initials&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.initials&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;else&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;img&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;src&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;url&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-value"&gt;onError&lt;/span&gt;&lt;span class="error"&gt;=&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.showInitials&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;alt&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;concat&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;name&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="error"&gt;'&lt;/span&gt;&lt;span class="attribute-name"&gt;s&lt;/span&gt; &lt;span class="attribute-name"&gt;avatar&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;if&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;name&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;name&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Everything looks good but we want to add a test to make sure this stellar feature doesn&amp;#39;t break in the future.&lt;/p&gt;

&lt;p&gt;With the hopefulness of a junior developer, we write a simple test for the failure case. We already saw it works in the real app, so it should be as simple as centering an element vertically in a container:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Integration | Component | Avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  setupRenderingTest(hooks);
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;It falls back to initials when the image cannot be loaded&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    await render(
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      hbs&lt;span class="error"&gt;`&lt;/span&gt;&amp;lt;Avatar &lt;span class="error"&gt;@&lt;/span&gt;name=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Marquis de Carabas&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;url=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/images/non-existent.webp&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; /&amp;gt;&lt;span class="error"&gt;`&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).hasText(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;MC&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Reality beckons: all is not so simple in the world of software, and our test fails.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/blog/testing-async-dom-functions/test-fails.png" alt="Failing test"&gt;&lt;/p&gt;

&lt;p&gt;However, if we insert an &lt;code&gt;await this.pauseTest()&lt;/code&gt; after the component is rendered, we see that the initials are correctly shown. Furthermore, if we release the breaks by typing &lt;code&gt;resumeTest()&lt;/code&gt; on the console, the test passes!&lt;/p&gt;

&lt;p&gt;What is this sorcery?&lt;/p&gt;

&lt;h2&gt;What do test helpers wait for?&lt;/h2&gt;

&lt;p&gt;One of the most educational moments in my Ember.js learning journey was to peek into what Ember&amp;#39;s test helpers wait for before moving on to the next test line to execute. The foundation of those helpers is called &lt;code&gt;wait&lt;/code&gt; and most test helpers return a call to that function. It tells us what the framework waits for before moving on. &lt;a href="https://github.com/emberjs/ember.js/blob/a93e12cafa29382320f2a40f9b2a3218f31327b0/packages/ember-testing/lib/helpers/wait.ts"&gt;Go take a look yourself&lt;/a&gt;, it&amp;#39;s a very short file and very well written.&lt;/p&gt;

&lt;p&gt;You&amp;#39;ll see that test helpers will wait for the router to finish transitioning, pending ajax requests, scheduled timers and the runloop, and something called waiters.&lt;/p&gt;

&lt;p&gt;None of these cover the case of the &lt;code&gt;onError&lt;/code&gt; callback so the &lt;code&gt;assert.dom&lt;/code&gt; assertion runs before the initials were rendered and we get a failing test.&lt;/p&gt;

&lt;p&gt;The solution (or at least one solution) for this is to integrate the &lt;code&gt;onError&lt;/code&gt; DOM element callback into Ember&amp;#39;s testing mechanism by defining a custom test waiter.&lt;/p&gt;

&lt;h2&gt;Adding a custom test waiter&lt;/h2&gt;

&lt;p&gt;If we define a waiter for our component, Ember&amp;#39;s testing mechanism will wait for it to complete before moving on which will make the test pass.&lt;/p&gt;

&lt;p&gt;We start by creating the waiter in the module&amp;#39;s scope:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Component from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/component&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { action } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/object&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { tracked } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/tracking&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { buildWaiter } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/test-waiters&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;const waiter = buildWaiter(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; AvatarComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The waiter has two functions: &lt;code&gt;beginAsync&lt;/code&gt; should be called to get a token and start the waiting:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; AvatarComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked isShowingInitials = &lt;span class="predefined-constant"&gt;false&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  constructor() {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="reserved"&gt;super&lt;/span&gt;(...&lt;span class="local-variable"&gt;arguments&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.waiterToken = waiter.beginAsync();
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;, and &lt;code&gt;endAsync&lt;/code&gt; should be called when the waiter completes, passing it the token we received earlier:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; AvatarComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  showInitials() {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.isShowingInitials = &lt;span class="predefined-constant"&gt;true&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    waiter.endAsync(&lt;span class="local-variable"&gt;this&lt;/span&gt;.waiterToken);
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Once we do that, the waiter will now make Ember&amp;#39;s testing framework wait it finishes, and only then move on to check the assertion. Our test is now reliably green.&lt;/p&gt;

&lt;h2&gt;Covering the happy path&lt;/h2&gt;

&lt;p&gt;We might think it&amp;#39;s time to call it a day and have some fun but then we&amp;#39;d miss the important non-corner case. What if the image is available at the provided URL and can be loaded? Well, let&amp;#39;s write a test for it:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Integration | Component | Avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  setupRenderingTest(hooks);
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;It renders the image when the image can be loaded&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    await render(
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      hbs&lt;span class="error"&gt;`&lt;/span&gt;&amp;lt;Avatar &lt;span class="error"&gt;@&lt;/span&gt;name=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Chelsea Hagon&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;url=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/images/chelsea.avif&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; /&amp;gt;&lt;span class="error"&gt;`&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;img&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The image is displayed&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).doesNotExist();
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Assuming that the image is available at &lt;code&gt;/images/chelsea.avif&lt;/code&gt; (it will be if we copy it to the &lt;code&gt;/public/images&lt;/code&gt; folder in our app), this test should pass. Instead, it hangs – it never runs to completion.&lt;/p&gt;

&lt;p&gt;We forgot (or maybe you didn&amp;#39;t ;) ) that &lt;code&gt;endAsync&lt;/code&gt; is only called from &lt;code&gt;showInitials&lt;/code&gt; when the image cannot be loaded. When it can be, the waiter just waits forever and the test never moves to the next line, the assertion.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;img&lt;/code&gt; element also has an &lt;code&gt;onLoad&lt;/code&gt; callback which is triggered when the image has been loaded, so we can use that for calling &lt;code&gt;endAsync&lt;/code&gt; on the happy path:&lt;/p&gt;
&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;avatar&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;if&lt;/span&gt; &lt;span class="attribute-name"&gt;this.isShowingInitials&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;initials&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.initials&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;else&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;img&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;src&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;url&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-value"&gt;onLoad&lt;/span&gt;&lt;span class="error"&gt;=&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.completeWaiter&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;onError&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.showInitials&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-value"&gt;alt&lt;/span&gt;&lt;span class="error"&gt;=&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;concat&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;name&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="error"&gt;'&lt;/span&gt;&lt;span class="attribute-name"&gt;s&lt;/span&gt; &lt;span class="attribute-name"&gt;avatar&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;if&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;name&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;name&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;completeWaiter&lt;/code&gt; action is only there to make sure we complete the async operation:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar.js&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; AvatarComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  completeWaiter() {
&lt;span class="line-numbers"&gt;&lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    waiter.endAsync(&lt;span class="local-variable"&gt;this&lt;/span&gt;.waiterToken);
&lt;span class="line-numbers"&gt;&lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Our second test now also passes.&lt;/p&gt;

&lt;p&gt;There is a small change which I think is worth doing – moving the image used for testing in a folder that clearly shows its purpose. One called testing is clear enough :) Otherwise, someone might delete the image when they see it&amp;#39;s not used anywhere in the app. If we do that, we have to update the image path in the test:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// avatar-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Integration | Component | Avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  setupRenderingTest(hooks);
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;It renders the image when the image can be loaded&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    await render(
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      hbs&lt;span class="error"&gt;`&lt;/span&gt;&amp;lt;Avatar &lt;span class="error"&gt;@&lt;/span&gt;name=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Chelsea Hagon&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;url=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/images/testing/chelsea.avif&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; /&amp;gt;&lt;span class="error"&gt;`&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;img&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The image is displayed&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).doesNotExist();
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;Adding code to make something testable&lt;/h2&gt;

&lt;p&gt;At this point you could justifiedly point out that we&amp;#39;re only adding code, all the code related to the custom waiter, to be able to test our &lt;code&gt;avatar&lt;/code&gt; component. Isn&amp;#39;t it wrong to make the code more complex for the purpose of testability? I think the value that having our component be tested and guarded against regression makes up for the fact that the code is slightly more complex. That said, it&amp;#39;d be great not having to add any code to be able to do that. Can we?&lt;/p&gt;

&lt;h2&gt;Does &lt;code&gt;settled&lt;/code&gt; settle it?&lt;/h2&gt;

&lt;p&gt;As I was going through the code for a second time for this post, I pulled out the joker that on occasions helps tests pass that otherwise would have failed for the same reason as explained in this post: when Ember&amp;#39;s testing framework doesn&amp;#39;t wait for an async event to complete before executing the next testing instruction. Lo and behold: it did work this time, too!&lt;/p&gt;

&lt;p&gt;Inserting a call to &lt;code&gt;settled()&lt;/code&gt; (imported from &lt;code&gt;@ember/test-helpers&lt;/code&gt;) makes the test pass reliably:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Integration | Component | avatar&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  setupRenderingTest(hooks);
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;It falls back to initials when the image cannot be loaded&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    await render(
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;      hbs&lt;span class="error"&gt;`&lt;/span&gt;&amp;lt;Avatar &lt;span class="error"&gt;@&lt;/span&gt;name=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;Marquis de Carabas&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;url=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;/images/non-existent.webp&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; /&amp;gt;&lt;span class="error"&gt;`&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    await settled();
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;.initials&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).hasText(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;MC&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;But... it shouldn&amp;#39;t. &lt;a href="https://github.com/emberjs/ember-test-helpers/blob/5dca0a8a41e57d47c48af30990a11d3b40b86870/addon/addon-test-support/%40ember/test-helpers/settled.ts"&gt;&lt;code&gt;settled&lt;/code&gt;&lt;/a&gt; doesn&amp;#39;t add anything that should make Ember wait for the callback to complete, so the things I explained in the &amp;quot;What do test helpers wait for?&amp;quot; section still hold. Actually, we even get a squiggly (a lint error) in our editor because our code now violates the &lt;a href="https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-settled-after-test-helper.md"&gt;ember/no-settled-after-test-helper rule&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight   one-line"&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&amp;gt; Do not call `settled()` right after a test helper that already calls it internally.
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And indeed, &lt;a href="https://github.com/emberjs/ember-test-helpers/blob/5dca0a8a41e57d47c48af30990a11d3b40b86870/addon/addon-test-support/%40ember/test-helpers/setup-rendering-context.ts#L101-L209"&gt;the &lt;code&gt;render&lt;/code&gt; call does call &lt;code&gt;settled&lt;/code&gt; under the hood&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So I&amp;#39;m not sure why it works and it seems like it shouldn&amp;#39;t and maybe I just got lucky all the times I ran the test?&lt;/p&gt;

&lt;p&gt;I&amp;#39;ll try to find out but in the meantime, I suggest adding a test waiter. It&amp;#39;s a very useful tool in one&amp;#39;s Ember.js arsenal and you can use it in several contexts to let Ember&amp;#39;s testing know about an added async operation.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using simple functions as helpers in Ember.js</title>
    <link rel="alternate" href="https://balinterdi.com/blog/using-simple-functions-as-helpers-in-emberjs/"/>
    <id>https://balinterdi.com/blog/using-simple-functions-as-helpers-in-emberjs/</id>
    <published>2023-11-13T11:00:00+00:00</published>
    <updated>2026-03-25T08:14:13+00:00</updated>
    <author>
      <name>Balint Erdi</name>
    </author>
    <content type="html">&lt;p&gt;Possibly one of the least known and most useful things in Ember is the ability &lt;a href="https://blog.emberjs.com/ember-4-5-released"&gt;to use simple functions as template helpers which was introduced in Ember 4.5&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before, if you wanted to use a function in the template, you needed to create a helper after which you could use it in all of your templates.  If you defined a &lt;code&gt;format-currency&lt;/code&gt; helper, you could call it as &lt;code&gt;{{format-currency value}}&lt;/code&gt; from everywhere.&lt;/p&gt;

&lt;p&gt;Handy, but I can think of two downsides. First, you need a special kind of function, a helper which is an Emberism. Second, and I think this is the bigger downside, it&amp;#39;s hard to say where a certain helper comes from. It could&amp;#39;ve been created by one of the developers of the app in which case you&amp;#39;d find the helper in &lt;code&gt;app/helpers&lt;/code&gt;. But it also could be in any of your dependencies and good luck searching through all of them.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s see an example of the modern, more explicit way.&lt;/p&gt;

&lt;h2&gt;A simple currency converter&lt;/h2&gt;

&lt;p&gt;Let&amp;#39;s say we want to display currencies, select a base currency, and display the value of each in the base currency.&lt;/p&gt;

&lt;p&gt;We start by just having the conversion rates (compared to the EUR), and listing the currencies and a dropdown to change the base currency:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Component from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/component&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CurrencyConverterComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  currencies = {
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;EUR&lt;/span&gt;: &lt;span class="integer"&gt;1&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;USD&lt;/span&gt;: &lt;span class="float"&gt;1.06&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;GBP&lt;/span&gt;: &lt;span class="float"&gt;0.87&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;CHF&lt;/span&gt;: &lt;span class="float"&gt;0.95&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;BTC&lt;/span&gt;: &lt;span class="float"&gt;0.000036&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currencies-container&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;header&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="attribute-name"&gt;for&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;Currencies&lt;span class="tag"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;select&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;name&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currency-dropdown&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;each-in&lt;/span&gt; &lt;span class="attribute-name"&gt;this.currencies&lt;/span&gt; &lt;span class="attribute-name"&gt;as&lt;/span&gt; &lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="attribute-name"&gt;symbol&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;option&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;each-in&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currencies&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;each-in&lt;/span&gt; &lt;span class="attribute-name"&gt;this.currencies&lt;/span&gt; &lt;span class="attribute-name"&gt;as&lt;/span&gt; &lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="attribute-name"&gt;symbol&lt;/span&gt; &lt;span class="attribute-name"&gt;rate&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currency-price&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;        &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;        &lt;span class="tag"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;rate&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;each-in&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="/images/blog/using-simple-functions-as-helpers/currencies-to-eur.png" alt="Simple list of currencies"&gt;&lt;/p&gt;

&lt;p&gt;Simple enough, but we&amp;#39;re missing the ability to change the base currency, so let&amp;#39;s make some changes.&lt;/p&gt;

&lt;h3&gt;Allowing to change the base currency&lt;/h3&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CurrencyConverterComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  eurRates = {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;EUR&lt;/span&gt;: &lt;span class="integer"&gt;1&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;USD&lt;/span&gt;: &lt;span class="float"&gt;1.06&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;GBP&lt;/span&gt;: &lt;span class="float"&gt;0.87&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;CHF&lt;/span&gt;: &lt;span class="float"&gt;0.95&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;BTC&lt;/span&gt;: &lt;span class="float"&gt;0.000036&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked baseCurrency = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;EUR&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  get currencyRates() {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; Object.entries(&lt;span class="local-variable"&gt;this&lt;/span&gt;.eurRates).map(([currency, rate]) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;symbol&lt;/span&gt;: currency,
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;rate&lt;/span&gt;: rate / &lt;span class="local-variable"&gt;this&lt;/span&gt;.eurRates[&lt;span class="local-variable"&gt;this&lt;/span&gt;.baseCurrency],
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;      };
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;  updateBaseCurrency(event) {
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.baseCurrency = event.target.value;
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Before we&amp;#39;d assumed the base currency would always be the EUR but if it can be changed, we need to show the rates compared to the base currency which is what &lt;code&gt;currencyRates&lt;/code&gt; does.&lt;/p&gt;

&lt;p&gt;The template also needs to change, though only slightly:&lt;/p&gt;
&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currencies-container&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;header&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="attribute-name"&gt;for&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;Currencies&lt;span class="tag"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;select&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;name&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currency-dropdown&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;on&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;change&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt; &lt;span class="attribute-name"&gt;this.updateBaseCurrency&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt; &lt;span class="attribute-name"&gt;this.currencyRates&lt;/span&gt; &lt;span class="attribute-name"&gt;as&lt;/span&gt; &lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="attribute-name"&gt;currency&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;option&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;currency.symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currencies&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt; &lt;span class="attribute-name"&gt;this.currencyRates&lt;/span&gt; &lt;span class="attribute-name"&gt;as&lt;/span&gt; &lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="attribute-name"&gt;currency&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currency-price&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;        &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;currency.symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;        &lt;span class="tag"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;currency.rate&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We want to bind the &lt;code&gt;selected&lt;/code&gt; option of the dropdown to the base currency. The classic way to do this would be using the &lt;code&gt;eq&lt;/code&gt; helper which you can either use from &lt;a href="https://github.com/jmurphyau/ember-truth-helpers/"&gt;ember-truth-helpers&lt;/a&gt; or write yourself.&lt;/p&gt;

&lt;p&gt;However, there is a more descriptive way (and this is a blog post about using simple functions as helpers, so let me have it my way), by writing a local helper function:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CurrencyConverterComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  eurRates = {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;EUR&lt;/span&gt;: &lt;span class="integer"&gt;1&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;USD&lt;/span&gt;: &lt;span class="float"&gt;1.06&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;GBP&lt;/span&gt;: &lt;span class="float"&gt;0.87&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;CHF&lt;/span&gt;: &lt;span class="float"&gt;0.95&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;BTC&lt;/span&gt;: &lt;span class="float"&gt;0.000036&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked baseCurrency = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;EUR&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  isBaseCurrency = (currency) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;   &lt;span class="keyword"&gt;return&lt;/span&gt; currency === &lt;span class="local-variable"&gt;this&lt;/span&gt;.baseCurrency;
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;select&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  &lt;span class="attribute-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="attribute-name"&gt;name&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;selected-currency&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currency-dropdown&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;on&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;change&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt; &lt;span class="attribute-name"&gt;this.updateBaseCurrency&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt; &lt;span class="attribute-name"&gt;this.currencyRates&lt;/span&gt; &lt;span class="attribute-name"&gt;as&lt;/span&gt; &lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="attribute-name"&gt;currency&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="attribute-name"&gt;selected&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.isBaseCurrency&lt;/span&gt; &lt;span class="attribute-name"&gt;currency.symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;      &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;currency.symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We use a local function as a helper so we need to use the &lt;code&gt;this&lt;/code&gt; prefix, just as when rendering a property or calling an action.&lt;/p&gt;

&lt;h3&gt;Formatting currencies (well, numbers)&lt;/h3&gt;

&lt;p&gt;The currencies I listed are all close to each other (with one exception) in value so it&amp;#39;s not trivial to see but when we switch the base currency to BTC, we can see that bigger values are somewhat hard to read:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/blog/using-simple-functions-as-helpers/currencies-to-btc-unformatted.png" alt="Big numbers, unformatted"&gt;&lt;/p&gt;

&lt;p&gt;We should add a formatter (helper) function to present the numbers is a nicer way:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CurrencyConverterComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  formatCurrency = (value) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="comment"&gt;// Format the value to two decimals and separate thousands by commas&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; value.toFixed(&lt;span class="integer"&gt;2&lt;/span&gt;).replace(&lt;span class="regexp"&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="char"&gt;\d&lt;/span&gt;&lt;span class="content"&gt;(?=(?:&lt;/span&gt;&lt;span class="char"&gt;\d&lt;/span&gt;&lt;span class="content"&gt;{3})+&lt;/span&gt;&lt;span class="content"&gt;\.&lt;/span&gt;&lt;span class="content"&gt;)&lt;/span&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="modifier"&gt;g&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;$&amp;amp;,&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt;&lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There is some regex magic to add the commas between the thousands but that&amp;#39;s not our focus today.&lt;/p&gt;

&lt;p&gt;We now have a function we can call from the template:&lt;/p&gt;
&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{#&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt; &lt;span class="attribute-name"&gt;this.currencyRates&lt;/span&gt; &lt;span class="attribute-name"&gt;as&lt;/span&gt; &lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="attribute-name"&gt;currency&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;currency-price&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;    &lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;currency.symbol&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;    &lt;span class="tag"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.formatCurrency&lt;/span&gt; &lt;span class="attribute-name"&gt;currency.rate&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  &lt;span class="tag"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{/&lt;/span&gt;&lt;span class="attribute-name"&gt;each&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note we call the &lt;code&gt;formatCurrency&lt;/code&gt; method with &lt;code&gt;this&lt;/code&gt; as it&amp;#39;s defined on the component – it&amp;#39;s not a global helper.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/blog/using-simple-functions-as-helpers/currencies-to-btc-formatted.png" alt="Nicely formatted currency values"&gt;&lt;/p&gt;

&lt;h3&gt;Extracting the function to be reused&lt;/h3&gt;

&lt;p&gt;You might justifiedly ask: &amp;quot;all right, but what if I need the &lt;code&gt;formatCurrency&lt;/code&gt; helper in other places of the app, too? Before, I could just define it once as a helper and then use it everywhere.&amp;quot; Good point, you don&amp;#39;t want to redefine the formatter every time you need it – but you don&amp;#39;t have to.&lt;/p&gt;

&lt;p&gt;The helper is just a regular function, so you can define it in a module and then import it each time you need it:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/helpers/format-currency.js&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;function&lt;/span&gt; &lt;span class="function"&gt;formatCurrency&lt;/span&gt;(value) {
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; value.toFixed(&lt;span class="integer"&gt;2&lt;/span&gt;).replace(&lt;span class="regexp"&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="char"&gt;\d&lt;/span&gt;&lt;span class="content"&gt;(?=(?:&lt;/span&gt;&lt;span class="char"&gt;\d&lt;/span&gt;&lt;span class="content"&gt;{3})+&lt;/span&gt;&lt;span class="content"&gt;\.&lt;/span&gt;&lt;span class="content"&gt;)&lt;/span&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="modifier"&gt;g&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;$&amp;amp;,&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { formatCurrency } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;currencies/helpers/currency&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CurrencyConverterComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  formatCurrency = formatCurrency;
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Assigning the imported function to a property on the component is needed because you can&amp;#39;t simply use &lt;code&gt;formatCurrency&lt;/code&gt; in the template.&lt;/p&gt;

&lt;p&gt;At least not in current Ember, Octane. With single-file components, in Polaris, you can: give it a whirl using the &lt;a href="https://github.com/ember-template-imports/ember-template-imports"&gt;ember-template-imports&lt;/a&gt; add-on.&lt;/p&gt;

&lt;h3&gt;Is there still a place for &amp;quot;classic&amp;quot; template helpers?&lt;/h3&gt;

&lt;p&gt;Before we could use simple functions as helpers, there were two types of Ember
helpers: functional and class-based ones.&lt;/p&gt;

&lt;h4&gt;Functional helpers&lt;/h4&gt;

&lt;p&gt;Above I showed how to use a simple function in a helper/util module by importing it, assigning it to a component property and calling it from a template.&lt;/p&gt;

&lt;p&gt;It&amp;#39;s true this is more ceremony than defining a special, global helper but I think it&amp;#39;s worth it because it is explicit, and it&amp;#39;s just JavaScript. With the exception of needing to assign the function to a component property, you don&amp;#39;t have to do anything about Ember to use this technique.&lt;/p&gt;

&lt;p&gt;So, unless the helper is already defined in the project (via libraries like &lt;a href="https://github.com/jmurphyau/ember-truth-helpers/"&gt;ember-truth-helpers&lt;/a&gt;, &lt;a href="https://github.com/DockYard/ember-composable-helpers"&gt;ember-composable-helpers&lt;/a&gt;, etc.), I see no reason to create and use global helpers.&lt;/p&gt;

&lt;h4&gt;Class-based helpers&lt;/h4&gt;

&lt;p&gt;We need a class-based global helper when we need some state to create the output. Some property that we can&amp;#39;t, or don&amp;#39;t want to pass in to the helper every time.&lt;/p&gt;

&lt;p&gt;A translation helper, like &lt;a href="https://github.com/ember-intl/ember-intl/"&gt;&lt;code&gt;t&lt;/code&gt; in ember-intl&lt;/a&gt; is a good example. You don&amp;#39;t want to pass in the current locale needed for the translation every time you call the helper – the class
implementing the helper can store it instead.&lt;/p&gt;

&lt;p&gt;It&amp;#39;s also a good example because under the hood, &lt;a href="https://github.com/ember-intl/ember-intl/blob/d6900036172ffa9a308048c70bec1788d819223f/ember-intl/addon/helpers/-format-base.js"&gt;the helper makes use of a service&lt;/a&gt;, so we can do the same.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s see how that would be done on the previous example.&lt;/p&gt;

&lt;p&gt;We define a &lt;code&gt;session&lt;/code&gt; service that keeps track of the currently selected base currency throughout the application. All operations related to the management of this base currency will live on the service:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Service from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/service&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { action } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/object&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { tracked } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/tracking&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; SessionService &lt;span class="reserved"&gt;extends&lt;/span&gt; Service {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked currency = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;EUR&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  formatCurrency(value) {
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    const formatted = value.toFixed(&lt;span class="integer"&gt;2&lt;/span&gt;).replace(&lt;span class="regexp"&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="char"&gt;\d&lt;/span&gt;&lt;span class="content"&gt;(?=(?:&lt;/span&gt;&lt;span class="char"&gt;\d&lt;/span&gt;&lt;span class="content"&gt;{3})+&lt;/span&gt;&lt;span class="content"&gt;\.&lt;/span&gt;&lt;span class="content"&gt;)&lt;/span&gt;&lt;span class="delimiter"&gt;/&lt;/span&gt;&lt;span class="modifier"&gt;g&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;$&amp;amp;,&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="error"&gt;`&lt;/span&gt;&lt;span class="predefined"&gt;$&lt;/span&gt;{&lt;span class="local-variable"&gt;this&lt;/span&gt;.currency} &lt;span class="predefined"&gt;$&lt;/span&gt;{formatted}&lt;span class="error"&gt;`&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;  setCurrency(currency) {
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.currency = currency;
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;formatCurrency&lt;/code&gt; helper can now use the base currency (&lt;code&gt;this.currency&lt;/code&gt;) which is defined on the service itself.&lt;/p&gt;

&lt;p&gt;Because previously this responsibility was implemented in the component, we now need to pass the &amp;quot;value + updater&amp;quot; pair into the component:&lt;/p&gt;
&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;CurrencyConverter&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;baseCurrency&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;get&lt;/span&gt; &lt;span class="error"&gt;(&lt;/span&gt;&lt;span class="attribute-name"&gt;service&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;session&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="error"&gt;)&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;currency&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-value"&gt;onChange&lt;/span&gt;&lt;span class="error"&gt;=&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;get&lt;/span&gt; &lt;span class="error"&gt;(&lt;/span&gt;&lt;span class="attribute-name"&gt;service&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;session&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="error"&gt;)&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;setCurrency&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(I used the very handy &lt;a href="https://github.com/buschtoens/ember-service-helper"&gt;ember-service-helper&lt;/a&gt; add-on so as not to create a controller for the service injection.)&lt;/p&gt;

&lt;p&gt;The component class needs to change to not manage the base currency itself:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Component from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/component&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { action } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/object&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { service } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/service&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CurrencyConverterComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;service session;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  eurRates = {
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;EUR&lt;/span&gt;: &lt;span class="integer"&gt;1&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="key"&gt;USD&lt;/span&gt;: &lt;span class="float"&gt;1.06&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;GBP&lt;/span&gt;: &lt;span class="float"&gt;0.87&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;CHF&lt;/span&gt;: &lt;span class="float"&gt;0.95&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    &lt;span class="key"&gt;BTC&lt;/span&gt;: &lt;span class="float"&gt;0.000036&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;  isBaseCurrency = (currency) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; currency === &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.baseCurrency;
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  formatCurrency = (value) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="local-variable"&gt;this&lt;/span&gt;.session.formatCurrency(value);
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;  };
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;  get currencyRates() {
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; Object.entries(&lt;span class="local-variable"&gt;this&lt;/span&gt;.eurRates).map(([currency, rate]) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;symbol&lt;/span&gt;: currency,
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;rate&lt;/span&gt;: rate / &lt;span class="local-variable"&gt;this&lt;/span&gt;.eurRates[&lt;span class="local-variable"&gt;this&lt;/span&gt;.args.baseCurrency],
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;      };
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;  updateBaseCurrency(event) {
&lt;span class="line-numbers"&gt;&lt;a href="#n35" name="n35"&gt;35&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.onChange(event.target.value);
&lt;span class="line-numbers"&gt;&lt;a href="#n36" name="n36"&gt;36&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n37" name="n37"&gt;37&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="/images/blog/using-simple-functions-as-helpers/currencies-to-btc-formatted.png" alt="Inserting the base currency for no good reason"&gt;&lt;/p&gt;

&lt;p&gt;(The example is contrived because there is no place for the base currency to be included in each value. A better example would be an app for finding flight tickets where the user can set a currency preference and wants to see all prices in that currency.)&lt;/p&gt;

&lt;p&gt;Why do we still need the &lt;code&gt;formatCurrency&lt;/code&gt; method on the component? Couldn&amp;#39;t we use &lt;code&gt;this.session.formatCurrency&lt;/code&gt; directly in the template?&lt;/p&gt;

&lt;p&gt;No, we couldn&amp;#39;t: &lt;code&gt;this.session.formatCurrency&lt;/code&gt; is a function and its context is not bound to the component instance so the &lt;code&gt;this&lt;/code&gt; in the service would be undefined during the call.&lt;/p&gt;

&lt;h3&gt;A note on currency formatting&lt;/h3&gt;

&lt;p&gt;One of my readers, &lt;a href="https://twitter.com/vidvinkel"&gt;Johan Roed&lt;/a&gt;, points out that we could use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat"&gt;Intl.NumberFormat&lt;/a&gt; to do the currency formatting. I prefer this solution to using a regex indeed, as it&amp;#39;s simpler and also adjusts to user preferences. Thanks, Johan!&lt;/p&gt;

&lt;h4&gt;Which one to use?&lt;/h4&gt;

&lt;p&gt;The decision to use a global, class-based Ember helper or a method on a service and a thin wrapper around it is harder to make than in the functional helper case. I&amp;#39;m still learning toward the latter because of its explicitness and the fact that you don&amp;#39;t have to learn about a special Ember thing.&lt;/p&gt;

&lt;p&gt;When single-file components become the norm in Polaris, we&amp;#39;ll have to import all the helpers anyway so the decision will be an easy one to make :)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Uploading images to S3 in Ember.js – Part 4: Testing</title>
    <link rel="alternate" href="https://balinterdi.com/blog/image-uploads-to-s3-in-ember-js-part-4/"/>
    <id>https://balinterdi.com/blog/image-uploads-to-s3-in-ember-js-part-4/</id>
    <published>2022-05-20T09:05:00+00:00</published>
    <updated>2026-03-25T08:14:13+00:00</updated>
    <author>
      <name>Balint Erdi</name>
    </author>
    <content type="html">&lt;p&gt;We concluded &lt;a href="/blog/image-uploads-to-s3-in-ember-js-part-3/"&gt;Part 3&lt;/a&gt; of this series by having added the ability to upload an image to a band both when creating and when editing one.&lt;/p&gt;

&lt;p&gt;In this post, we&amp;#39;ll add a couple of tests to safeguard those user scenarios.&lt;/p&gt;

&lt;h3&gt;Existing test for creating a band&lt;/h3&gt;

&lt;p&gt;In the &lt;a href="https://balinterdi.com/rock-and-roll-with-emberjs"&gt;Rock &amp;amp; Roll with Ember book&lt;/a&gt; we&amp;#39;d written a test to verify creating a band works fine. It looks something like this:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// tests/acceptance/bands-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Create a band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="local-variable"&gt;this&lt;/span&gt;.server.create(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, { &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Royal Blood&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt; });
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  await visit(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  await createBand(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Red Hot Chili Peppers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  await waitFor(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;no-songs-text&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;  assert
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-list-item&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    .exists({ &lt;span class="key"&gt;count&lt;/span&gt;: &lt;span class="integer"&gt;2&lt;/span&gt; }, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;A new band link is rendered&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  assert
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-list-item&amp;quot;]:last-child&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;    .hasText(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Red Hot Chili Peppers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The new band link is rendered as the last item&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;createBand&lt;/code&gt; is a custom test helper that reduces boilerplate without reducing the clarity of what&amp;#39;s going on in the test setup (or so I hope): &lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; async &lt;span class="keyword"&gt;function&lt;/span&gt; &lt;span class="function"&gt;createBand&lt;/span&gt;(name) {
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;  await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;new-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  await fillIn(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;new-band-name&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, name);
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;save-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We need to extend the test to allow uploading an image. More precisely, we want to end up with two test cases: one where an image is uploaded and one where it&amp;#39;s not.&lt;/p&gt;

&lt;h3&gt;A bug revealed&lt;/h3&gt;

&lt;p&gt;In my experience, even &lt;em&gt;just thinking about what to test&lt;/em&gt; is a great way to reduce the number of bugs in your code. It forces you to consider scenarios you might have forgot about when writing the implementation (which I guess is one of the reasons test-driven development is valuable).&lt;/p&gt;

&lt;p&gt;In our case, I&amp;#39;d made a colossal error – I assumed that an image was always uploaded when creating a new band.&lt;/p&gt;

&lt;p&gt;Look at the code:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/components/band-form.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandFormComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  async save(event) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    event.preventDefault();
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    let response = await fetch(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/presign-aws-request&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    let { url, &lt;span class="key"&gt;url_fields&lt;/span&gt;: urlFields } = await response.json();
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    let bandProperties = {
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="local-variable"&gt;this&lt;/span&gt;.name,
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    };
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;    let formData = &lt;span class="keyword"&gt;new&lt;/span&gt; FormData();
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;for&lt;/span&gt; (let field &lt;span class="keyword"&gt;in&lt;/span&gt; urlFields) {
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;      formData.append(field, urlFields[field]);
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;    formData.append(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;file&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;    let imageUploadResponse = await fetch(url, {
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;body&lt;/span&gt;: formData,
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (imageUploadResponse.ok) {
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;      bandProperties[&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;image-url&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;] = imageUploadResponse.headers.get(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Location&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;      &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload = &lt;span class="predefined-constant"&gt;null&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; await &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.onSave(bandProperties);
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Nothing checks for the presence of &lt;code&gt;this.imageToUpload&lt;/code&gt; and the very first line posts to the back-end for a pre-signed URL. This is not needed if the user hasn&amp;#39;t uploaded the image, just as all the following code is not.&lt;/p&gt;

&lt;p&gt;So the code should be modified as follows:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/components/band-form.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandFormComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  async save(event) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    event.preventDefault();
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    let bandProperties = {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="local-variable"&gt;this&lt;/span&gt;.name,
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    };
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload) {
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;      let response = await fetch(&lt;span class="error"&gt;`&lt;/span&gt;&lt;span class="predefined"&gt;$&lt;/span&gt;{ENV.apiHost}/presign-aws-request&lt;span class="error"&gt;`&lt;/span&gt;, {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;      });
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;      let { url, &lt;span class="key"&gt;url_fields&lt;/span&gt;: urlFields } = await response.json();
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;      let formData = &lt;span class="keyword"&gt;new&lt;/span&gt; FormData();
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;for&lt;/span&gt; (let field &lt;span class="keyword"&gt;in&lt;/span&gt; urlFields) {
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;        formData.append(field, urlFields[field]);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;      formData.append(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;file&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload);
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      let imageUploadResponse = await fetch(url, {
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;body&lt;/span&gt;: formData,
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;      });
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (imageUploadResponse.ok) {
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;        bandProperties[&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;image-url&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;] =
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;          imageUploadResponse.headers.get(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Location&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;        &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload = &lt;span class="predefined-constant"&gt;null&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n35" name="n35"&gt;35&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; await &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.onSave(bandProperties);
&lt;span class="line-numbers"&gt;&lt;a href="#n36" name="n36"&gt;36&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n37" name="n37"&gt;37&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I&amp;#39;m following the path I trod when adding the tests - I realized the above bug &lt;em&gt;before&lt;/em&gt; even starting to write the tests. As the next step, let&amp;#39;s write the tests as I did.&lt;/p&gt;

&lt;h3&gt;Writing new tests&lt;/h3&gt;

&lt;p&gt;We want at least two test scenarios for band creation: one where an image is uploaded and one where it&amp;#39;s not. If you take a look at the existing test, it makes use of a custom test helper called &lt;code&gt;createBand&lt;/code&gt; which hides the details about the needed user actions:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// tests/helpers/custom-helpers.js&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { click, fillIn } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/test-helpers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; async &lt;span class="keyword"&gt;function&lt;/span&gt; &lt;span class="function"&gt;createBand&lt;/span&gt;(name) {
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;new-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  await fillIn(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;new-band-name&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, name);
&lt;span class="line-numbers"&gt;&lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;save-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can extend this helper to take an optional &lt;code&gt;image&lt;/code&gt; argument and then select that file when it&amp;#39;s passed.&lt;/p&gt;

&lt;p&gt;Selecting a file is done by triggering a change event on the file upload input, passing along the selected files:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// tests/helpers/custom-helpers.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { click, fillIn, triggerEvent } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/test-helpers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; async &lt;span class="keyword"&gt;function&lt;/span&gt; &lt;span class="function"&gt;createBand&lt;/span&gt;({ name, image }) {
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;new-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  await fillIn(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;new-band-name&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, name);
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;if&lt;/span&gt; (image) {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    await triggerEvent(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[name=&amp;quot;file-upload&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;change&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, { &lt;span class="key"&gt;files&lt;/span&gt;: [image] });
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  &lt;span class="keyword"&gt;return&lt;/span&gt; click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;save-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the process, we also switched to named arguments.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s now write the test scenarios we&amp;#39;ve imagined.&lt;/p&gt;

&lt;h4&gt;Test case for creating a band with an image&lt;/h4&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// tests/acceptance/bands-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { createBand } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;rarwe/tests/helpers/custom-helpers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Acceptance | bands&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Create a band — with image&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.server.create(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, { &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Royal Blood&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt; });
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    await visit(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    let image = &lt;span class="keyword"&gt;new&lt;/span&gt; File([], &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;red-hot-chilli-peppers.jpg&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, {
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;type&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;image/jpeg&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;    await createBand({
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Red Hot Chili Peppers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;      image,
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;    await waitFor(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-image&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-image&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The band image is shown&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-list-item&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      .exists({ &lt;span class="key"&gt;count&lt;/span&gt;: &lt;span class="integer"&gt;2&lt;/span&gt; }, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;A new band link is rendered&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-list-item&amp;quot;]:last-child&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;      .hasText(
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;        &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Red Hot Chili Peppers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;        &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The new band link is rendered as the last item&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;      );
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;details-nav-item&amp;quot;] &amp;gt; .active&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;      .exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The Details tab is active&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File/File"&gt;&lt;code&gt;File&lt;/code&gt; object&lt;/a&gt; is what&amp;#39;s passed &amp;quot;in the real app&amp;quot; so we create one manually in the tests. The reason we don&amp;#39;t have to pass its contents (see the empty array passed as the first argument to the constructor) is that the endpoints are mocked out in the test anyways, so we&amp;#39;ll pretend we got the right thing.&lt;/p&gt;

&lt;p&gt;We add a &lt;code&gt;waitFor&lt;/code&gt; to make sure the transition to the Details page of the newly created band is complete.&lt;/p&gt;

&lt;p&gt;The test will fail as we haven&amp;#39;t added Mirage handlers for the presign back-end endpoint and the AWS bucket URL. Let&amp;#39;s open up &lt;code&gt;mirage/config.js&lt;/code&gt; and add them.&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// mirage/config.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { Response } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;miragejs&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;const s3BucketUrl = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;https://rarwe-dev.s3.eu-west-1.amazonaws.com&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;const bucketName = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;rarwe-dev&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;const imageId = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;69c4abfc-e900-4ec7-9263-c2506bdd4c19&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;const keyInBucket = &lt;span class="error"&gt;`&lt;/span&gt;image-uploads/&lt;span class="predefined"&gt;$&lt;/span&gt;{imageId}/&lt;span class="error"&gt;`&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;const imageFileName = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;red-hot-chilli-peppers.jpg&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="keyword"&gt;function&lt;/span&gt; () {
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  &lt;span class="local-variable"&gt;this&lt;/span&gt;.post(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/presign-aws-request&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; () {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; Response(
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;      &lt;span class="integer"&gt;200&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;      {},
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;      {
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;url&lt;/span&gt;: s3BucketUrl,
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;url_fields&lt;/span&gt;: {
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;          &lt;span class="key"&gt;key&lt;/span&gt;: keyInBucket + &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;${filename}&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;          &lt;span class="key"&gt;success_action_status&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;201&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;          &lt;span class="key"&gt;policy&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;...&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="comment"&gt;// the actual, serialized bucket policy&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;        },
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;  &lt;span class="local-variable"&gt;this&lt;/span&gt;.post(s3BucketUrl, &lt;span class="keyword"&gt;function&lt;/span&gt; (schema, request) {
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;    let key = request.requestBody
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;      .get(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;key&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      .replace(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;${filename}&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, imageFileName);
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;    let location = &lt;span class="error"&gt;`&lt;/span&gt;&lt;span class="predefined"&gt;$&lt;/span&gt;{s3BucketUrl}/&lt;span class="predefined"&gt;$&lt;/span&gt;{encodeURIComponent(key)}&lt;span class="error"&gt;`&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; Response(
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;      &lt;span class="integer"&gt;201&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n35" name="n35"&gt;35&lt;/a&gt;&lt;/span&gt;      {
&lt;span class="line-numbers"&gt;&lt;a href="#n36" name="n36"&gt;36&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;Location&lt;/span&gt;: location,
&lt;span class="line-numbers"&gt;&lt;a href="#n37" name="n37"&gt;37&lt;/a&gt;&lt;/span&gt;      },
&lt;span class="line-numbers"&gt;&lt;a href="#n38" name="n38"&gt;38&lt;/a&gt;&lt;/span&gt;      &lt;span class="error"&gt;`&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;PostResponse&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n39" name="n39"&gt;39&lt;/a&gt;&lt;/span&gt;        &lt;span class="tag"&gt;&amp;lt;Location&amp;gt;&lt;/span&gt;${location}&lt;span class="tag"&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n40" name="n40"&gt;40&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;        &lt;span class="tag"&gt;&amp;lt;Bucket&amp;gt;&lt;/span&gt;${bucketName}&lt;span class="tag"&gt;&amp;lt;/Bucket&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n41" name="n41"&gt;41&lt;/a&gt;&lt;/span&gt;        &lt;span class="tag"&gt;&amp;lt;Key&amp;gt;&lt;/span&gt;${keyInBucket}&lt;span class="tag"&gt;&amp;lt;/Key&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n42" name="n42"&gt;42&lt;/a&gt;&lt;/span&gt;        &lt;span class="tag"&gt;&amp;lt;ETag&amp;gt;&lt;/span&gt;&amp;quot;0af669d3f4786c05d779a0a7d44f6c61&amp;quot;&lt;span class="tag"&gt;&amp;lt;/ETag&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n43" name="n43"&gt;43&lt;/a&gt;&lt;/span&gt;      &lt;span class="tag"&gt;&amp;lt;/PostResponse&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n44" name="n44"&gt;44&lt;/a&gt;&lt;/span&gt;      &lt;span class="error"&gt;`&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n45" name="n45"&gt;45&lt;/a&gt;&lt;/span&gt;    );
&lt;span class="line-numbers"&gt;&lt;a href="#n46" name="n46"&gt;46&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n47" name="n47"&gt;47&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I took the properties of an image that had been uploaded and used them as properties of the mock response, mimicking how the real endpoints work. The most important bit is the &lt;code&gt;Location&lt;/code&gt; header of the AWS response since that&amp;#39;s going to be set as the &lt;code&gt;image-url&lt;/code&gt; property of the band.&lt;/p&gt;

&lt;p&gt;After we&amp;#39;ve added the above handlers, the test passes fine:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/blog/image-uploads-ember/test-image-upload.png" alt="Test for creating band with uploaded image"&gt;&lt;/p&gt;

&lt;h4&gt;Thinking about lying tests&lt;/h4&gt;

&lt;p&gt;Since we&amp;#39;re testing against a mock back-end, let&amp;#39;s pause for a moment to think through what exactly we are testing, or, more importantly, how could the test give us a false positive or negative.&lt;/p&gt;

&lt;p&gt;How could the test give us false security? We always set the same image URL in the &lt;code&gt;Location&lt;/code&gt; header and thus the same &lt;code&gt;image-url&lt;/code&gt; will be set for the band, we don&amp;#39;t know if the &lt;em&gt;actual image&lt;/em&gt; that was uploaded will be properly assigned to the band object. If we were to introduce a bug to the Ember app which modifies the &lt;code&gt;image-url&lt;/code&gt; somehow we wouldn&amp;#39;t know because the &lt;code&gt;Location&lt;/code&gt; is still sent back correctly.&lt;/p&gt;

&lt;p&gt;However, if you look at the code again, you&amp;#39;ll find that the Ember app doesn&amp;#39;t alter any part of that URL, so that&amp;#39;s not probable. This is done by our back-end (and S3) and should be tested there.&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/components/band-form.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandFormComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  async save(event) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    event.preventDefault();
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;    let bandProperties = {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="local-variable"&gt;this&lt;/span&gt;.name,
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    };
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload) {
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;      let response = await fetch(&lt;span class="error"&gt;`&lt;/span&gt;&lt;span class="predefined"&gt;$&lt;/span&gt;{ENV.apiHost}/presign-aws-request&lt;span class="error"&gt;`&lt;/span&gt;, {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;      });
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;      let { url, &lt;span class="key"&gt;url_fields&lt;/span&gt;: urlFields } = await response.json();
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;      let formData = &lt;span class="keyword"&gt;new&lt;/span&gt; FormData();
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;for&lt;/span&gt; (let field &lt;span class="keyword"&gt;in&lt;/span&gt; urlFields) {
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;        formData.append(field, urlFields[field]);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;      formData.append(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;file&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload);
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      let imageUploadResponse = await fetch(url, {
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;body&lt;/span&gt;: formData,
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;      });
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (imageUploadResponse.ok) {
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;        bandProperties[&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;image-url&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;] =
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;          imageUploadResponse.headers.get(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Location&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;        &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload = &lt;span class="predefined-constant"&gt;null&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n35" name="n35"&gt;35&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; await &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.onSave(bandProperties);
&lt;span class="line-numbers"&gt;&lt;a href="#n36" name="n36"&gt;36&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n37" name="n37"&gt;37&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;A second, important scenario we&amp;#39;re not currently testing is whether the image is too big to upload. Since that&amp;#39;s done on the client-side, this has nothing to do with mocking, though, and we could test it by passing in &amp;quot;bytes&amp;quot; as the first argument of &lt;code&gt;new File&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s now think about false negatives, the situation in which the test fails even though the feature works fine.&lt;/p&gt;

&lt;p&gt;The only thing I can think of as a &amp;quot;false negative candidate&amp;quot; is the deletion of the hard-wired image from the S3 bucket. However, even though the image wouldn&amp;#39;t display, the &lt;code&gt;image-url&lt;/code&gt; would still be set and thus the &lt;code&gt;assert.dom(&amp;#39;[data-test-rr=&amp;quot;band-image&amp;quot;]&amp;#39;).exists()&lt;/code&gt; assertion would still pass.&lt;/p&gt;

&lt;p&gt;With all of the above said, in &amp;quot;real&amp;quot; apps it&amp;#39;s crucial to have end-to-end tests that don&amp;#39;t switch out any of the architectural pieces so as to minimize the chance of tests not telling the truth.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s wrap up by writing a couple of tests for scenarios we haven&amp;#39;t tested yet.&lt;/p&gt;

&lt;h4&gt;Test case for creating a band without an image&lt;/h4&gt;

&lt;p&gt;The bug I realized the app when I started to think about tests occurred when no image was uploaded for the band.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s write the test for this occurrence so that we don&amp;#39;t regress on it:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// tests/acceptance/bands-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { createBand } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;rarwe/tests/helpers/custom-helpers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Acceptance | bands&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Create a band — without image&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.server.create(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, { &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Royal Blood&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt; });
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    await visit(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    await createBand({ &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Caspian&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt; });
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    await waitFor(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;no-songs-text&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-list-item&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;      .exists({ &lt;span class="key"&gt;count&lt;/span&gt;: &lt;span class="integer"&gt;2&lt;/span&gt; }, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;A new band link is rendered&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-list-item&amp;quot;]:last-child&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      .hasText(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Caspian&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The new band link is rendered as the last item&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;songs-nav-item&amp;quot;] &amp;gt; .active&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      .exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The Songs tab is active&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h4&gt;Test case for adding an image to an existing band&lt;/h4&gt;

&lt;p&gt;The other possibility to upload an image for a band is after creation, through editing the band:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// tests/acceptance/bands-test.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { createBand } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;rarwe/tests/helpers/custom-helpers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;module(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Acceptance | bands&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="keyword"&gt;function&lt;/span&gt; (hooks) {
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  test(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Add image to band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, async &lt;span class="keyword"&gt;function&lt;/span&gt; (assert) {
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.server.create(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, { &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Red Hot Chili Peppers&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt; });
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    let image = &lt;span class="keyword"&gt;new&lt;/span&gt; File([], &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;red-hot-chilli-peppers.jpg&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, {
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;type&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;image/jpeg&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;    await visit(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;    await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-link&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;    await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;details-nav-item&amp;quot;] &amp;gt; a&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;    await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;edit-band-link&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;    await triggerEvent(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[name=&amp;quot;file-upload&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;change&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, { &lt;span class="key"&gt;files&lt;/span&gt;: [image] });
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;    await click(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;save-band-button&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;    assert.dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;band-image&amp;quot;]&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;).exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The band image is shown&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;    assert
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      .dom(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;[data-test-rr=&amp;quot;details-nav-item&amp;quot;] &amp;gt; .active&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;      .exists(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;The Details tab is active&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;  });
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;});
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h3&gt;Why do we need the &lt;code&gt;waitFor&lt;/code&gt; calls?&lt;/h3&gt;

&lt;p&gt;Something that bothered me is that in several testing scenarios we needed to insert &lt;code&gt;waitFor&lt;/code&gt; calls before we could assert the presence of DOM elements.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;waitFor&lt;/code&gt;s should only be needed in cases where Ember&amp;#39;s testing framework can&amp;#39;t figure it out on its own that it still needs to wait for some async operation. This could be the case when we integrate a 3rd-party library (like rendering a D3 graph) but it didn&amp;#39;t seem like this was the case here.&lt;/p&gt;

&lt;p&gt;It then occurred to me that when I save the band, I call &lt;code&gt;await this.catalog.create(&amp;#39;band&amp;#39;, properties)&lt;/code&gt;. This is a custom action in the catalog service I wrote and it calls &lt;code&gt;fetch&lt;/code&gt; in its body. However, it doesn&amp;#39;t use the &lt;code&gt;fetch&lt;/code&gt; exported by &lt;a href="https://github.com/ember-cli/ember-fetch"&gt;&lt;code&gt;ember-fetch&lt;/code&gt;&lt;/a&gt; which means &lt;a href="https://github.com/ember-cli/ember-fetch#q--a"&gt;the fetch is not run-loop&lt;/a&gt; aware and so Ember&amp;#39;s test runner will not know to wait for it. The test runner moves on to the next line (which is usually an assertion) before the &lt;code&gt;catalog.create&lt;/code&gt; could finish.&lt;/p&gt;

&lt;p&gt;And indeed, simply putting the &lt;code&gt;import fetch from &amp;#39;fetch&amp;#39;&lt;/code&gt; as the first line in the catalog service makes the &lt;code&gt;waitFor&lt;/code&gt; calls unnecessary.&lt;/p&gt;

&lt;p&gt;Cool, we now have a decent test coverage for the image upload scenarios.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Uploading images to S3 in Ember.js – Part 3: Extracting a form component</title>
    <link rel="alternate" href="https://balinterdi.com/blog/image-uploads-to-s3-in-ember-js-part-3/"/>
    <id>https://balinterdi.com/blog/image-uploads-to-s3-in-ember-js-part-3/</id>
    <published>2022-03-10T09:40:00+00:00</published>
    <updated>2026-03-25T08:14:13+00:00</updated>
    <author>
      <name>Balint Erdi</name>
    </author>
    <content type="html">&lt;p&gt;By the end of &lt;a href="/blog/image-uploads-to-s3-in-ember-js-part-2/"&gt;Part 2 of this series&lt;/a&gt;, we could create bands with a name and image whose size was validated to not be above a specific limit.&lt;/p&gt;

&lt;p&gt;What&amp;#39;s sorely missing from our implementation is that we can&amp;#39;t edit existing bands, so we have no way to fix our errors or upload a band image later. We prepared the necessary work by having a dedicated page for editing bands and linking to it from the Details page.&lt;/p&gt;

&lt;p&gt;In this part, we&amp;#39;ll make managing the creation and editing of a band possible and maintainable.&lt;/p&gt;

&lt;h2&gt;Extract a form component&lt;/h2&gt;

&lt;p&gt;As we don&amp;#39;t want to copy the form code for creating a new band and editing an existing one, we&amp;#39;ll start by extracting the code we currently have in the controller and the related template into a component.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s start by creating a &lt;code&gt;BandForm&lt;/code&gt; component, with a component class:&lt;/p&gt;
&lt;div class="highlight   one-line"&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;$ ember g component band-form --with-component-class
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The extraction should move the UI-related pieces from the controller to the new component but leave the &lt;code&gt;saveBand&lt;/code&gt; action, and the logic around warning (or not) about unsaved changes as those belong in the invoking context.&lt;/p&gt;

&lt;p&gt;That means our component will look like this:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/components/band-form.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Component from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/component&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { action } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/object&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { tracked } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@glimmer/tracking&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; fetch from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;fetch&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;const MAX_IMAGE_SIZE_MB = &lt;span class="integer"&gt;1&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;const MAX_IMAGE_SIZE = MAX_IMAGE_SIZE_MB * &lt;span class="integer"&gt;1024&lt;/span&gt; * &lt;span class="integer"&gt;1024&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandFormComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked name;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked imagePreviewSrc;
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;  imageToUpload;
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;tracked validationError;
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;  updateName(event) {
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.name = event.target.value;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;  didUploadImage(event) {
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.validationError = &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;    let [file] = event.target.files;
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (file.size &amp;gt; MAX_IMAGE_SIZE) {
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;      &lt;span class="local-variable"&gt;this&lt;/span&gt;.validationError = &lt;span class="error"&gt;`&lt;/span&gt;Image should be smaller than &lt;span class="predefined"&gt;$&lt;/span&gt;{MAX_IMAGE_SIZE_MB}MB.&lt;span class="error"&gt;`&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload = file;
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.imagePreviewSrc = URL.createObjectURL(file);
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n35" name="n35"&gt;35&lt;/a&gt;&lt;/span&gt;  async saveBand(event) {
&lt;span class="line-numbers"&gt;&lt;a href="#n36" name="n36"&gt;36&lt;/a&gt;&lt;/span&gt;    event.preventDefault();
&lt;span class="line-numbers"&gt;&lt;a href="#n37" name="n37"&gt;37&lt;/a&gt;&lt;/span&gt;    let response = await fetch(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;/presign-aws-request&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, {
&lt;span class="line-numbers"&gt;&lt;a href="#n38" name="n38"&gt;38&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n39" name="n39"&gt;39&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n40" name="n40"&gt;40&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    let { url, &lt;span class="key"&gt;url_fields&lt;/span&gt;: urlFields } = await response.json();
&lt;span class="line-numbers"&gt;&lt;a href="#n41" name="n41"&gt;41&lt;/a&gt;&lt;/span&gt;    let bandProperties = {
&lt;span class="line-numbers"&gt;&lt;a href="#n42" name="n42"&gt;42&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;name&lt;/span&gt;: &lt;span class="local-variable"&gt;this&lt;/span&gt;.name,
&lt;span class="line-numbers"&gt;&lt;a href="#n43" name="n43"&gt;43&lt;/a&gt;&lt;/span&gt;    };
&lt;span class="line-numbers"&gt;&lt;a href="#n44" name="n44"&gt;44&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n45" name="n45"&gt;45&lt;/a&gt;&lt;/span&gt;    let formData = &lt;span class="keyword"&gt;new&lt;/span&gt; FormData();
&lt;span class="line-numbers"&gt;&lt;a href="#n46" name="n46"&gt;46&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;for&lt;/span&gt; (let field &lt;span class="keyword"&gt;in&lt;/span&gt; urlFields) {
&lt;span class="line-numbers"&gt;&lt;a href="#n47" name="n47"&gt;47&lt;/a&gt;&lt;/span&gt;      formData.append(field, urlFields[field]);
&lt;span class="line-numbers"&gt;&lt;a href="#n48" name="n48"&gt;48&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;a href="#n49" name="n49"&gt;49&lt;/a&gt;&lt;/span&gt;    formData.append(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;file&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n50" name="n50"&gt;50&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n51" name="n51"&gt;51&lt;/a&gt;&lt;/span&gt;    let imageUploadResponse = await fetch(url, {
&lt;span class="line-numbers"&gt;&lt;a href="#n52" name="n52"&gt;52&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;POST&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;a href="#n53" name="n53"&gt;53&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;body&lt;/span&gt;: formData,
&lt;span class="line-numbers"&gt;&lt;a href="#n54" name="n54"&gt;54&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n55" name="n55"&gt;55&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n56" name="n56"&gt;56&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (imageUploadResponse.ok) {
&lt;span class="line-numbers"&gt;&lt;a href="#n57" name="n57"&gt;57&lt;/a&gt;&lt;/span&gt;      bandProperties[&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;image-url&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;] = imageUploadResponse.headers.get(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Location&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n58" name="n58"&gt;58&lt;/a&gt;&lt;/span&gt;      URL.revokeObjectURL(&lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload);
&lt;span class="line-numbers"&gt;&lt;a href="#n59" name="n59"&gt;59&lt;/a&gt;&lt;/span&gt;      &lt;span class="local-variable"&gt;this&lt;/span&gt;.imageToUpload = &lt;span class="predefined-constant"&gt;null&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n60" name="n60"&gt;60&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;a href="#n61" name="n61"&gt;61&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n62" name="n62"&gt;62&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; await &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.onSave(bandProperties);
&lt;span class="line-numbers"&gt;&lt;a href="#n63" name="n63"&gt;63&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n64" name="n64"&gt;64&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;(I renamed the &lt;code&gt;this.saveBand&lt;/code&gt; to &lt;code&gt;this.save&lt;/code&gt; in the component because the &lt;code&gt;Band&lt;/code&gt; suffix is redundant in this context.)&lt;/p&gt;

&lt;p&gt;While the controller code is reduced to the following:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/controllers/bands/new.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Controller from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/controller&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { action } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/object&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { inject as service } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/service&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandsNewController &lt;span class="reserved"&gt;extends&lt;/span&gt; Controller {
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;service catalog;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;service router;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  constructor() {
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    &lt;span class="reserved"&gt;super&lt;/span&gt;(...&lt;span class="local-variable"&gt;arguments&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.router.on(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;routeWillChange&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, (transition) =&amp;gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (transition.isAborted) {
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="local-variable"&gt;this&lt;/span&gt;.confirmedLeave) {
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;if&lt;/span&gt; (transition.from?.name === &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;bands.new&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;) {
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="local-variable"&gt;this&lt;/span&gt;.name) {
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;          let leave = window.confirm(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;You have unsaved changes. Are you sure?&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;          &lt;span class="keyword"&gt;if&lt;/span&gt; (leave) {
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;            &lt;span class="local-variable"&gt;this&lt;/span&gt;.confirmedLeave = &lt;span class="predefined-constant"&gt;true&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;          } &lt;span class="keyword"&gt;else&lt;/span&gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;            transition.abort();
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;          }
&lt;span class="line-numbers"&gt;&lt;a href="#n27" name="n27"&gt;27&lt;/a&gt;&lt;/span&gt;        }
&lt;span class="line-numbers"&gt;&lt;a href="#n28" name="n28"&gt;28&lt;/a&gt;&lt;/span&gt;      }
&lt;span class="line-numbers"&gt;&lt;a href="#n29" name="n29"&gt;29&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n30" name="n30"&gt;30&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n31" name="n31"&gt;31&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n32" name="n32"&gt;32&lt;/a&gt;&lt;/span&gt;  G&lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n33" name="n33"&gt;33&lt;/a&gt;&lt;/span&gt;  async saveBand(properties) {
&lt;span class="line-numbers"&gt;&lt;a href="#n34" name="n34"&gt;34&lt;/a&gt;&lt;/span&gt;    let band = await &lt;span class="local-variable"&gt;this&lt;/span&gt;.catalog.create(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, properties);
&lt;span class="line-numbers"&gt;&lt;a href="#n35" name="n35"&gt;35&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.confirmedLeave = &lt;span class="predefined-constant"&gt;true&lt;/span&gt;;
&lt;span class="line-numbers"&gt;&lt;a href="#n36" name="n36"&gt;36&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.router.transitionTo(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;bands.band.songs&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, band.id);
&lt;span class="line-numbers"&gt;&lt;a href="#n37" name="n37"&gt;37&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n38" name="n38"&gt;38&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;saveBand&lt;/code&gt; action now only contains what&amp;#39;s specific to saving a band &lt;em&gt;in this context&lt;/em&gt;. All the image-related URL fetching, composing the payload, and validating now live in the component where it will get reused when editing a band.&lt;/p&gt;

&lt;p&gt;Consequently, the component template, &lt;code&gt;band-form.hbs&lt;/code&gt;, is 99% identical to the template belonging to the controller, &lt;code&gt;app/templates/bands/new.hbs&lt;/code&gt;. The only difference is that we call &lt;code&gt;this.save&lt;/code&gt; instead of &lt;code&gt;this.saveBand&lt;/code&gt; when submitting the form due to the name change I mentioned earlier.&lt;/p&gt;

&lt;p&gt;The only thing remaining in the controller template is rendering the component:&lt;/p&gt;
&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;{{!-- app/templates/bands/new.hbs --}}&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;text-lg leading-6 font-medium text-gray-100&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  New band
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;BandForm&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;onSave&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;this.saveBand&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We managed to extract the component while the app works just as before:&lt;/p&gt;

&lt;video controls width="700"&gt;
    &lt;source src="/images/blog/image-uploads-ember/extract-band-form-1.mp4"
            type="video/mp4"&gt;
    Sorry, your browser doesn't support embedded videos.
&lt;/video&gt;

&lt;h2&gt;Using the form component to edit bands&lt;/h2&gt;

&lt;p&gt;Now that we&amp;#39;re armed with a &lt;code&gt;BandForm&lt;/code&gt; component, we should also use it to allow editing an existing band which is the reason we extracted the component in the first place.&lt;/p&gt;

&lt;p&gt;The main difference between using the component for creating and editing band objects is that we need to use the form with an existing band object in the latter case. So at the outset, we&amp;#39;ll need to fetch it and pass it to the &lt;code&gt;BandForm&lt;/code&gt; component to work with.&lt;/p&gt;

&lt;p&gt;Let&amp;#39;s generate a route (which also creates the corresponding template) for the &amp;quot;Edit band&amp;quot; page – we manually added a route entry for this page in &lt;a href="/blog/image-uploads-to-s3-in-ember-js-part-2/"&gt;the previous post&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight   one-line"&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;$  ember g route bands/edit-band
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the route, we fetch the band so that we can pass it into the component:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/routes/bands/edit-band.js&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandsEditBandRoute &lt;span class="reserved"&gt;extends&lt;/span&gt; Route {
&lt;span class="line-numbers"&gt;&lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;service catalog;
&lt;span class="line-numbers"&gt;&lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  model(params) {
&lt;span class="line-numbers"&gt;&lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="local-variable"&gt;this&lt;/span&gt;.catalog.find(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, (band) =&amp;gt; band.id === params.id);
&lt;span class="line-numbers"&gt;&lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class="highlight handlebars  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;{{!-- app/templates/bands/edit-band.hbs --}}&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;page-title&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="attribute-name"&gt;Edit&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;model.name&lt;/span&gt; &lt;span class="error"&gt;&amp;quot;&lt;/span&gt;&lt;span class="error"&gt;|&lt;/span&gt; &lt;span class="attribute-name"&gt;Rock&lt;/span&gt; &lt;span class="error"&gt;&amp;amp;&lt;/span&gt; &lt;span class="attribute-name"&gt;Roll&lt;/span&gt; &lt;span class="attribute-name"&gt;with&lt;/span&gt; &lt;span class="attribute-name"&gt;Octane&lt;/span&gt;&lt;span class="error"&gt;&amp;quot;&lt;/span&gt; &lt;span class="attribute-name"&gt;replace&lt;/span&gt;=&lt;span class="attribute-value"&gt;true&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;span class="content"&gt;text-lg leading-6 font-medium text-gray-100&lt;/span&gt;&lt;span class="delimiter"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;  Edit band
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;lt;BandForm&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;band&lt;/span&gt;=&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;model&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-value"&gt;onSave&lt;/span&gt;&lt;span class="error"&gt;=&lt;/span&gt;&lt;span class="inline"&gt;&lt;span class="inline-delimiter"&gt;{{&lt;/span&gt;&lt;span class="attribute-name"&gt;fn&lt;/span&gt; &lt;span class="attribute-name"&gt;this.updateBand&lt;/span&gt; &lt;span class="error"&gt;@&lt;/span&gt;&lt;span class="attribute-name"&gt;model&lt;/span&gt;&lt;span class="inline-delimiter"&gt;}}&lt;/span&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We assumed that an &lt;code&gt;updateBand&lt;/code&gt; action exists in the context and that &lt;code&gt;BandForm&lt;/code&gt; can handle a &lt;code&gt;@band&lt;/code&gt; being passed in. Neither of these holds at the moment, so let&amp;#39;s make them so.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;updateBand&lt;/code&gt; needs to exist on the controller, which we&amp;#39;ll first need to create:&lt;/p&gt;
&lt;div class="highlight   one-line"&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt;&lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;$  ember g controller bands/edit-band
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;updateBand&lt;/code&gt; action should be very similar to the &lt;code&gt;saveBand&lt;/code&gt; one in &lt;code&gt;app/controllers/bands/new.js&lt;/code&gt;. Instead of creating a band, we should update the existing one:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/controllers/band/edit-band.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; Controller from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/controller&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { action } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/object&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;import&lt;/span&gt; { inject as service } from &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;@ember/service&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandsEditBandController &lt;span class="reserved"&gt;extends&lt;/span&gt; Controller {
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;service catalog;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;service router;
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;  &lt;span class="error"&gt;@&lt;/span&gt;action
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;  async updateBand(band, attributes) {
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    await &lt;span class="local-variable"&gt;this&lt;/span&gt;.catalog.update(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, band, attributes);
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.router.transitionTo(&lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;bands.band.details&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;, band.id);
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;What&amp;#39;s missing is that even though we pass the &lt;code&gt;@band&lt;/code&gt; to the &lt;code&gt;BandForm&lt;/code&gt; component on the Edit band page, we don&amp;#39;t initialize the form taking that object into account. That means the form looks as if we were creating a band from scratch:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/blog/image-uploads-ember/edit-band-no-init.jpg" alt="Band form is not initialized from object"&gt;&lt;/p&gt;

&lt;p&gt;If we take another look at our form, it uses individual properties for each field (&lt;code&gt;this.name&lt;/code&gt;, &lt;code&gt;this.imagePreviewSrc&lt;/code&gt;) instead of using a form object. So to show the existing band properties in the fields, we need to initialize the form properties from them:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/components/band-form.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; BandFormComponent &lt;span class="reserved"&gt;extends&lt;/span&gt; Component {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  constructor() {
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="reserved"&gt;super&lt;/span&gt;(...&lt;span class="local-variable"&gt;arguments&lt;/span&gt;);
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (!&lt;span class="local-variable"&gt;this&lt;/span&gt;.args.band) {
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      &lt;span class="keyword"&gt;return&lt;/span&gt;;
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;    let { name, imageUrl } = &lt;span class="local-variable"&gt;this&lt;/span&gt;.args.band;
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.name = name;
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;    &lt;span class="local-variable"&gt;this&lt;/span&gt;.imagePreviewSrc = imageUrl;
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Perhaps somewhat surprisingly, that&amp;#39;s the only change we need to apply to make this work:&lt;/p&gt;

&lt;video controls width="700"&gt;
    &lt;source src="/images/blog/image-uploads-ember/edit-band-works.mp4"
            type="video/mp4"&gt;
    Sorry, your browser doesn't support embedded videos.
&lt;/video&gt;

&lt;h3&gt;A boring fix&lt;/h3&gt;

&lt;p&gt;When I said the above code change was the only change in the component&amp;#39;s code to make the edit scenario work, it wasn&amp;#39;t entirely true.&lt;/p&gt;

&lt;p&gt;I needed to change some code in the catalog service for the update to work correctly. For the readers who have coded the Rock &amp;amp; Roll app and now follow along with this series also writing the code (if you&amp;#39;re one of them, you&amp;#39;re great!), they&amp;#39;ll need to make the following code change:&lt;/p&gt;
&lt;div class="highlight js  "&gt;&lt;div class="ribbon"&gt;&lt;/div&gt;&lt;div class="scroller"&gt;&lt;div class="CodeRay"&gt;
  &lt;div class="code"&gt;&lt;pre&gt;&lt;span class="line-numbers"&gt; &lt;a href="#n1" name="n1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span class="comment"&gt;// app/services/catalog.js&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n2" name="n2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span class="reserved"&gt;export&lt;/span&gt; &lt;span class="keyword"&gt;default&lt;/span&gt; &lt;span class="reserved"&gt;class&lt;/span&gt; CatalogService &lt;span class="reserved"&gt;extends&lt;/span&gt; Service {
&lt;span class="line-numbers"&gt; &lt;a href="#n3" name="n3"&gt;3&lt;/a&gt;&lt;/span&gt;  &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n4" name="n4"&gt;4&lt;/a&gt;&lt;/span&gt;  async update(type, record, attributes) {
&lt;span class="line-numbers"&gt; &lt;a href="#n5" name="n5"&gt;5&lt;/a&gt;&lt;/span&gt;    &lt;span class="comment"&gt;// (...)&lt;/span&gt;
&lt;span class="line-numbers"&gt; &lt;a href="#n6" name="n6"&gt;6&lt;/a&gt;&lt;/span&gt;    let response = await fetch(url, {
&lt;span class="line-numbers"&gt; &lt;a href="#n7" name="n7"&gt;7&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;method&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;PATCH&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt; &lt;a href="#n8" name="n8"&gt;8&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;headers&lt;/span&gt;: {
&lt;span class="line-numbers"&gt; &lt;a href="#n9" name="n9"&gt;9&lt;/a&gt;&lt;/span&gt;        &lt;span class="key"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;Content-Type&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;: &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;application/vnd.api+json&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n10" name="n10"&gt;10&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;      },
&lt;span class="line-numbers"&gt;&lt;a href="#n11" name="n11"&gt;11&lt;/a&gt;&lt;/span&gt;      &lt;span class="key"&gt;body&lt;/span&gt;: JSON.stringify(payload),
&lt;span class="line-numbers"&gt;&lt;a href="#n12" name="n12"&gt;12&lt;/a&gt;&lt;/span&gt;    });
&lt;span class="line-numbers"&gt;&lt;a href="#n13" name="n13"&gt;13&lt;/a&gt;&lt;/span&gt;    let json = await response.json();
&lt;span class="line-numbers"&gt;&lt;a href="#n14" name="n14"&gt;14&lt;/a&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="local-variable"&gt;this&lt;/span&gt;.load(json);
&lt;span class="line-numbers"&gt;&lt;a href="#n15" name="n15"&gt;15&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n16" name="n16"&gt;16&lt;/a&gt;&lt;/span&gt;
&lt;span class="line-numbers"&gt;&lt;a href="#n17" name="n17"&gt;17&lt;/a&gt;&lt;/span&gt;  add(type, record) {
&lt;span class="line-numbers"&gt;&lt;a href="#n18" name="n18"&gt;18&lt;/a&gt;&lt;/span&gt;    let collection = type === &lt;span class="string"&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;span class="content"&gt;band&lt;/span&gt;&lt;span class="delimiter"&gt;'&lt;/span&gt;&lt;/span&gt; ? &lt;span class="local-variable"&gt;this&lt;/span&gt;.storage.bands : &lt;span class="local-variable"&gt;this&lt;/span&gt;.storage.songs;
&lt;span class="line-numbers"&gt;&lt;a href="#n19" name="n19"&gt;19&lt;/a&gt;&lt;/span&gt;    let existingIndex = collection.findIndex((r) =&amp;gt; record.id === r.id);
&lt;span class="line-numbers"&gt;&lt;strong&gt;&lt;a href="#n20" name="n20"&gt;20&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (existingIndex === -&lt;span class="integer"&gt;1&lt;/span&gt;) {
&lt;span class="line-numbers"&gt;&lt;a href="#n21" name="n21"&gt;21&lt;/a&gt;&lt;/span&gt;      collection.push(record);
&lt;span class="line-numbers"&gt;&lt;a href="#n22" name="n22"&gt;22&lt;/a&gt;&lt;/span&gt;    } &lt;span class="keyword"&gt;else&lt;/span&gt; {
&lt;span class="line-numbers"&gt;&lt;a href="#n23" name="n23"&gt;23&lt;/a&gt;&lt;/span&gt;      collection.splice(existingIndex, &lt;span class="integer"&gt;1&lt;/span&gt;, record);
&lt;span class="line-numbers"&gt;&lt;a href="#n24" name="n24"&gt;24&lt;/a&gt;&lt;/span&gt;    }
&lt;span class="line-numbers"&gt;&lt;a href="#n25" name="n25"&gt;25&lt;/a&gt;&lt;/span&gt;  }
&lt;span class="line-numbers"&gt;&lt;a href="#n26" name="n26"&gt;26&lt;/a&gt;&lt;/span&gt;}
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There are still fascinating further improvements to make, some of which I&amp;#39;m planning to write about. Removing or replacing the band image, post-processing the uploaded image, and testing the image upload come to mind at first, but I&amp;#39;m sure we could add several other features.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;
</content>
  </entry>
</feed>
