I am a little puzzled I am trying to unit test a child (ratingsItem component) emitting to a parent component. Here is the test code:
it("rating component should emit call to parent updateRating function", () => {
const wrapper = factory({});
const ratingComp = wrapper.find({ref: "ratings"});
ratingComp.vm.$emit("updateRating", 1);
expect(wrapper.emitted().updateRating).toEqual(1);
expect((wrapper.vm as any).updateRating).toHaveBeenCalled();
});
This is the code inside the parent component. You can see the ratingsItem component inside it:
<template>
<div>
<div class="overlay" v-show="isModalOpen" #click="closeModal"></div>
<div ref="modal" class="modal" v-show="isModalOpen">
<div slot="header" class="modal_header">
<div class="modal_header_title">{{headerTitle}}</div>
<div #click="closeModal" class="modal_header_close">
<img src="../assets/modal_close_icon.svg" />
</div>
</div>
<div>
<label>Restaurant Rating</label>
<!-- rating component -->
<ratingsItem ref="ratings" :rating="rating" :starSizePerc="12" #updateRating="updateRating"></ratingsItem>
</div>
<!-- footer -->
<button class="pointer cancel_btn" #click="closeModal">Cancel</button>
<button class="pointer space_left save_btn" #click="submitDetails()">Save</button>
</div>
</div>
</template>
But when I run the test I get this error:
expect(received).toEqual(expected) // deep equality
Expected: 1
Received: undefined
71 |
72 |
> 73 | expect(wrapper.emitted().updateRating).toEqual(1);
| ^
74 | // expect((wrapper.vm as any).updateRating).toHaveBeenCalled();
75 | });
76 | });
Can anyone help?
I checked vue-test-utils docs for 'emitted':
https://vue-test-utils.vuejs.org/api/wrapper/emitted.html
Can you try to wrap it in vm.$nextTick:
it("rating component should emit call to parent updateRating function", (done) => {
const wrapper = factory({});
const ratingComp = wrapper.find({ref: "ratings"});
ratingComp.vm.$emit("updateRating", 1);
wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().updateRating).toEqual(1);
expect((wrapper.vm as any).updateRating).toHaveBeenCalled();
done()
})
});
I figured it out this is what I used below:
it("rating component should emit call to parent updateRating function", () => {
const wrapper = factory({});
const updateRating = jest.fn();
wrapper.setMethods({ updateRating });
wrapper.find({ ref: "ratings" }).vm.$emit("updateRating", { idx: 9 });
expect(updateRating).toHaveBeenCalled();
expect(updateRating).toHaveBeenCalledWith({ idx: 9 });
});
Thanks for your help with this :)
Related
I am new to React TypeScript. I have a problem with the passing state. When I tried to pass the state to a child component and conosle.log(state), I can see the correct object. But, when I tried to do console.log(state.name), I have an error. How can I solve this problem?
App.tsx
export interface Information {
name: string;
age: string;
}
const App: FC = () => {
const [state, setState] = useState<Information | null>({
name: "young",
age: "10",
});
return (
<div className="App">
<div className="header">
<div className="inputContainer">
<input type="text" placeholder="Task.." name="task" />
<input type="number" placeholder="Deadline" name="deadline" />
</div>
<button>Add Task</button>
<div>
<MyForm state={state} />
</div>
</div>
</div>
);
};
Child component
type Props = {
state: ReactNode;
};
const MyForm: FC<Props> = ({ state }: Props) => {
console.log(state.name); // Error
return <div>Hello, {state}</div>;
};
export default MyForm;
Thank you!
The error because you're trying to read the state object inside JSX
return <div>Hello, {state}</div>
Read it like you would with objects instead:
return <div>Hello, {state.name}</div>
Also in your MyForm Component Props, use your Information interface as a type definition instead of ReactNode
export interface Information {
name: string
age: string
}
type Props = {
state: Information
}
My component template is correctly
<v-radio-group row :mandatory="false" v-model="gender" name="gender">
<v-radio :label="genderLabels.f" value="f" name="female"></v-radio>
<v-radio :label="genderLabels.m" value="m" name="male"></v-radio>
</v-radio-group>
genderLabels is correctly set upon mounted()
mounted() {
this.contactLang = this.language;
// eslint-disable-next-line
this.customDico = this.$validator.dictionary.container[this.language].custom;
this.genderLabels.f = this.customDico.gender.f;
this.genderLabels.m = this.customDico.gender.m;
}
there is no problem when executing yarn serve, one can see the radio label attributes..
but when I test it, they are not present...
ContactForm.spec.js
....
wrapper = mount(ContactForm, options);
console.log(wrapper.vm.genderLabels.f);
const radio = wrapper.find('[name="female"]');
console.log("radio: ", radio.html())
// then
console.log("radioLabels attributes: ", radio.attributes());
expect(radio.attributes("label")).toEqual("Mrs");
consolelog
wrapper = mount(ContactForm, options);
console.log(wrapper.vm.genderLabels.f);
const radio = wrapper.find('[name="female"]');
console.log("radio: ", radio.html())
// then
console.log("radioLabels attributes: ", radio.attributes());
expect(radio.attributes("label")).toEqual("Mrs");
● ContactForm.vue › uses the default form language
expect(received).toEqual(expected)
Expected value to equal:
"Mrs"
Received:
undefined
Difference:
Comparing two different types of values. Expected string but received undefined.
110 | // then
111 | console.log("radioLabels attributes: ", radio.attributes());
> 112 | expect(radio.attributes("label")).toEqual("Mrs");
| ^
113 | });
114 | /*
115 | it("change the form language when locale changed in store", async () => {
at Object.toEqual (tests/unit/ContactForm.spec.js:112:39)
console.log tests/unit/ContactForm.spec.js:107
Mrs
console.log tests/unit/ContactForm.spec.js:109
radio: <input aria-checked="false" role="radio" type="radio" value="f" name="female">
console.log tests/unit/ContactForm.spec.js:111
radioLabels attributes: { 'aria-checked': 'false',
role: 'radio',
type: 'radio',
value: 'f',
name: 'female' }
Test Suites: 1 failed, 1 total
where could I be wrong in my test code ?
thanks for feedback
==== UPDATE 1 ====
Using shallowMount , I gte the following in console.log(wrapper.html()); :
<vradiogroup-stub column="true" height="auto" name="gender" row="true" value="f">
<vradio-stub color="accent" onicon="$vuetify.icons.radioOn" officon="$vuetify.icons.radioOff" value="f" name="female"></vradio-stub>
<vradio-stub color="accent" onicon="$vuetify.icons.radioOn" officon="$vuetify.icons.radioOff" value="m" name="male"></vradio-stub>
</vradiogroup-stub>
using mount() , I get the follwoing in wrapper.html()
<input aria-checked="false" role="radio" type="radio" value="f" name="female">
<div role="radiogroup" class="v-input--radio-group__input">
<div class="v-radio theme--light">
<div class="v-input--selection-controls__input">
<input aria-checked="false" role="radio" type="radio" value="f" name="female">
<div class="v-input--selection-controls__ripple">
</div><i aria-hidden="true" class="v-icon material-icons theme--light">radio_button_unchecked</i></div>
<label aria-hidden="true" class="v-label theme--light" style="left: 0px; position: relative;"></label>
</div>
<div class="v-radio theme--light">
<div class="v-input--selection-controls__input">
<input aria-checked="false" role="radio" type="radio" value="m" name="male">
<div class="v-input--selection-controls__ripple">
</div>
<i aria-hidden="true" class="v-icon material-icons theme--light">radio_button_unchecked</i>
</div>
<label aria-hidden="true" class="v-label theme--light" style="left: 0px; position: relative;"></label>
</div>
</div>
It's strange ... no text in the label ...
SOLVED...
I do not understand yet why ... but I need to set my test as asynchronous :
then I can get the label attribute set to the correct value ...
it("uses the default form language", async () => {
...
wrapper = shallowMount(ContactForm, options);
await wrapper.vm.$nextTick();
const radioInput = wrapper.find('[name="female"]');
console.log(radioInput.html());
console.log tests/unit/ContactForm.spec.js:110
<vradio-stub color="accent" onicon="$vuetify.icons.radioOn"
officon="$vuetify.icons.radioOff"
value="f" name="female" label="Mrs"></vradio-stub>
Hello I am trying to simulate an 'ended' event on and HTML5 audio element without success. Here is my component:
export class AudioPlayer extends Component {
constructor (props) {
super(props);
// Auto play turned off when component render for the first time
this.autoPlay = false;
this.shouldAudioAutoPlay = this.shouldAudioAutoPlay.bind(this);
}
componentDidMount () {
let audio = findDOMNode(this.player);
if (audio) {
console.log('in')
audio.addEventListener('ended', () => {
console.log('event dispatched')
this.props.audioFinished();
});
}
}
render () {
return (
<Media>
<div>
<div className="media-player">
<Player src={this.props.audioUrl.url}
autoPlay={false}
id="activeModuleAudio"
vendor="audio"
ref={(player) => { this.player = player }}
/>
</div>
<div className="ap-main">
<div className="ap-controls">
<PlayButton />
<ProgressBar enableUserInput={this.props.enableUserInput} audio={this.props.audioUrl} />
</div>
<a href={this.props.facebookUrl}><div className="ap-facebook-group" /></a>
</div>
</div>
</Media>
);
}
}
And here the test:
it('should dispatch an action when audio is finished', () => {
let mockedAudioFinishedAction = jest.fn();
let mockedEnableUserInputAction = jest.fn();
let mountedWrapper = mount(
<AudioPlayer
audioUrl={{ url: 'audio-url', autoPlay: true }}
facebookUrl="facebook-url" audioFinished={mockedAudioFinishedAction}
enableUserInput={mockedEnableUserInputAction}
/>
);
mountedWrapper.update();
mountedWrapper.update();
mountedWrapper.find(Player).simulate('ended');
expect(mockedAudioFinishedAction.mock.calls.length).toBe(1);
});
The simulate seems to not be dispatching the event,despite working on the browser. Any thoughts?
Thanks in advance!
I'm somewhat new to Angular. I am trying to display a Bootstap 3 modal dialog when an invalid user role is detected. I cannot get my modal template to display. The behavior seems to work i.e. I can dismiss the faded overlay..I just don't see the actual modal template.
Bootstrap 3
AngularJS 1.0.7
AngularJS UI Bootstrap 0.6.0
Controller
gsApp.controller('MainController', ['$rootScope', '$scope', '$q', '$window', '$location', '$modal', 'ApplicationCache', 'UserService',
function MainController($rootScope, $scope, $q, $window, $location, $modal, ApplicationCache, UserService) {
$scope.userRole = "BadRole";
$scope.showBadRoleModel = function () {
var showBadRoleModelInstance = $modal.open({
templateUrl: "badRoleModal.html",
backdrop: true,
windowClass: 'modal',
controller: badRoleModalInstance,
resolve: {
items: function () {
return $scope.userRole;
}
}
});
}
var badRoleModalInstance = function($scope, $modalInstance, items){
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
}]);
HTML
<div class="row" ng-controller="MainController">
<script type="text/ng-template" id="badRoleModal.html">
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<div class="modal-body">
<h2>body</h2>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<button class="btn" ng-click="showBadRoleModel()">Show bad role modal</button>
</div>
AngularJs UI Bootstrap doesn't work with Bootstrap 3 yet.
See more details here: https://github.com/angular-ui/bootstrap/issues/331
Here's a reusable Angular directive that will hide and show a Bootstrap 3 (or 2.x) modal.
app.directive("modalShow", function () {
return {
restrict: "A",
scope: {
modalVisible: "="
},
link: function (scope, element, attrs) {
//Hide or show the modal
scope.showModal = function (visible) {
if (visible)
{
element.modal("show");
}
else
{
element.modal("hide");
}
}
//Check to see if the modal-visible attribute exists
if (!attrs.modalVisible)
{
//The attribute isn't defined, show the modal by default
scope.showModal(true);
}
else
{
//Watch for changes to the modal-visible attribute
scope.$watch("modalVisible", function (newValue, oldValue) {
scope.showModal(newValue);
});
//Update the visible value when the dialog is closed through UI actions (Ok, cancel, etc.)
element.bind("hide.bs.modal", function () {
scope.modalVisible = false;
if (!scope.$$phase && !scope.$root.$$phase)
scope.$apply();
});
}
}
};
});
Usage Example #1 - this assumes you want to show the modal - you could add ng-if as a condition
<div modal-show class="modal fade"> ...bootstrap modal... </div>
Usage Example #2 - this uses an Angular expression in the modal-visible attribute
<div modal-show modal-visible="showDialog" class="modal fade"> ...bootstrap modal... </div>
Another Example - to demo the controller interaction, you could add something like this to your controller and it will show the modal after 2 seconds and then hide it after 5 seconds.
$scope.showDialog = false;
$timeout(function () { $scope.showDialog = true; }, 2000)
$timeout(function () { $scope.showDialog = false; }, 5000)
I'm late to contribute to this question - created this directive for another question. Here are some related links: Simple Angular Directive for Bootstrap Modal and https://stackoverflow.com/a/19668616/1009125
Hope this helps.
when ember.js tries to render my template containing the following bindAttr. the following exception is thrown in handlebars.js
Uncaught TypeError: Object [object Object] has no method 'replace' handlebars.js:848
bind attr tag:
<div class="postWrapper" {{bindAttr style="display:none"}}>
Update
this also happens when i use the action helper
<div {{action Toggle}} class="btn pull-right">
<i class="postToggler icon-chevron-down " ></i>
</div>
Update Full Code
Template
<script type="text/x-handlebars" data-template-name="Composer">
<div class="postWrapper">
<div class="postContentWrapper" {{bindAttr style="controller.display"}}>
<div class="row-fluid">
<div class="pull-left span10">
To :
<input id="test2" type="text" style="margin-top: 7px;width:90%" />
</div>
<div {{action Toggle}} class="btn pull-right">
<i class="postToggler icon-chevron-down " ></i>
</div>
</div>
<div class="row-fluid" style="height:100%" >
<div id="wmd-button-bar" style="width:48%;display:inline-block" ></div>
<div class="pull-right">
<a>Hide preview</a>
</div>
<div class="wmdWrapper" style="height:80%">
<div class="wmd-panel" style="vertical-align: top;">
<textarea class="wmd-input" id="wmd-input" style="height: 100%;"></textarea>
</div>
<div id="wmd-preview" class="wmd-preview pull-right"></div>
</div>
<br />
</div>
<div class="row-fluid">
<div class="span6 ">
<p>
Tags :
<input id="test" type="text" style="width:80%"/>
</p>
</div>
<div class="span2 pull-right">
<button id="btnSubmitPost" class="btn btn-success pull-right">{{controller.buttonText}}</button>
<button id="btnCanelPost" class="btn btn-warning pull-right">Cancel</button>
</div>
</div>
<div class="row-fluid">
</div>
</div>
</div>
</script>
View and render
/*
MODES
NEW
REPLY
*/
Thoughts.ComposerController = Ember.Object.extend({
mode: 2,
visible: false,
messageContent: "",
buttonText: function () {
switch (this.get("mode")) {
case 1: return "Post";
case 2: return "Reply";
}
}.property(),
display: function () {
if (this.get("visible")) {
return 'display:block';
} else
return 'display:none';
}.property(),
Toggle: function(){
console.log('Helllo');
}
});
Thoughts.ComposerController = Thoughts.ComposerController.create();
Error Information
object dump
string: "data-ember-action="1""
__proto__: Object
constructor: function (string) {
toString: function () {
__proto__: Object
Crashes on the replace method, because the method replace is undefined
Handlebars.Utils = {
escapeExpression: function (string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
if (!possible.test(string)) { return string; }
----> return string.replace(badChars, escapeChar);
},
So first of all you need to define only need to define the controller. You don't have to create an instance. Ember will do it for you when application initialize.
If you define a property that is observing another in other words its value depends on another, you need this to specify as parameter to property helper.
Thoughts.ComposerController = Ember.Controller.extend({
mode: 2,
visible: false,
messageContent: "",
buttonText: function () {
switch (this.get("mode")) {
case 1: return "Post";
case 2: return "Reply";
}
}.property('mode'),
display: function () {
return 'display:' + this.get('visible') ? 'block' : 'none';
}.property('visible'),
Toggle: function () {
this.toggleProperty('visible');
this.set('mode', this.get('mode') == 2 ? 1 : 2);
}
});
Template itself seems valid.
You can get this working by creating a composer route like this:
this.route('composer');
or by rendering it in another template like this:
{{render 'composer'}}
That should be answer to your question. BUT
Wouldn't be better to use {{if}} helper for showing some content inside of template based on a condition?
i finally found some time to work on this again.
all i did was replace the ember and handlebars js files, and the code is working fine now thanks