I have the following unit test for my custom polymer component:
<head>
<meta charset="UTF-8">
<title>survey</title>
<script src="../bower_components/webcomponentsjs/webcomponents.js"></script>
<script src="/web-component-tester/browser.js"></script>
<script src="../bower_components/test-fixture/test-fixture-mocha.js"></script>
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/test-fixture/test-fixture.html">
<link rel="import" href="../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="../views/components/survey.html">
</head>
<body>
<test-fixture id="Network">
<template>
<survey></survey>
</template>
</test-fixture>
<script>
describe('<survey>', function() {
var survey;
describe('network', function() {
beforeEach(function(done) {
survey = fixture('Network');
})
it('should work', function() {
expect(survey.$.dog).to.exist;
});
});
});
</script>
And the following custom polymer survey component:
<link rel="import" href="../../bower_components/paper-checkbox/paper-checkbox.html">
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
<dom-module id="survey">
<template>
<h3 class="text-center">Tell us about yourself!</h3>
<div class="form-group">
<label>I'm a...</label>
<array-selector id="imaSelector" items="{{ima}}" selected="{{imaSelected}}" multi toggle></array-selector>
<template is="dom-repeat" id="imaList" items="{{ima}}">
<div class="checkbox">
<paper-checkbox id="{{item.id}}" on-iron-change="toggleIma">{{item.name}}</paper-checkbox>
</div>
</template>
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'survey',
properties: {
ima: {
type: Array,
value: function() {
return [ {
name: 'House Cat',
id: 'houseCat'
}, {
name: 'Basic Dog',
id: 'dog'
}, {
name: 'Swimming Fish',
id: 'fish'
}];
}
},
},
toggleIma: function(e) {
var item = this.$.imaList.itemForElement(e.target);
if (item) {
this.$.imaSelector.select(item.id);
}
}
})
</script>
This test will fail, because the local dom is not initialized, due to the fact that I'm using a dom-repeat element.
How do I want until the local dom is stamped?
There are two parts to this. Waiting for the async render, and finding the node.
For the render: Either listen for the dom-change event from the dom-repeat template, or call the render() method on the dom-repeat to force a synchronous render.
In unit tests, you probably just want to call render().
For finding the node--this.$ is only populated with statically created elements (not, for example, elements from a dom-if or dom-repeat template), as described in the docs. This is a frequent source of confusion.
You can use the this.$$ convenience method to query a local DOM element by selector, so you could do something like this:
survey.$.imaList.render();
expect(survey.$$(#dog)).to.exist;
You could return a Promise instead of expecting something immediately:
it('should work', function() {
return expect(survey.$.dog).should.eventually.exist();
});
See http://mochajs.org/#asynchronous-code for more information.
This seems to be a Polymer issue. The issue is I was trying to use the this.$ selector to reference dynamically created nodes. However, the polymer documentation explicitly states that the this.$ will only include statically created nodes, not dynamically created nodes.
See the note in this link. This is for the 0.5 version, but I am assuming this is the same in the 1.0 version. If there are any other know solutions than those mentioned in the link, I would love to hear them.
https://www.polymer-project.org/0.5/docs/polymer/polymer.html#automatic-node-finding
Note the final solution, looks something like this:
describe('network', function() {
beforeEach(function(done) {
survey = fixture('Network');
flush(function(){
done()
});
})
it('should work', function() {
expect(survey.querySelector('#dog')).to.exist;
});
});
Note that flush() is needed to ensure the dom is loaded.
https://www.polymer-project.org/0.5/articles/unit-testing-elements.html#wct-specific-helpers
Related
I have the following example:
<!DOCTYPE html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.1.2/mocha.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.3/vue-resource.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/1.15.4/sinon.js"></script>
<script>mocha.setup('bdd');</script>
<script>
"use strict";
var assert = chai.assert;
var should = chai.should();
var vm = new Vue({
data: {
message: "Hello"
},
methods: {
loadMessage: function() {
this.$http.get("/get").then(
function(value) {
this.message = value.body.message;
});
},
}
});
describe('getMessage', function() {
let server;
beforeEach(function () {
server = sinon.fakeServer.create();
});
it("should get the message", function(done) {
server.respondWith([200, { 'Content-Type': 'application/json' },
JSON.stringify({message: "Test"})]);
vm.message.should.equal("Hello");
vm.loadMessage();
server.respond();
setTimeout(function() {
// This one works, but it's quirky and a possible error is not well represented in the HTML output.
vm.message.should.equal("Test");
done();
}, 100);
// This one doesn't work
//vm.message.should.equal("Test");
});
});
</script>
<script>
mocha.run();
</script>
</body>
</html>
I want to test that Vue asynchronously gets data from the server. Though, I mock out the actual HTTP request with Sinon FakeServer.
Naturally, directly after the call to loadMessage, the message is not yet set. I could use a timeout function for the test, but I believe there should be a better method. I've looked into respondImmediately, but it did not change. Also, there is the possibility to call a done() function. However, as I understand this, this done would need to be called within the loadMessage function, hence modifying the code under test.
What is the correct approach to handle this problem?
Edit: I have found at least a partial solution, but it seems to be still messy: call the done() function in the mocha unit test. When the assertion fails, it is at least shown in the HTML output. However, the assertion message is not as clear as in a normal test. Also, the technique still seems messy to me.
Since updating of vue component is done asynchronously you would need to use
// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(MyComponent).$mount()
vm.message = 'foo'
// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
expect(vm.$el.textContent).toBe('foo')
done()
})
})
Taken from official docs.
I have a simple test I'm trying to run through jasmine. Here are the ts files.
Unit-Test.html
<html>
<head>
<title>1st Jasmine Tests</title>
<link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css" />
<script src="../node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
<!--<script src="../node_modules/zone/lib/zone.js"></script>-->
</head>
<body>
<script>
// #2. Configure systemjs to use the .js extension
// for imports from the app folder
System.config({
transpiler: 'typescript',
typescriptOptions: { emitDecoratorMetadata: true },
packages: {
'test': { defaultExtension: 'js' },
'app': { defaultExtension: 'js' }
}
});
// #3. Import the spec file explicitly
System.import('test/test.spec')
// #4. wait for all imports to load ...
// then re-execute `window.onload` which
// triggers the Jasmine test-runner start
// or explain what went wrong
.then(window.onload)
.catch(console.error.bind(console));
</script>
</body>
</html>
test.spec.ts
import {TestComponent} from "../app/components/about/test.component"
describe('Test Component->', () => {
it('has name given in the constructor', () => {
var t1 = new TestComponent('Super Cat');
expect(t1.myValue).toEqual('Super Cat');
});
it('does not have the id given in the constructor', () => {
var t2 = new TestComponent('Super Cat');
expect(t2.myValue).not.toEqual(1);
});
});
test.component.ts NOTICE THE COMMENTED OUT COMPONENT ANNOTATION
import {Component} from 'angular2/core';
//#Component({
// selector: 'test-component',
// templateUrl: "<div></div>",
//})
export class TestComponent {
constructor(value: string) {
this.myValue = value;
}
public myValue = '';
onKey2() {
return this.myValue;
}
}
Now if I hit the unit-test.html with the #Copmonent annotation commented out I get the following result
however if I uncomment the #Component annotation line, as this is really how my components will be defined... I get the following error
Can someone please tell me why I'm getting this error. I've tried importing "reflect-metadata" as well with no success
Ok, I think I got it... I had to change the script section in my unit-test.html to the following in this exact order!! Now I just have to figure out how to pull this into a separate project
<script src="../node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="../node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/rxjs/bundles/Rx.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
<script src="../node_modules/angular2/bundles/testing.dev.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
<script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
I want App.IndexRowController to be the controller for the three row views. Instead Ember sets them to plain Objects. I believe I'm properly setting itemController in the DataIndexController. I a version of this code without the nested route working as expected. Do I need to do something special when working with nested routes/needs?
JSBin: http://jsbin.com/sazafi/edit?html,css,js,output
To see the behavior go to #/data/index. Notice there are three li elements but no corresponding text (from getName). The getName controller property isn't accessible from the row template. Ember docs say that setting the itemController in the ArrayController should make that controller available to the template specified in itemViewClass. Take a look at the Ember Inspector and see that the controller for the three views is an Ember.Object, not App.IndexRowController.
JavaScript:
App = Ember.Application.create();
App.Router.map(function() {
this.resource("data", function() {
this.route("index")
});
});
App.DataIndexRoute = Ember.Route.extend({
model: function() {
return(
[
Ember.Object.create({name: 'row 1'}),
Ember.Object.create({name: 'row 2'}),
Ember.Object.create({name: 'row 3'})
]);
}
});
App.DataController = Ember.ArrayController.extend({
filter: ''
});
App.DataIndexController = Ember.ArrayController.extend({
needs: ['data'],
itemController: 'indexRow',
filter: Ember.computed.alias("controllers.data.filter"),
filteredContent: function(){
var filter = this.get('filter');
var list = this.get('arrangedContent');
return list.filter(function(item) {
return item.get('name').match(filter);
});
}.property('content', 'filter')
});
App.IndexRowController = Ember.ObjectController.extend({
// This method isn't accessible from the row template
getName: function() {
return(this.get('content').get('name'));
}.property()
});
App.DataIndexView = Ember.CollectionView.extend({
tagName: 'ul',
content: function(){
return this.get('controller.filteredContent')
}.property('controller.filteredContent'),
itemViewClass: Ember.View.extend({
controllerBinding: 'content',
templateName: 'row'
})
});
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Collection View</title>
<script src="http://code.jquery.com/jquery-2.0.2.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.1.2.js"> </script>
<script src="http://builds.emberjs.com/release/ember.js"></script>
<script src="app.js"></script>
</head>
<body>
<script type="text/x-handlebars" data-template-name="application">
<h1>CollectionView With Item View</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="data">
{{input type="text" placeholder='row 1' value=filter}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="row">
{{getName}}
</script>
</body>
</html>
EDIT: I have a working example of how to set the controller in a child view of a Ember.ContainerView and how to filter the contents here: https://github.com/mkolenda/ember-listview-with-filtering. ListView is a descendent of ContainerView.
Simple solution is to use an {{each}} instead of a CollectionView.
This is a well-known "feature", aka design bug, in the Ember design for array controllers, item controllers, and collection views. It shouldn't be too hard to find references on the web about the problem and some suggested hacks/workarounds. You might start with https://github.com/emberjs/ember.js/issues/4137 or https://github.com/emberjs/ember.js/issues/5267.
Apologies if this isn't quite the right place (as opposed to either libraries own github issue page, but as I've not been able to determine exactly which library is not quite working correctly hard to log it specifically).
I'm using ember data fragments on my model (an array), and localstorage to save down my model. When calling rollback upon the saved model, it seems to reset the fragments back to their original state (i.e. no values), but it still maintains the fragment itself on the array, rather than dropping the item out of the array.
I've got a fiddle setup, click 'add' to add a model, click to view it's details, then click 'add' in there, followed by 'cancel'. You can see that the type + desc values drop out, but the element is still there.
If I switch out to using the Fixture adapter then it all works as expected, just not sure where to start even attempting to debug, I've stepped through many lines of _super calls, and what not trying to figure it out, but just get lost.
Note
This is a pseudo version of my actual app, and curiously enough when you navigate to the home page and then back to the details page, it seems to resolve the type/desc correctly, which it is not doing on my actual app, it still maintains the default values. However refreshing the page makes it work perfectly from then onwards.
Any help greatly appreciated!
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="//builds.emberjs.com/tags/v1.7.0/ember.js"></script>
<script src="//builds.emberjs.com/canary/ember-data.js"></script>
<script src="//raw.githubusercontent.com/lytics/ember-data.model-fragments/master/dist/ember-data.model-fragments.js"></script>
<script src="//raw.githubusercontent.com/kurko/ember-localstorage-adapter/master/localstorage_adapter.js"></script>
<script>
window.App = Ember.Application.create();
App.ApplicationStore = DS.Store.extend();
App.ApplicationSerializer = DS.LSSerializer.extend();
App.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'cars'
});
App.Car = DS.Model.extend({
make: DS.attr(),
model: DS.attr(),
features: DS.hasManyFragments('feature')
});
App.Feature = DS.ModelFragment.extend({
type: DS.attr(),
description: DS.attr()
});
App.Router.map(function () {
this.route('index', { path: '/' });
this.route('car', { path: '/car/:car_id'});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('car');
},
actions : {
add: function(model) {
var car = this.store.createRecord('car', {
make: 'Dodge',
model: 'Viper',
features: []
});
car.save();
}
}
});
App.CarRoute = Ember.Route.extend({
actions: {
add: function(model) {
model.get('features').createFragment({
type: 'Something',
description: 'Some desc'
});
model.save(); //*/
},
cancel: function(model) {
model.rollback();
}
}
});
</script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script type="text/x-handlebars" data-template-name="index">
{{#link-to 'index'}}Home{{/link-to}}
<ol>{{#each}}
<li>{{#link-to 'car' this}}{{name}} {{model}}{{/link-to}}</li>
{{else}}
<button {{action 'add' model}}>Add</button>
{{/each}}</ol>
</script>
<script type="text/x-handlebars" data-template-name="car">
{{#link-to 'index'}}Home{{/link-to}}
<dl>
<dt>Make</dt>
<dd>{{make}}
<dt>Model</dt>
<dd>{{model.model}}</dd>{{#each features}}
<dt>{{_view.contentIndex}}. {{type}}</dt>
<dd>{{description}}</dd>
{{/each}}
</dl>
<button {{action 'add' model}}>Add</button>
<button {{action 'cancel' model}}>Cancel</button>
</script>
</body>
</html>
I havent worked with data fragments but fragment is a model itself so the element/fragment is still there because you have created a record for it.
This record is stored in the ember store until you do something with it.
Rollback, via emberjs.com,does this - "If the model isDirty this function will discard any unsaved changes".
The model in this case seems to be the fragment. Rollback gets rid of the changes, which is what it is doing in your case, removing the type and desc values, but the record itself is still in the store.
If you want to get rid of the fragment altogether you would have to delete it. http://emberjs.com/guides/models/creating-and-deleting-records/
I'm starting simple, trying to display a single value from a simple model.
This answer to "accessing the model from the template" suggests that it's necessary to extend ObjectController. At this point, there's have no application logic, so it doesn't seem like a controller (or a view) is really needed yet.
Likewise, there are no routes yet, so it doesn't seem like anything should be needed beyond App.IndexRoute.
The single object in the dictionary fixture has a title property with the value Hello Ember. I'm expecting to see that text displayed between two hard-coded arrows. Instead, all I get is the arrows.
The Index.html is:
<!DOCTYPE html>
<html>
<head>
<title>Dictionary</title>
</head>
<body>
<!-- Main body of the application -->
<script type="text/x-handlebars">
<p>Title: -->{{title}}<--</p>
</script>
<!-- ... Ember.js and other JavaScript dependencies ... -->
<script src="js/libs/jquery-1.10.2.min.js"></script>
<script src="js/libs/handlebars-1.0.0.js"></script>
<script src="js/libs/ember.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/app/application.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/models/dictionary_model.js"></script>
<script src="js/controllers/dictionary_controller.js"></script>
</body>
</html>
And then the JavaScript:
// application.js
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter.extend();
// router.js
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('dictionary', 0);
}
});
// dictionary_model.js
App.Dictionary = DS.Model.extend({
title: DS.attr("string")
});
App.Dictionary.FIXTURES = [
{
id: 0,
title: "Hello Ember"
}];
// dictionary_controller.js
App.DictionaryController = Ember.ObjectController.extend({
});
I'm not sure where you're reading in the documentation that's contradicting, please update your question with the contradicting statements so they can be fixed.
The controller really only need be defined if you need to add additional computed properties, actions, or other methods. In your case you are correct in that it needn't be defined.
That being said, the application template (or unnamed template as in your case) is the root of your ember app. Any child routes/resources will be rendered in the {{outlet}} located in the application template(examples below).
The index route is a route underneath the application route. Resources are considered routes that can have children and generally associated with a model.
All this comes up to the main problem you're seeing. You've returned your model from the index route, but you are attempting to use it in the application route's template.
Here's a simplified version of your code
Code
App = Ember.Application.create();
App.ApplicationAdapter= DS.FixtureAdapter;
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('dictionary', 0);
}
});
App.Dictionary = DS.Model.extend({
title: DS.attr("string")
});
App.Dictionary.FIXTURES = [
{
id: 0,
title: "Hello Ember"
}];
Templates
<script type="text/x-handlebars">
<h2>Application Template</h2>
Here we Are in the Application Template
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Index Template</h2>
{{title}}
</script>
Example in action
http://emberjs.jsbin.com/OxIDiVU/443/edit