Click event inside of a Modal - test - javascript

Trying to run a test for the following code, but node can't be found .Using jest and enzyme for ReactJS
render () {
return (
this.state.permissionsLoaded ?
this.state.localPermissions[globals.UI_DATASOURCEDESIGNER] ?
this.state.datasourcePermissionsLoaded ?
this.state.allowCurrentDatasource ?
<div>
<Modal isOpen={this.state.addRequestModalOpen} style={shareModal}>
<div title="Close Window Without Saving" className="sidemodal_addnew_x" onClick={() => {this.closeAddModal()}}><FontAwesome name='xbutton' className='fa-times' /></div>
Keep getting the following error: Method “simulate” is meant to be run on 1 node. 0 found instead.
Here is what I have so far for my test:
beforeEach(() => wrapper = mount(<MemoryRouter keyLength={0}><Datasource {...baseProps} /></MemoryRouter>));
it("Test Click event on Add DataSource ", () => {
wrapper.find('Datasource').setState({
permissionsLoaded:true,
localPermissions:true,
datasourcePermissionsLoaded:true,
allowCurrentDatasource:true,
addRequestModalOpen:true
})
wrapper.update();
wrapper.find('Datasource').find('.sidemodal_addnew_x').simulate('click')
});
Here as list of my state:
permissionsLoaded: false,
datasourcePermissionsLoaded: false,
allowCurrentDatasource: false,
localPermissions:{
[globals.UI_DATASOURCEDESIGNER]:false,
}

Well it looks like you are trying to find a node which will be conditionally rendered if all of the state variables you've mentioned are true, which none of them are (you are actually setting them all to false and updating the wrapper beforehand). This means that there is no .sidemodal_addnew_x to be found that can be used to simulate a click on, hence why you get that error message.
In case you've wanted to test for the existence of that component instead, you can do the following:
expect(wrapper.find('Datasource').find('.sidemodal_addnew_x').exists()).to.equal(false);
If you do want to test the click make sure the component gets .sidemodal_addnew_x gets rendered by settings the state variables to true:
it("Test Click event on Close Window Without Saving", (done) => {
baseProps.onClick.mockClear();
wrapper.find('Datasource').setState({
permissionsLoaded:true,
localPermissions:true,
datasourcePermissionsLoaded:true,
allowCurrentDatasource:true,
addRequestModalOpen:true,
}, () => {
wrapper.update();
wrapper.find('Datasource').find('.sidemodal_addnew_x').simulate('click');
done();
});
});

Related

How to reset Cypress window.location.href after test

I am testing some ui that on click updates the window.location.href. I have two tests, the first one works, but the second starts in the location set by the ui in the previous test. This is wrong and stops test two from starting on the right page.
How can I reset the window.location.href or just the Cypress browser location in general back to where it was at the beginning of the first test?
I have checked the window.location.href at the start of the second test and it looks autogenerated and so I don't think wise to try and hardcode that value into window.location.href at the start of the second test.
Looking for something I can run at afterEach.
Test
it.only('should send asynchronous analytics event after provider selection click', () => {
rewire$useFlag(() => true);
cy.location().then((location) => console.log('window !!'));
const analyticsAsyncStub = cy.stub().as('sendAnalyticsAsyncStub');
rewire$sendAnalyticsAsync(analyticsAsyncStub);
// #NOTE hacking browser detection so required provider options are availiable
cy.window().then(($window) => {
console.log('window !!', $window.location.href);
($window as any).chrome = {};
($window as any).chrome.runtime = {
sendMessage() {
'mock function';
},
};
});
mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={defaultFlags} /> });
cyGetByTestId('provider--metaMask').click();
cy.get('#sendAnalyticsAsyncStub').should(
'have.been.calledWithMatch',
analyticsUtilsModule.createButtonEvent(ButtonEventName.providerSelectionPressed),
);
});
mountFixtureWithProviders function
export const mountFixtureWithProviders = ({
children,
mountInsideVisualMock = true,
setErrorLog = () => ({}),
renderErrorScreens,
}: {
children: ReactNode;
mountInsideVisualMock?: boolean;
setErrorLog?: SetErrorLog;
renderErrorScreens?: boolean;
}) => {
const RouterMockedChildren = () => <MemoryRouter>{children}.
</MemoryRouter>;
const ProvidedChildren = () =>
renderErrorScreens ? (
<DemoAppToRenderErrorMessages>
<RouterMockedChildren />
</DemoAppToRenderErrorMessages>
) : (
<LinkUiCoreContext.Provider value={{ setErrorLog,
imageResizerServiceUrl: DEV_IMAGE_RESIZER_SERVICE_URL }}>
<RouterMockedChildren />
</LinkUiCoreContext.Provider>
);
return mount(mountInsideVisualMock ? linkPageVisualMock({
children: <ProvidedChildren /> }) : <ProvidedChildren />);
};
Thank you.
Usually you can just do a visit in a beforeEach() to get a clean start for each test
beforeEach(() => {
cy.visit('/')
})
There's also cy.go('back') which you can run at the end of the first test.
But be aware that a fail in test 1 will then fail test 2 because the navigation won't happen - same applies to adding into afterEach().
In Cypress window is the test runner window, but you can access the app window with
cy.window().then(win => console.log(win.location.href))
or the location directly with
cy.location().then(loc => console.log(loc.href))
Don't use cy.log() for debugging, use console.log() as there are side-effects to cy.log() that may give you the wrong debugging info.
(mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={{}} /> }) looks like a component test, so yes I agree that should give you a fresh start for each test.
Can you add the two tests and also mountFixtureWithProviders in the question to give the full picture please.

