Testcafe Vue Selectors can't grab Vue component - javascript

I'm using Testcafe Vue Selectors to perform e2e testing on my Vue application but it looks like I can't grab any of my components:
1) An error occurred in getVue code:
TypeError: Cannot read property '__vue__' of undefined
This is a sample test I have created:
import VueSelector from "testcafe-vue-selectors";
import { Selector } from 'testcafe';
fixture `Getting Started`
.page `http://localhost:8081/`;
test('test totalValue format', async t => {
const totalValue = VueSelector("total-value");
await t
.click("#total-value-toggle-format")
.expect(totalValue.getVue(({ props }) => props.formatProperty)).eql(null)
});
The structure of my components tree is the following:
Root
|___App
|___Hello
|___TotalValue
And I import the component like this:
"total-value": TotalValue,
Why is this not working?
EDIT: this is the page where I test the component
<template>
<div class="hello">
<div class="component-wrapper">
<total-value
:value="totalValueValue"
:formatProperty="computedFormatNumber">
</total-value>
</div>
</div>
</template>
<script>
import TotalValue from "../../core/TotalValue";
export default {
name: "hello",
components: {
"total-value": TotalValue,
},
data() {
return {
totalValueValue: 1000000,
formatNumber: true,
formatFunction: Assets.formatNumber,
};
},
computed: {
computedFormatNumber() {
return this.formatNumber ? ["nl", "0,0 a"] : [];
},
},
};

Just a follow-up, we have fixed the issue described in this thread:
Support component loaded via vue-loader

Related

How to bind TipTap to parent v-model in Vue 3 script setup?

I'm trying to use tiptap as a child component and pass its content to the parent's v-model but tiptap's documentation only seems to provide info on how to do this without script setup, which uses a different API.
This is my parent component:
<template>
<cms-custom-editor v-model:modelValue="state.content"/>
<p>{{state.content}}</p>
</template>
<script setup>
import CmsCustomEditor from '../../components/backend/cms-custom-editor.vue'
import {reactive} from "vue";
const state = reactive({
content: '<p>A Vue.js wrapper component for tiptap to use <code>v-model</code>.</p>',
})
</script>
and this the child component with tiptap:
<template>
<div id="cms-custom-editor" class="cms-custom-editor">
<editor-content :editor="editor"/>
</div>
</template>
<script setup>
import {useEditor, EditorContent} from '#tiptap/vue-3'
import StarterKit from '#tiptap/starter-kit'
const props = defineProps({
modelValue: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const editor = useEditor({
extensions: [StarterKit],
content: props.modelValue,
onUpdate: () => {
emit('update:modelValue', editor.getHTML())
}
})
</script>
As soon as I type something into the editor field, this code line fails:
emit('update:modelValue', editor.getHTML())
and throws this error:
Uncaught TypeError: editor.getHTML is not a function
at Editor2.onUpdate (cms-custom-editor.vue?t=1654253729389:28:42)
at chunk-RCTGLYYN.js?v=89d16c61:11965:48
at Array.forEach (<anonymous>)
at Editor2.emit (chunk-RCTGLYYN.js?v=89d16c61:11965:17)
at Editor2.dispatchTransaction (chunk-RCTGLYYN.js?v=89d16c61:12252:10)
at EditorView.dispatch (chunk-RCTGLYYN.js?v=89d16c61:9138:27)
at readDOMChange (chunk-RCTGLYYN.js?v=89d16c61:8813:8)
at DOMObserver.handleDOMChange (chunk-RCTGLYYN.js?v=89d16c61:8924:77)
at DOMObserver.flush (chunk-RCTGLYYN.js?v=89d16c61:8575:12)
at DOMObserver.observer (chunk-RCTGLYYN.js?v=89d16c61:8455:14)
I've used the approach from the docs (chapter 5. v-model), which like I said, is not designed for script setup.
Man, the docs are confusing. They mix up standard composition api and script setup. Anyway this is how it works:
const editor = useEditor({
extensions: [StarterKit],
content: props.modelValue,
onUpdate: ({editor}) => {
emit('update:modelValue', editor.getHTML())
}
})

Vue.js - Component is missing template or render function

In Vue 3, I created the following Home component, 2 other components (Foo and Bar), and passed it to vue-router as shown below. The Home component is created using Vue's component function, whereas Foo and Bar components are created using plain objects.
The error that I get:
Component is missing template or render function.
Here, the Home component is causing the problem. Can't we pass the result of component() to a route object for vue-router?
<div id="app">
<ul>
<li><router-link to="/">Home</router-link></li>
<li><router-link to="/foo">Foo</router-link></li>
<li><router-link to="/bar">Bar</router-link></li>
</ul>
<home></home>
<router-view></router-view>
</div>
<script>
const { createRouter, createWebHistory, createWebHashHistory } = VueRouter
const { createApp } = Vue
const app = createApp({})
var Home = app.component('home', {
template: '<div>home</div>',
})
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
],
})
app.use(router)
app.mount('#app')
</script>
See the problem in codesandbox.
FOR vue-cli vue 3
render function missed in createApp.
When setting your app by using createApp function you have to include the render function that include App.
in main.js
update to :
FIRST
change the second line in javascript from:-
const { createApp } = Vue
to the following lines:
import { createApp,h } from 'vue'
import App from './App.vue'
SECOND
Change from :-
const app = createApp({})
to:
const app = createApp({
render: ()=>h(App)
});
app.mount("#app")
When app.component(...) is provided a definition object (the 2nd argument), it returns the application instance (in order to allow chaining calls). To get the component definition, omit the definition object and provide only the name:
app.component('home', { /* definition */ })
const Home = app.component('home')
const router = createRouter({
routes: [
{ path: '/', component: Home },
//...
]
})
demo
Make sure you have added <router-view></router-view> in your #app container.
The solution was simple on my side, I created a component that was empty, after filling in the template and a simple text HTML code, it was fixed.
The solution for me was to upgrade node module vue-loader to version 16.8.1.
I had this issue too. It's a timing issue. I added a v-if to create the component when the page is mounted. That fixed it for me.
<review-info
v-if="initDone"
:review-info="reviewInfo"
/>
// script
onMounted(() => {
initDone = true
})
I was extending a Quasar component in Vue 3, and ran into this problem. I solved it by adding the setup: QInput.setup line last in the component options.
<script>
import { defineComponent } from 'vue'
import { QInput } from 'quasar'
const { props } = QInput
export default defineComponent({
props: {
...props,
outlined: {
type: Boolean,
default: true
},
dense: {
type: Boolean,
default: true
},
uppercase: {
type: Boolean,
default: false
}
},
watch: {
modelValue (v) {
this.uppercase && this.$emit('update:modelValue', v.toUpperCase())
}
},
setup: QInput.setup
})
</script>

