From c32aa34f39b4257fcf72c6bd18c69d9893816bb2 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 11 Jan 2021 12:47:36 -0800 Subject: [PATCH] Fix issues with prettier See https://github.com/emberjs/ember.js/issues/19333 See https://github.com/emberjs/data/issues/7418 --- .../tutorial/part-1/01-orientation.md | 2 +- .../tutorial/part-1/03-automated-testing.md | 28 +++- .../tutorial/part-1/04-component-basics.md | 31 +++- .../part-1/05-more-about-components.md | 69 +++++++-- .../part-1/06-interactive-components.md | 2 +- .../tutorial/part-1/07-reusable-components.md | 142 +++++++++++++----- .../tutorial/part-1/08-working-with-data.md | 22 ++- .../tutorial/part-2/09-route-params.md | 72 ++++++--- .../tutorial/part-2/10-service-injection.md | 126 ++++++++++------ src/markdown/tutorial/part-2/11-ember-data.md | 60 +++++--- .../tutorial/part-2/12-provider-components.md | 124 ++++++++------- 11 files changed, 460 insertions(+), 218 deletions(-) diff --git a/src/markdown/tutorial/part-1/01-orientation.md b/src/markdown/tutorial/part-1/01-orientation.md index e8830c21..1c4630ca 100644 --- a/src/markdown/tutorial/part-1/01-orientation.md +++ b/src/markdown/tutorial/part-1/01-orientation.md @@ -182,7 +182,7 @@ del package.json + details.runtime = totalRuntime; + }); + -+ QUnit.begin(function( details ) { ++ QUnit.begin(details => { + let ua = document.getElementById('qunit-userAgent'); + ua.innerText = ua.innerText.replace(/QUnit [0-9\.]+/g, 'QUnit'); + ua.innerText = ua.innerText.replace(/(WebKit|Chrome|Safari)\/[0-9\.]+/g, '$1'); diff --git a/src/markdown/tutorial/part-1/03-automated-testing.md b/src/markdown/tutorial/part-1/03-automated-testing.md index e3d6c08f..b7ff9cc7 100644 --- a/src/markdown/tutorial/part-1/03-automated-testing.md +++ b/src/markdown/tutorial/part-1/03-automated-testing.md @@ -45,6 +45,26 @@ In this case, we generated an *[acceptance test](../../../testing/test-types/#to git add tests/acceptance/super-rentals-test.js ``` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/acceptance/super-rentals-test.js +@@ -4,6 +4,6 @@ + +-module('Acceptance | super rentals', function(hooks) { ++module('Acceptance | super rentals', function (hooks) { + setupApplicationTest(hooks); + +- test('visiting /super-rentals', async function(assert) { ++ test('visiting /super-rentals', async function (assert) { + await visit('/super-rentals'); +``` + +```run:command hidden=true cwd=super-rentals +git add tests/acceptance/super-rentals-test.js +``` + + + Generators aren't required; we *could* have created the file ourselves which would have accomplished the exact same thing. But, generators certainly save us a lot of typing. Go ahead and take a peek at the acceptance test file and see for yourself. > Zoey says... @@ -65,11 +85,11 @@ Let's open the generated test file and replace the boilerplate test with our own import { setupApplicationTest } from 'ember-qunit'; @@ -7,6 +7,12 @@ -- test('visiting /super-rentals', async function(assert) { +- test('visiting /super-rentals', async function (assert) { - await visit('/super-rentals'); - - assert.equal(currentURL(), '/super-rentals'); -+ test('visiting /', async function(assert) { ++ test('visiting /', async function (assert) { + await visit('/'); + + assert.equal(currentURL(), '/'); @@ -149,7 +169,7 @@ Let's practice what we learned by adding tests for the remaining pages: @@ -18,2 +18,26 @@ }); + -+ test('visiting /about', async function(assert) { ++ test('visiting /about', async function (assert) { + await visit('/about'); + + assert.equal(currentURL(), '/about'); @@ -161,7 +181,7 @@ Let's practice what we learned by adding tests for the remaining pages: + assert.equal(currentURL(), '/getting-in-touch'); + }); + -+ test('visiting /getting-in-touch', async function(assert) { ++ test('visiting /getting-in-touch', async function (assert) { + await visit('/getting-in-touch'); + + assert.equal(currentURL(), '/getting-in-touch'); diff --git a/src/markdown/tutorial/part-1/04-component-basics.md b/src/markdown/tutorial/part-1/04-component-basics.md index db1d9ada..e5c8efc0 100644 --- a/src/markdown/tutorial/part-1/04-component-basics.md +++ b/src/markdown/tutorial/part-1/04-component-basics.md @@ -153,6 +153,29 @@ ember generate component-test jumbo git add tests/integration/components/jumbo-test.js ``` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/integration/components/jumbo-test.js +@@ -5,8 +5,8 @@ + +-module('Integration | Component | jumbo', function(hooks) { ++module('Integration | Component | jumbo', function (hooks) { + setupRenderingTest(hooks); + +- test('it renders', async function(assert) { ++ test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); +- // Handle any actions with this.set('myAction', function(val) { ... }); ++ // Handle any actions with this.set('myAction', function (val) { ... }); + +``` + +```run:command hidden=true cwd=super-rentals +git add tests/integration/components/jumbo-test.js +``` + + + Here, we used the generator to generate a *[component test](../../../testing/testing-components/)*, also known as a rendering test. These are used to render and test a single component at a time. This is in contrast to the acceptance tests that we wrote earlier, which have to navigate and render entire pages worth of content. Let's replace the boilerplate code that was generated for us with our own test: @@ -160,9 +183,9 @@ Let's replace the boilerplate code that was generated for us with our own test: ```run:file:patch lang=js cwd=super-rentals filename=tests/integration/components/jumbo-test.js @@ -8,18 +8,8 @@ -- test('it renders', async function(assert) { +- test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); -- // Handle any actions with this.set('myAction', function(val) { ... }); +- // Handle any actions with this.set('myAction', function (val) { ... }); - - await render(hbs``); - @@ -176,7 +199,7 @@ Let's replace the boilerplate code that was generated for us with our own test: - `); - - assert.equal(this.element.textContent.trim(), 'template block text'); -+ test('it renders the content inside a jumbo header with a tomster', async function(assert) { ++ test('it renders the content inside a jumbo header with a tomster', async function (assert) { + await render(hbs`Hello World`); + + assert.dom('.jumbo').exists(); @@ -282,7 +305,7 @@ But what kind of test? We *could* write a component test for the `` by i @@ -42,2 +48,20 @@ }); + -+ test('navigating using the nav-bar', async function(assert) { ++ test('navigating using the nav-bar', async function (assert) { + await visit('/'); + + assert.dom('nav').exists(); diff --git a/src/markdown/tutorial/part-1/05-more-about-components.md b/src/markdown/tutorial/part-1/05-more-about-components.md index 3f00535e..86d592b2 100644 --- a/src/markdown/tutorial/part-1/05-more-about-components.md +++ b/src/markdown/tutorial/part-1/05-more-about-components.md @@ -31,6 +31,29 @@ git add app/components/rental.hbs git add tests/integration/components/rental-test.js ``` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/integration/components/rental-test.js +@@ -5,8 +5,8 @@ + +-module('Integration | Component | rental', function(hooks) { ++module('Integration | Component | rental', function (hooks) { + setupRenderingTest(hooks); + +- test('it renders', async function(assert) { ++ test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); +- // Handle any actions with this.set('myAction', function(val) { ... }); ++ // Handle any actions with this.set('myAction', function (val) { ... }); + +``` + +```run:command hidden=true cwd=super-rentals +git add tests/integration/components/rental-test.js +``` + + + We will start by editing the template. Let's *[hard-code](https://en.wikipedia.org/wiki/Hard_coding)* the details for one rental property for now, and replace it with the real data from the server later on. ```run:file:patch lang=handlebars cwd=super-rentals filename=app/components/rental.hbs @@ -61,9 +84,9 @@ Then, we will write a test to ensure all of the details are present. We will rep ```run:file:patch lang=js cwd=super-rentals filename=tests/integration/components/rental-test.js @@ -8,18 +8,11 @@ -- test('it renders', async function(assert) { +- test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); -- // Handle any actions with this.set('myAction', function(val) { ... }); +- // Handle any actions with this.set('myAction', function (val) { ... }); - - await render(hbs``); - @@ -77,7 +100,7 @@ Then, we will write a test to ensure all of the details are present. We will rep - `); - - assert.equal(this.element.textContent.trim(), 'template block text'); -+ test('it renders information about a rental property', async function(assert) { ++ test('it renders information about a rental property', async function (assert) { + await render(hbs``); + + assert.dom('article').hasClass('rental'); @@ -147,6 +170,30 @@ git add app/components/rental/image.hbs git add tests/integration/components/rental/image-test.js ``` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/integration/components/rental/image-test.js +@@ -5,8 +5,8 @@ + +-module('Integration | Component | rental/image', function(hooks) { ++module('Integration | Component | rental/image', function (hooks) { + setupRenderingTest(hooks); + +- test('it renders', async function(assert) { ++ test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); +- // Handle any actions with this.set('myAction', function(val) { ... }); ++ // Handle any actions with this.set('myAction', function (val) { ... }); + +``` + +```run:command hidden=true cwd=super-rentals +git add tests/integration/components/rental/image-test.js +``` + + + + Components like these are known as *[namespaced](https://en.wikipedia.org/wiki/Namespace)* components. Namespacing allows us to organize our components by folders according to their purpose. This is completely optional—namespaced components are not special in any way. ## Forwarding HTML Attributes with `...attributes` @@ -188,11 +235,11 @@ In general, it is a good idea to add `...attributes` to the primary element in y Let's write a test for our new component! ```run:file:patch lang=js cwd=super-rentals filename=tests/integration/components/rental/image-test.js -@@ -8,18 +8,13 @@ +@@ -8,18 +8,15 @@ -- test('it renders', async function(assert) { +- test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); -- // Handle any actions with this.set('myAction', function(val) { ... }); +- // Handle any actions with this.set('myAction', function (val) { ... }); - - await render(hbs``); - @@ -206,7 +253,7 @@ Let's write a test for our new component! - `); - - assert.equal(this.element.textContent.trim(), 'template block text'); -+ test('it renders the given image', async function(assert) { ++ test('it renders the given image', async function (assert) { + await render(hbs` + + `); + -+ assert.dom('.image').exists(); -+ assert.dom('.image img').hasAttribute('src', '/assets/images/teaching-tomster.png'); -+ assert.dom('.image img').hasAttribute('alt', 'Teaching Tomster'); ++ assert ++ .dom('.image img') ++ .exists() ++ .hasAttribute('src', '/assets/images/teaching-tomster.png') ++ .hasAttribute('alt', 'Teaching Tomster'); }); ``` diff --git a/src/markdown/tutorial/part-1/06-interactive-components.md b/src/markdown/tutorial/part-1/06-interactive-components.md index a0f64edb..ef2855f7 100644 --- a/src/markdown/tutorial/part-1/06-interactive-components.md +++ b/src/markdown/tutorial/part-1/06-interactive-components.md @@ -240,7 +240,7 @@ Finally, let's write a test for this new behavior: @@ -20,2 +21,26 @@ }); + -+ test('clicking on the component toggles its size', async function(assert) { ++ test('clicking on the component toggles its size', async function (assert) { + await render(hbs` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/integration/components/map-test.js +@@ -5,8 +5,8 @@ + +-module('Integration | Component | map', function(hooks) { ++module('Integration | Component | map', function (hooks) { + setupRenderingTest(hooks); + +- test('it renders', async function(assert) { ++ test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); +- // Handle any actions with this.set('myAction', function(val) { ... }); ++ // Handle any actions with this.set('myAction', function (val) { ... }); + +``` + +```run:command hidden=true cwd=super-rentals +git add tests/integration/components/map-test.js +``` + + + ## Parameterizing Components with Arguments Let's start with our JavaScript file: @@ -166,48 +189,62 @@ We just added a lot of behavior into a single component, so let's write some tes import { hbs } from 'ember-cli-htmlbars'; +import ENV from 'super-rentals/config/environment'; -@@ -8,18 +9,53 @@ +@@ -8,18 +9,73 @@ -- test('it renders', async function(assert) { +- test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); -- // Handle any actions with this.set('myAction', function(val) { ... }); -- +- // Handle any actions with this.set('myAction', function (val) { ... }); ++ test('it renders a map image for the specified parameters', async function (assert) { ++ await render(hbs``); + - await render(hbs``); -- ++ assert ++ .dom('.map img') ++ .exists() ++ .hasAttribute('alt', 'Map image at coordinates 37.7797,-122.4184') ++ .hasAttribute('src') ++ .hasAttribute('width', '150') ++ .hasAttribute('height', '120'); + - assert.equal(this.element.textContent.trim(), ''); -- ++ let { src } = find('.map img'); ++ let token = encodeURIComponent(ENV.MAPBOX_ACCESS_TOKEN); + - // Template block usage: - await render(hbs` - - template block text - - `); -- ++ assert.ok( ++ src.startsWith('https://api.mapbox.com/'), ++ 'the src starts with "https://api.mapbox.com/"' ++ ); + - assert.equal(this.element.textContent.trim(), 'template block text'); -+ test('it renders a map image for the specified parameters', async function(assert) { -+ await render(hbs``); -+ -+ assert.dom('.map').exists(); -+ assert.dom('.map img').hasAttribute('alt', 'Map image at coordinates 37.7797,-122.4184'); -+ assert.dom('.map img').hasAttribute('src', /^https:\/\/api\.mapbox\.com/, 'the src starts with "https://api.mapbox.com"'); -+ assert.dom('.map img').hasAttribute('width', '150'); -+ assert.dom('.map img').hasAttribute('height', '120'); ++ assert.ok( ++ src.includes('-122.4184,37.7797,10'), ++ 'the src should include the lng,lat,zoom parameter' ++ ); + -+ let { src } = find('.map img'); -+ let token = encodeURIComponent(ENV.MAPBOX_ACCESS_TOKEN); ++ assert.ok( ++ src.includes('150x120@2x'), ++ 'the src should include the width,height and @2x parameter' ++ ); + -+ assert.ok(src.includes('-122.4184,37.7797,10'), 'the src should include the lng,lat,zoom parameter'); -+ assert.ok(src.includes('150x120@2x'), 'the src should include the width,height and @2x parameter'); -+ assert.ok(src.includes(`access_token=${token}`), 'the src should include the escaped access token'); ++ assert.ok( ++ src.includes(`access_token=${token}`), ++ 'the src should include the escaped access token' ++ ); + }); + -+ test('the default alt attribute can be overridden', async function(assert) { ++ test('the default alt attribute can be overridden', async function (assert) { + await render(hbs``); + -+ assert.dom('.map img').hasAttribute('src', /^https:\/\/api\.mapbox\.com/, 'the src starts with "https://api.mapbox.com"'); -+ assert.dom('.map img').hasAttribute('width', '150'); -+ assert.dom('.map img').hasAttribute('height', '120'); ++ assert ++ .dom('.map img') ++ .hasAttribute('src', /^https:\/\/api\.mapbox\.com\//) ++ .hasAttribute('width', '150') ++ .hasAttribute('height', '120'); }); ``` @@ -322,7 +361,7 @@ index 78e765f..1cad468 100644 + let { lng, lat, width, height, zoom } = this.args; + + let coordinates = `${lng},${lat},${zoom}`; -+ let dimensions = `${width}x${height}`; ++ let dimensions = `${width}x${height}`; + let accessToken = `access_token=${this.token}`; + + return `${MAPBOX_API}/${coordinates}/${dimensions}@2x?${accessToken}`; @@ -363,9 +402,9 @@ Ember does this by automatically tracking any variables that were accessed while Just to be sure, we can add a test for this behavior: ```run:file:patch lang=js cwd=super-rentals filename=tests/integration/components/map-test.js -@@ -32,2 +32,42 @@ +@@ -32,2 +32,63 @@ -+ test('it updates the `src` attribute when the arguments change', async function(assert) { ++ test('it updates the `src` attribute when the arguments change', async function (assert) { + this.setProperties({ + lat: 37.7749, + lng: -122.4194, @@ -384,8 +423,15 @@ Just to be sure, we can add a test for this behavior: + + let img = find('.map img'); + -+ assert.ok(img.src.includes('-122.4194,37.7749,10'), 'the src should include the lng,lat,zoom parameter'); -+ assert.ok(img.src.includes('150x120@2x'), 'the src should include the width,height and @2x parameter'); ++ assert.ok( ++ img.src.includes('-122.4194,37.7749,10'), ++ 'the src should include the lng,lat,zoom parameter' ++ ); ++ ++ assert.ok( ++ img.src.includes('150x120@2x'), ++ 'the src should include the width,height and @2x parameter' ++ ); + + this.setProperties({ + width: 300, @@ -393,19 +439,33 @@ Just to be sure, we can add a test for this behavior: + zoom: 12, + }); + -+ assert.ok(img.src.includes('-122.4194,37.7749,12'), 'the src should include the lng,lat,zoom parameter'); -+ assert.ok(img.src.includes('300x200@2x'), 'the src should include the width,height and @2x parameter'); ++ assert.ok( ++ img.src.includes('-122.4194,37.7749,12'), ++ 'the src should include the lng,lat,zoom parameter' ++ ); ++ ++ assert.ok( ++ img.src.includes('300x200@2x'), ++ 'the src should include the width,height and @2x parameter' ++ ); + + this.setProperties({ + lat: 47.6062, + lng: -122.3321, + }); + -+ assert.ok(img.src.includes('-122.3321,47.6062,12'), 'the src should include the lng,lat,zoom parameter'); -+ assert.ok(img.src.includes('300x200@2x'), 'the src should include the width,height and @2x parameter'); ++ assert.ok( ++ img.src.includes('-122.3321,47.6062,12'), ++ 'the src should include the lng,lat,zoom parameter' ++ ); ++ ++ assert.ok( ++ img.src.includes('300x200@2x'), ++ 'the src should include the width,height and @2x parameter' ++ ); + }); + - test('the default alt attribute can be overridden', async function(assert) { + test('the default alt attribute can be overridden', async function (assert) { ``` Using the special `this.setProperties` testing API, we can pass arbitrary values into our component. diff --git a/src/markdown/tutorial/part-1/08-working-with-data.md b/src/markdown/tutorial/part-1/08-working-with-data.md index 172a5625..925fed02 100644 --- a/src/markdown/tutorial/part-1/08-working-with-data.md +++ b/src/markdown/tutorial/part-1/08-working-with-data.md @@ -176,8 +176,8 @@ Because component tests are meant to render and test a single component in isola Therefore, in our `` component's test, we will have to feed the data into it some other way. We can do this using the `setProperties` we learned about from the [previous chapter](../reusable-components/). ```run:file:patch lang=js cwd=super-rentals filename=tests/integration/components/rental-test.js -@@ -9,3 +9,20 @@ - test('it renders information about a rental property', async function(assert) { +@@ -9,3 +9,22 @@ + test('it renders information about a rental property', async function (assert) { - await render(hbs``); + this.setProperties({ + rental: { @@ -191,9 +191,11 @@ Therefore, in our `` component's test, we will have to feed the data int + category: 'Estate', + type: 'Standalone', + bedrooms: 15, -+ image: 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', -+ description: 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', -+ } ++ image: ++ 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', ++ description: ++ 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', ++ }, + }); + + await render(hbs``); @@ -310,13 +312,9 @@ In [Part 2](../../part-2/) of this tutorial, we will learn about a more convenie We can handle it all in our model hook: ```run:file:patch lang=js cwd=super-rentals filename=app/routes/index.js -@@ -2,7 +2,25 @@ +@@ -2,7 +2,21 @@ -+const COMMUNITY_CATEGORIES = [ -+ 'Condo', -+ 'Townhouse', -+ 'Apartment' -+]; ++const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; + export default class IndexRoute extends Route { async model() { @@ -325,7 +323,7 @@ We can handle it all in our model hook: - return parsed; + let { data } = await response.json(); + -+ return data.map(model => { ++ return data.map((model) => { + let { attributes } = model; + let type; + diff --git a/src/markdown/tutorial/part-2/09-route-params.md b/src/markdown/tutorial/part-2/09-route-params.md index 11f3761e..b04d7318 100644 --- a/src/markdown/tutorial/part-2/09-route-params.md +++ b/src/markdown/tutorial/part-2/09-route-params.md @@ -76,7 +76,7 @@ If we look at the JSON data here, we can see that the `id` is included right alo ```run:file:patch lang=js cwd=super-rentals filename=app/routes/index.js @@ -14,3 +14,3 @@ - return data.map(model => { + return data.map((model) => { - let { attributes } = model; + let { id, attributes } = model; let type; @@ -102,9 +102,11 @@ Alright, we have just one more step left here: updating the tests. We can add an rental: { + id: 'grand-old-mansion', title: 'Grand Old Mansion', -@@ -30,2 +31,3 @@ +@@ -30,2 +31,5 @@ assert.dom('article h3').hasText('Grand Old Mansion'); -+ assert.dom('article h3 a').hasAttribute('href', '/rentals/grand-old-mansion'); ++ assert ++ .dom('article h3 a') ++ .hasAttribute('href', '/rentals/grand-old-mansion'); assert.dom('article .detail.owner').includesText('Veruca Salt'); ``` @@ -130,11 +132,11 @@ In this situation, we essentially need to *specifically* opt-in to explicitly us ```run:file:patch lang=js cwd=super-rentals filename=tests/integration/components/rental-test.js @@ -8,2 +8,6 @@ -+ hooks.beforeEach(function() { ++ hooks.beforeEach(function () { + this.owner.setupRouter(); + }); + - test('it renders information about a rental property', async function(assert) { + test('it renders information about a rental property', async function (assert) { ``` > Zoey says... @@ -162,11 +164,7 @@ Now that we have our `rental` route, let's finish up our `rental` page. The firs ```run:file:create lang=js cwd=super-rentals filename=app/routes/rental.js import Route from '@ember/routing/route'; -const COMMUNITY_CATEGORIES = [ - 'Condo', - 'Townhouse', - 'Apartment' -]; +const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalRoute extends Route { async model(params) { @@ -213,6 +211,30 @@ git add app/components/rental/detailed.hbs git add tests/integration/components/rental/detailed-test.js ``` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/integration/components/rental/detailed-test.js +@@ -5,8 +5,8 @@ + +-module('Integration | Component | rental/detailed', function(hooks) { ++module('Integration | Component | rental/detailed', function (hooks) { + setupRenderingTest(hooks); + +- test('it renders', async function(assert) { ++ test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); +- // Handle any actions with this.set('myAction', function(val) { ... }); ++ // Handle any actions with this.set('myAction', function (val) { ... }); + +``` + +```run:command hidden=true cwd=super-rentals +git add tests/integration/components/rental/detailed-test.js +``` + + + + ```run:file:patch lang=handlebars cwd=super-rentals filename=app/components/rental/detailed.hbs @@ -1 +1,44 @@ -{{yield}} @@ -275,12 +297,12 @@ This component is similar to our `` component, except for the following Now that we have this template in place, we can add some tests for this new component of ours. ```run:file:patch lang=handlebars cwd=super-rentals filename=tests/integration/components/rental/detailed-test.js -@@ -8,18 +8,42 @@ +@@ -8,18 +8,46 @@ -- test('it renders', async function(assert) { +- test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); -- // Handle any actions with this.set('myAction', function(val) { ... }); -+ hooks.beforeEach(function() { +- // Handle any actions with this.set('myAction', function (val) { ... }); ++ hooks.beforeEach(function () { + this.setProperties({ + rental: { + id: 'grand-old-mansion', @@ -294,20 +316,24 @@ Now that we have this template in place, we can add some tests for this new comp + category: 'Estate', + type: 'Standalone', + bedrooms: 15, -+ image: 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', -+ description: 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', -+ } ++ image: ++ 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', ++ description: ++ 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', ++ }, + }); + }); - await render(hbs``); -+ test('it renders a header with a share button', async function(assert) { ++ test('it renders a header with a share button', async function (assert) { + await render(hbs``); - assert.equal(this.element.textContent.trim(), ''); + assert.dom('.jumbo').exists(); + assert.dom('.jumbo h2').containsText('Grand Old Mansion'); -+ assert.dom('.jumbo p').containsText('a nice place to stay near San Francisco'); ++ assert ++ .dom('.jumbo p') ++ .containsText('a nice place to stay near San Francisco'); + assert.dom('.jumbo a.button').containsText('Share on Twitter'); + }); @@ -317,7 +343,7 @@ Now that we have this template in place, we can add some tests for this new comp - template block text - - `); -+ test('it renders detailed information about a rental property', async function(assert) { ++ test('it renders detailed information about a rental property', async function (assert) { + await render(hbs``); - assert.equal(this.element.textContent.trim(), 'template block text'); @@ -356,7 +382,7 @@ Finally, let's add a `rental` template to actually *invoke* our ` + +```run:file:patch hidden=true cwd=super-rentals filename=tests/integration/components/share-button-test.js +@@ -5,8 +5,8 @@ + +-module('Integration | Component | share-button', function(hooks) { ++module('Integration | Component | share-button', function (hooks) { + setupRenderingTest(hooks); + +- test('it renders', async function(assert) { ++ test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); +- // Handle any actions with this.set('myAction', function(val) { ... }); ++ // Handle any actions with this.set('myAction', function (val) { ... }); + +``` + +```run:command hidden=true cwd=super-rentals +git add tests/integration/components/share-button-test.js +``` + + + Let's start with the template that was generated for this component. We already have some markup for the share button in the `` component we made earlier, so let's just copy that over into our new `` component. ```run:file:patch lang=handlebars cwd=super-rentals filename=app/components/share-button.hbs @@ -271,17 +294,17 @@ We will take advantage of this capability in our component test: + } +} + - module('Integration | Component | share-button', function(hooks) { -@@ -8,18 +15,20 @@ + module('Integration | Component | share-button', function (hooks) { +@@ -8,18 +15,22 @@ -- test('it renders', async function(assert) { +- test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); -- // Handle any actions with this.set('myAction', function(val) { ... }); +- // Handle any actions with this.set('myAction', function (val) { ... }); - - await render(hbs``); - - assert.equal(this.element.textContent.trim(), ''); -+ hooks.beforeEach(function() { ++ hooks.beforeEach(function () { + this.owner.register('service:router', MockRouterService); + }); @@ -291,21 +314,23 @@ We will take advantage of this capability in our component test: - template block text - - `); -+ test('basic usage', async function(assert) { ++ test('basic usage', async function (assert) { + await render(hbs`Tweet this!`); - assert.equal(this.element.textContent.trim(), 'template block text'); -+ assert.dom('a').exists(); -+ assert.dom('a').hasAttribute('target', '_blank'); -+ assert.dom('a').hasAttribute('rel', 'external nofollow noopener noreferrer'); -+ assert.dom('a').hasAttribute('href', `https://twitter.com/intent/tweet?url=${ -+ encodeURIComponent( -+ new URL('/foo/bar?baz=true#some-section', window.location.origin) ++ assert ++ .dom('a') ++ .hasAttribute('target', '_blank') ++ .hasAttribute('rel', 'external nofollow noopener noreferrer') ++ .hasAttribute( ++ 'href', ++ `https://twitter.com/intent/tweet?url=${encodeURIComponent( ++ new URL('/foo/bar?baz=true#some-section', window.location.origin) ++ )}` + ) -+ }`); -+ assert.dom('a').hasClass('share'); -+ assert.dom('a').hasClass('button'); -+ assert.dom('a').containsText('Tweet this!'); ++ .hasClass('share') ++ .hasClass('button') ++ .containsText('Tweet this!'); }); ``` @@ -331,23 +356,24 @@ While we are here, let's add some more tests for the various functionalities of @@ -17,2 +17,8 @@ this.owner.register('service:router', MockRouterService); + -+ this.tweetParam = param => { ++ this.tweetParam = (param) => { + let link = find('a'); + let url = new URL(link.href); + return url.searchParams.get(param); + }; }); -@@ -25,7 +31,3 @@ - assert.dom('a').hasAttribute('rel', 'external nofollow noopener noreferrer'); -- assert.dom('a').hasAttribute('href', `https://twitter.com/intent/tweet?url=${ -- encodeURIComponent( -- new URL('/foo/bar?baz=true#some-section', window.location.origin) +@@ -26,8 +32,3 @@ + .hasAttribute('rel', 'external nofollow noopener noreferrer') +- .hasAttribute( +- 'href', +- `https://twitter.com/intent/tweet?url=${encodeURIComponent( +- new URL('/foo/bar?baz=true#some-section', window.location.origin) +- )}` - ) -- }`); -+ assert.dom('a').hasAttribute('href', /^https:\/\/twitter\.com\/intent\/tweet/); - assert.dom('a').hasClass('share'); -@@ -33,2 +35,39 @@ - assert.dom('a').containsText('Tweet this!'); ++ .hasAttribute('href', /^https:\/\/twitter\.com\/intent\/tweet/) + .hasClass('share') +@@ -35,2 +36,53 @@ + .containsText('Tweet this!'); + + assert.equal( + this.tweetParam('url'), @@ -355,36 +381,50 @@ While we are here, let's add some more tests for the various functionalities of + ); + }); + -+ test('it supports passing @text', async function(assert) { -+ await render(hbs`Tweet this!`); ++ test('it supports passing @text', async function (assert) { ++ await render( ++ hbs`Tweet this!` ++ ); ++ + assert.equal(this.tweetParam('text'), 'Hello Twitter!'); + }); + -+ test('it supports passing @hashtags', async function(assert) { -+ await render(hbs`Tweet this!`); ++ test('it supports passing @hashtags', async function (assert) { ++ await render( ++ hbs`Tweet this!` ++ ); ++ + assert.equal(this.tweetParam('hashtags'), 'foo,bar,baz'); + }); + -+ test('it supports passing @via', async function(assert) { ++ test('it supports passing @via', async function (assert) { + await render(hbs`Tweet this!`); + assert.equal(this.tweetParam('via'), 'emberjs'); + }); + -+ test('it supports adding extra classes', async function(assert) { -+ await render(hbs`Tweet this!`); ++ test('it supports adding extra classes', async function (assert) { ++ await render( ++ hbs`Tweet this!` ++ ); + -+ assert.dom('a').hasClass('share'); -+ assert.dom('a').hasClass('button'); -+ assert.dom('a').hasClass('extra'); -+ assert.dom('a').hasClass('things'); ++ assert ++ .dom('a') ++ .hasClass('share') ++ .hasClass('button') ++ .hasClass('extra') ++ .hasClass('things'); + }); + -+ test('the target, rel and href attributes cannot be overridden', async function(assert) { -+ await render(hbs`Not a Tweet!`); ++ test('the target, rel and href attributes cannot be overridden', async function (assert) { ++ await render( ++ hbs`Not a Tweet!` ++ ); + -+ assert.dom('a').hasAttribute('target', '_blank'); -+ assert.dom('a').hasAttribute('rel', 'external nofollow noopener noreferrer'); -+ assert.dom('a').hasAttribute('href', /^https:\/\/twitter\.com\/intent\/tweet/); ++ assert ++ .dom('a') ++ .hasAttribute('target', '_blank') ++ .hasAttribute('rel', 'external nofollow noopener noreferrer') ++ .hasAttribute('href', /^https:\/\/twitter\.com\/intent\/tweet/); }); ``` diff --git a/src/markdown/tutorial/part-2/11-ember-data.md b/src/markdown/tutorial/part-2/11-ember-data.md index 5e38df93..8dc8ba6d 100644 --- a/src/markdown/tutorial/part-2/11-ember-data.md +++ b/src/markdown/tutorial/part-2/11-ember-data.md @@ -44,11 +44,7 @@ Enough talking, why don't we give that a try! ```run:file:create lang=js cwd=super-rentals filename=app/models/rental.js import Model, { attr } from '@ember-data/model'; -const COMMUNITY_CATEGORIES = [ - 'Condo', - 'Townhouse', - 'Apartment' -]; +const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; export default class RentalModel extends Model { @attr title; @@ -101,6 +97,27 @@ ember test --path dist git add tests/unit/models/rental-test.js ``` + + +```run:file:patch hidden=true cwd=super-rentals filename=tests/unit/models/rental-test.js +@@ -3,3 +3,3 @@ + +-module('Unit | Model | rental', function(hooks) { ++module('Unit | Model | rental', function (hooks) { + setupTest(hooks); +@@ -7,3 +7,3 @@ + // Replace this with your real tests. +- test('it exists', function(assert) { ++ test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); +``` + +```run:command hidden=true cwd=super-rentals +git add tests/unit/models/rental-test.js +``` + + + > Zoey says... > > We could also have used the `ember generate model rental` command in the first place, which would have created both the model and test file for us. @@ -108,11 +125,11 @@ git add tests/unit/models/rental-test.js The generator created some boilerplate code for us, which serves as a pretty good starting point for writing our test: ```run:file:patch lang=js cwd=super-rentals filename=tests/unit/models/rental-test.js -@@ -6,7 +6,32 @@ +@@ -6,7 +6,34 @@ - // Replace this with your real tests. -- test('it exists', function(assert) { -+ test('it has the right type', function(assert) { +- test('it exists', function (assert) { ++ test('it has the right type', function (assert) { let store = this.owner.lookup('service:store'); - let model = store.createRecord('rental', {}); - assert.ok(model); @@ -127,8 +144,10 @@ The generator created some boilerplate code for us, which serves as a pretty goo + }, + category: 'Estate', + bedrooms: 15, -+ image: 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', -+ description: 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', ++ image: ++ 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', ++ description: ++ 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', + }); + + assert.equal(rental.type, 'Standalone'); @@ -168,14 +187,10 @@ wait #qunit-banner.qunit-pass Alright, now that we have our model set up, it's time to refactor our route handlers to use Ember Data and remove the duplication! ```run:file:patch lang=js cwd=super-rentals filename=app/routes/index.js -@@ -1,26 +1,9 @@ +@@ -1,22 +1,9 @@ import Route from '@ember/routing/route'; - --const COMMUNITY_CATEGORIES = [ -- 'Condo', -- 'Townhouse', -- 'Apartment' --]; +-const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; +import { inject as service } from '@ember/service'; export default class IndexRoute extends Route { @@ -183,7 +198,7 @@ Alright, now that we have our model set up, it's time to refactor our route hand - let response = await fetch('/api/rentals.json'); - let { data } = await response.json(); - -- return data.map(model => { +- return data.map((model) => { - let { id, attributes } = model; - let type; + @service store; @@ -202,14 +217,10 @@ Alright, now that we have our model set up, it's time to refactor our route hand ``` ```run:file:patch lang=js cwd=super-rentals filename=app/routes/rental.js -@@ -1,24 +1,9 @@ +@@ -1,20 +1,9 @@ import Route from '@ember/routing/route'; - --const COMMUNITY_CATEGORIES = [ -- 'Condo', -- 'Townhouse', -- 'Apartment' --]; +-const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment']; +import { inject as service } from '@ember/service'; export default class RentalRoute extends Route { @@ -282,8 +293,7 @@ export default class ApplicationAdapter extends JSONAPIAdapter { ```run:file:create lang=js cwd=super-rentals filename=app/serializers/application.js import JSONAPISerializer from '@ember-data/serializer/json-api'; -export default class ApplicationSerializer extends JSONAPISerializer { -} +export default class ApplicationSerializer extends JSONAPISerializer {} ``` By convention, adapters are located at `app/adapters`. Furthermore, the adapter named `application` is called the *application adapter*, which will be used to fetch data for all models in our app. diff --git a/src/markdown/tutorial/part-2/12-provider-components.md b/src/markdown/tutorial/part-2/12-provider-components.md index 27363732..a0007359 100644 --- a/src/markdown/tutorial/part-2/12-provider-components.md +++ b/src/markdown/tutorial/part-2/12-provider-components.md @@ -118,56 +118,64 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Component | rentals', function(hooks) { +module('Integration | Component | rentals', function (hooks) { setupRenderingTest(hooks); - test('it renders all given rental properties by default', async function(assert) { + test('it renders all given rental properties by default', async function (assert) { this.setProperties({ - rentals: [{ - id: 'grand-old-mansion', - title: 'Grand Old Mansion', - owner: 'Veruca Salt', - city: 'San Francisco', - location: { - lat: 37.7749, - lng: -122.4194 + rentals: [ + { + id: 'grand-old-mansion', + title: 'Grand Old Mansion', + owner: 'Veruca Salt', + city: 'San Francisco', + location: { + lat: 37.7749, + lng: -122.4194, + }, + category: 'Estate', + type: 'Standalone', + bedrooms: 15, + image: + 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', + description: + 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.', }, - category: 'Estate', - type: 'Standalone', - bedrooms: 15, - image: 'https://upload.wikimedia.org/wikipedia/commons/c/cb/Crane_estate_(5).jpg', - description: 'This grand old mansion sits on over 100 acres of rolling hills and dense redwood forests.' - }, - { - id: 'urban-living', - title: 'Urban Living', - owner: 'Mike Teavee', - city: 'Seattle', - location: { - lat: 47.6062, - lng: -122.3321 + { + id: 'urban-living', + title: 'Urban Living', + owner: 'Mike Teavee', + city: 'Seattle', + location: { + lat: 47.6062, + lng: -122.3321, + }, + category: 'Condo', + type: 'Community', + bedrooms: 1, + image: + 'https://upload.wikimedia.org/wikipedia/commons/0/0e/Alfonso_13_Highrise_Tegucigalpa.jpg', + description: + 'A commuters dream. This rental is within walking distance of 2 bus stops and the Metro.', }, - category: 'Condo', - type: 'Community', - bedrooms: 1, - image: 'https://upload.wikimedia.org/wikipedia/commons/0/0e/Alfonso_13_Highrise_Tegucigalpa.jpg', - description: 'A commuters dream. This rental is within walking distance of 2 bus stops and the Metro.' - }, - { - id: 'downtown-charm', - title: 'Downtown Charm', - owner: 'Violet Beauregarde', - city: 'Portland', - location: { - lat: 45.5175, - lng: -122.6801 + { + id: 'downtown-charm', + title: 'Downtown Charm', + owner: 'Violet Beauregarde', + city: 'Portland', + location: { + lat: 45.5175, + lng: -122.6801, + }, + category: 'Apartment', + type: 'Community', + bedrooms: 3, + image: + 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Wheeldon_Apartment_Building_-_Portland_Oregon.jpg', + description: + 'Convenience is at your doorstep with this charming downtown rental. Great restaurants and active night life are within a few feet.', }, - category: 'Apartment', - type: 'Community', - bedrooms: 3, - image: 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Wheeldon_Apartment_Building_-_Portland_Oregon.jpg', - description: 'Convenience is at your doorstep with this charming downtown rental. Great restaurants and active night life are within a few feet.' - }] + ], }); await render(hbs``); @@ -178,9 +186,17 @@ module('Integration | Component | rentals', function(hooks) { assert.dom('.rentals .results').exists(); assert.dom('.rentals .results li').exists({ count: 3 }); - assert.dom('.rentals .results li:nth-of-type(1)').containsText('Grand Old Mansion'); - assert.dom('.rentals .results li:nth-of-type(2)').containsText('Urban Living'); - assert.dom('.rentals .results li:nth-of-type(3)').containsText('Downtown Charm'); + assert + .dom('.rentals .results li:nth-of-type(1)') + .containsText('Grand Old Mansion'); + + assert + .dom('.rentals .results li:nth-of-type(2)') + .containsText('Urban Living'); + + assert + .dom('.rentals .results li:nth-of-type(3)') + .containsText('Downtown Charm'); }); }); ``` @@ -246,7 +262,7 @@ export default class RentalsFilterComponent extends Component { let { rentals, query } = this.args; if (query) { - rentals = rentals.filter(rental => rental.title.includes(query)); + rentals = rentals.filter((rental) => rental.title.includes(query)); } return rentals; @@ -322,19 +338,19 @@ Hooray, it works! Awesome. Now that we've tried this out manually in the UI, let import { hbs } from 'ember-cli-htmlbars'; @@ -8,3 +8,3 @@ -- test('it renders all given rental properties by default', async function(assert) { -+ hooks.beforeEach(function() { +- test('it renders all given rental properties by default', async function (assert) { ++ hooks.beforeEach(function () { this.setProperties({ -@@ -56,3 +56,5 @@ +@@ -64,3 +64,5 @@ }); + }); -+ test('it renders all given rental properties by default', async function(assert) { ++ test('it renders all given rental properties by default', async function (assert) { await render(hbs``); -@@ -69,2 +71,21 @@ +@@ -85,2 +87,21 @@ }); + -+ test('it updates the results according to the search query', async function(assert) { ++ test('it updates the results according to the search query', async function (assert) { + await render(hbs``); + + assert.dom('.rentals').exists();