onreadystatechange is not working with nuxt-link (vue) - vue.js

I want to check if media and files are fully loaded with readystate in vue.
Below is mounted at testparent/index.vue. I was just testing every form of onreadystatechange, bc none of them works.
mounted() {
document.onreadystatechange = () => {
if (document.readyState === 'interactive') {
console.log(document.readyState)
}
if (document.readyState === 'complete') {
console.log(document.readyState)
}
}
window.addEventListener('load', function (event) {
console.log(document.readyState)
})
window.addEventListener('DOMContentLoaded', () => {
console.log(document.readyState)
})
document.addEventListener('readystatechange', (event) => {
console.log(document.readyState)
})
}
Below is my template
<nuxt-link to="/testparent/_slug">link to slug</nuxt-link>
<nuxt-link to="/testparent/test">other test link</nuxt-link>
I cannot find any log from the console. but when I refresh the page, the logs comes out.
Is there something I did wrong?
Or is there other way to do? Thank you in advance

Related

Timeout simulation not working with testing-library and useFakeTimers

I'm working on a vueJS component that allows to display a modal after 5 seconds. the component works well as expected.
<template>
<vue-modal v-if="showModal" data-testid="modal-testid" />
</template>
<script>
export default {
name: "TimeoutExample",
data() {
return {
showModal: false,
}
},
mounted() {
setTimeout(() => this.displayModal(), 5000)
},
methods: {
displayModal: function() {
this.showModal = true;
}
}
};
</script>
I implemented the unit tests using jest, testing-library and I wanted to use jest.useFakeTimers to simulate the timeout, but the test is KO.
// testing file
describe.only('Vue Component (mobile) 2', () => {
beforeAll(() => {
isMobile.mockImplementation(() => true)
})
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
})
it('should render title after `props.delay` milliseconds', () => {
const { queryByTestId } = myRender({
localVue: myMakeLocalVue(),
})
jest.advanceTimersByTime(5001)
expect(queryByTestId('modal-testid')).toBeVisible()
})
})
do you have any idea how i can test this behavior?
remove this jest.spyOn(global, 'setTimeout'). jest will do it's own magic with for this with useFakeTimers
I suppose you can not use async and done callback in one test case. Which version of jest do you use?
Add await localVue.$nextTick() after advanceTimersByTime to wait until Vue apply all the changes
It works for me after calling advanceTimersByTime inside waitFor.
describe.only('Vue Component (mobile) 2', () => {
beforeAll(() => {
isMobile.mockImplementation(() => true)
})
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
})
it('should render title after `props.delay` milliseconds', async () => {
const { queryByTestId } = myRender({
localVue: myMakeLocalVue(),
})
await waitFor(() => {
jest.advanceTimersByTime(5001)
})
expect(queryByTestId('modal-testid')).toBeVisible()
})
})

Axios/Vue/Nuxt - Find out when all API calls are finished

