I have a property mood which is part of the interface for a component. Behind the scenes I have a computed property called _mood:
const { computed, typeOf } = Ember;
_mood: computed('mood','A','B','C' function() {
let mood = this.get('mood');
if (typeOf(mood) === 'function') {
mood = mood(this);
}
return !mood || mood === 'default' ? '' : `mood-${mood}`;
}).volatile(),
I have a situation where with the volatile() that hangs of of Ember's computed object resolves all non DOM unit tests successfully but for some reason it is not triggering the template to update the DOM under any circumstance. It should, at the very least, update if any of properties being watched change (in this case ['mood','A', 'B', 'C']) change. Because it is volatile (aka, doesn't cache results) the new results would show up in a template if the template knew to re-render the component but for some reason it doesn't.
If I remove the volatile() tag it works fine for static/scalar values but if mood is a function then the result is cached and therefore it only works once (not a working solution for this problem).
How do I get the best of both worlds?
I'm still not sure why the volatile() method is turning off updates to templates. This might be a real bug but in terms of solving my problem the important thing to recognise was that the volatile approach was never the best approach.
Instead the important thing to ensure is that when mood comes in as a function that the function's dependencies are included in the CP's dependencies. So, for instance, if mood is passed the following function:
mood: function(context) {
return context.title === 'Monkeys' ? 'happy' : 'sad';
}
For this function to evaluate effectively -- and more importantly to trigger a re-evaluation at the right time -- the title property must be part of the computed property. Hopefully that's straight forward as to why but here's how I thought I might accommodated this:
_moodDependencies: ['title','subHeading','style'],
_mood: computed('mood','size','_moodDependencies', function() {
let mood = this.get('mood');
console.log('mood is: %o', mood);
if (typeOf(mood) === 'function') {
run( ()=> {
mood = mood(this);
});
}
return !mood || mood === 'default' ? '' : `mood-${mood}`;
}),
That's better, as it allows at build time for a static set of properties to be defined per component (the _mood CP for me is part of a mixin used by a few components). Unfortunately this doesn't yet work completely as the it apparently doesn't unpack/destructure the _moodDependencies.
I'll get this sorted and update this unless someone else beats me to the punch.
Related
I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!
As the question states, is there any downside in referencing the service directly in the template as such :
[disabled]="stateService.selectedClient == null || stateService.currentStep == 1"
In my opinion this doesn't seem like good practice and I'd much rather keep a "selectedClient" object in whatever component needs to use it. How can I get the state and store it into local variables, while observing the changes:
example: I want to move from step1 to step2 by changing "currentStep" in the "stateService", however I want the component that keeps "currentStep" ALSO as a local variable to reflect the change in the state?
Is it good practice to reference services in html templates in Angular
2?
I'd generally avoid it. It seems to bring more chaos than good.
Cons:
Coming from OOP background, this approach looks like it breaks the Law of Demeter, but more importantly,
It's no longer MVC, where your controller (Angular2's Component) acts like a mediator between the view and the services.
Like Ced said, what if a call to a service's member is costly and we need to refer to it multiple times in the view?
At the moment my editor of choice (VS Code) does not fully support Angular2 templates; referencing too many things outside of its own Component's scope in a template makes refactoring not fun anymore.
Pros:
Sometimes it looks more elegant (because it saves you 2 lines of code), but trust me, it's not.
How can I get the state and store it into local variables, while
observing the changes
Madhu Ranjan has a good answer to this. I'll just try to make it more complete here for your particular example:
In your StateService, define:
currentStep : Subject<number> = new Subject<number>();
selectedClient: Subject<Client> = new Subject<Client>();
changeStep(nextStep: number){
this.currentStep.next(nextStep);
}
selectClient(client: Client) {
this.selectedClient.next(client);
}
In your Component:
currentStep: number;
constructor(stateService : StateService){
stateService.currentStep.combineLatest(
stateService.selectedClient,
(currStep, client) => {
if (client == null) {
// I'm assuming you are not showing any step here, replace it with your logic
return -1;
}
return currStep;
})
.subscribe(val => {
this.currentStep = val;
});
}
You may try below,
stateService
currentStep : Subject<number> = new Subject<number>();
somestepChangeMethod(){
this.currentStep.next(<set step here to depending on your logic>);
}
component
// use this in template
currentStep: number;
constructor(stateService : stateServiceClass){
stateService.currentStep.subscribe(val => {
this.currentStep = val;
});
}
Hope this helps!!
It is probably not a good idea to expose your subject inside of your state service. Something like this would be better.
StateService
private currentStep: Subject<number> = new Subject<number>();
changeStep(value: number) {
this.currentStep.next(value);
}
get theCurrentStep(): Observable<number> {
this.currentStep.asObservable();
}
Component
currentStep: number;
constructor(private stateService: StateService) {
this.currentStep = this.stateService.theCurrentStep;
}
Template
[disabled]="(currentStep | async) == 1" // Not sure if this part would work
In my Ember app, I would like to make nice Liquid Fire transitions within the same route when the model of a dynamic route changes. Here's my router:
// app/router.js
Router.map(function() {
this.route("campaigns", { path: "/" }, function() {
this.route("campaign", { path: "/campaign/:election_year" });
});
});
I would like the view to leave the screen to the left when switching to an election_year that is in the future (e.g., from campaign/2008 to campaign/2012) and to the right the other way around.
My first thought was to use use the {{liquid-outlet}} and the toModel function in the app/transitions.js file, but Edward Faulkner (creator of Liquid Fire) says in this thread that
liquid-outlet does not handle model-to-model transitions on the same
route
and that I should use {{liquid-with}} instead. However I'm at a loss as to how to handle the directionality, i.e. make the view go left when election_year increases, and go right when it decreases. Any clues?
When using a liquid-with (or liquid-bind) helper, there is not necessarily a route or model involved in the animation, and so constraints like toRoute or toModel do not apply. Instead, you can use toValue which will match against whatever you're passing into the helper.
Assuming you're passing a Campaign model to liquid-with, you could use a rule like this in transitions.js:
this.transition(
this.toValue(function(newValue, oldValue) {
return newValue instanceof Campaign &&
oldValue instanceof Campaign &&
newValue.electionYear > oldValue.electionYear
}),
this.use('toLeft'),
this.reverse('toRight')
)
Explanation:
We have a constraint, an animation to use when it matches (toLeft), and an animation to use when it matches with "to" and "from" reversed (toRight),
All the rule constraints (including toValue, fromRoute, etc) can accept:
simple values like toValue(42) or toRoute('posts') that will be compared with ===
regular expressions like toRoute(/unicorn/)
functions that test the value, like toValue(n => n > 1).
functions that compare the value and the "other value", like
toValue((newThing, oldThing) => newThing.betterThan(oldThing))
The final case is what we're using in the solution above.
(We are continuing to refine these APIs. In this particular case, I think we should add an explicit name for the comparison rule, like this.compareValues(function(oldValue, newValue){...}), instead of just overloading toValue to also do comparison. But this should not affect your solution, as I'm not going to break the existing behavior in the foreseeable future.)
Let's say I have Articles which are backed by Sources. Each article has one source.
Sources have associated HTML which will be rendered on screen.
I want this HTML to be rendered only if the source changed.
App.ArticleView = Ember.View.extend({
didInsertElement: function() {
this.addObserver('controller.source.id', function() {
console.log(arguments);
renderHTML();
});
});
});
This behaves exactly as stated in the addObserver documentation, "Note that the observers are triggered any time the value is set, regardless of whether it has actually changed. Your observer should be prepared to handle that."
If setting a controller.model of Article A with source 1 is followed by setting a controller.model of Article B with source 1, the observer will call the method but I want to prevent renderHTML() from happening.
The documentation mentions "Observer Methods" which I'm not sure how to put to use in this case. Its signature (function(sender, key, value, rev) { };) looks exactly like what I need, but in my tests the arguments to the observer method are always 0: (current view), 1: "controller.source.id".
How can I get the previous value of controller.source.id, so as to determine whether to renderHTML() or not?
Ember.set won't set the value if it's the same as the current value, so your observer won't fire unless the value changes.
Here's an example:
http://emberjs.jsbin.com/jiyesuzi/2/edit
And here's the code in Ember.set that does it (https://github.com/emberjs/ember.js/blob/master/packages_es6/ember-metal/lib/property_set.js#L73)
// only trigger a change if the value has changed
if (value !== currentValue) {
Ember.propertyWillChange(obj, keyName);
if (MANDATORY_SETTER) {
if ((currentValue === undefined && !(keyName in obj)) || !obj.propertyIsEnumerable(keyName)) {
Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
} else {
meta.values[keyName] = value;
}
} else {
obj[keyName] = value;
}
Ember.propertyDidChange(obj, keyName);
}
Unfortunately for you're case, Ember considers changing a portion of the chain as changing the item, aka if controller.source changes, then the observer will fire. You'll need to track your id differently to avoid you're observer from firing. You can create a different observer that always sets the current id on a local property, and then it won't fire the update when the chain is broken.
Inside the controller
currentArticleId: null,
watchArticle: function(){
this.set('currentArticleId', this.get('article.id'));
}.observes('article.id')
And then you would watch controller.currentArticleId
In a handlebars template in Ember.js, I have blocks like the following:
{{content.some_attribute}}
{{content.some_other_attr}}
{{content.more_attr}}
Some of these attributes don't exist and I'm implementing them slowly.
Is there a way to get these templates to compile and either ignore the blocks that don't evaluate or better yet, replace them with a html element so they're easier to spot in the browser?
(the template is pretty large and it's being converted from ERB slowly,
Is there a way to get these templates to compile and either ignore the blocks that don't evaluate
Properties that don't exist are undefined, and don't get rendered at all. In other words {{thisDoesNotExist}} will simply be invisible -- it will compile just fine.
or better yet, replace them with a html element so they're easier to spot in the browser
As Cory said, you could use a helper for this that checks for undefined, using Ember.Handlebars.registerBoundHelper.
This seems like a perfect case for a handlebars helper. The helper could validate the value and return the value or the html that you desire.
The following code should be used very carefully, since it has not been tested within an application.
A possible solution to replace the possible undefined value in a template is to overwrite Ember.getPath, which is used to lookup the value of a path in a template, see http://jsfiddle.net/pangratz666/hKK8p/:
var getPath = Ember.getPath;
Ember.getPath = function(obj, path) {
var value = getPath(obj, path);
return (Ember.none(value) ? 'OMG %# is not defined!!'.fmt(path) : value);
};
If this code would be used temporarily in an application, I would also restrict the check for undefined values to a specific obj. So something along those lines:
App.objectWhichHasUndefinedProps = Ember.Object.create({
...
});
Ember.View.create({
templateName: 'templateWithAttributes',
objBinding: 'App.objectWhichHasUndefinedProps'
}).append();
var getPath = Ember.getPath;
Ember.getPath = function(obj, path) {
var value = getPath(obj, path);
if (obj === App.objectWhichHasUndefinedProps) {
return (Ember.none(value) ? 'OMG %# is not defined!!'.fmt(path) : value);
}
return value;
};