For Super Rentals, we want to arrive at a home page which displays a list of rentals. From there, we should be able to visit an about page and our contact page.
An About Route
Let's start by building our "about" page.
In Ember, when we want to make a new page that can be visited using a URL, we need to generate a "route" using Ember CLI. For a quick overview of how Ember structures things, see our diagram on the Core Concepts page.
Let's use Ember's route generator to start our about
route.
ember generate route about
or for short,
ember g route about
Note: Running ember help generate
will list a number of other Ember resources you can create as well ...
And here's what our generator prints out:
installing route
create app/routes/about.js
create app/templates/about.hbs
updating router
add route about
installing route-test
create tests/unit/routes/about-test.js
An Ember route is built with three parts:
- An entry in the Ember router (
/app/router.js
), which maps between our route name and a specific URI - A route handler file, which sets up what should happen when that route is loaded
(app/routes/about.js)
- A route template, which is where we display the actual content for the page
(app/templates/about.hbs)
If we open /app/router.js
, we'll see a new line of code for the about route, calling
this.route('about')
in the Router.map
function. That new line of code tells the Ember router
to run our /app/routes/about.js
file when a visitor navigates to /about
.
import EmberRouter from '@ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('about');
});
export default Router;
Because we only plan to display static content on our about page, we won't adjust the /app/routes/about.js
route handler file right now. Instead, let's open our /app/templates/about.hbs
template file and add some info about
Super Rentals:
Now run ember serve
(or ember server
, or even ember s
for short) on your command line to start
the Ember development server and then go to http://localhost:4200/about
to
see our new page in action!
A Contact Route
Now let's create another route with contact details for the company. Once again, we'll start by generating a route:
ember g route contact
Here again, we add a new contact
route in app/router.js
and generate a route handler in app/routes/contact.js
.
In the route template /app/templates/contact.hbs
, let's add our contact details:
Now when we go to http://localhost:4200/contact
, we'll see our contact page.
Navigating with Links and the {{link-to}} Helper
Moving around our site is a bit of a pain right now, so let's make that easier. We'll put a link to the contact page on the about page, and a corresponding link to the about page on the contact page.
To do that, we'll use a {{link-to}}
helper that Ember provides
that makes it easy to link between our routes. Let's adjust our about.hbs
file:
In this case, we're telling the {{link-to}}
helper the name of the route we want to link to: contact
.
When we look at our about page at http://localhost:4200/about
, we now have
a working link to our contact page:
Now, we'll add our corresponding link to the contact page so we can move back and forth between about
and contact
:
A Rentals Route
In addition to our about
and contact
pages, we want to show a list of rentals that
our visitors can look through. So let's add a third route and call it rentals
:
ember g route rentals
And then let's update our new template (/app/templates/rentals.hbs
) with some initial content.
We'll come back to this page in a bit to add in the actual rental properties.
An Index Route
With our three routes in place, we are ready to add an index route, which will handle requests to the root URI (/
) of our site.
We'd like to make the rentals page the main page of our application, and we've already created a route.
Therefore, we want our index route to simply forward to the rentals
route we've already created.
Using the same process we did for our about and contact pages, we will first generate a new route called index
.
ember g route index
We can see the now familiar output for the route generator:
installing route
create app/routes/index.js
create app/templates/index.hbs
installing route-test
create tests/unit/routes/index-test.js
Unlike the other route handlers we've made so far, the index
route is special:
it does NOT require an entry in the router's mapping.
We'll learn more about why the entry isn't required later on when we look at nested routes in Ember.
All we want to do when a user visits the root (/
) URL is transition to
/rentals
. To do this we will add code to our index route handler by
implementing a route lifecycle hook called beforeModel
.
Route lifecycle hooks are special methods that are called automatically when a route renders or data changes.
Inside, we'll call the
replaceWith
function:
import Route from '@ember/routing/route';
export default Route.extend({
beforeModel() {
this.replaceWith('rentals');
}
});
The replaceWith
function is similar to the route's
transitionTo()
function, the difference being that replaceWith
will replace the current URL
in the browser's history, while transitionTo
will add to the history. Since we
want our rentals
route to serve as our home page, we will use the
replaceWith
function.
Now visiting the root route at /
will result in the /rentals
URL loading.
Adding a Banner with Navigation
In addition to adding individual links to each route of our app, we'd like to add a common header across the top of our page to display our app's title and its navigation bar.
To show something on every page, we can use the application template (which we edited earlier).
Let's open it again (/app/templates/application.hbs
) and replace its contents with the following:
We've seen most of this before, but the {{outlet}}
beneath <div class="body">
is new.
The {{outlet}}
helper tells Ember where content for our current route (such as about
or contact
) should be shown.
At this point, we should be able to navigate between our about
, contact
, and rentals
pages.
From here you can move on to the next page or dive into testing the new functionality we just added.
Implementing Application Tests
Now that we have various pages in our application, let's walk through how to build tests for them.
As mentioned earlier on the Planning the Application page, an Ember application test automates interacting with our app in a similar way to a visitor.
If you open the application test we created (/tests/acceptance/list-rentals-test.js
), you'll see our
goals, which include the ability to navigate to an about
page and a contact
page. Let's start there.
First, we want to test that visiting /
properly redirects to /rentals
. We'll use the Ember visit
helper
and then make sure our current URL is /rentals
once the redirect occurs.
import { module, test } from 'qunit';
import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | list rentals', function (hooks) {
setupApplicationTest(hooks);
test('should show rentals as the home page', async function (assert) {
await visit('/');
assert.equal(currentURL(), '/rentals', 'should redirect automatically');
});
});
Now run the tests by typing ember test --server
in the command line (or ember t -s
for short).
Instead of 7 failures there should now be 6 (5 application failures and 1 ESLint). You can also run our specific test by selecting the entry called "Acceptance | list rentals" in the drop down input labeled "Module" on the test UI.
You can also toggle "Hide passed tests" to show your passing test case along with the tests that are still failing (because we haven't yet built them/).
Ember's test helpers
Ember provides a variety of application test helpers to make common tasks easier, such as visiting routes, filling in fields, clicking on links/buttons, and waiting for pages to display.
Some of the helpers we'll use commonly are:
visit
- loads a given URLclick
- pretends to be a user clicking on a specific part of the screencurrentURL
- returns the URL of the page we're currently on
Let's import these helpers into our application test:
import { visit, currentURL } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import {
click,
currentURL,
visit
} from '@ember/test-helpers'
Test visiting our About and Contact pages
Now let's add code that simulates a visitor arriving on our homepage, clicking one of our links and then visiting a new page.
test('should link to information about the company', async function(assert) {
await visit('/');
await click(".menu-about");
assert.equal(currentURL(), '/about', 'should navigate to about');
});
test('should link to contact information', async function(assert) {
await visit('/');
await click(".menu-contact");
assert.equal(currentURL(), '/contact', 'should navigate to contact');
});
In the tests above, we're using assert.equal()
to check if the first and second arguments equal each other.
If they don't, our test will fail.
The third optional argument allows us to provide a nicer message which will be shown if this test fails.
In our tests, we also call two helpers (visit
and click
) one after another. Although Ember does a number
of things when we make those calls, Ember hides those complexities by giving us these asynchronous test helpers.
If you left ember test
running, it should have automatically updated to show the three tests related to
navigating have now passed.
In the screen recording below, we run the tests, deselect "Hide passed tests", and set the module to our application test, revealing the 3 tests we got passing.