I want to make several API calls to get data into a component. I created a PostService.ts that looks like this:
const apiClient = axios.create({
baseURL: '/api/v1',
})
export default {
async getPosts() {
const { data }: { data: Post[] } = await apiClient.get('/posts')
// transform data ...
return data
},
async getTags() {
const { data }: { data: Tag[] } = await apiClient.get('/tags')
return data
},
async getComments() {
const { data }: { data: Comment[] } = await apiClient.get('/comments')
return data
},
}
This is my posts.vue:
<template>
<div>
<div v-if="dataLoaded">
content
</div>
<div v-else>
loading...
</div>
</div>
</template>
<script>
finishedApiCalls = 0
get dataLoaded() {
return this.finishedApiCalls === 3
}
created() {
PostService.getPosts()
.then((posts) => {
this.posts = posts
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getTags()
.then((tags) => {
this.tags = tags
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getComments()
.then((comments) => {
this.comments = comments
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
}
</script>
The key point is that I want to display a loading spinner as long as the data has not been loaded. Is it recommended to make the API calls from created()? What would be a more elegant way to find out when all calls are finished? It does not feel right to use the finishedApiCalls variable.
I recommend using Nuxt's fetch method along with Promise.all() on all your async PostService fetches:
// MyComponent.vue
export default {
fetch() {
return Promise.all([
PostService.getPosts().then((posts) => ...).catch((error) => ...),
PostService.getTags().then((tags) => ...).catch((error) => ...),
PostService.getComments().then((comments) => ...).catch((error) => ...)
])
}
}
Nuxt provides a $fetchState.pending prop that you could use for conditionally rendering a loader:
<template>
<div>
<Loading v-if="$fetchState.pending" />
<div v-else>My component data<div>
</div>
</template>
You can use Promise.all for this kind of requirements.
this.loading = true
Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
.then(values => {
let [posts, tags, comments] = values
this.posts = posts
this.tags = tags
this.comments = comments
//Here you can toggle your fetching flag like below
this.loading = false
})
You can use Promise.all(). This will wait till all resolves or if 1 fails.
With async / await you can make it "synchronous"
data() {
return {
loaded: false
}
},
async created() {
let [posts, tags, comments] = await Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
this.posts = posts;
this.tags = tags;
this.comments = comments;
this.loaded = true;
}

Vue render a component two times

When I logged in the app and select edit some item, they make a $router.push to the edit view, the problem is that they render the component two times, I figured this out by doing a console.log on mounted(){}.. However, if I reload the page and click edit other time they make the render correctly, only one time.
This is the relevant code:
//listItemsView script
editItem(item) {
this.$router.push({ name: 'editPolicy', params:{policyTest: item}})
},
//editItemView script
export default {
props:{
policyTest:{
type: Object,
required: true,
}
mounted(){
console.log(this.policyTest);
console.log('entra');
},
}
//router script
{
path: '/editPolicy/',
name: 'editPolicy',
component: () => import('../views/policies/editPolicy.vue'),
props: true,
meta:{requireAuth:true}
}
router.beforeEach((to, from, next) => {
const user = auth.currentUser;
if(user !== null){
user.getIdTokenResult(true)
.then(function ({
claims
}) {
if (to.name === 'NewClient' && !claims.permissions.includes('Agregar Cliente')) {
next({name: 'notFoundPage'});
}else{
//In this case the router execute this next()
next()
}
})
} else {
if (to.matched.some(record => record.meta.requireAuth)) {
next({name: 'SignIn'});
} else {
next()
}
}
})
//html
<td class="text-left">
<v-icon small class="mr-2" #click="editItem(item)">fas fa-edit</v-icon>
</td>
i solved the problem, I'm use firebase authentication and in the main.js i'm detecting the user state change and for error i was creating two vue instances, one when start up the App and other when the user attemp to make their logging.. solved, thanks

How to access Vue $refs in a plugin?

methods: {
async create () {
this.disableSubmit = true;
await this.$firestore
.collection('collectionName')
.add(this.item)
.then(() => {
this.$refs.createForm.reset();
this.$notify('positive', 'Item successfully created!');
})
.catch(error => {
this.$notify('negative', 'ERROR! Try again later!', error);
});
this.disableSubmit = false;
},
}
If I use the code above inside the methods property, then everything works fine, but I would like to access that ref from outside the Vue component, for example a plugin, but it gives me an error.
TypeError: "_this.$refs is undefined"
Even when I just import it as a function, the error is the same, so I would like to know how to access the ref outside vue?
Bellow is the code for my plugin, and I would also like to point that I am using the quasar framework.
export let plugin = {
install (Vue, options) {
Vue.prototype.$plugin = async (collection, item) => {
return await firestore
.collection(collection)
.add(item)
.then(() => {
this.$refs.createFrom.reset();
notify('positive', 'Booking successfully created!');
})
.catch(error => {
notify('negative', 'ERROR creating booking! Try again later!', error);
});
};
}
};
I hope my question makes sense, and thanks in advance for any help
you could pass the context of your component, to apply the reset form from your plugin:
// plugin declaration
Vue.prototype.$plugin = async (collection, item, ctx) {
...
ctx.$refs.createFrom.reset()
...
}
then when u call to your plugin from yours components can do it like this:
// your component
methods: {
myFunction () {
this.$plugin(collection, item, this)
}
}
this is the reference of the context of your current component that will be used inside of your plugin
for example:
Vue.component('my-form', {
methods: {
resetForm() {
console.log('the form has been reset')
}
}
})
Vue.prototype.$plugin = (item, ctx) => {
console.log('item passed:', item)
ctx.$refs.refToMyForm.resetForm()
}
new Vue({
el: '#app',
data: {
item: 'foo'
},
methods: {
submit() {
this.$plugin(this.item, this)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<my-form ref="refToMyForm"></my-form>
<button #click="submit">submit</button>
</div>

How can I test data returned from an ajax call in mounted is correctly rendered?

I have a component (simplified)
<template>
<div v-if="account">
<h1 v-text="accountName"></h1>
</div>
</template>
<script>
import repo from '../../repo';
export default {
data() {
return {
account: {}
}
},
mounted() {
return this.load();
},
computed: {
accountName: function () {
return this.account.forename + ' ' + this.account.surname;
}
},
methods: {
load() {
return repo
.get(repo.accounts, {
params: {
id: this.$route.params.id
}
})
.then((response) => {
console.log(response.data);
this.account = response.data;
this.validateObj = this.account;
}, (error) => {
switch (error.response.status) {
case 403:
this.$router.push({name: '403'});
break;
default:
this.$refs['generic_modal'].open(error.message);
}
});
}
}
}
</script>
Which on mount, calls an API, gets the returned data, and renders the forename and surname.
I'm trying to write a mocha test to check that this works. I can do it using a timeout.
it('mounts', done => {
const $route = {
params: {
id: 1
}
};
mock.onGet('/api/accounts/1').reply(200, {
forename: 'Tom',
surname: 'Hart'
});
const wrapper = shallowMount(AccountsEdit, {
i18n,
mocks: {
$route
}
});
setTimeout(a => {
expect(wrapper.html()).toContain('Tom Hart');
done();
}, 1900);
});
But I wondered is there a better way? I was hoping to hook into the axios.get call, and run the check once that's finished, however, I can't seem to figure out how to do it.
EDIT: I tried using $nextTick, however, that didn't work either
wrapper.vm.$nextTick(() => {
expect(wrapper.html()).toContain('Tom Hart');
done();
});
{ Error: expect(received).toContain(expected) // indexOf
Expected substring: "Tom Hart"
Received string: "<div><h1>undefined undefined</h1></div>"
at VueComponent.<anonymous> (/home/tom/Dev/V6/Admin/.tmp/mocha-webpack/1554202885644/tests/Javascript/Components/Pages/account-edit.spec.js:37:1)
at Array.<anonymous> (/home/tom/Dev/V6/Admin/node_modules/vue/dist/vue.runtime.common.dev.js:1976:12)
at flushCallbacks (/home/tom/Dev/V6/Admin/node_modules/vue/dist/vue.runtime.common.dev.js:1902:14)
matcherResult: { message: [Function: message], pass: false } }
{ forename: 'Tom', surname: 'Hart' }
1) mounts
0 passing (2s)
1 failing
1) Accounts Edit Page
mounts:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/tom/Dev/V6/Admin/.tmp/mocha-webpack/1554202885644/bundle.js)
EDIT 2: It seems just as a test, chaining $nextTick eventually works, so I guess something else is causing ticks before my call returns? Is there anyway to tell what caused a tick to happen?
wrapper.vm.$nextTick(() => {
wrapper.vm.$nextTick(() => {
wrapper.vm.$nextTick(() => {
wrapper.vm.$nextTick(() => {
wrapper.vm.$nextTick(() => {
wrapper.vm.$nextTick(() => {
expect(wrapper.find('h1').html()).toContain('Tom Hart');
done();
});
});
});
});
});
});
Hey we had similar problem and found this library:
https://www.npmjs.com/package/flush-promises
Which allow to us wait all promises before continue testing.
Try to do something like this:
const flushPromises = require('flush-promises');
it('mounts', (done) => {
const $route = {
params: {
id: 1
}
};
mock.onGet('/api/accounts/1').reply(200, {
forename: 'Tom',
surname: 'Hart'
});
const wrapper = shallowMount(AccountsEdit, {
i18n,
mocks: {
$route
}
});
flushPromises().then(() => {
expect(wrapper.html()).toContain('Tom Hart');
done();
});
});