I am getting an error because my computed property is expecting for some variable to be full of data, but I do my data loading in onBeforeMount which for some reason is being triggered AFTER my computed property.
MY THEORY WHY THIS IS HAPPENING:
When I remove my watcher the problem goes away.
Watcher triggers the computation of the "myComputedProperty" because it needs the old value so it can compare it to the new value and it does that as soon as watch() is called.
Does that mean I have to register my watchers in onBeforeMount to avoid this type of behavior?
Relevant code:
<script setup>
import { ref, watch, computed, onBeforeMount } from 'vue';
onBeforeMount(() => {
console.log("onBeforeMount")
valueThatIsFilledInBeforeMount = 1;
});
const myComputedProperty = computed(() => {
//some code
return valueThatIsFilledInBeforeMount + 1;
})
watch(myComputedProperty , async (newValue, oldValue) => {
//some code
})
</script>
Stuff I am using:
"#quasar/extras": "^1.0.0",
"axios": "^0.21.1",
"fast-xml-parser": "^4.0.10",
"pinia": "^2.0.11",
"quasar": "^2.6.0",
"vue": "^3.0.0",
"vue-router": "^4.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.18.11/xlsx-0.18.11.tgz"
Did you try initialising your valueThatIsFilledInBeforeMount in your data Export?
I tested it with my config and unless im not exporting my valueToCompute with an initial value (0 or null or smth.) my computed value works just fine:
export default {
data() {
return {
valueBeforeMount: null,
}
},
beforeMount() {
this.valueBeforeMount = 1;
},
computed: {
valueBeforeMountComp() {
return this.valueBeforeMount + 1;
},
},
watch: {
valueBeforeMountComp: function(newVal, oldVal) {
console.log(newVal) // Should be 2
console.log(oldVal) // Should be 1
},
}
}
At least if i get your problem right.
Related
I am working on a product overview page, that send out an API-call based on the current Category you are looking at:
store.dispatch("tweakwise/fetchAPIAttributesLayeredNavigation", {
tweakwiseCategory,
this.pageNumber,
}
In my Store, the data from this API-call will be set in the following VueX Store State:
this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes: []
I want to react to this data in my front-end but my Computed methods do not seem to react to this change. As you can also see in the function below I added a Catch to prevent a "Non defined" error. The function, however, will not be called after the state has been set.
This computed property is also added to the Mount() op the component
computed: {
initialFetchProducts() {
this.fetchProducts(
this.$store.state.tweakwise?.tweakwiseLayeredNavigationAttributes || []
);
},
},
make computed property for state you want to watch,
than create watch() for this prop. In watch you can react on computed property change.
<template>
<div v-for="product in products"></div>
</template>
<script>
export default {
data: {
return {
products: [],
}
},
computed: {
tweakwiseLayeredNavigationAttributes() {
return this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes;
},
},
watch: {
// on every tweakwiseLayeredNavigationAttributes change we call fetchProducts
tweakwiseLayeredNavigationAttributes: {
handler(newValue, oldValue) {
this.fetchProducts(newValue);
},
deep: true, // necessary for watching Arrays, Object
immediate: true, // will be fired like inside mounted()
}
},
methods: {
async fetchProducts(params) {
const products = await axios.get('/api', params);
this.products = products;
}
}
};
</script>
I'm trying to create a "settings" component which saves selected values into a store so that all the other components can use those values to change their appearance.
SettingsView.vue:
One of the settings (you can also see it on codepen):
[...]
<p>{{ themeColor }}</p>
<v-radio-group v-model="themeColor">
<v-radio label="light" value="light"></v-radio>
<v-radio label="dark" value="dark"></v-radio>
</v-radio-group>
[...]
<script>
export default {
data () {
return {
// default value
themeColor: 'light',
}
},
computed: {
themeColor () {
return this.$store.state.themeColor
}
},
methods: {
changeThemeColor() {
this.$store.commit('changeThemeColor')
},
}
}
</script>
I don't know how to properly send the selected value of that setting to the store so I just created a mutation with a method (plus the need to have some default value, e.g. themeColor: 'light' like shown above, make it more confusing)
store/modules/Settings.js
const state = {
themeColor: ''
}
const mutations = {
changeThemeColor: state => {
state.themeColor = ''
}
}
export default {
state,
mutations
}
How do I do this properly, so I can then use that value in all the components?
Do I have to use something like getters or actions? I don't really know.
From https://vuex.vuejs.org/en/forms.html, I would use a computed property with getter and setter, ie
export default {
computed: {
themeColor: {
get () {
return this.$store.state.themeColor
},
set (value) {
this.$store.commit('changeThemeColor', value)
}
}
}
}
Note, you do not need data or methods.
Your store should also look more like
const state = {
themeColor: 'light' // default value
}
const mutations = {
changeThemeColor (state, themeColor) {
state.themeColor = themeColor
}
}
Demo ~ https://codepen.io/anon/pen/YYbPww?editors=1011
For instances where you just want to display / read the themeColor state in your component, I recommend using the mapState helper.
import { mapState } from 'vuex'
export default {
// ...
computed: mapState(['themeColor'])
}
I have a Vue2 project with Vuetify, and i am using Jest for unit testing my code. I am starting out testing some sample code and i simple cannot get Jest to determine if a Vuetify v-alert component is visible or not. I have tried the built in Jest methods as well as adding Jest-dom and using the toBeVisible() method and nothing is working so far.
If you look at the Test.vue component, the v-alert component is hidden by default.(Its style is set to display: none;)
The unit test says expect(alert).not.toBeVisible() which should pass, but it always fails regardless of what the v-alert model is set to. If i change the test to expect(alert).toBeVisible() it passes regardless of the v-alert model is set to true/false.
If i change the test to be expect(alert).toHaveStyle({ display: 'none' }); it fails regardless of if i have the model set to true/false.
So as far as i can tell the Jest unit test CANNOT determine the visibility of the v-alert component at all. These same test work fine on the v-btn component just fine so why does the v-alert break? This is just my first unit test sample that ive been trying to get working for 2 days now. I have an entire application to write tests for and so far Jest is not working very well with Vuetify...any suggestions?
// Test.vue component
<template>
<div>
<v-btn ref="btn" depressed tile #click="showAlert">Show Alert</v-btn>
<v-alert
v-model="showError"
ref="error-msg"
type="error"
transition="scale-transition"
width="410"
tile
dense
dismissible
#input="clearError"
>
{{ errorText }}
</v-alert>
</div>
</template>
<script>
export default {
data() {
return {
showError: false,
errorText: ''
};
},
methods: {
showAlert() {
this.errorText = 'Test Error message';
this.showError = true;
},
clearError() {
this.errorText = '';
}
}
};
</script>
// Jest Unit test
// Libraries
import Vue from 'vue';
import Vuetify from 'vuetify';
// Components
import Test from '#/components/Login/Test.vue';
// Utilities
import { createLocalVue, shallowMount } from '#vue/test-utils';
// Import Jest Dom test utils.
import '#testing-library/jest-dom';
const localVue = createLocalVue();
Vue.use(Vuetify);
describe('Test Page', () => {
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
it('Check visibility of button', () => {
const wrapper = shallowMount(Test, {
localVue,
vuetify
});
const btn = wrapper.findComponent({ ref: 'btn' }).element;
expect(btn).toBeVisible();
});
it('Error Message hidden on page load', () => {
const wrapper = shallowMount(Test, {
localVue,
vuetify
});
const alert = wrapper.findComponent({ ref: 'error-msg' }).element;
expect(alert).not.toBeVisible();
});
});
// Package.json
"dependencies": {
"vue": "^2.6.11",
"vue-click-outside": "^1.1.0",
"vue-debounce": "^2.5.7",
"vue-router": "^3.3.4",
"vuetify": "^2.2.11",
"vuex": "^3.4.0"
},
"devDependencies": {
"#babel/plugin-transform-runtime": "^7.10.3",
"#babel/polyfill": "^7.10.1",
"#fortawesome/fontawesome-free": "^5.13.1",
"#testing-library/jest-dom": "^5.10.1",
"#vue/cli-plugin-babel": "^4.4.5",
"#vue/cli-plugin-e2e-nightwatch": "^4.4.5",
"#vue/cli-plugin-eslint": "^4.4.5",
"#vue/cli-plugin-unit-jest": "^4.4.5",
"#vue/cli-service": "^4.4.5",
"#vue/eslint-config-prettier": "^4.0.1",
"#vue/test-utils": "^1.0.3",
"babel-eslint": "^10.0.3",
"babel-jest": "^26.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^6.2.2",
"node-sass": "^4.14.1",
"sass": "^1.26.9",
"sass-loader": "^8.0.2",
"vue-cli-plugin-vuetify": "^2.0.6",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.5.0"
}
I ran into a similar issue, so I decided to use exists from #vue/test-utils instead.
Docs for exists: https://vue-test-utils.vuejs.org/api/wrapper/#exists
I also decided to use v-if (instead of v-model) on the v-alert element to hide / show the component.
It looks like if v-if receives a value of false, the component/element in the document is replaced with <!---->, which is great for checking if your component/element is hidden or displayed.
See v-if test spec: https://github.com/vuejs/vue/blob/52719ccab8fccffbdf497b96d3731dc86f04c1ce/test/unit/features/directives/if.spec.js
SFC
Template:
<template>
<v-container>
<v-btn
#click='showError()'
ref="showErrorButton">
Show Error
</v-btn>
<v-alert
v-if="errorEncountered"
ref="errorAlert"
colored-border
type="error"
elevation="2"
>
Oops! Something went wrong!
</v-alert>
<v-container>
<template>
Javascript:
export default {
methods: {
showError() {
this.errorEncountered = true;
}
}
data() {
return {
errorEncountered: false,
};
},
};
Whenever errorEncountered is updated, the v-alert component will show/hide depending on whether the value is true/false.
Tests
describe('Component', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(Component, {
localVue,
vuetify,
});
});
describe('When component is mounted', () => {
it('Then the default value for errorEncountered should be false', () => {
expect(wrapper.vm.errorEncountered).toBeFalse();
});
it('Then the default state for the error alert should be hidden', async () => {
const errorAlert = wrapper.find({ ref: 'errorAlert' });
expect(errorAlert.exists()).toBeFalse();
});
describe('When an error is encountered', () => {
it('Then errorEncountered should be set to true', async () => {
const showErrorButton = wrapper.find({ ref: 'showErrorButton' });
showErrorButton.trigger('click');
await Vue.nextTick();
expect(wrapper.vm.errorEncountered).toBeTrue();
});
it('Then error alert should be visible', async () => {
const showErrorButton = wrapper.find({ ref: 'showErrorButton' });
showErrorButton.trigger('click');
await Vue.nextTick();
const errorAlert = wrapper.find({ ref: 'errorAlert' });
expect(errorAlert.exists()).toBeTrue();
});
});
});
When I write test case for form submit, i m getting issue with 1wrapper.vm.$refs.editForm.validate is not a function
I am unable to figure out the problem.. please Help me.
"#vue/cli-plugin-babel": "^3.11.0", "#vue/cli-plugin-eslint": "^3.11.0", "#vue/cli-plugin-pwa": "^3.11.0", "#vue/cli-plugin-unit-jest": "^3.11.0", "#vue/cli-service": "^3.11.0", "#vue/eslint-config-prettier": "^5.0.0", "#vue/test-utils": "^1.0.0-beta.29", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.0.1", "babel-jest": "^23.6.0"
==== EditProperty.vue======
<v-form ref="editForm" lazy-validation>
<v-flex>
<v-text-field label="Label Text" v-model="labelName" :rules="[v => !!v || 'Label Text is required']"
/>
</v-flex>
</v-form>
<script>
export default {
data() {
return {
labelName: ''
}
},
methods: {
save() {
if (this.$refs.editForm.validate()) {
this.$emit('updateLable', this.labelName)
}
}
}
}
</script>
======EditProperty.spec.js =====
import { shallowMount, createLocalVue } from '#vue/test-utils'
import EditProperty from '#/components/EditProperty.vue'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuetify)
let wrapper
describe('EditProperty.vue', () => {
beforeEach(() => {
wrapper = shallowMount(EditProperty, {
localVue,
data() {
return {
labelName: 'Username'
}
}
})
})
it('should save called correctly', () => {
wrapper.vm.save()
})
})
expected => test should be pass
getting => wrapper.vm.$refs.editForm.validate is not a function
When I write test case for form submit, i m getting issue with 1wrapper.vm.$refs.editForm.validate is not a function
I am unable to figure out the problem.. please Help me.
shallowMount does not render the child components. I.E. in your case, v-form won't be rendered in the test. In fact if you call html from your wrapper, you will see a HTML comment in place of the <edit-form>.
The rationale behind that vue test utils feature is that, when you're unit testing a component, you test only the logic of such component in isolation, and don't rely on code from other modules.
Now you could manually pass an object as stub and provide any test double to allow the validate() call, via the stubs option:
import { shallowMount, createLocalVue } from '#vue/test-utils'
import EditProperty from '#/components/EditProperty.vue'
import Vuetify from 'vuetify'
const localVue = createLocalVue()
localVue.use(Vuetify)
let wrapper
describe('EditProperty.vue', () => {
beforeEach(() => {
const EditFormStub = {
render: () => {},
methods: {
validate: () => true,
}
};
wrapper = shallowMount(EditProperty, {
localVue,
stubs: {
'edit-form': EditFormStub,
}
data() {
return {
labelName: 'Username'
}
}
})
})
it('should save called correctly', () => {
wrapper.vm.save()
})
})
So we are passing a fake editForm as a stub, with a fake validate() method which always returns true.
Then you can test your component code. For instance, you could test that your label is emitted as updateLabel (in your original snippet it was 'updateLable', be wary):
it('should save called correctly', () => {
wrapper.vm.save();
expect(wrapper.emitted('updateLabel')[0][0]).toBe(whatever the label should be)
})
if you got in a situation where you don't need to stub. You could easily do this:
import { nextTick } from 'vue'
it('test', async () => {
wrapper.vm.$refs.editForm.validate = jest.fn()
await nextTick()
})
What is the corect way to import vue packages in laravel 5.6? It comes with vue and bootstrap preinstall. I see they are all compile in app.js from public directory but I can figure out how to import https://github.com/moreta/vue-search-select and use it. After I tried to import it on my own:
Error:
ncaught TypeError: Vue.component is not a function
At line:
Vue.component('search-user', __webpack_require__(42));
Until now I tried this:
assets/js/bootstrap.js:
import { BasicSelect } from 'vue-search-select';
window.BasicSelect = BasicSelect;
assets/js/app.js:
require('./bootstrap');
window.Vue = require('vue');
window.Vue = require('vue-search-select');
Vue.component('search-user', require('./components/SearchUser.vue'));
var app = new Vue({
el: '#app'
})
components
<template>
<basic-select :options="options"
:selected-option="item"
placeholder="select item"
#select="onSelect">
</basic-select>
</template>
<script>
export default {
data() {
return {
keywords: null,
options: []
};
},
watch: {
keywords(after, before) {
if (this.keywords.length > 0)
this.fetch();
}
},
methods: {
fetch() {
axios.get('/api/search', {params: {keywords: this.keywords}})
.then(response => this.options = response.data)
.catch(error => {
});
},
onSelect (item) {
this.item = item
},
reset () {
this.item = {}
},
selectOption () {
// select option from parent component
this.item = this.options[0]
},
components: {
BasicSelect
}
}
}
</script>
I ran: npm install and npm run watch:
"devDependencies": {
"ajv": "^6.0.0",
"bootstrap": "^4.0.0",
"cross-env": "^5.1",
"laravel-mix": "^2.0",
"lodash": "^4.17.4",
"popper.js": "^1.12",
"uikit": "^3.0.0-beta.35",
"vue": "^2.5.7",
"vue-search-select": "^2.5.0"
},
"dependencies": {
"axios": "^0.17.1",
"jquery": "^3.3.1"
}
I think that the simple will do
window.Vue = require('vue');
require('vue-search-select');
Then in your components you can import what you need on top:
import { BasicSelect } from 'vue-search-select';
export default {
data() {
return {
keywords: null,
options: [],
item: null
};
},
...
One missing detail that tricked me with this one, you need to register the components like this, otherwise it won't be found:
components: {
ModelSelect,
BasicSelect
},