Stub Element Is Not Effective In The Ready Function - polymer

With Polymer 1.* and WCT, when testing my element <sp-veteran></sp-veteran> I am not able to stub out the methods ._getSpComboBox() and ._getItems() in the ready function. I get Error thrown outside of test function: this._getSpComboBox(...)._getItems is not a function.
Since it is in the ready function, I need to use the WCT api stub instead of sinon.stub since the later requires me to grab the element which I can not do before fixture().
Any suggestions?
original code:
_getSpComboBox: function() {
return Polymer.dom(this.$.veteran.root).querySelector('sp-combo-box');
},
ready: function() {
if (this.editMode) {
this._getSpComboBox()._getItems();
}
this.$.veteranNoAjax.read();
this._setStyle();
}
test:
<test-fixture id="sp-veteran">
<template>
<h2>edit veteran in edit mode</h2>
<sp-app>
<sp-toast></sp-toast>
<sp-veteran edit-mode></sp-veteran>
</sp-app>
</template>
</test-fixture>
before(() => {
replace('sp-app').with('fake-sp-app');
stub('sp-ajax', {read: ()=> entitiesMock});
const _getItems = ()=> entitiesMock;
stub('sp-veteran', {_getSpComboBox: ()=> _getItems});

Unfortunately testing ready in Polymer1 is kind of a pain, or at least I haven't found an easy way that doesn't have odd side-effects. Calling the ready method after you've attached your stubs/spies is always an option but as I mentioned it can cause some odd issues. This was alleviated in Polymer2 as ready is called by the first call of connectedCallback for your element, so you can create the element then bind your spies and manually add to trigger it, just don't forget to remove after.
In the case of DOM manipulation in a Polymer element, you should be using the attached lifecycle instead, this will solve your issue as I mentioned above for testing, but it also saves you a weird potential usage case in the future. Since ready only runs once for an instance of an element, any logic in your ready statement won't get re-run if that element is re-used later, instead if you put the logic in your attached lifecycle if that element is removed from the DOM then added again later in another location it will rerun it's logic to fetch it's new children.

Related

How to use Polymer lifecycle hooks

I have a component that contains video. My component is nested in a dom-if and can disappear. When this happens the video (and it sound) keep playing.
Is there a way in which my component can detect that is has disappeared from the DOM? I have tried to use the 'detached' callback as described here: https://www.polymer-project.org/1.0/docs/devguide/registering-elements
Polymer({
is: 'my-component-with-video',
properties: {
// some properties
},
detached: function() {
console.log('Component detached');
// more code to stop video
},
but when my element is removed by the dom-if nothing happens, I don't see the console.log message. What am I doing wrong?
There are two scenarios possible here:
You want your element to be discarded and recreated fresh when the condition changes.
You want to keep it in the dom but freeze it.
In the first case you need to add the restamp attribute to the dom-if to make sure the template DOM is destroyed, not hidden. By default the dom-if stamps the template at first initialization and then hides it from the view if the condition becomes falsy.
In the second case, the suggestion given by Intervalia will not work, because the dom-if in "hide" mode does not detach anything from the DOM. Setting restamp attribute will make the detached callback run but then no point in pausing anything since the element will be discarded.
If you want to keep it in the DOM and freeze it's state you need to listen to dom-change event on the dom-if and run the .pause() accordingly.
No need for any workaround other than simply using your dom-if and rather than
<dom-if if="[[condietionBoolean]]">
<your-video-element id="giveanId"></your-video-element>
</dom-if>
write the if statement like below and so each time your condition changes, you check and make sure the video is paused if when you like. See below.
...
<dom-if if="[[_shouldShowVideo(conditionBoolean)]]">
<your-video-element id="giveanId"></your-video-element>
</dom-if>
...
Polymer({
is: 'my-component-with-video',
properties: {
conditionBoolean : {
type: Boolean,
},
},
_shouldShowVideo: function(conditionBoolean ) {
if (!conditionBoolean) this.$$(#yourVideoElementId).pause();
return conditionBoolean ;
}
});
In your detached function you need to get the Video Element and call .pause() on it.
You will probably also need to call .pause() when your condition changes that would cause the dom-if to remove the player.

Unit testing dynamically-rendered elements in Polymer

Overview
DOM elements that are dynamically-rendered within dom-if, dom-repeat <templates> seem to be rendered asynchronously thus making unit-testing a bit of a pain.
The Polymer Component
template(is='dom-if', if='{{!defaultPrintAll}}')
template(is='dom-repeat', items='{{_pageBucketItems}}')
button(type='button', class$='{{_computeDefaultClass(item)}}', on-tap='_togglePageClick') {{item}}
The Test
test("Clicking 'Export All' to off, reveals board-selection tiles", function() {
$("#export-pdf-checkbox-all").siblings(".checkbox").trigger("click");
Polymer.dom.flush()
expect($(".board-range__button")).to.be.visible;
});
Why it seems to fail:
When clicking a button which triggers the dom-if/dom-repeat the elements don't render in a synchronous order.
The dom-if and it's subsequent/nested dom-repeat render asynchronously.
To make matters worse, the button itself get's it's class in a computed/binded manner (mind the class$= on the button).
So the question boils down to this:
Is it possible to force render the dom-if, dom-repeat, and the computed-binding of the class in a synchronous order after I simulate the click to the button which activates all 3 of those conditions?
Notes:
I'm using Polymer's official WCT as the test harness.
I'm also using chai-jquery.
I've also used Polymer.dom.flush() but it still doesn't, ahem.. flush.
I'm aware that I can use chai-as-promised.js instead but it adds unnecessary complexity to my tests for a trivial matter such as this, so I'd like to avoid it.
Rather than using Polymer.dom.flush(), try using the flush function that WCT puts on the window. This will enqueue a callback function to be executed, in theory, after the template has rendered.
test("Clicking 'Export All' to off, reveals board-selection tiles", function(done) {
$("#export-pdf-checkbox-all").siblings(".checkbox").trigger("click");
flush(function () {
expect($(".board-range__button")).to.be.visible;
done();
}
});
Important to notice: Asynchronous tests require the done function to be passed into the test callback, and require done to be called after your conditions have been evaluated.

How can I know that Template Repeat has finished?

Element needs some time for template-repeat to render all content, so paper-spinner is used to notify the user to wait.
How can I know that template-repeat has finished so I can turn off the spinner?
And related question: how can inner element "item-details" be selected? Again, template-repeat has to be finished first.
Here's the code I am using:
<polymer-element name="item-list">
<template>
<paper-spinner active></paper-spinner>
<template id="repeat_items" repeat="{{ item in car.items }}">
<item-details id="item_details" item="{{item}}"></item-details>
</template>....
This is some simulation of the problem: plnkr.co
Edit
links from research:
spinner example
why does onmutation disconnect after first mutation?
polymer-how-to-watch-for-change-in-content-properties
There are component lifecycle hooks.
You are probably looking for domReady.
Called when the element’s initial set of children are guaranteed to exist. This is an appropriate time to poke at the element’s parent or light DOM children. Another use is when you have sibling custom elements (e.g. they’re .innerHTML‘d together, at the same time). Before element A can use B’s API/properties, element B needs to be upgraded. The domReady callback ensures both elements exist.
Polymer('tag-name', {
domReady: function() {
// hide the spinner
// select the first item details element
}
});
As for selecting elements, you can traverse the component's shadow dom like so:
this.shadowRoot.querySelector(selector);
EDIT...
The domReady hook is great if you have all of your data up-front. If you get data asynchronously, then you can use a change watcher.
Here's is a fork of your plunkr that successfully selects the child components after the data changes. Notice the setTimeout(f, 1) that defers selection until after the DOM updates.
carsChanged: function(){
var _this = this;
setTimeout(function(){
console.log(_this.shadowRoot.querySelectorAll('item-details'))
},1)
}
I suggest something like this - http://jsbin.com/bifene/4/edit
Leverages Polymer's onMutation function to watch for changes to a DOM node. Note that it only gets called once so you'll need to re-register it every time you load new items & restart the spinner.

How to monitor indexdb stores for content changes?

I currently have a PolymerElement which is binding to a observable list, and using
<template repeat="{{cardnames}}">
<div>{{ }}</div>
</template>
So far, so good. The cardnames is populated from a IndexDB store, I'm using lawndart for this. It works for getting all the items at startup, but when I add a item to the database, from a separate PolymerElement, there is no way to update the cardnames list from this other PolymerElement. So one of the ideas I have come up with, without putting all this logic into the same PolymerElement, of having the one with the cardnames in it monitor the Database for changes, and update the list from there, when a change happens. My problem is I don't know if the is already a change event that can be listened to, and I was hoping someone could enlighten me of if there is, and where to find it or show me how to do it.
Of coarse I could run a background Isolate process to check for changes, or a timer of some sort, but that seems clumsy, costly and an increase in complexity I could do without.
Thanks
OK I'm going to answer my own question.
I think the answer to if there is a IndexedDB event that is triggered when an item is added, is no. I'd like to be proven wrong, but the only update event I've seen is for version updates, not added content updates. I shouldn't be surprised by this.
Anyway, I found that using the code below, unsurprisingly, works, though I'd have preferred to use an event generated by IndexedDB or something, but you don't always get what you want. This is done using Lawndart, which effectively uses IndexedDb in most modern browsers.
...
//define an observable to hold the list
#observable List<String> cardnames;
static const INTERVAL = const Duration(seconds: 1);
Timer poller;
...
poller = new Timer.periodic(INTERVAL, pollDB);
...
void pollDB(Timer timer) {
Store db = new Store("magic-card-collection", "magic-card");
db.open().then((_) {
db.keys().forEach((item) {
if(!cardnames.contains(item)) {
cardnames.add(item);
print("added " + item);
}
});
});
}
Now every time a item is added to cardnames, the view gets updated, dynamically as more are added to the DB. So I can now add cards from any outside source, and the view will be updated within seconds.
UPDATE:
With some help from both Seth Ladd and the Polymer documentation, I figured out the best way is not to poll the db, but to have one central DB store contained in one element, have the other elements fire custom events when they do something, passing the info as part of the fired event, and having the controller element listen for those events and both add to the store plus update the UI. For example create an element that collects data to put in the db, and have it fire an event when finished collecting that info.
<polymer-element name="collect-data">
<template>
<div>
<button on-click="{{save}}">Ok</button>
</div>
</template>
<script type="application/dart" src="collectdata.dart"></script>
</polymer-element>
The collectors dart file
#CustomTag('collect-data')
class CollectData extends PolymerElement {
CollectData.created() : super.created();
void save(Event e, var detail, var target) {
fire("update", detail: "hello");
}
}
The controller
<polymer-element name="app-controller">
<link rel="import" href="collectdata.html">
<template>
<div>
<collect-data on-update="{{save}}"></collect-data>
</div>
</template>
<script type="application/dart" src="appcontroller.dart"></script>
</polymer-element>
The app controller dart file
#CustomTag('app-controller')
class AppController extends PolymerElement {
AppController.created() : super.created();
void save(Event e) {
var data = e.detail;
//update your db here, which would, if you had one, include updating an observable list
...
}
}
The important things to note are the method
fire('update', detail: ...);
The detail can be any valid object, I think. I know you can pass a string or a dict to it. The other part is
<collect-data on-update="{{save}}"></collect-data>
The thing that fires the event calls it update, the listener listens to on-update. I'll leave it to you to figure out the pattern requirement here.
However the DB still doesn't emit a changed event that I know of at the moment.

Polymer custom element attribute access or send

I'm searching for a way to access an attribute on a Polymer custom element from the DOM
or to send data from Polymer.register to the DOM.
This really simple element below takes two values and multiplies them, placing the result in its result attribute.
How can I access this result from the outside?
<element attributes='value times result' name='value-box'>
<template>
<p>{{result}}</p>
</template>
<script>
Polymer.register(this, {
ready: function() {
if (this.value != null && this.times != null) {
this.result = this.value * this.times;
}
}
});
</script>
</element>
result is a property on your element just like times and value. You can access it from outside JS, as you would any property on a normal HTML element. For example:
<value-box value="2" times="10"></value-box>
<script>
document.querySelector('value-box').result;
</script>
Internal to your element, what you want is to keep the result computed property up to date as times/value change. There are a couple of ways to do that. One is to use <property>Changed watchers [1]:
<element name="value-box" attributes="value times result">
<template>
<p>result: {{result}}</p>
</template>
<script>
Polymer.register(this, {
valueChanged: function() {
this.result = this.value * this.times;
},
timesChanged: function() {
this.result = this.value * this.times;
}
});
</script>
</element>
Demo: http://jsbin.com/idecun/2/edit
Alternatively, you can use a getter for result:
Polymer.register(this, {
get result() {
return this.value * this.times;
}
});
Demo: http://jsbin.com/oquvap/2/edit
Note For this second case, if the browser doesn't support Object.observe, Polymer will setup a timer to dirty check result. This is why you see "here" printed in the console for this second example. Run the same thing in Chrome Canary with "Experimental WebKit features" enabled in about:flags, and you won't see the timer. Yet another reason why I can't wait for Object.observe to be everywhere! :)
Hope this helps.
Just wanted to add a useful follow up to this (Even though the question has been answered).
My follow up is in response to the following comment on the actual answer:
I'm curious as to why selection with jQuery didn't work. Does it not recognize Custom Elements? – CletusW Jul 8 '13 at 19:57
The most likely reason jQuery didn't see your element is because it was not fully formed by the browsers run time at that point.
I ran into this problem while developing my ASP.NET MVC + polymer js sample app on my github page, and essentially what I was trying to do was call methods and access properties on my polymer object before polymer had made everything usable.
Once I moved the code I was using into a button click (So I could trigger it manually after I visually could see my component was ready) everything worked fine.
For now, if you try to access anything too soon, EG: in your jQ doc.ready handler, there's a good chance you'll run into all sorts of daft problems like this.
If you can find a way of delaying your action, or even better using polymer signals to signal from the components ready handler to an outside agent, that sets a flag telling you the component is ready, then you can sort this easily.