Nuxt handle redirect after deletion without errors : beforeUpdate direction not working?

So I have this nuxt page /pages/:id.
In there, I do load the page content with:
content: function(){
return this.$store.state.pages.find(p => p.id === this.$route.params.id)
},
subcontent: function() {
return this.content.subcontent;
}
But I also have an action in this page to delete it. When the user clicks this button, I need to:
call the server and update the state with the result
redirect to the index: /pages
// 1
const serverCall = async () => {
const remainingPages = await mutateApi({
name: 'deletePage',
params: {id}
});
this.$store.dispatch('applications/updateState', remainingPages)
}
// 2
const redirect = () => {
this.$router.push({
path: '/pages'
});
}
Those two actions happen concurrently and I can't orchestrate those correctly:
I get an error TypeError: Cannot read property 'subcontent' of undefined, which means that the page properties are recalculated before the redirect actually happens.
I tried:
await server call then redirect
set a beforeUpdate() in the component hooks to handle redirect if this.content is empty.
delay of 0ms the server call and redirecting first
subcontent: function() {
if (!this.content.subcontent) return redirect();
return this.content.subcontent;
}
None of those worked. In all cases the current page components are recalculated first.
What worked is:
redirect();
setTimeout(() => {
serverCall();
}, 1000);
But it is obviously ugly.
Can anyone help on this?
As you hinted, using a timeout is not a good practice since you don't know how long it will take for the page to be destroyed, and thus you don't know which event will be executed first by the javascript event loop.
A good practice would be to dynamically register a 'destroyed' hook to your page, like so:
methods: {
deletePage() {
this.$once('hook:destroyed', serverCall)
redirect()
},
},
Note: you can also use the 'beforeDestroy' hook and it should work equally fine.
This is the sequence of events occurring:
serverCall() dispatches an update, modifying $store.state.pages.
content (which depends on $store.state.pages) recomputes, but $route.params.id is equal to the ID of the page just deleted, so Array.prototype.find() returns undefined.
subcontent (which depends on content) recomputes, and dereferences the undefined.
One solution is to check for the undefined before dereferencing:
export default {
computed: {
content() {...},
subcontent() {
return this.content?.subcontent
👆
// OR
return this.content && this.content.subcontent
}
}
}
demo

React Native navigation.navigate params not updating

I try to navigate from home screen to detail screen using navigation.navigate(). When the first time i click button to navigate it works fine, but after that when i go back from detail to home and click another item to see the detail, the params on detail screen is not changed (its still the params of the 1st click). Here my code :
<TouchableNativeFeedback
key={index}
onPress={() => {
navigation.navigate('PenghuniScreen', {
screen: 'DetailPenghuni',
params: {
penghuni: item,
},
});
}}></TouchableNativeFeedback>;
item is object from map loop . i try to console log using react navigation usefocuseffect and the params not changin after 1st click.
useFocusEffect(
React.useCallback(() => {
console.log('My params:', route.params.penghuni);
setPenghuni(route.params.penghuni);
return () => {
// source.cancel('Api Canceled');
console.log('tutup detail penghuni');
};
}, []),
);
anyone got solution for this?
You forgot to add the route dependency to your useCallback, so it is getting the old value.
Try adding [route]:
React.useCallback(() => {
console.log('My params:', route.params.penghuni);
setPenghuni(route.params.penghuni);
return () => {
// source.cancel('Api Canceled');
console.log('tutup detail penghuni');
};
}, [route]),
I suggest you adding the eslint for hooks to your project, to detect missing dependecies.
Observation: Why don't you pass these values though the navigation params instead of using useFocusEffect?
navigation.navigate('DetailPenghuni', { penghuni: item1 }) // item1 press
navigation.navigate('DetailPenghuni', { penghuni: item2 }) // item2 press
If you need to cancel something you can always use the useEffect clean callback called automatically when you close a screen.
You can use route.params.penghuni directly instead of setting setPenghuni(route.params.penghuni)
useEffect(() => {
console.log('My params:', route.params.penghuni);
// setPenghuni(route.params.penghuni);
return () => {
// source.cancel('Api Canceled');
console.log('tutup detail penghuni');
};
}, [route.params?.penghuni]),

