Using Ember cli 2.9 I'm making a simple app to convert swiss francs to euros. The app works fine manually in my browser but the integration test I've written for the converter fails. It exists as an Ember component called home-index
Template:
<h2>INPUT GOES HERE</h2>
{{input value=userInput class="user-input" placeholder="enter your francs please"}}
<button class="convert-button" {{action 'convertInput'}}>CONVERT</button>
<div class="display-output">{{outputNumber}}</div>
Component logic:
import Ember from 'ember';
export default Ember.Component.extend({
userInput: "",
outputNumber : 0,
numberifiedInput: Ember.computed('userInput', function() {
let userInput = this.get('userInput');
return parseFloat(userInput);
}),
actions: {
convertInput() {
var input = this.get('numberifiedInput');
var converted = input * 0.93;
this.set('outputNumber', converted);
}
}
});
Integration test:
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('home-index', 'Integration | Component | home index', {
integration: true
});
test('should recieve input from user and display output', function(assert) {
assert.expect(1);
this.render(hbs`{{home-index}}`);
this.$(".user-input").val("1");
this.$('.convert-button').click();
assert.equal(this.$(".display-output").text(), "0.93", "should display correct converted amount");
});
When using the app manually in the browser the value is correctly converted from 1 to 0.93 and displayed in the div. However, the integration test returns "NaN' instead of "0.93".
When I write the test into an acceptance test instead, it passes and gives the correct result. This led me to believe it was due to the use of asynchronous helpers.
I then tried to rewrite the integration test wrapped in an imported wait method as follows:
return wait()
.then(() => {
assert.equal(this.$(".display-output").text(), "0.93", "should display correct converted amount");
});
});
But still gives "NaN" as a result in the integration test.
Does anyone know why this is happening?
PS. Sorry about posting in snippets, stack overflow code blocks are being temperamental..
Try triggering the events 'input' and 'change' on the input field after setting the val.
$('.user-input').val('1');
$('.user-input').trigger('input');
$('.user-input').change();
See https://github.com/emberjs/ember.js/blob/v2.9.1/packages/ember-testing/lib/helpers/fill_in.js#L15 for how the acceptance test helper fillIn does it (but without jQuery).
If an acceptance test is acceptable, it can be a better way to test this sort of thing since the built-in helpers handle the various events that would normally be triggered by user interaction.
Related
I'm struggling with the latest version of SvelteKit, the docs available only works with SSR, and I'm developing SPA app (static page), so, what is the way to pass data from my +layout.svelte to +page.svelte?.
The documentation says that with load function from page.js (I've already set the SSR=false, and I understood that page.js is for SSR), but that doesn't work with SPA, and if I have the load function from the layout it's seems not work.
Aditionaly I want to trigger a function from my +page.svelte that is in the layout page.
Any ideas?
here my try
//+layout.svelte
<script>
export function load() {
return {
data: { title: 'default title' }
};
}
export let data;
</script>
//+page.svelte
<script>
export let data;
console.log(data.title); //undefined
</script>
the docs says that don't use: <script context="module">, and I don't want to use the store becouse I think that sholud be a better way.
Load functions belong in the accompanying files +layout.js/ts, not on the page. They also do not return a property data, everything returned is the data. See the docs.
If SSR is disabled, you can event return a store that could be modified from the page.
To get a store from the data so it can be used with $-syntax, the data property can be destructured on the page:
export let data;
$: ({ title } = data);
You could also create a store and set it as a context in the layout. Pages then can get said context and interact with it. This allows two-way interaction.
Using a store is necessary if the contents require reactivity (i.e. are changed) and the page or layout needs to update.
I have mainly worked with cypress previously for e2e automated testing, I have now started working on webdriverIO. So for a cypress command such as
cy.get("[data-testid='nav-bar']").contains("Search Box").click();
What would be the equivalent for this in webdriverIO? I have tried the following approach in a PageObject Model.
class HomePage extends Page {
get navBar() {
return browser.$("[data-testid='nav-bar']");
}
openSearchBox() {
this.navBar().click('//*[text="Search Box"]');
}
}
However, this approach does not seem to work, any help on this would be appreciated.
Leaving Page Objects asside for now, you'd type this in WebdriverIO:
const bar = $('[data-testid='nav-bar']');
expect(bar.getText()).toInclude('Search Box');
bar.click();
You can use chai for the assertion instead of Jest Matchers:
const expectChai = require('chai').expect;
// ...
expectChai(bar.getText()).to.have.string('Search Box');
// ...
The exact analog to
cy.get("[data-testid='nav-bar']").contains("Search Box").click();
can be achieved with xpath selector
$("[data-testid='nav-bar']").$("./*[descendant-or-self::*[contains(text(), 'Search Box')]]").click();
It looks a bit ugly though, consider adding a custom command that would mimic Cypress's contains:
// put this to `before` hook in your wdio.conf.js
browser.addCommand('cyContains', function(text) {
this.waitForExist()
return this.$(`./*[descendant-or-self::*[contains(text(), '${text}')]]`)
}, true)
$("[data-testid='nav-bar']").cyContains("Search Box").click();
P.S.
Check out the selector in the browser console right on this page, paste in the browser console
$x("//span[descendant-or-self::*[contains(text(), 'Search Box')]]")
Ok, I believe I am VERY close to having my first working Vue JS application but I keep hitting little snag after little snag. I hope this is the last little snag.
I am using vue-async-computed and axios to fetch a customer object from my API.
I am then passing that property to a child component and rendering to screen like: {{customer.fName}}.
As far as I can see, the ajax call is being made and the response coming back is expected, the problem is there is nothing on the page, the customer object doesnt seem to update after the ajax call maybe.
Here is the profile page .vue file I'm working on
http://pastebin.com/DJH9pAtU
The component has a computed property called "customer" and as I said, I can see in the network tab, that request is being made and there are no errors. The response is being sent to the child component here:
<app-customerInfo :customer="customer"></app-customerInfo>
within that component I am rendering the data to the page:
{{customer.fName}}
But, the page shows no results. Is there a way to verify the value of the property "customer" in inspector? is there something obvious I am missing?
I've been using Vue for about a year and a half, and I realize the struggle that is dealing with async data loading and that good stuff. Here's how I would set up your component:
<script>
export default {
components: {
// your components were fine
},
data: () => ({ customer: {} }),
async mounted() {
const { data } = await this.axios.get(`/api/customer/get/${this.$route.params.id}`);
this.customer = data;
}
}
</script>
so what I did was initialize customer in the data function for your component, then when the component gets mounted, send an axios call to the server. When that call returns, set this.customer to the data. And like I said in my comment above, definitely check out Vue's devtools, they make tracking down variables and events super easy!
I believed your error is with naming. The vue-async-computed plugin needs a new property of the Vue object.
computed: {
customer: async function() {
this.axios.get('/api/customer/get/' + this.$route.params.id).then(function(response){
return(response.data);
});
}
}
should be:
asyncComputed: {
async customer() {
const res = await this.axios.get(`/api/customer/get/${this.$route.params.id}`);
return res.data;
}
}
I'm building an app with Meteor using the react-komposer package. It is very simple: There's a top-level component (App) containing a search form and a list of results. The list gets its entries through the props, provided by the komposer container (AppContainer). It works perfectly well, until I try to implement the search, to narrow down the results displayed in the list.
This is the code I've started with (AppContainer.jsx):
import { Meteor } from 'meteor/meteor';
import { composeWithTracker } from 'react-komposer';
import React, { Component } from 'react';
import Entries from '../api/entries.js';
import App from '../ui/App.jsx';
function composer(props, onData) {
if (Meteor.subscribe('entries').ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
export default composeWithTracker(composer)(App);
App simply renders out the whole list of entries.
What I'd like to achieve, is to pass query parameters to Entries.find({}).fetch(); with data coming from the App component (captured via a text input e.g.).
In other words: How can I feed a parameter into the AppContainer from the App (child) component, in order to search for specific entries and ultimately re-render the corresponding results?
To further clarify, here is the code for App.jsx:
import React, { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
<form>
<input type="text" placeholder="Search" />
</form>
<ul>
{this.props.entries.map((entry) => (
<li key={entry._id}>{entry.name}</li>
))}
</ul>
</div>
);
}
}
Thanks in advance!
I was going to write a comment for this to clarify on nupac's answer, but the amount of characters was too restrictive.
The sample code you're looking for is in the search tutorial link provided by nupac. Here is the composer function with the corresponding changes:
function composer(props, onData) {
if (Meteor.subscribe('entries', Session.get("searchValues")).ready()) {
const entries = Entries.find({}).fetch();
onData(null, {entries});
};
};
The solution is the session package. You may need to add it to your packages file and it should be available without having to import it. Otherwise try with import { Session } from 'meteor/session';
You just need to set the session when submitting the search form. Like this for instance:
Session.set("searchValues", {
key: value
});
The subscription will fetch the data automatically every time the specific session value changes.
Finally, you'll be able to access the values in the publish method on the server side:
Meteor.publish('entries', (query) => {
if (query) {
return Entries.find(query);
} else {
return Entries.find();
}
});
Hope this helps. If that's not the case, just let me know.
There are 2 approaches that you can take.
The Subscription way,
The Meteor.call way,
The Subscription way
It involves you setting a property that you fetch from the url. So you setup your routes to send a query property to you Component.Your component uses that property as a param to send to your publication and only subscribe to stuff that fits the search criteria. Then you put your query in your fetch statement and render the result.
The Meteor.call way
Forget subscription and do it the old way. Send your query to an endpoint, in this case a Meteor method, and render the results. I prefer this method for one reason, $text. Minimongo does not support $text so you cannot use $text to search for stuff on the client. Instead you can set up your server's mongo with text indexes and meteor method to handle the search and render the results.
See what suits your priorities. The meteor.call way requires you to do a bit more work to make a "Search result" shareable through url but you get richer search results. The subscription way is easier to implement.
Here is a link to a search tutorial for meteor and read about $text if you are interested
I faced a problem, I solve it by cookies but I want to solve the problem without cookies. I have a component which called app-header and It has another component which called outmodal.
Now, My first Vue instance require component app-header.
var vue = new Vue({
el : "html",
data : {
title : "Site Title",
description : "description of page",
keywords : "my keywords",
view : "home",
login : "login"
},
components:{
"app-header" :require("../../components/header"),
"app-footer" :require("../../components/footer"),
"home" :require("../../views/home")
},
});
code of app-header
var Vue = require("vue");
Vue.partial("login",require("../../partials/login.html"));
Vue.partial("logged",require("../../partials/logged.html"));
module.exports = {
template : require("./template.html"),
replace : true,
components : {
outmodal : require("../outmodal")
},
props : ['login']
}
code of outmodal
var Vue = require("vue");
Vue.partial("loginModal",require("../../partials/loginModal.html"));
module.exports = {
template : require("./template.html"),
replace : true,
props : ['name'],
data : function () {
return {
userLogin : { mail : "", password : "", remember : ""}
}
},
methods : {
formSubmit : function(e){
e.preventDefault();
this.$http.post("http://example.com/auth/login",{ "email": this.userLogin.mail , "password": this.userLogin.password },function(data,status,request){
$.cookie("site_token",data.token,{expires : 1})
}).error(function(data,status,request){
});
}
}, ready : function(){
console.log("it works")
}
}
In outmodal component I connect the API and I check the login, If login will be succesfull, I want to change value of login variable in my Vue instance. I use web pack to build all requires. So I don't know how can I data binding between these files.
How can I solve It? I
The Best Solution which I found
For 0.12
http://012.vuejs.org/guide/components.html#Inheriting_Parent_Scope
for 1.0
http://v1.vuejs.org/guide/components.html#Parent-Child-Communication
for 2.0
https://v2.vuejs.org/v2/guide/components.html#Composing-Components (use props to one-way bind data from parent to child)
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.
i found this one to be more accurate.
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
only in 2.3.0+ tho.
and honestly it's still not good enough. should simply be a easy option for 'two-way' data binding. so none of these options is good.
try using vuex instead. they have more options for such purpose.
https://vuex.vuejs.org/en/state.html
I would prefer event-driven updates as recommended in the documentation. However, I was limited by the existing ("third-party") component already using props and $emit. This component is my grandchild. The following is my solution (passing value through child using props, sync and computed value with $emit.
Comments are welcome.
Value can be modified in parent and grandchild without error:
Grandchild (simplified third-party component):
<template>
<div v-show="value">{{ value}}</div>
<button #click="closeBox">Close</button>
</template>
<script>
export default {
props: {
value: null
},
methods: {
closeBox() {
this.$emit('update:value', null);
}
}
}
</script>
Child:
<template>
<grandchild-component :value.sync="passedValue" />
</template>
<script>
export default {
props: {
value: null
},
computed: {
passedValue: {
get() {
return this.value;
},
set(newVal) {
this.$emit('update:value', newVal);
}
}
}
}
</script>
Parent:
<template>
<child-component :value.sync="value" />
</template>
<script>
export default {
data() {
return {
value: null,
}
},
// ... e.g. method setting/modifying the value
}
</script>