From 6c5dd4f3525d2f159134f39320315c124285cf16 Mon Sep 17 00:00:00 2001 From: Thomas Schwery Date: Thu, 17 Nov 2016 08:18:48 +0100 Subject: [PATCH] End of tutorial --- app/adapters/application.js | 5 ++ app/components/item-listing.js | 4 + app/components/list-filter.js | 20 +++++ app/controllers/items.js | 13 ++++ app/controllers/items/index.js | 3 + app/models/item.js | 11 +++ app/router.js | 4 + app/routes/about.js | 4 + app/routes/index.js | 8 ++ app/routes/items.js | 4 + app/routes/items/index.js | 7 ++ app/routes/items/show.js | 7 ++ app/templates/about.hbs | 9 +++ app/templates/application.hbs | 17 +++++ app/templates/components/item-listing.hbs | 15 ++++ app/templates/components/list-filter.hbs | 2 + app/templates/index.hbs | 1 + app/templates/items.hbs | 1 + app/templates/items/index.hbs | 10 +++ app/templates/items/show.hbs | 22 ++++++ bower.json | 3 +- ember-cli-build.js | 3 + mirage/config.js | 56 ++++++++++++++ mirage/scenarios/default.js | 11 +++ mirage/serializers/application.js | 4 + package.json | 2 + tests/acceptance/list-items-test.js | 47 ++++++++++++ .../components/item-listing-test.js | 21 ++++++ .../components/list-filter-test.js | 73 +++++++++++++++++++ tests/unit/adapters/application-test.js | 12 +++ tests/unit/controllers/items-test.js | 12 +++ tests/unit/controllers/items/index-test.js | 12 +++ tests/unit/models/item-test.js | 12 +++ tests/unit/routes/about-test.js | 11 +++ tests/unit/routes/index-test.js | 11 +++ tests/unit/routes/items-test.js | 11 +++ tests/unit/routes/items/index-test.js | 11 +++ tests/unit/routes/items/show-test.js | 11 +++ 38 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 app/adapters/application.js create mode 100644 app/components/item-listing.js create mode 100644 app/components/list-filter.js create mode 100644 app/controllers/items.js create mode 100644 app/controllers/items/index.js create mode 100644 app/models/item.js create mode 100644 app/routes/about.js create mode 100644 app/routes/index.js create mode 100644 app/routes/items.js create mode 100644 app/routes/items/index.js create mode 100644 app/routes/items/show.js create mode 100644 app/templates/about.hbs create mode 100644 app/templates/application.hbs create mode 100644 app/templates/components/item-listing.hbs create mode 100644 app/templates/components/list-filter.hbs create mode 100644 app/templates/index.hbs create mode 100644 app/templates/items.hbs create mode 100644 app/templates/items/index.hbs create mode 100644 app/templates/items/show.hbs create mode 100644 mirage/config.js create mode 100644 mirage/scenarios/default.js create mode 100644 mirage/serializers/application.js create mode 100644 tests/acceptance/list-items-test.js create mode 100644 tests/integration/components/item-listing-test.js create mode 100644 tests/integration/components/list-filter-test.js create mode 100644 tests/unit/adapters/application-test.js create mode 100644 tests/unit/controllers/items-test.js create mode 100644 tests/unit/controllers/items/index-test.js create mode 100644 tests/unit/models/item-test.js create mode 100644 tests/unit/routes/about-test.js create mode 100644 tests/unit/routes/index-test.js create mode 100644 tests/unit/routes/items-test.js create mode 100644 tests/unit/routes/items/index-test.js create mode 100644 tests/unit/routes/items/show-test.js diff --git a/app/adapters/application.js b/app/adapters/application.js new file mode 100644 index 0000000..f1b1ab1 --- /dev/null +++ b/app/adapters/application.js @@ -0,0 +1,5 @@ +import DS from 'ember-data'; + +export default DS.JSONAPIAdapter.extend({ + namespace: 'api' +}); diff --git a/app/components/item-listing.js b/app/components/item-listing.js new file mode 100644 index 0000000..926b613 --- /dev/null +++ b/app/components/item-listing.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ +}); diff --git a/app/components/list-filter.js b/app/components/list-filter.js new file mode 100644 index 0000000..e5b72da --- /dev/null +++ b/app/components/list-filter.js @@ -0,0 +1,20 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['list-filter'], + value: '', + + init() { + this._super(...arguments); + this.get('filter')('').then((results) => this.set('results', results)); + }, + + actions: { + handleFilterEntry() { + let filterInputValue = this.get('value'); + let filterAction = this.get('filter'); + filterAction(filterInputValue).then((filterResults) => this.set('results', filterResults)); + } + } + +}); diff --git a/app/controllers/items.js b/app/controllers/items.js new file mode 100644 index 0000000..004e97d --- /dev/null +++ b/app/controllers/items.js @@ -0,0 +1,13 @@ +import Ember from 'ember'; + +export default Ember.Controller.extend({ + actions: { + filterByName(param) { + if (param !== '') { + return this.get('store').query('item', { name: param }); + } else { + return this.get('store').findAll('item'); + } + } + } +}); diff --git a/app/controllers/items/index.js b/app/controllers/items/index.js new file mode 100644 index 0000000..c3cd6ec --- /dev/null +++ b/app/controllers/items/index.js @@ -0,0 +1,3 @@ +import ItemsController from '../items'; + +export default ItemsController; diff --git a/app/models/item.js b/app/models/item.js new file mode 100644 index 0000000..ce75458 --- /dev/null +++ b/app/models/item.js @@ -0,0 +1,11 @@ +import DS from 'ember-data'; + +export default DS.Model.extend({ + title: DS.attr(), + owner: DS.attr(), + location: DS.attr(), + type: DS.attr(), + image: DS.attr(), + bedrooms: DS.attr(), + description: DS.attr() +}); diff --git a/app/router.js b/app/router.js index cdc2578..698f36b 100644 --- a/app/router.js +++ b/app/router.js @@ -7,6 +7,10 @@ const Router = Ember.Router.extend({ }); Router.map(function() { + this.route('about'); + this.route('items', function() { + this.route('show', {path: '/:item_id'}); + }); }); export default Router; diff --git a/app/routes/about.js b/app/routes/about.js new file mode 100644 index 0000000..26d9f31 --- /dev/null +++ b/app/routes/about.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ +}); diff --git a/app/routes/index.js b/app/routes/index.js new file mode 100644 index 0000000..66009cf --- /dev/null +++ b/app/routes/index.js @@ -0,0 +1,8 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + beforeModel() { + this._super(...arguments); + this.replaceWith('items'); + } +}); diff --git a/app/routes/items.js b/app/routes/items.js new file mode 100644 index 0000000..26d9f31 --- /dev/null +++ b/app/routes/items.js @@ -0,0 +1,4 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ +}); diff --git a/app/routes/items/index.js b/app/routes/items/index.js new file mode 100644 index 0000000..dd06f61 --- /dev/null +++ b/app/routes/items/index.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + return this.get('store').findAll('item'); + } +}); diff --git a/app/routes/items/show.js b/app/routes/items/show.js new file mode 100644 index 0000000..fc24043 --- /dev/null +++ b/app/routes/items/show.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model(params) { + return this.get('store').findRecord('item', params.item_id); + } +}); diff --git a/app/templates/about.hbs b/app/templates/about.hbs new file mode 100644 index 0000000..0e051a0 --- /dev/null +++ b/app/templates/about.hbs @@ -0,0 +1,9 @@ +
+
+