Why is the reactive Value sometimes not updating in template? (Vue)

I have a simple h3 tag containing a title that is bound to a reactive data property.
I am fetching the value from a Firestore database and assign it to the data property. When I don't reload and access the page through client-side navigation, everything works fine.
However once I reload the title value gets updated properly (seen in console logs and vue dev tools) but the h3-tag remains empty.
Here is the code:
<template>
<h3 #click="displayCoursePreview" class="mt-5">{{ titl }}</h3>
</template>
<script>
props: {
student: {
type: Boolean
}
},
watch: {
rehydrated: {
// Always triggers once store data is rehydrated (seems to work without any problems)
immediate: true,
async handler(newVal, oldVal) {
if (newVal) {
await this.getSections();
return this.getTopics();
}
}
}
},
data() {
return {
titl: null
};
},
computed: {
rehydrated() {
return this.$store.state.rehydrated; // Equals true once store is rehydrated from local storage
}
},
methods: {
getSections() {
console.log('running') // Runs every time
let ref = this.$store.state.courses;
var cid = this.student
? ref.currentlyStudying.cid
: ref.currentlyPreviewing.cid;
// Get Course Title
this.$fireStore
.collection("courses")
.doc(cid)
.get()
.then(doc => {
console.log(doc.data().name) // Logs correct title every time
this.titl = doc.data().name;
this.thumbSrc = doc.data().imgsrc;
})
.catch(err => console.log(err));
}
</script>
I can't figure out why it sometimes displays the title and sometimes does not. Is there another way to bind titl to the content of the h3-tag without the {{}} syntax?
Thank you in advance!
EDIT:
I have changed the {{}} syntax to v-text like so:
<h3 #click="displayCoursePreview" class="mt-5" v-text="titl"></h3>
And now it works every time, even after a hard reload. Can anyone explain the difference and why this works?
To answer the original question it looks like you might have a race condition between this component and the store. The watch will only trigger 'getSections' if it sees a change in this.$store.state.rehydrated after it's been mounted, but the store might have completed that before this component got mounted, so then the watch never gets triggered.
Not sure why switching to v-text would have altered this, maybe it allows the component to mount slightly faster so it's getting mounted before the store completes it's rehydration?

Jest: Mock a _HOC_ or _curried_ function

Given the following function:
./http.js
const http = {
refetch() {
return (component) => component;
}
}
I would like to mock the function in a test as follows:
./__tests__/someTest.js
import { refetch } from './http';
jest.mock('./http', () => {
return {
refetch: jest.fn();
}
}
refetch.mockImplementation((component) => {
// doing some stuff
})
But I'm receiving the error
TypeError: _http.refetch.mockImplementation is not a function
How can I mock the refetch function in the given example?
update:
When I modify the mock function slightly to:
jest.mock(
'../http',
() => ({ refetch: jest.fn() }),
);
I get a different error:
TypeError: (0 , _http.refetch)(...) is not a function
My guess it's something with the syntax where the curried function (or HOC function) is not mapped properly. But I don't know how to solve it.
Some of the real code I'm trying to test.
Note: The example is a bit sloppy. It works in the application. The example given is to give an idea of the workings.
./SettingsContainer
// ...some code
return (
<FormComponent
settingsFetch={settingsFetch}
settingsPutResponse={settingsPutResponse}
/>
);
}
const ConnectedSettingsContainer = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'https://some-url.com/api/v1/f',
},
settingsPut: (data) => ({
settingsPutResponse: {
url: 'https://some-url.com/api/v1/p',
}
}),
}),
)(SettingsContainer);
export default ConnectedSettingsContainer;
Then in my component I am getting the settingsPutResponse via the props which react-refetch does.
I want to test if the user can re-submit a form after the server has responded once or twice with a 500 until a 204 is given back.
./FormComponent
// ...code
const FormComp = ({ settingsResponse }) => {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsResponse && settingsResponse.fulfilled) {
setSuccess(true);
}
}, [settingsResponse]);
if (success) {
// state of the form wil be reset
}
return (
<form>
<label htmlFor"username">
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
)
};
The first question to ask yourself about mocking is "do I really need to mock this?" The most straightforward solution here is to test "component" directly instead of trying to fake out an http HOC wrapper around it.
I generally avoid trying to unit test things related to I/O. Those things are best handled with functional or integration tests. You can accomplish that by making sure that, given same props, component always renders the same output. Then, it becomes trivial to unit test component with no mocks required.
Then use functional and/or integration tests to ensure that the actual http I/O happens correctly
To more directly answer you question though, jest.fn is not a component, but React is expecting one. If you want the mock to work, you must give it a real component.
Your sample code here doesn't make sense because every part of your example is fake code. Which real code are you trying to test? I've seen gigantic test files that never actually exercize any real code - they were just testing an elaborate system of mocks. Be careful not to fall into that trap.

Categories