Vue js import components dynamically

I have the following parent component which has to render a list of dynamic children components:
<template>
<div>
<div v-for="(componentName, index) in supportedComponents" :key="index">
<component v-bind:is="componentName"></component>
</div>
</div>
</template>
<script>
const Component1 = () => import("/components/Component1.vue");
const Component2 = () => import("/components/Component2.vue");
export default {
name: "parentComponent",
components: {
Component1,
Component2
},
props: {
supportedComponents: {
type: Array,
required: true
}
}
};
</script>
The supportedComponents property is a list of component names which I want to render in the parent conponent.
In order to use the children components in the parent I have to import them and register them.
But the only way to do this is to hard code the import paths of the components:
const Component1 = () => import("/components/Component1.vue");
const Component2 = () => import("/components/Component2.vue");
And then register them like this:
components: {
Component1,
Component2
}
I want to keep my parentComponent as generic as possible. This means I have to find a way to avoid hard coded components paths on import statements and registering. I want to inject into the parentComponent what children components it should import and render.
Is this possible in Vue? If yes, then how?
You can load the components inside the created lifecycle and register them according to your array property:
<template>
<div>
<div v-for="(componentName, index) in supportedComponents" :key="index">
<component :is="componentName"></component>
</div>
</div>
</template>
<script>
export default {
name: "parentComponent",
components: {},
props: {
supportedComponents: {
type: Array,
required: true
}
},
created () {
for(let c=0; c<this.supportedComponents.length; c++) {
let componentName = this.supportedComponents[c];
this.$options.components[componentName] = () => import('./' + componentName + '.vue');
}
}
};
</script>
Works pretty well
Here's a working code, just make sure you have some string inside your dynamic import otherwise you'll get "module not found"
<component :is="current" />
export default {  data () {
    return {
      componentToDisplay: null
    }
  },
  computed: {
    current () {
      if (this.componentToDisplay) {
        return () => import('#/components/notices/' + this.componentToDisplay)
      }
      return () => import('#/components/notices/LoadingNotice.vue')
    }
  },
  mounted () {
    this.componentToDisplay = 'Notice' + this.$route.query.id + '.vue'
  }
}
Resolving dynamic webpack import() at runtime
You can dynamically set the path of your import() function to load different components depending on component state.
<template>
<component :is="myComponent" />
</template>
<script>
export default {
props: {
component: String,
},
data() {
return {
myComponent: '',
};
},
computed: {
loader() {
return () => import(`../components/${this.component}`);
},
},
created() {
this.loader().then(res => {
// components can be defined as a function that returns a promise;
this.myComponent = () => this.loader();
},
},
}
</script>
Note: JavaScript is compiled by your browser right before it runs. This has nothing to do with how webpack imports are resolved.
I think we need some plugin that can have code and every time it should load automatically. This solution is working for me.
import { App, defineAsyncComponent } from 'vue'
const componentList = ['Button', 'Card']
export const registerComponents = async (app: App): void => {
// import.meta.globEager('../components/Base/*.vue')
componentList.forEach(async (component) => {
const asyncComponent = defineAsyncComponent(
() => import(`../components/Base/${component}.vue`)
)
app.component(component, asyncComponent)
})
}
you can also try glob that also work pretty well but I have checked it for this solution but check this out worth reading
Dynamic import
[Update]
I tried same with import.meta.globEage and it works only issue its little bit lazy loaded you may feel it loading slow but isn't noticeable much.
import { App, defineAsyncComponent } from 'vue'
export const registerComponents = async (app: App): void => {
Object.keys(import.meta.globEager('../components/Base/*.vue')).forEach(
async (component) => {
const asyncComponent = defineAsyncComponent(
() => import(/* #vite-ignore */ component)
)
app.component(
(component && component.split('/').pop()?.split('.')[0]) || '',asyncComponent
)
})
}

Find a data-attribute in test with Vue.js

Trying to figure out with shallowMount how I can find a data attribute and check if that attribute is there in the Vue template:
myVue.vue
<template>
<div>
<div data-test="apple"></div>
</div>
</template>
<script>
export default {
name: "fruit-main",
extends: registerStore(),
components: {
},
props: {
},
data() {
},
mounted() {
},
computed: {
},
methods: {
},
watch: {
}
}
</script>
spec.js (using Vue Test Utils and mocha):
it('shows an apple', () => {
let fakeStore = new Vuex.Store({ state: {} });
const apple = shallowMount(myVue, {fakeStore}),
expect(apple).to.have.length(1);
});
OR maybe this way???
myVue.vue
<template>
<div>
<div apple></div>
</div>
</template>
spec.js
it('shows an apple', () => {
const vue = shallowMount(myVue),
apple = vue.element.getAttribute('apple')
expect(apple).to.exist
// or expect(apple).to.have.length(1); - which is how you'd do it with enzyme but I'm not sure yet what getAttribute returns yet
});
but I'm not sure how to go about it, obviously this is not right. I'm an enzyme and React guy trying to figure out how to do the same kinds of tests with Vue basically.
Note: I use shallowMount solely for TDD I am not interested in mount() and I'm not about to debate that right now for this post. I'm just asking help for shallowMount here and how to find a a data-attribute for my tests to assert on.
Use wrapper attributes() assuming the dataset is in the wrapper itself.
ie.
import FruitMain from '#components/FruitMain.vue'
import { shallowMount } from '#vue/test-utils'
describe('#components/FruitMain.vue', () => {
it('shows an apple', () => {
// We are assuming the FruitMain component receives a 'fruit' prop.
const wrapper = shallowMount(FruitMain, { propsData: { fruit: 'apple' } })
// Has the correct starting data fruit
expect(wrapper.attributes()['data-fruit']).toBe('apple')
// Has the correct fruit value when props change
// wrapper.setProps({ fruit: 'banana' })
// expect(wrapper.attributes()['data-fruit']).toBe('banana')
})
})
To search for a child that has a dataset use contains().
import FruitMain from '#components/FruitMain.vue'
import { shallowMount } from '#vue/test-utils'
describe('#components/FruitMain.vue', () => {
it('shows an apple', () => {
// We are assuming the FruitMain component receives a 'fruit' prop.
const wrapper = shallowMount(FruitMain, { propsData: { fruit: 'apple' } })
// Has a data fruit
expect(wrapper.contains('[data-fruit]')).toBe(true) // or [data-fruit="apple"]
})
})
CodeSandbox Demo

Property Undefined on Vue Component

Getting an error that project is undefined when trying to render out some text using {{project.projectSummary}} in my ProjectContainer component. I'm guessing when the component is initialized project is undefined because it shows up in VueTools fine on my component.
So next, I try to set v-if="resource.project" on the parent component
<template v-if="resource.project">
<div class="resourceContainer">
<resource-card :resource="resource" :showResourceBtn="showResourceBtn"></resource-card>
<project-container :project="resource.project"></project-container>
</div>
</template>
<script>
import { firebaseAuth, database } from '../../database/database'
import Vue from 'vue'
import VueFire from 'vuefire'
import ResourceCard from '../ResourceCard/ResourceCard'
import ProjectContainer from './Project'
Vue.use(VueFire);
export default {
name: 'Resource',
components: {
ResourceCard,
ProjectContainer
},
data () {
return {
showResourceBtn: true
}
},
computed: {
fbUser () {
return firebaseAuth.currentUser
}
},
created () {
// Load resource
this.$bindAsObject('resource', database.ref('/resources/' + this.$route.params.resourceKey));
}
}
</script>
But this isn't working either. Gives me a warning in the console that Invalid prop: type check failed for prop "project". Expected Object, got Undefined.
Any thoughts? Thanks

Categories

Resources