Correctly testing vee-validate validated form submit with Jest - vue.js

I am trying to submit a form that uses vee-validate and test if the form calls the underlying store with Jest.
Here is my code:
Form:
<template>
<div class="flex flex-col justify-center h-screen bg-site-100">
<!-- Login body -->
<div class="container">
<div class="mx-auto w-4/12 p-7 bg-white">
<!-- Form -->
<Form id="loginForm" #submit="login" :validation-schema="schema" v-slot="{ errors }">
<div class="mt-4">
<div>
<text-box
:type="'email'"
:id="'email'"
:label="'Your Email'"
v-model="email"
:place-holder="'Email'"
:required="true"
:error="errors.email"
/>
</div>
<div>
<text-box
:type="'password'"
:id="'password'"
:label="'Parool'"
v-model="password"
:place-holder="'Password'"
:required="true"
:error="errors.password"
/>
</div>
<!-- Submit -->
<Button
type="submit"
id="loginButton"
:disabled="Object.keys(errors).length > 0"
class="text-white bg-site-600 w-full hover:bg-site-700 focus:ring-4 focus:ring-site-300 font-medium rounded-md text-sm px-5 py-2.5 mr-2 mb-2 focus:outline-none"
>
Log In
</Button>
</div>
</Form>
</div>
</div>
</div>
</template>
<script lang="ts">
import * as Yup from "yup";
import { Form } from "vee-validate";
import { defineComponent } from "vue";
import Button from "../core/Button.vue";
import TextBox from "../core/TextBox.vue";
import { mapActions, mapStores } from "pinia";
import { useAuthStore } from "../../store/auth";
import LoginDataType from "../../types/login_data";
export default defineComponent({
name: "Login",
components: { TextBox, Form, Button },
computed: { ...mapStores(useAuthStore) },
data() {
return {
email: "",
password: "",
schema: Yup.object().shape({
email: Yup.string().required("Email is required").email("Email is invalid"),
password: Yup.string().required("Password is required"),
}),
};
},
methods: {
async login() {
console.log("Logged in mock");
let data: LoginDataType = {
email: this.email,
password: this.password,
};
await this.authStore.login(data);
},
},
});
</script>
Store:
import { defineStore } from "pinia";
export const useAuthStore = defineStore("auth", {
state: () => ({
}),
getters: {
},
actions: {
async login(data: LoginDataType) {
// do something
},
}
})
Test:
it('logs in correctly when right username and password sent to API', async () => {
const store = useAuthStore();
jest.spyOn(store, 'login');
const wrapper = mount(Login, {
stubs: ['router-link']
});
const email = wrapper.find('input[id="email"]');
await email.setValue('testEmail#gmail.com');
// Check if model is set
expect(wrapper.vm.email).toBe(testEmail);
const password = wrapper.find('input[id="password"');
await password.setValue('testPw');
// Check if model is set
expect(wrapper.vm.password).toBe(testPw);
// Check form exists
const loginForm = wrapper.find('#loginForm');
expect(loginForm.exists()).toBe(true);
await loginForm.trigger('submit');
// Check if store method has been called
expect(store.login).toHaveBeenCalled();
expect(store.login).toHaveBeenCalledWith({
email: 'testEmail#gmail.com',
password: 'testPw'
})
});
The test fails at expect(store.login).toHaveBeenCalled(). Implying the form doesn't get submitted. The test works just fine when I replace the vee-validate component Form with a regular HTML form tag.
What might be causing this behaviour any help is highly appreciated? :)

Related

How to pass vuelidate object with props for jest test?

I have several components inside each of them have fields that are validated with vuelidate. All field validation rules are described in the parent component wrapper in the child components, I pass the object :v="$v", everything works. But in the test, when mounting, I get an error, and as I understand it, because when mounting, I do not pass this object, tell me how to do it right?
parent wrapper
<template>
<VModal>
<template v-slot:content>
<div class="layer">
<institutes-info
:v="$v"
ref="info-component"
/>
</div>
</template>
</VModal>
</template>
export default {
name: "WrapModalInstitutes",
validations: {
si: {
name: {
required,
maxLength: maxLength(256),
},
},
},
}
child component
<template>
<form action="" id="form-info" #submit.prevent="uploadInfo($event, si.id)">
<p class="sub-text">{{ $t("vnz.info") }}</p>
<div class="institut-info">
<div class="input-group" :class="{ 'form-group--error': v.si.name.$error, 'form-group--success': !v.si.name.$invalid }">
<label for="name-institut">{{ $t("vnz.nameVNZ") }}</label>
<div class="input-container">
<input id="name-institut" name="title" type="text" :placeholder="$t('vnz.nameVNZ')" v-model.trim="name" #keydown="trimSpecialCharacters($event)" #input="trimSpecialCharacters($event, 'name')">
<div class="error" v-if="!v.si.name.required && v.si.name.$dirty">{{ $t("vnz.notEmpty") }}</div>
</div>
</div>
</div>
<button v-if="edit" type="submit" class="btn-primary edit" :disabled="submitStatus === 'PENDING'">{{ $t("vnz.save") }}</button>
</form>
</template>
export default {
name: "InstitutesInfoModal",
props: ['v'],
}
test file
import Vuex from "vuex"
import {mount, createLocalVue, config} from "#vue/test-utils"
import InstitutesInfo from '../../assets/src/components/InstitutesInfo'
import Vuelidate from 'vuelidate'
import VueTheMask from 'vue-the-mask'
import translations from "../../assets/src/common-resources/translations/translations"
import fetchMock from 'jest-fetch-mock'
const locale = "uk";
config.mocks["$t"] = msg => translations[locale][msg]
fetchMock.enableMocks()
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Vuelidate)
localVue.use(VueTheMask)
describe('testing institute-info component', () => {
let store;
let mutations;
let actions;
beforeEach(() => {
mutations = {
'institutes/EDIT_SI_OBJ': jest.fn(),
'institutes/SUBMIT_STATUS': jest.fn()
}
actions = {
'institutes/UPLOAD_INFO': jest.fn()
}
store = new Vuex.Store({
modules: {
institutes: {
state: {
si: {
name: null,
},
submitStatus: null
},
mutations,
actions
}
}
})
})
test('some test"',() => {
const wrapper = mount(InstitutesInfo, {
store, localVue
})
})
})
[Vue warn]: Error in render: "TypeError: Cannot read property 'si' of undefined"

How to use composition API with custom field in Vee Validate 4 correctly

After reading documentation of Vee Validate 4 about using composition api with custom inputs, do I understand correctly that hook useField a have to call only inside input component(in my example is VInput.vue)? Is any way that i can use hook functionality in parent component? The VInput is used for another functionality that don't need validation so it will be extra functionality add useForm for global component in out project
For example I have List.vue
<template>
<form class="shadow-lg p-3 mb-5 bg-white rounded" #submit.prevent="submitPostForm">
<VFormGroup label="Title" :error="titleError">
<VInput v-model="postTitle" type="text" name="title" />
</VFormGroup>
<VFormGroup label="Body" :error="bodyError">
<VInput v-model="postBody" type="text" name="body" />
</VFormGroup>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
<script>
import { ref, onMounted } from 'vue';
import { Form, useForm, useField } from 'vee-validate';
import * as Yup from 'yup';
export default {
components: { Form },
setup() {
const schema = Yup.object().shape({
title: Yup.string().required(),
body: Yup.string().min(6).required(),
});
//hooks
const { handleSubmit } = useForm({ validationSchema: schema });
// No need to define rules for fields because of schema
const { value: postTitle, errorMessage: titleError } = useField('title');
const { value: postBody, errorMessage: bodyError } = useField('body');
//methods
const submitPostForm = handleSubmit(() => {
addPost({ title: postTitle, body: postBody });
});
return { schema, postTitle, postBody, titleError, bodyError, submitPostForm };
},
};
</script>
The problem that input error I have to show only in VFormGroup so how I can manage this form?
I am not sure if understood your question correctly.
But in your case you only have 1 issue, you destructure errorMessage and value twice in one file, which isn't working.
you could change your useField's like this:
const { errorMessage: titleError, value: titleValue } = useField('title', Yup.string().required());
const { errorMessage: bodyError, value: bodyValue } = useField('body', Yup.string().required().min(8));
In your template you then want to use titleError and bodyError in your VFormGroup.
In your VInput you want to use titleValue and bodyValue for v-model
because you initialise your title and body in your setup() I guess that those do not have any predefiend values. If that would be the case you might want to take a look at the Options for useField() where you can have as a thridParam as an Object with e.g. initialValue as key which then would be your post.value.title. But for your use case I wouldn't recommend this.
to answer the Code question from the comments:
<template>
<form class="shadow-lg p-3 mb-5 bg-white rounded" #submit="handleSubmit">
<VFormGroup label="Title" :error="titleError">
<VInput v-model="titleValue" type="text" name="title" />
</VFormGroup>
<VFormGroup label="Body" :error="bodyError">
<VInput v-model="bodyValue" type="text" name="body" />
</VFormGroup>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
<script>
import { ref } from 'vue';
import { Form, useForm, useField } from 'vee-validate';
import * as Yup from 'yup';
export default {
components: { Form },
setup() {
const title = ref('');
const body = ref('');
//hooks
const { handleSubmit } = useForm();
// No need to define rules for fields because of schema
const { errorMessage: titleError, value: titleValue } = useField('title', Yup.string().required());
const { errorMessage: bodyError, value: bodyValue } = useField('body', Yup.string().required().min(8));
return { titleError, bodyError, titleValue, bodyValue, handleSubmit };
},
};
</script>

Issue with nuxt/auth

For auth I do use nuxt-auth, when the login is successful, I want to redirect to the main page using this.$router.push('/'), then I get a response like blank page with the following message
2021
,
// for login
<template>
<div class="limiter">
<div
class="container-login100"
:style="{
backgroundImage: 'url(' + require(`#/assets/login/images/bg-01.jpg`) + ')',
}"
>
<div class="wrap-login100 p-l-110 p-r-110 p-t-62 p-b-33">
<form class="login100-form validate-form flex-sb flex-w">
<span class="login100-form-title p-b-53"> Login Admin </span>
<a href="facebook.com" class="btn-face m-b-20">
<i class="fa fa-facebook-official"></i>
Facebook
</a>
<a href="google.com" class="btn-google m-b-20">
<img :src="require(`#/assets/login/images/icons/icon-google.png`)" alt="GOOGLE" />
Google
</a>
<div class="p-t-31 p-b-9">
<span class="txt1"> Email </span>
</div>
<div class="wrap-input100 validate-input" data-validate="Email is required">
<input v-model="auth.email" class="input100" type="email" name="email" />
<span class="focus-input100"></span>
</div>
<div class="p-t-13 p-b-9">
<span class="txt1"> Password </span>
Forgot?
</div>
<div class="wrap-input100 validate-input" data-validate="Password is required">
<input v-model="auth.password" class="input100" type="password" name="pass" />
<span class="focus-input100"></span>
</div>
<div class="container-login100-form-btn m-t-17">
Login
</div>
<div class="w-full text-center p-t-55">
<span class="txt2"> Not a member? </span>
Register now
</div>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
auth: false,
data() {
return {
auth: {
email: null,
password: null,
},
}
},
mounted() {
if (this.$auth.loggedIn) {
this.$router.push('/')
}
},
methods: {
async submit() {
try {
const response = await this.$auth.loginWith('local', { data: this.auth })
this.$router.push('/')
} catch (err) {
console.log(err)
}
},
},
}
</script>
store vuex index.js
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
}}
}
layout default.vue
<template>
<div class="wrapper">
<Sidebar v-if="isAuthenticated" />
<div :class="isAuthenticated ? 'main-panel' : ''">
<Navbar v-if="isAuthenticated" />
<Nuxt />
<Footer v-if="isAuthenticated" />
</div>
</div>
</template>
<script>
import Sidebar from '#/components/layout/Sidebar.vue'
import Navbar from '#/components/layout/Navbar.vue'
import Footer from '#/components/layout/Footer.vue'
import { mapGetters } from 'vuex'
export default {
components: { Sidebar, Navbar, Footer },
computed: {
...mapGetters(['isAuthenticated', 'loggedInUser']),
},
}
</script>
// auth nuxt config
auth : {
strategies: {
local: {
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
autoFetch: true
},
endpoints: {
login: { url: '/sign/login', method: 'post' },
logout: { url: '/sign/logout', method: 'post' },
user: { url: '/sign/user-login', method: 'get' }
}
}
}
}
base index ('/')
<template>
<div class="container">
<div>
<Logo />
<h1 class="title">Learn Nuxt</h1>
<div class="links">
<a href="https://nuxtjs.org/" target="_blank" rel="noopener noreferrer" class="button--green">
Documentation
</a>
<a
href="https://github.com/nuxt/nuxt.js"
target="_blank"
rel="noopener noreferrer"
class="button--grey"
>
GitHub
</a>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['isAuthenticated', 'loggedInUser']),
},
}
</script>
In your vuex store, the state parameter in your getter only has access to local state. You can't access the auth state the way you tried.
In a vuex module, a getter gets 4 arguments, namely local state, local getters, root state and root getters. So if you would rewrite your getters like this it would probably work:
export const getters = {
isAuthenticated(state, getters, rootState) {
return rootState.auth.loggedIn
},
loggedInUser(state, getters, rootState) {
return rootState.auth.user
}}
}
But I still think it is a bit redundant doing it like that. I would replace isAuthenticated with this.$auth.loggedIn in your default layout. The nuxt-auth module globally injects the $auth instance, meaning that you can access it anywhere using this.$auth.
I had same problem after authorizing user and redirect user to the home page.
After many tries and doing many works, the right config of auth in nuxt.config.js seemed like this:
auth: {
strategies: {
local: {
scheme: 'refresh',
token: {
property: 'access_token',
tokenType: false,
maxAge: 60 * 60
},
refreshToken: {
property: 'refresh_token',
data: '',
maxAge: 60 * 60
},
endpoints: {
login: {
url: 'url/of/token',
method: 'urlMethod'
},
refresh: {
url: 'url/of/refreshToken',
method: 'urlMethod'
},
logout: false,
user: false
}
}
},
cookie: false,
redirect: {
login: '/login/page',
logout: '/login/page',
callback: false,
home: '/home/page'
}
},
Note that I didn't have any refreshToken property, but I should set it as empty string in config to be able to work with nuxt/auth!
Hope I could help

Get URL query parameters in Vue 3 on Component

i am new to Vue JS, i must say i really love this platform. I started using it just 3 days back. I am just trying to get the URL query parameter and i am using vue-router as well. Here is how i have it:
http://localhost:8001/login?id=1
Here is how my controller look like.
<template>
<section class="contact-info-area">
<div class="container">
<div class="row">
<div class="col-lg-12">
<section class="write-massage-area">
<div class="mb-5"></div>
<div class="row justify-content-center">
<div class="col-lg-5">
<div class="write-massage-content">
<div class="write-massage-title text-center">
<h3 class="title">Login {{ $route.query.id }}</h3> <!-- THIS IS WORKING -->
</div>
<div class="write-massage-input">
<form #submit.prevent="onSubmitForm">
<div class="row">
<div class="col-lg-12">
<div class="input-box mt-10">
<input type="text" placeholder="Email" v-model="form['email']">
<ErrorMessage :validationStatus="v$.form.email"></ErrorMessage>
</div>
</div>
<div class="col-lg-12">
<div class="input-box mt-10">
<input type="text" placeholder="Password" v-model="form['password']">
</div>
</div>
<div class="col-lg-12">
<div class="input-box mt-10 text-center">
<button type="submit" class="main-btn main-btn-2">Login</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="mb-5"></div>
</section>
</div>
</div>
</div>
</section>
</template>
<script>
import { ref } from 'vue'
import useVuelidate from '#vuelidate/core'
import { required, minLength, email } from '#vuelidate/validators'
import ErrorMessage from '../components/ErrorMessage'
export default {
name: "login",
components: {
ErrorMessage
},
created(){
},
setup(){
const form = ref({})
const rules = {
form: {
email: {
required,
email
},
password: {
required,
minLength : minLength(5)
}
}
}
const v$ = useVuelidate(rules, { form })
function onSubmitForm(){
console.log(this.$route.query.id) **<!-- THIS IS NOT WORKING -->**
v$.value.$touch()
console.log(form.value)
}
return { form, onSubmitForm, v$}
}
}
</script>
here on the above code. On button submit i am going to a function called onSubmitForm, there i am using console.log(this.$route.query.id) but this is giving a below error :
Uncaught TypeError: Cannot read property 'query' of undefined
at Proxy.onSubmitForm (Login.vue?a55b:84)
Why this is happening? I am seeing that in the vue document as well, they mentioned in the same way. What i am missing in this?
Thank you!
You can call useRoute to access the query params...
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.query)
</script>
If you use parameters and your endpoint looks something like this:
{
path: '/inspect_detail/:id',
name: 'inspect_detail',
component: function() {
return import ( '../views/Inspect_detail.vue')
},
params: true
},
and you are routing like this:
<router-link :to="{ name: 'inspect_detail', params: { id: akey }}">...</router-link>
then you can pickup the values like this:
<script>
import { useRoute } from 'vue-router';
export default {
setup(){
const route = useRoute()
console.log( route.params );
}
}
</script>
Bit late but if you want query params to work on page refresh you have to wait for the router to get ready as its asynchronous. The router wont be ready if its a page refresh. The router will be ready if its navigation from another page in that case the query params will be available from the begining.
<script setup>
import { onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
//just to demonstrate
console.log(route.query)
// If it is a page refresh it will print null.
// The router wont be ready if its a page refresh. In that case the query wont be available.
// The router will be ready if its navigation from another page in that case the query params will be available
onMounted(() => {
getUrlQueryParams()
});
getUrlQueryParams = async () => {
//router is async so we wait for it to be ready
await router.isReady()
//once its ready we can access the query params
console.log(route.query)
});
</script>
I hadn't done frontend in a couple of months and had to refresh on Vue 3 and route query properties, and this question came up first.
Now the memory jog has kicked in, I believe the correct way is to pass the props from the router to the component as shown in the examples here https://router.vuejs.org/guide/essentials/passing-props.html
Essentially, call your route
{
path: "/login",
name: "Login",
props: route => ({ id: route.query.id }),
component: () => import(/* webpackChunkName: "login" */ "../views/Login.vue"),
},
to be able to access the id field.
Alternately, you can sent the whole lot with props: route => ({ query: route.query })
Pick it up as a prop in your view/component
export default {
name: "login",
components: {
ErrorMessage
},
created(){
},
props: {
id: {
type: String,
default: "",
}
}
setup(props){
const form = ref({})
const rules = {
form: {
email: {
required,
email
},
password: {
required,
minLength : minLength(5)
}
}
}
const v$ = useVuelidate(rules, { form })
function onSubmitForm(){
console.log(props.id)
v$.value.$touch()
console.log(form.value)
}
return { form, onSubmitForm, v$}
}
}

VueJS display dynamic modal component

I have posts and replys s.t. replies belong to posts via the attribute reply.posts_id.
I am attempting to show the reply form as a modal for the user to enter a reply. However, I want to create a generic Modal component that I can use everywhere with content that is specified in another component built for a specific context.
Reply to post is the first place I woul like this to work.
Currently, the Vuex correctly returns Modal visible:true when the reply button is clicked, but the modal does not render and I get the error message showing that the Modal component is not found:
Unknown custom element: <ModalReplyForm> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I am using vuex to manage the visibility of the modal. Here are the relevant files:
store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
...
Vue.use(Vuex)
export default new Vuex.Store({
state: {
status: '',
...
modalVisible: false,
modalComponent: null
},
mutations: {
...
showModal(state, componentName) {
console.log('showing the modal')
state.modalVisible = true;
state.modalComponent = componentName;
},
hideModal(state) {
console.log('hiding the modal')
state.modalVisible = false;
}
},
actions: {
...
}
},
getters: {
isAuthenticated: state => !!state.user,
authStatus: state => state.status,
user: state => state.user,
token: state => state.token,
posts: state => {
return state.posts;
}
...
}
})
App.vue
<template>
<div id="app">
<app-modal></app-modal>
<NavigationBar />
<div class="container mt-20">
<router-view />
</div>
<vue-snotify></vue-snotify>
</div>
</template>
<script>
import AppModal from '#/components/global/AppModal';
import NavigationBar from '#/components/layout/NavigationBar'
export default {
name: "App",
components: {
AppModal,
NavigationBar
}
};
</script>
<style>
body {
background-color: #f7f7f7;
}
.is-danger {
color: #9f3a38;
}
</style>
Post.vue (houses the button to call the reply modal):
<template>
<div class="row ui dividing header news">
<!-- Label -->
<div class="m-1 col-md-2 ui image justify-content-center align-self-center">
<img v-if="post.avatar_url" :src="post.avatar_url" class="mini rounded"/>
<v-gravatar v-else :email="post.email" class="mini thumbnail rounded image rounded-circle z-depth-1-half"/>
</div>
<!-- Excerpt -->
<div class="col-md-9 excerpt">
...
<!-- Feed footer -->
<div class="feed-footer row">
<div class="small"> {{ post.created_at | timeAgo }}</div>
<button type="button" flat color="green" #click="showModal('ModalReplyForm')">
<i class="fa fa-reply" ></i>
...
<div v-show="postOwner(post)" class="">
<button type="button" flat color="grey" #click="deletePost(post.id)">
<i class="fa fa-trash " ></i>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex';
import PostsService from '../../services/PostsService'
import RepliesService from '../../services/RepliesService'
import Replies from '#/components/Reply/Replies'
import ReplyForm from '#/components/Reply/ReplyForm'
export default {
name: "Post",
props: {
post: {
type: Object,
required: true
}
},
components: {
Replies,
ReplyForm
},
computed: {
me() {
return this.$store.getters.user
}
},
methods: {
...mapMutations(['showModal']),
...
}
};
</script>
AppModal.vue - generic Modal component
<template>
<div class="c-appModal">
<div class="c-appModal__overlay" v-if="visible"></div>
<div class="c-appModal__content" v-if="visible" #click.self="hideModal"></div>
<div class="c-appModal__innerContent">
<component :is="component"></component>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import { mapState, mapMutations } from 'vuex';
export default {
name: 'AppModal',
data() {
return {
component: null
}
},
computed: {
...mapState({
visible: 'modalVisible',
modalComponent: 'modalComponent'
}),
},
methods: {
...mapMutations(['hideModal'])
},
watch: {
modalComponent(componentName) {
if (!componentName) return;
Vue.component(componentName, () => import(`#/components/modals/${componentName}`));
this.component = componentName;
}
},
created() {
const escapeHandler = (e) => {
if (e.key === 'Escape' && this.visible) {
this.hideModal();
}
};
document.addEventListener('keydown', escapeHandler);
this.$once('hook:destroyed', () => {
document.removeEventListener('keydown', escapeHandler);
});
},
};
</script>
ModalReplyForm - specific reply modal content
<template>
<div>
<div class="c-modalReply">
<div>
<label for="reply">Your comment</label>
<div class="field">
<textarea name="reply" v-model="reply" rows="2" placeholder="Compose reply"></textarea>
</div>
</div>
<button class="c-modalReply__cancel" #click="hideModal">Cancel</button>
<button class="c-modalReply__post" :disabled="!isFormValid" #click="createReply">Reply</button>
</div>
</div>
</template>
<script>
import RepliesService from '#/services/RepliesService'
import { mapMutations } from 'vuex';
export default {
name: "ModalReplyForm",
// props: {
// post: {
// type: Object,
// required: true
// }
// },
data() {
return {
reply: ""
};
},
computed: {
isFormValid() {
return !!this.reply;
},
currentGroup() {
return this.$store.getters.currentPost;
}
},
methods: {
...mapMutations([
'hideModal'
]),
async createReply () {
let result = await RepliesService.addReply({
reply: {
body: this.reply,
postId: this.post.id
}
});
this.$emit("reply-created");
this.hideModal();
}
}
};
</script>
Unknown custom element: - did you register the
component correctly? For recursive components, make sure to provide
the "name" option.
This message says that you never imported/defined ModalReplyForm, which you have not.
In my own generic modal, I ended up having to import all the components that might appear within the modal itself.
If you add a:
import ModalReportForm from ...
and a:
components: {
ModalReplyForm
}
to AppModal.vue, the modal should then do what you expect.