I am writing tests in a vue app (using Jest). While testing a certain component, I need to trigger a change event on a checkbox (which I am using BFormCheckbox for).
When I select the checkbox using the selector the actual checkbox evaluates to ('.custom-control-input'), I can get the test below to pass. However, I would like to use the name of the actual component (BFormCheckbox), which I feel would be easier to follow. Is there any way to make this work?
it('is triggered by clicking a phase checkbox', () => {
// I would like to write:
// const phaseCheckbox = wrapper.find(BFormCheckbox);
// However, I can only get the following to work:
const phaseCheckbox = wrapper.find('.custom-control-input');
// this is true for both selectors
expect(phaseCheckbox.exists()).toBe(true);
phaseCheckbox.trigger('change');
// this fails for wrapper.find(BFormCheckbox)
expect(togglePhaseSpy).toHaveBeenCalled();
});
Since the <input type="checkbox"> is nested inside additional HTML markup (as defined by Bootstrap v4), use the following to access the hidden input:
const phaseCheckbox = wrapper.find(BFormCheckbox).find('input')
This will not tie you to using the inner element classname(s), as they do change depending on the rendering style mode of <b-form-checkbox> (i.e. default custom checkbox, switch style checkbox, button style checkbox, or plain mode checkbox).
Jest documentation shows examples of how to do as you are asking.
Component Constructors:
import Foo from '../components/Foo';
const wrapper = mount(<MyComponent />);
expect(wrapper.find(Foo)).to.have.lengthOf(1);
Component Display Name:
const wrapper = mount(<MyComponent />);
expect(wrapper.find('Foo')).to.have.lengthOf(1);
Related
I have 3 components Map, Field & Section. Map is the parent component. Field & Section are child components.
There is an action called testField in Field component. Inside the test function first I want to trigger an event SaveSection on Section component and then continue other stuff.
// In field component
public function testField(): void
{
// Save section
$this->emitTo('section', 'saveSection');
Log::debug('Testing.....');
}
// In section component
public function saveSection()
{
Log::debug('saveSection+++++');
$this->emitTo('map', 'storeSection', $this->section, $this->sectionIndex);
}
// In map component
public function storeSection(array $section, int $sectionIndex)
{
Log::debug('storeSection-----');
// Store to DB
....
}
But it prints Testing..... before storeSection-----. Is there way I can wait for events to finish before continue.
Hi based on our conversation, I think you would want to go with hooking into Livewire and listening for message processed events.
The first step would be to wrap Log::debug('Testing.....'); in a function like this. The original idea came from #Prospero Livewire show loader when an event is emitted
public function testLog()
{
Log::debug('Testing.....');
}
You might want to tweak this to your satisfaction as this is just a recommendation.
Now you need to prepare your application to stack scripts in the base view right before the closing body tag. eg in app.blade.php like this
<body>
...//Whatever thats before it
#stack('scripts')
</body>
This would help you stack the custom script tag you would define in the component view.
After this you now use the Livewire hook. Do this properly to avoid DOM diffing issues as Livewire would cry tears in your developer console:
<script>
document.addEventListener('livewire:load', function () {
Livewire.hook("message.processed", (message, component) => {
if (message.updateQueue[0].payload.event === "storeSection") {
// I have not implemented it this way so try any of them
//#this is a brilliant decorator that finds the component
#this.emit('testLog') or #this.call('testLog')
}
});
})
</script>
This would show Testing....`` after storeSection``` is processed.
Please do let me know if this does not work as I might have to implement changes.
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!
In my app I have a sidebar with a list of "saved searches" and a central area that should show the results of a search. Whenever I click on a saved search link, I want to update the central area with the results of that search.
What is the proper way to do this with apollo-react?
I tried with this:
// SidebarConnector.js:
const withVideoSearch = graphql(
VIDEO_SEARCH_QUERY,
{
name: 'videoSearchQuery',
props: ({ videoSearchQuery }) => {
return ({
searchVideos: videoSearchQuery.refetch,
});
},
}
);
export default withVideoSearch(Sidebar);
My saved searches are doing a searchVideos({ query: "some query" }) on click which, based on the above, is doing a refetch for the VIDEO_SEARCH_QUERY query with different variables.
This works fine, the call is made to the graphql server and results are returned just fine.
For the main component that shows the list of results I use:
export default graphql(VIDEO_SEARCH_QUERY)(ResultList);
Initially the main component gets its results from the server as if the query was done without variables which is fine, exactly how I want it.
The problem is that every refetch seems to create a different entry in ROOT_QUERY in apollo's store and my main component is "locked" into the one without variables.
Here's what apollo's store looks like after the initial fetch and one of the refetches triggered from a saved search:
ROOT_QUERY
searchVideos({"query":"coke"}): [Video]
0:▾Video:arLaecAu5ns
searchVideos({"query":null}): [Video]
0:▾Video:3HXg-oVMA0c
So my question is how to either switch the main component to the "current search" or how to overwrite the store on every refresh so that there's only one key so the main component updates correctly.
For completeness here's my VIDEO_SEARCH_QUERY:
export const VIDEO_SEARCH_QUERY = gql`
query searchVideos($query: String) {
searchVideos(query: $query) {
...fVideo
}
}
${fVideo}
`;
Maybe I'm misunderstanding your use case, but it seems like there's no need to utilize refetch here. It would be simpler to persist whatever the selected search string is as state, pass that state down as a prop to your main component and then just use that prop as the variable in your GraphQL request. So the graphql call inside your ResultList component would look something like this:
const options = props => ({ variables: { query: props.searchString } })
export default graphql(VIDEO_SEARCH_QUERY, { options })(ResultList);
Then just have your onClick handler for each saved search set the state to whatever that search string is, and Apollo will do the rest. This is super easy with Redux -- just fire off the appropriate action. If you're not using Redux, you may have to lift the state up so it can then be passed down as a prop, but the concept is the same.
I have two test methods in one file. However when I run them the last one fails.
When I try to debug the test inside the browser after the first answer.dispatchEvent(new MouseEvent('click')); executes the event listeners on the second row won't activate as if they weren't able to listen to any click events. I tried clicking manually as well and the rows didn't change color like the first row (using the exact same code).
import 'dart:html';
import 'package:angular2/angular2.dart';
import 'package:angular_test/angular_test.dart';
import 'package:test/test.dart';
import '...multiple_choice_quiz_component.dart';
#AngularEntrypoint()
void main()
{
tearDown(disposeAnyRunningTest);
group('$MultipleChoiceQuizComponent', () {
test('should add "incorrect" css class to incorrect answer', () async {
NgTestBed bed = new NgTestBed<MultipleChoiceQuizComponent>();
NgTestFixture fixture = await bed.create();
Element answer = fixture.rootElement.querySelector('.quiz-choice:nth-child(2)');
answer.dispatchEvent(new MouseEvent('click'));
bool hasClass = answer.classes.contains('incorrect');
expect(hasClass, true);
});
test('should add "correct" css class to correct answer', () async {
NgTestBed bed = new NgTestBed<MultipleChoiceQuizComponent>();
NgTestFixture fixture = await bed.create();
Element answer = fixture.rootElement.querySelector('.quiz-choice:nth-child(3)');
answer.dispatchEvent(new MouseEvent('click'));
bool hasClass = answer.classes.contains('correct');
expect(hasClass, true);
});
});
}
Here is a screenshot.
The first row is the first test method. answer.dispatchEvent(new MouseEvent('click')); executes correctly and the color changes.
However on the second row with the exact same code the whole second row is unresponsive. Like I said it's almost as if the event listeners for the second row are disabled.
In fact, if I put the two methods in different dart test files both tests pass.
Why can't I have these two tests in one file?
There appears to be a bug where disposeAnyRunningTest doesn't actually do what we want.
https://github.com/dart-lang/angular_test/issues/46
This is why it behaves differently for you when you have the test cases in separate files, but doesn't work when they run together.
The tests might not work when they aren't properly disposed for a few reasons.
The component you are testing might have a bug which does not allow multiple copies on the page at once. Do you use element ids anywhere that might make them only work when there is a single instance?
Fixture.rootElement might have a bug which is causing it to return the element from the previous test instead of the new one.
My code:
signup.emblem:
= validating-form onsubmit=(action 'signUp')
= input-field value=username
span {{usernameError}}
validating-form.js:
submit(event) {
console.log(this.get('username') //undefined
this.sendAction('onsubmit')
}
signup.js:
actions: {
signUp() {
console.log(this.get('username')) // value from input
}
}
As you can see the basic idea is some value in input gets validated in validating-form component and then if everything is fine it'll call some controller action or set some properties.
The problem is that apparently this form component isn't bind to properties from controller, even though its child component (input-field) is. Can you tell me what am I doing wrong here?
If I have to bind it explicitely, is there some way to do that with multiple properties at once?
The problem is that the standard input element isn't two-way bound to your username variable. You can bind it quickly using the action and mut helpers.
(example in handlebars, but you should be able to convert to emblem easily enough)
<input value={{username}} onblur={{action (mut username) value='target.value'}}>
This is saying:
on the onblur event
mut(ate) the username
to match the current target.value - which is the value of the input box
You can see evidence of this working in this twiddle
The other option is Input Helpers
I've not used these, as they don't follow the current Ember thinking of Data Down Actions Up, but it should be as simple as:
{{input value=username}}
And this will two-way-bind directly username.