About Super Rentals

+

+ The Super Rentals website is a delightful project created to explore Ember. + By building a property rental site, we can simultaneously imagine traveling + AND building Ember applications. +

+
diff --git a/app/templates/application.hbs b/app/templates/application.hbs new file mode 100644 index 0000000..e599a76 --- /dev/null +++ b/app/templates/application.hbs @@ -0,0 +1,17 @@ +
+ +
+ {{outlet}} +
+
diff --git a/app/templates/components/item-listing.hbs b/app/templates/components/item-listing.hbs new file mode 100644 index 0000000..9b564c4 --- /dev/null +++ b/app/templates/components/item-listing.hbs @@ -0,0 +1,15 @@ +
+

{{#link-to "items.show" item}}{{item.title}}{{/link-to}}

+
+ Owner: {{item.owner}} +
+
+ Type: {{item.type}} +
+
+ Location: {{item.location}} +
+
+ Number of bedrooms: {{item.bedrooms}} +
+
diff --git a/app/templates/components/list-filter.hbs b/app/templates/components/list-filter.hbs new file mode 100644 index 0000000..e91f11a --- /dev/null +++ b/app/templates/components/list-filter.hbs @@ -0,0 +1,2 @@ +{{input value=value key-up=(action 'handleFilterEntry') class="light" placeholder="Filter By Name"}} +{{yield results}} diff --git a/app/templates/index.hbs b/app/templates/index.hbs new file mode 100644 index 0000000..c24cd68 --- /dev/null +++ b/app/templates/index.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/app/templates/items.hbs b/app/templates/items.hbs new file mode 100644 index 0000000..c24cd68 --- /dev/null +++ b/app/templates/items.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/app/templates/items/index.hbs b/app/templates/items/index.hbs new file mode 100644 index 0000000..e1b0f64 --- /dev/null +++ b/app/templates/items/index.hbs @@ -0,0 +1,10 @@ +{{#list-filter + filter=(action 'filterByName') as |items| }} + + +{{/list-filter}} +{{outlet}} diff --git a/app/templates/items/show.hbs b/app/templates/items/show.hbs new file mode 100644 index 0000000..1ee7708 --- /dev/null +++ b/app/templates/items/show.hbs @@ -0,0 +1,22 @@ +
+

{{model.title}}

+
+
+ Owner: {{model.owner}} +
+
+ Type: {{model.type}} +
+
+ Location: {{model.city}} +
+
+ Number of bedrooms: {{model.bedrooms}} +
+

{{model.description}}

+
+ +
+ + +{{outlet}} diff --git a/bower.json b/bower.json index 1177fac..573cbde 100644 --- a/bower.json +++ b/bower.json @@ -2,6 +2,7 @@ "name": "ember-quickstart", "dependencies": { "ember": "~2.9.0", - "ember-cli-shims": "0.1.3" + "ember-cli-shims": "0.1.3", + "bootstrap": "~3.3.5" } } diff --git a/ember-cli-build.js b/ember-cli-build.js index 2537ce2..0336b37 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -5,6 +5,9 @@ var EmberApp = require('ember-cli/lib/broccoli/ember-app'); module.exports = function(defaults) { var app = new EmberApp(defaults, { // Add options here + 'ember-bootstrap': { + 'importBootstrapTheme': true + } }); // Use `app.import` to add additional libraries to the generated diff --git a/mirage/config.js b/mirage/config.js new file mode 100644 index 0000000..231a2f9 --- /dev/null +++ b/mirage/config.js @@ -0,0 +1,56 @@ +export default function() { + this.namespace = '/api'; + + let items = [{ + type: 'items', + id: 'grand-old-mansion', + attributes: { + title: 'Grand Old Mansion', + owner: 'Veruca Salt', + city: 'San Francisco', + type: '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." + } + }, { + type: 'items', + id: 'urban-living', + attributes: { + title: 'Urban Living', + owner: 'Mike Teavee', + city: 'Seattle', + type: 'Condo', + 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." + } + }, { + type: 'items', + id: 'downtown-charm', + attributes: { + title: 'Downtown Charm', + owner: 'Violet Beauregarde', + city: 'Portland', + type: 'Apartment', + 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." + } + }]; + + this.get('/items', function(db, request) { + if(request.queryParams.name !== undefined) { + let filteredItems = items.filter(function(i) { + return i.attributes.title.toLowerCase().indexOf(request.queryParams.name.toLowerCase()) !== -1; + }); + return { data: filteredItems }; + } else { + return { data: items }; + } + }); + + this.get('/items/:id', function (db, request) { + return { data: items.find((item) => request.params.id === item.id) }; + }); +} diff --git a/mirage/scenarios/default.js b/mirage/scenarios/default.js new file mode 100644 index 0000000..0d2db8d --- /dev/null +++ b/mirage/scenarios/default.js @@ -0,0 +1,11 @@ +export default function(/* server */) { + + /* + Seed your development database using your factories. + This data will not be loaded in your tests. + + Make sure to define a factory for each model you want to create. + */ + + // server.createList('post', 10); +} diff --git a/mirage/serializers/application.js b/mirage/serializers/application.js new file mode 100644 index 0000000..6d47a36 --- /dev/null +++ b/mirage/serializers/application.js @@ -0,0 +1,4 @@ +import { JSONAPISerializer } from 'ember-cli-mirage'; + +export default JSONAPISerializer.extend({ +}); diff --git a/package.json b/package.json index f1782de..edd63a1 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "devDependencies": { "broccoli-asset-rev": "^2.4.5", "ember-ajax": "^2.4.1", + "ember-bootstrap": "0.11.2", "ember-cli": "2.9.1", "ember-cli-app-version": "^2.0.0", "ember-cli-babel": "^5.1.7", @@ -29,6 +30,7 @@ "ember-cli-htmlbars-inline-precompile": "^0.3.3", "ember-cli-inject-live-reload": "^1.4.1", "ember-cli-jshint": "^1.0.4", + "ember-cli-mirage": "0.2.4", "ember-cli-qunit": "^3.0.1", "ember-cli-release": "^0.2.9", "ember-cli-sri": "^2.1.0", diff --git a/tests/acceptance/list-items-test.js b/tests/acceptance/list-items-test.js new file mode 100644 index 0000000..6022c95 --- /dev/null +++ b/tests/acceptance/list-items-test.js @@ -0,0 +1,47 @@ +import { test } from 'qunit'; +import moduleForAcceptance from 'ember-quickstart/tests/helpers/module-for-acceptance'; + +moduleForAcceptance('Acceptance | list items'); + +test('visiting /items', function(assert) { + visit('/items'); + andThen(function() { + assert.equal(currentURL(), '/items'); + }); +}); + +test('should link to information about the company.', function (assert) { + visit('/items'); + click('a:contains("About")'); + andThen(function () { + assert.equal(currentURL(), '/about', 'should navigate to about'); + }); +}); + +test('should list available rentals.', function (assert) { + visit('/items'); + + andThen(function() { + assert.equal(find('.listing').length, 3, 'should see 3 listings'); + }); +}); + +test('should filter the list of rentals by city.', function (assert) { + visit('/items'); + fillIn('.list-filter input', 'urban'); + keyEvent('.list-filter input', 'keyup', 69); + andThen(function () { + assert.equal(find('.listing').length, 1, 'should show 1 listing'); + assert.equal(find('.listing .name:contains("Urban Living")').length, 1, 'should contain 1 listing with name Urban Living'); + }); +}); + +test('should show details for a specific rental', function (assert) { + visit('/items'); + click('a:contains("Grand Old Mansion")'); + andThen(function() { + assert.equal(currentURL(), '/items/grand-old-mansion', 'should navigate to show route'); + assert.equal(find('.show-listing h2').text(), "Grand Old Mansion", 'should list rental title'); + assert.equal(find('.description').length, 1, 'should list a description of the property'); + }); +}); diff --git a/tests/integration/components/item-listing-test.js b/tests/integration/components/item-listing-test.js new file mode 100644 index 0000000..e01649f --- /dev/null +++ b/tests/integration/components/item-listing-test.js @@ -0,0 +1,21 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('item-listing', 'Integration | Component | item listing', { + integration: true +}); + +test('it renders', function(assert) { + this.render(hbs`{{item-listing}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage: + this.render(hbs` + {{#item-listing}} + template block text + {{/item-listing}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/tests/integration/components/list-filter-test.js b/tests/integration/components/list-filter-test.js new file mode 100644 index 0000000..0330215 --- /dev/null +++ b/tests/integration/components/list-filter-test.js @@ -0,0 +1,73 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import wait from 'ember-test-helpers/wait'; +import RSVP from 'rsvp'; + +moduleForComponent('list-filter', 'Integration | Component | list filter', { + integration: true +}); + +const ITEMS = [{title: 'Grand Old Mansion'}, {title: 'Urban Living'}, {title: 'Downtown Charm'}]; +const FILTERED_ITEMS = [{title: 'Grand Old Mansion'}]; + +test('should initially load all listings', function (assert) { + // we want our actions to return promises, since they are potentially fetching data asynchronously + this.on('filterByName', (val) => { + if (val === '') { + return RSVP.resolve(ITEMS); + } else { + return RSVP.resolve(FILTERED_ITEMS); + } + }); + + // with an integration test, you can set up and use your component in the same way your application + // will use it. + this.render(hbs` + {{#list-filter filter=(action 'filterByName') as |results|}} + + {{/list-filter}} + `); + + // the wait function will return a promise that will wait for all promises + // and xhr requests to resolve before running the contents of the then block. + return wait().then(() => { + assert.equal(this.$('.name').length, 3); + assert.equal(this.$('.name').first().text().trim(), 'Grand Old Mansion'); + }); +}); + +test('should update with matching listings', function (assert) { + this.on('filterByName', (val) => { + if (val === '') { + return RSVP.resolve(ITEMS); + } else { + return RSVP.resolve(FILTERED_ITEMS); + } + }); + + this.render(hbs` + {{#list-filter filter=(action 'filterByName') as |results|}} + + {{/list-filter}} + `); + + // The keyup event here should invoke an action that will cause the list to be filtered + this.$('.list-filter input').val('Grand').keyup(); + + return wait().then(() => { + assert.equal(this.$('.name').length, 1); + assert.equal(this.$('.name').text().trim(), 'Grand Old Mansion'); + }); +}); diff --git a/tests/unit/adapters/application-test.js b/tests/unit/adapters/application-test.js new file mode 100644 index 0000000..f0a2101 --- /dev/null +++ b/tests/unit/adapters/application-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('adapter:application', 'Unit | Adapter | application', { + // Specify the other units that are required for this test. + // needs: ['serializer:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + let adapter = this.subject(); + assert.ok(adapter); +}); diff --git a/tests/unit/controllers/items-test.js b/tests/unit/controllers/items-test.js new file mode 100644 index 0000000..3a45297 --- /dev/null +++ b/tests/unit/controllers/items-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('controller:items', 'Unit | Controller | items', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + let controller = this.subject(); + assert.ok(controller); +}); diff --git a/tests/unit/controllers/items/index-test.js b/tests/unit/controllers/items/index-test.js new file mode 100644 index 0000000..9a39902 --- /dev/null +++ b/tests/unit/controllers/items/index-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('controller:items/index', 'Unit | Controller | items/index', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + let controller = this.subject(); + assert.ok(controller); +}); diff --git a/tests/unit/models/item-test.js b/tests/unit/models/item-test.js new file mode 100644 index 0000000..378a318 --- /dev/null +++ b/tests/unit/models/item-test.js @@ -0,0 +1,12 @@ +import { moduleForModel, test } from 'ember-qunit'; + +moduleForModel('item', 'Unit | Model | item', { + // Specify the other units that are required for this test. + needs: [] +}); + +test('it exists', function(assert) { + let model = this.subject(); + // let store = this.store(); + assert.ok(!!model); +}); diff --git a/tests/unit/routes/about-test.js b/tests/unit/routes/about-test.js new file mode 100644 index 0000000..e4e647c --- /dev/null +++ b/tests/unit/routes/about-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:about', 'Unit | Route | about', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/tests/unit/routes/index-test.js b/tests/unit/routes/index-test.js new file mode 100644 index 0000000..5d0f50d --- /dev/null +++ b/tests/unit/routes/index-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:index', 'Unit | Route | index', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/tests/unit/routes/items-test.js b/tests/unit/routes/items-test.js new file mode 100644 index 0000000..a175054 --- /dev/null +++ b/tests/unit/routes/items-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:items', 'Unit | Route | items', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/tests/unit/routes/items/index-test.js b/tests/unit/routes/items/index-test.js new file mode 100644 index 0000000..bf147d5 --- /dev/null +++ b/tests/unit/routes/items/index-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:items/index', 'Unit | Route | items/index', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/tests/unit/routes/items/show-test.js b/tests/unit/routes/items/show-test.js new file mode 100644 index 0000000..57dc736 --- /dev/null +++ b/tests/unit/routes/items/show-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:items/show', 'Unit | Route | items/show', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +});