Vuex Store keeps state somehow between tests - vue.js

I have a strange problem with testing the frontend part of my project. I use Vue components for the frontend. The project is website for teachers to set appointments for the assistent(s) so the assistant can ready everything for class.
First let me explain the structure.
I have a component which lists all appointments by date. Every date is a seperate card and all the appointments for one date are rows on that card. Each row is a specific timeslot. Appointments can be added to the list either by clicking on a button at the top of the card or by clicking the row number.
So I created three components: AppointmentsList.Vue which gets the appointments from the backend and builds a list of the cards, AppointmentsCard.Vue, which receives the date and all the appointments for that date as props, and lastly AppointmentRow.Vue which show the detailsof the appointment in a row of the table. The state, appointments, requested dates and other data, is kept in a Vuex store.
I build the project using TDD, using Jest and Vue-test-utils for writing the tests. Mocks are used to simulate the responses of the backend. Tests showing the appointments with the cards and rows works fine. But when testing the buttons I encountered a strange problem.
In the following code I show you my tests, redacted for brevity. First everything is imported and then the responses of the backend for varieous endpoints are defined. Only the appointmentResponse is important. Note that two appointments are returned.
The function createStore builds a store out of the modules. I keep all state, getters and mutations in modules. Before each test is run, I create a new store and initialize the store with the data of the responses using the mutations of the store. After each test the jest mocks are cleared and the vue-test-utils wrapper is destroyed.
*AppointmentList.spec.js*
import {mount, createLocalVue} from '#vue/test-utils'
import flushPromises from 'flush-promises'
import AppointmentsList from '../../resources/js/components/AppointmentsList.vue'
import axios from 'axios'
import Vuex from 'vuex'
import appointmentsmodule from '../../resources/js/storemodules/appointmentsModule.js'
import classhoursmodule from '../../resources/js/storemodules/classhoursModule.js'
import classroomsmodule from '../../resources/js/storemodules/classroomsModule.js'
import experimentsmodule from '../../resources/js/storemodules/experimentsModule.js'
import locationsmodule from '../../resources/js/storemodules/locationsModule.js'
import subjectsmodule from '../../resources/js/storemodules/subjectsModule.js'
import usersmodule from '../../resources/js/storemodules/usersModule.js'
import Vue from 'vue'
import { wrap } from 'lodash'
let appointmentResponse={"status":200,"lines":2,"data":[{"id":1,"subject_id":1,"owner_id":1,"group_id":1,"appointment_at":"2021-12-29T00:00:00.000000Z","classhour_id":1,"classroom_id":1,"experiment_id":null,"short_name":"magnam","description":"Sit cum quae quae quo quo consequatur.","demo":"0","toa_preferred_id":null,"location_id":1,"created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":2,"subject_id":2,"owner_id":1,"group_id":1,"appointment_at":"2021-12-28T00:00:00.000000Z","classhour_id":1,"classroom_id":1,"experiment_id":null,"short_name":"possimus","description":"Velit eos sed esse reprehenderit.","demo":"0","toa_preferred_id":null,"location_id":1,"created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let userResponse={"status":200,"lines":3,"data":[{"id":1,"code":"Est","name":"Mr. Americo Mertz I","email":"user1#hetstreek.nl","actual_location":"1","registrar":"1","email_verified_at":"2022-01-04T15:50:26.000000Z","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","department_location_id":null,"welcome_valid_until":null,"roles":[{"id":2,"name":"toa","guard_name":"web","created_at":"2022-01-04T15:50:24.000000Z","updated_at":"2022-01-04T15:50:24.000000Z","pivot":{"model_id":"1","role_id":"2","model_type":"App\\Models\\User"}},{"id":1,"name":"beheerder","guard_name":"web","created_at":"2022-01-04T15:50:22.000000Z","updated_at":"2022-01-04T15:50:22.000000Z","pivot":{"model_id":"1","role_id":"1","model_type":"App\\Models\\User"}}]},{"id":2,"code":"Est","name":"Mr. Americo Mertz I","email":"user2#hetstreek.nl","actual_location":"2","registrar":"1","email_verified_at":"2022-01-04T15:50:26.000000Z","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","department_location_id":null,"welcome_valid_until":null,"roles":[{"id":3,"name":"docent","guard_name":"web","created_at":"2022-01-04T15:50:25.000000Z","updated_at":"2022-01-04T15:50:25.000000Z","pivot":{"model_id":"2","role_id":"3","model_type":"App\\Models\\User"}}]},{"id":3,"code":"Est","name":"Mr. Americo Mertz I","email":"user3#hetstreek.nl","actual_location":"1","registrar":"1","email_verified_at":"2022-01-04T15:50:26.000000Z","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","department_location_id":"{\"department_id\":1,\"location_id\":1,\"updated_at\":\"2022-01-04T15:50:26.000000Z\",\"created_at\":\"2022-01-04T15:50:26.000000Z\",\"id\":1}","welcome_valid_until":null,"roles":[{"id":4,"name":"leerling","guard_name":"web","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z","pivot":{"model_id":"3","role_id":"4","model_type":"App\\Models\\User"}}]}]}
let classhourResponse={"status":200,"lines":2,"data":[{"id":1,"name":"9","starttime":"15:22","endtime":"12:56","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":3,"name":"1","starttime":"21:47","endtime":"20:16","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let classroomResponse={"status":200,"lines":2,"data":[{"id":1,"name":"non","number":"756","in_use":"1","student_accessible":"0","teacher_accessible":"1","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":3,"name":"ut","number":"214","in_use":"1","student_accessible":"0","teacher_accessible":"1","location_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let experimentResponse={"status":200,"lines":2,"data":[{"id":1,"name":"nam","description":"Aliquam nihil voluptas aut vel neque.","student_selectable":"0","user_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":3,"name":"similique","description":"Exercitationem officiis excepturi aut veniam voluptatum.","student_selectable":"1","user_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let locationResponse={"status":200,"lines":2,"data":[{"id":1,"name":"Omnis.","school_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"},{"id":2,"name":"Illo.","school_id":"1","created_at":"2022-01-04T15:50:26.000000Z","updated_at":"2022-01-04T15:50:26.000000Z"}]}
let schoolResponse={"status":200,"lines":1,"data":[{"id":1,"schoolname":"Jaydon Mante","domain":"hetstreek.nl","max_locations":"1","payed_at":null,"due_date":null,"active":"1","storage_folder":"jaydonmante_hetstreeknl","created_at":"2022-01-05T14:41:49.000000Z","updated_at":"2022-01-05T14:41:49.000000Z"}]}
let subjectResponse={"status":200,"lines":2,"data":[{"id":1,"code":"SK","description":"Quis.","block_for_days":"9","color":"A3A3A3","created_at":"2022-01-05T18:54:41.000000Z","updated_at":"2022-01-05T18:54:41.000000Z"},{"id":2,"code":"re","description":"Libero.","block_for_days":"3","color":"A3A3A3","created_at":"2022-01-05T18:54:41.000000Z","updated_at":"2022-01-05T18:54:41.000000Z"}]}
function createStore(){
return {
modules:{
appointments:appointmentsmodule,
classhours:classhoursmodule,
classrooms:classroomsmodule,
experiments:experimentsmodule,
locations:locationsmodule,
subjects: subjectsmodule,
users: usersmodule
}
}
}
let wrapper
let store
beforeEach(()=>{
const newStore=createStore()
//define a new store for each test
store=new Vuex.Store(newStore)
//sets the range of dates for which the appointments need to be shown.
let startdate = new Date(2021, 11, 27)
let enddate = new Date(startdate)
enddate.setDate(startdate.getDate()+3)
//initialize the store
store.commit('setAppointmentsPeriod',{startdate,enddate})
store.commit('setSubjectFilter', [])
store.commit('storeAppointments',[])
store.commit('storeClasshours',classhourResponse.data)
store.commit('storeClassrooms',classroomResponse.data)
store.commit('storeExperiments',experimentResponse.data)
store.commit('storeLocations',locationResponse.data)
store.commit('storeSubjects',subjectResponse.data)
store.commit('storeUsers',userResponse.data)
//log to show the appointments are empty
console.log(store.state.appointments.appointments)
jest.clearAllMocks()
})
afterEach(()=>{
jest.clearAllMocks
wrapper.destroy()
})
jest.mock("axios")
const localVue =createLocalVue()
localVue.use(Vuex)
When the component AppointmentsList is mounted in the test it detects the change of period from the store, which triggers a request to the backend and loads the appointments for that period.
The first test tests the button a top of the card. It triggers the button and verifies the modal to add an appointment is opened, fills in the form, triggers the submit button and verifies that the data is send to the backend, and the store is updated so now three appointments should be in the store. This works fine.
test('add button adds appointment to list', async ()=>{
//set up the mock data.
let classhour=1
let appointmentToAdd={
id:3,
owner_id:1,
experiment_id:null,
short_name:"Schaduwpracticum",
description:"Lichtkastje met voeding, kartonnetje, scherm, liniaal",
group_id:1,
subject_id:1,
toa_preferred_id:1,
appointment_at:"2021-12-27",
classhour_id:classhour,
classroom_id:1,
demo:false,
location_id:1,
created_at:new Date(),
updated_at:new Date()
}
//mock axios responses. Get returns the appointments, post returns the added appointment.
axios.get.mockResolvedValue({status:200 , data:appointmentResponse.data})
axios.post.mockResolvedValue({status:200, data:{"status":200, "lines":1,"data":appointmentToAdd}})
//appointment is added for this date.
let checkDate=new Date(2021,11,27)
//show all two appointments
wrapper = mount(AppointmentsList, {store, localVue})
await flushPromises()
//verify two appointments in the store
expect(store.state.appointments.appointments).toHaveLength(2)
//find add button for first date
//click buttons open add/edit modal with date field prefilled
await wrapper.find('[data-cy="20211227"]').trigger('click')
const wrappedAddAppointment=wrapper.findComponent({name:'add-appointment'})
//check modal is opened with correct date
expect(wrappedAddAppointment.vm.$props.modalState).toBe(true)
expect(wrappedAddAppointment.vm.$data.appointment.appointment_at).toStrictEqual(checkDate)
//fill in fields and click submit
await wrappedAddAppointment.find('input[id="shortname"]').setValue(appointmentToAdd.short_name)
await wrappedAddAppointment.find('input[id="description"]').setValue(appointmentToAdd.desc)
await wrappedAddAppointment.find('input[id="teacher"]').setValue(1)
await wrappedAddAppointment.find('input[id="group"]').setValue(appointmentToAdd.group_id)
await wrappedAddAppointment.find('input[id="subject"]').setValue(appointmentToAdd.subject_id)
await wrappedAddAppointment.find('input[id="preferredtoa"]').setValue(appointmentToAdd.toa_preferred_id)
await wrappedAddAppointment.find('input[id="appointment_at"]').setValue(appointmentToAdd.appointment_at)
await wrappedAddAppointment.find('input[id="classhour"]').setValue(appointmentToAdd.classhour_id)
await wrappedAddAppointment.find('input[id="classroom"]').setValue(appointmentToAdd.classroom_id)
await wrappedAddAppointment.find('input[id="demo"]').setChecked(false)
await wrappedAddAppointment.find('button[name="save-button"]').trigger('click')
//check axios post(/appointments) is called
expect(axios.post).toHaveBeenCalledTimes(1)
expect(axios.post.mock.calls[0][0]).toContain('/appointments')
expect(axios.get).toHaveBeenCalledTimes(2)
//verify the modal is closed
expect(wrappedAddAppointment.vm.$props.modalState).toBe(false)
//verify the appointment is added to the store
expect(store.state.appointments.appointments).toHaveLength(3)
//verify added appointment is in list
expect(wrapper.text()).toContain(appointmentToAdd.short_name)
})
The next test tests clicking the row number. It should also open the AddAppointment modal with date and classhour (=row number) prefilled. As adding the appointment is already tested, the test stops.
test('clicking classhour adds appointment to list', async ()=>{
//setup ajax responses
axios.get.mockResolvedValue({status:200 , data:appointmentResponse.data})
axios.post.mockResolvedValue({status:200, data:{"status":200, "lines":1,"data":appointmentToAdd}})
let checkDate=new Date(2021,11,27)
console.log(store.state.appointments.appointments)
//show all appointments
const wrapper = mount(AppointmentsList, {store, localVue})
await flushPromises()
expect(axios.get).toHaveBeenCalledTimes(1)
--> expect(store.state.appointments.appointments).toHaveLength(2)
//test fails on line above. Added next two lines to check the appointments in the store and the return value of the axios call.
console.log(store.state.appointments.appointments) //shows three appointments with the last one being the appointment added in the previous test
console.log(axios.get.mock.results[0].value) //shows only two appointments returned as expected
//find classhour button of date 27-dec-2021 and classhour 3
await wrapper.find('[data-cy="20211227classhour3"]').trigger('click')
const wrappedAddAppointment=wrapper.findComponent({name:'add-appointment'})
//check modal is opened with correct date and classhour
expect(wrappedAddAppointment.vm.$props.modalState).toBe(true)
expect(wrappedAddAppointment.vm.$data.appointment.appointment_at).toStrictEqual(checkDate)
expect(wrappedAddAppointment.vm.$data.appointment.classhour.id).toBe(3)
})
This test fails at the line marked with an arrow. Jest reports not two appointments but three in the store. As I have rebuild the store between tests, this make no sense to me. The added appointment should no longer be in the store. The mock call clearly shows two appointments in the response. But it gets stranger even more. Instead of adding the two initial appointments to the store I decided to change the response to return no appointments. I changed the mockresponse to:
axios.get.mockResolvedValue({status:200 , data:{status:200, lines:0, data:[]} })
and the verification to
expect(store.state.appointments.appointments).toHaveLength(0)
This test passes, so the added appointment from the first test is not retained.
Can anyone help me shed some light on this?
Edit. Added the appointmentModule as requested in the comments.
const appointmentsmodule = {
state(){
return{
appointments:[],
appointmentPeriod:{
startdate:null,
enddate:null
},
subjectFilter:[],
}
},
mutations:{
storeAppointments(state, appointments){
//console.log('storeAppointments', appointments)
state.appointments = appointments
},
setAppointmentsPeriod(state, period){
//console.log('setAppointmentsPeriod', period)
state.appointmentPeriod = period
},
setSubjectFilter(state, filter){
//console.log('set Filter', filter)
state.subjectFilter= filter
},
addAppointment(state, appointmentToAdd){
state.appointments.push(appointmentToAdd)
}
},
getters:{
getFilteredAppointments: state=>{
//console.log('getting filtered appointments')
if(state.subjectFilter.length>0){
////console.log('filtered')
return state.appointments.filter(appointment=>{
/* //console.log(state.subjectFilter)
//console.log(appointment.subject_id)
//console.log(state.subjectFilter.includes(appointment.subject_id)) */
return state.subjectFilter.includes(appointment.subject_id)})
}
else{
////console.log('no filter')
return state.appointments
}
},
getAppointmentsPeriod(state){
//console.log('getting appointments period')
return state.appointmentPeriod
}
}
}
export default appointmentsmodule

I solved the problem. When storing the response from the backend I replaced the array in the vuex.state with the new array, thereby breaking reactivity.
When I use
state.appointments.splice(0,Infinity, ...newAppointments)
all the elements of the original array are replaced with the new elements. Reactivity is preserved and the tests passes

Related

React Native cheerio.load() not working properly neither is JSDOM library

Well I'm new to this app development thing especially react-native and I wanted to know when I'm trying to scrap a website using cheerio and axios in react-native and then save it to firebase realtime database in the following way:
and yes i have done all the imports and also initalized my app using firebaseConfig
const db = firebase.database();
async function loadFurniture() {
const Url = 'https://hoid.pk/product-category/bedroom/beds-bedroom/';
const html = await axios.get(Url); // fetch page
const $ = cheerio.load(html); //parse html String
const furniture = [];
$('.product-wrapper ').each((i, element) => {
const title = $(element).find('h2.product-name').text();
const imageUrl = $(element).find('img.primary_image').attr('src');
const price = $(element).find('span.woocommerce-Price-amount amount').text();
console.log(title);
furniture.push({ title, imageUrl, price });
});
// Save the furniture to the Firebase Realtime Database
db
.ref('/furniture/bed')
.set({
title: furniture.title,
price: furniture.price,
object_image : furniture.imageUrl,
})
.then(() => console.log('Data set.'));
console.log(furniture);
// Return the extracted information
return furniture;
}
and then calling this function in a button
<Button
title="Fetch"
onPress = {() => loadFurniture() }
/>
The data was not being scraped so I tried to console.log() the data being fetched.
Whenever I click the button there is no error but just a log [ Function initialize ] with respect to console.log(title)
And before anyone says yup I've looked into the structure and 9it does returns me my desired classes after axios.get()
I just want to know that if there's some error in my code or if I'm going wrong somewhere.
I tried to scrap furniture titles, images and prices from certain website and then save it to database for any further use but it's just not working.
I've checked my network issues the html page being scraped and everything else one can think of. Now i just want to know either my code is accurate or if there's some mistake.
I tired to scrap the data of same website using python and it scraps it perfectly.
Edit:
I found out that the cheerio.load() function is not working there was no problem with the database... Is there some problem with cheerio.load() in it's latest version "1.0.0-rc.12" ?? If so what's the solution... I've tried number of libraries and each is giving a different kind of error so cheerio might be the only possible solution so if there's an alternative way of using cheerio.load() in react native do let me know.

Is it okay to modify some state in Redux if after we modify it we call an action to overwrite the old state?

OK, say I have an initial state in our Redux store that looks like this:
const initialState = {
userReports: [],
activeReport: null,
}
userReports is a list of reports. activeReport is one of those reports (the one that is actively being worked with).
I want the active report to point to one in the array. In other words, if I modify the active report, it would modify one in the userReports array. This means, the two objects must point to the same memory space. That's easy to set up.
The alternative to this approach would be to copy one of the reports that is in the userReports array and set it as the active report (now it has a different memory address). The problem is now, when I edit the activeReport, I also have to search through the array of userReports, find the report that resembles the active report and modify it there too. This feels verbose.
Here is the question:
Would it be bad practice to have the activeReport point to a report in the array (same object). When I want to change the report I could do something like this (example is using redux thunk):
export const updateReport = (report) => async (dispatch, getState) => {
try {
const report = getState().reports.activeReport
// modify the active report here
report.title = "blah blah blah"
dispatch({ type: ACTIONS.UPDATE_REPORT, payload: report })
} catch (error) {
console.log(`ERROR: ${error.message}`)
}
}
And in my reducer:
case ACTIONS.UPDATE_REPORT:
return { ...state, activeReport: action.payload }
as you can see, after updating the report I still return a "new version" of that report and set it as active, but this approach also updates the report in the userReports array because they point to the same memory address.
I would say thats not ideal, do the reports have id's? If they do I would rather hold the userReports in an object with keys being the id's, then active report can just be an id and renamed to activeReportId so you can fetch the activeReport with userReports[activeReportId]
You also asked for reasons:
So firstly any screen that looks at userReports wont rerender because the reports aren't being reassigned.
Secondly if someone later wants to update those screens they will reassign userReports which could cause problems.
Thirdly its an unusual pattern which is a huge no no for redux. The point of redux is that it has a very obvious pattern so when you add things to it you don't have to think and can just make changes with confidence.
Your activeReport should not be pointing to an object in the userReports array, but rather it should be an id of the report, which the user is currently working on. Each of the report in the userReports will have a unique id field to identify the report - this would be helpful when rendering in react - this id field can be used as key.
Then your action creator/dispatcher will look like this:
export const updateReport = (updatedReport) => async (dispatch, getState) => {
dispatch({ type: ACTIONS.UPDATE_REPORT, payload: updatedReport });
}
You will call this on change in your component:
const onTitleChangeHandler = (e) => {
var newTitle = e.target.value;
// you will get the userReports and activeReport from props or by using some redux selector, also you will need to get dispatch and getState from redux
var activeReportObj = userReports.filter((r) => r.id === activeReport)[0];
updateReport({ title: newTitle, ...activeReportObj })(dispatch, getState);
}
Lastly, your reducer will be:
case ACTIONS.UPDATE_REPORT:
var newUserReports = state.userReports.map((r) => {
if (r.id === state.activeReport) {
return action.payload;
}
return r;
});
return { newUserReports, ...state };

vuejs router.go(-1) not showing on second time

I'm currently using vue-router to manage the differents Vue of my project.
My main.js
import Vue from 'vue'
import App from './App.vue'
import jQuery from 'jquery'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.css'
global.jQuery = jQuery
global.$ = jQuery
import './assets/css/animate.css'
import router from './router'
import store from './vuex'
Vue.config.productionTip = false
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
When I'm on my dashboard ('/dashboard') for the first time, the 'created' methods is called. Data are retrieved from the API and shows up in my array.
After that I click on one element of my array that route me to '/details/:id' (with id the id of my element). Everything works well and then I click on a 'Go back' button.
I finish again on my dashboard page, i see that the 'create' methods is called again, data are well retrived from the API but nothing shows up and my array stays empty.
I really don't understand why.
There is the code of the the 'created' function:
export default {
created: function() {
console.log('created => dashboard');
let store = this.$store;
let q = this.rows;
//get rows
if (store.state.socket.io._callbacks["$rows"] == undefined) {
console.log("Binding rows");
//Where I receive the rows from API
store.state.socket.io.on("rows", data => {
console.log("rows reponse:", data);
if (data.success) {
this.nbrItems = data.rows.length;
q.splice(0, q.length); //Clean the array without replacing the instance
data.rows.map(a => q.push(a));
console.log("Queue length: " + q.length);
}
});
}
//get the queue
this.refresh(); // This send a request to the API to ask it to send us back the datas
},
And I use this.$router.go(-1) to navigate back on the '/dashboard' page.
Edit: Is there a problem of state or something like that? I do not understand why, because in-memory I can access to all data, there is just no more binding anymore...
Do you pop every element of the array before you call the created function?
I'm still an apprentice but it seems to me like you have to pop everything before adding new elements to the array.
I figure it out:
The problem was coming from socket.io. I'm checking if the event is bind already before to subscribe to a function and this function contains 'this' that was still referring to the previous Vue instance.
Simply fixed by replacing this:
//get rows
if (store.state.socket.io._callbacks["$rows"] == undefined) {
console.log("Binding rows");
//Where I receive the rows from API
store.state.socket.io.on("rows", data => {
console.log("rows reponse:", data);
if (data.success) {
this.nbrItems = data.rows.length;
q.splice(0, q.length); //Clean the array without replacing the instance
data.rows.map(a => q.push(a));
console.log("Queue length: " + q.length);
}
});
}
by this:
if (store.state.socket.io._callbacks["$rows"] != undefined) {
store.state.socket.io.off("rows");
}
console.log("Binding rows");
store.state.socket.io.on("rows", data => {
console.log("rows reponse:", data);
if (data.success) {
this.nbrItems = data.rows.length;
q.splice(0, q.length);
data.rows.map(a => q.push(a));
console.log("Queue length: " + q.length);
}
});
But this makes me wonder, if I can still access to a previous Vue instance is it meaning that it will be some kind of memory leak with time?
I suppose that no with the garbage collector but would mean that nothing else refers to the previous instance.

How to refresh v-data-table after REST API Patch call?

I have a data table in Vuetify that is populated via a REST get request, using a function "getData" that is called when the app is mounted. The <td>'s in the table have buttons that the user can hit to "lock" the period (the row/column intersection).
When they hit the button, they get a popup confirmation dialog. When they hit "OK", there is a save method called to write the current date back to the db via a REST PATCH request (see below).
My problem is, the grid is not updating with the results of the patch request. I have to manually refresh the page to see the result. What is the common pattern here? Should i pull down the data again via getData to refresh the table? Should i update the array that the data-table sits on directly?
getData method:
getData() {
var self = this;
return axios
.get("http://127.0.0.1:5000/api/estimatefinal/periods?dataset=capital")
.then(function(response) {
self.periods = response.data;
})
.catch(function(error) {
alert(error);
});
},
Save method:
save(item) {
var self = this;
axios
.patch("http://localhost:5000/api/estimatefinal/period/" + self.id, {
date: moment(self.selected_date, "YYYY-MM-DD").format(
"YYYY-MM-DDTH:m:s"
)
})
.then(function() {
this.getData(); // ????
})
.catch(function(error) {
alert(error)
});
this.getData(); // ????
this.close();
}
If your PATCH changes only one row in DB, means has visually effect on only one row on your v-data-table, then you can change the data locally when you get "success" response from back-end.
If, in other hand, your PATCH changes many other things in DB (also in v-data-table) your best option is probably to getData() after you get PATCH response.
Point is to keep that same "picture" of values in DB and on screen v-data-table.

jest snapshot testing: how to ignore part of the snapshot file in jest test results

Problem: ignore some part of the .snap file test results
the question here: there are some components in my test that have a random values and i don't really care about testing them. is there any way to ignore part of my X.snap file? so when i run tests in the future it won't give me test fail results.
Now you can also use property matcher for these cases.
By example to be able to use snapshot with these object :
const obj = {
id: dynamic(),
foo: 'bar',
other: 'value',
val: 1,
};
You can use :
expect(obj).toMatchSnapshot({
id: expect.any(String),
});
Jest will just check that id is a String and will process the other fields in the snapshot as usual.
Actually, you need to mock the moving parts.
As stated in jest docs:
Your tests should be deterministic. That is, running the same tests multiple times on a component that has not changed should produce the same results every time. You're responsible for making sure your generated snapshots do not include platform specific or other non-deterministic data.
If it's something related to time, you could use
Date.now = jest.fn(() => 1482363367071);
I know it's quite old question but I know one more solution. You can modify property you want to ignore, so it will be always constant instead of random / dynamic. This is best for cases when you are using third party code and thus may not be able to control the non deterministic property generation
Example:
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Card from './Card';
import toJSON from 'enzyme-to-json';
Enzyme.configure({ adapter: new Adapter() });
describe('<Card />', () => {
it('renders <Card /> component', () => {
const card = shallow(
<Card
baseChance={1}
name={`test name`}
description={`long description`}
imageURL={'https://d2ph5fj80uercy.cloudfront.net/03/cat1425.jpg'}
id={0}
canBeIgnored={false}
isPassive={false}
/>
);
const snapshot = toJSON(card);
// for some reason snapshot.node.props.style.backgroundColor = "#cfc5f6"
// does not work, seems the prop is being set later
Object.defineProperty(snapshot.node.props.style, 'backgroundColor', { value: "#cfc5f6", writable: false });
// second expect statement is enaugh but this is the prop we care about:
expect(snapshot.node.props.style.backgroundColor).toBe("#cfc5f6");
expect(snapshot).toMatchSnapshot();
});
});
You can ignore some parts in the snapshot tests replacing the properties in the HTML. Using jest with testing-library, it would look something like this:
it('should match snapshot', async () => {
expect(removeUnstableHtmlProperties(await screen.findByTestId('main-container'))).toMatchSnapshot();
});
function removeUnstableHtmlProperties(htmlElement: HTMLElement) {
const domHTML = prettyDOM(htmlElement, Infinity);
if (!domHTML) return undefined;
return domHTML.replace(/id(.*)"(.*)"/g, '');
}
I used this to override moment's fromNow to make my snapshots deterministic:
import moment, {Moment} from "moment";
moment.fn.fromNow = jest.fn(function (this: Moment) {
const withoutSuffix = false;
return this.from(moment("2023-01-12T20:14:00"), withoutSuffix);
});