I am trying to remove a single component from an object in gridstack.js using index and keys. Components are created by v-for directive.
I already have the index and key but components.splice(index,1) and grid.removeWidget(key, false); functions don't achieve this as shown in the documentation. What I am i doing wrong.
Thanks
<template>
<div class="right">
<button class="ds" #click="addNewWidget()">Add Widget</button>
//works okay
<button class="ds" #click="deleteWidget()">Clear</button>
</div>
<section class="grid-stack">
<div v-for="(component, key, index) in components" :key="'component' + index" :gs-id="key"
class="grid-stack-item" :gs-x="component.gridPos.x" :gs-y="component.gridPos.y" :gs-h="component.gridPos.h"
:gs-w="component.gridPos.w" gs-auto-position="true">
<div class="grid-stack-item-content">
<component :is="component.name" v-bind="component.props" />
//Uncaught TypeError: Cannot read properties of undefined (reading 'splice')
<button #click="remove(index, key, component)">remove</button>
</div>
</div>
</section>
</template>
<script>
import { ref, onMounted, reactive, nextTick } from 'vue';
import "gridstack/dist/h5/gridstack-dd-native";
import 'gridstack/dist/gridstack.min.css';
import { GridStack } from 'gridstack';
import BarChart from './BarChart.vue';
import PieChart from './PieChart.vue';
import LineChart from './LineChart.vue';
import Widget from './HalfDoughnut.vue';
export default {
name: "Dashboard",
setup() {
let info = ref("");
let grid = null;
let components = reactive({
yourRandomComponent1: {
name: "BarChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 }
},
yourRandomComponent2: {
name: "PieChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 }
},
});
onMounted(() => {
grid = GridStack.init({
float: true,
cellHeight: "70px",
minRow: 1,
});
grid.on("dragstop", (event, element) => {
console.log("move event!", event, element);
const node = element.gridstackNode;
info.value = `you just dragged node #${node.id} to ${node.x},${node.y} – good job!`;
});
});
// this will of course only work once because of the object-key
function addNewWidget() {
components.yourRandomComponent3 = {
name: "Widget", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 }
};
// we have to wait for vue to update v-for,
// until then the querySelector wont find the element
nextTick(() => {
console.log(grid);
let compEl = document.querySelector('[gs-id="yourRandomComponent3"]');
console.log(compEl);
grid.makeWidget(compEl);
});
console.warn("i will only work once, fix my inputs to reuse me");
}
function remove(index, key) {
console.log(key)
//Uncaught TypeError: Cannot read properties of undefined (reading 'splice')
components.splice(index, 1);
grid.removeWidget(key, false);
}
function deleteWidget(){
grid.removeAll(true);
}
return {
info,
components,
addNewWidget,
deleteWidget,
remove
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
BarChart, PieChart, LineChart, Widget
}
}
</script>
you are declaring components as a reactive which means you don't need to use .value use delete componennts[key] but a regular object
also use const to declare ref, reactive... to avoid errors
Related
I have a fresh project using vue3 and Element-plus.
I have this component that uses el-table
<template>
<el-table :data="data">
<el-table-column label="id">
<template #default="scope">
<el-button>
{{ scope.row.id }} // This line is failing the test.
</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
const data = [{ id: 1 }, { id: 2 }, { id: 3 }]
</script>
And a simple test:
import { describe, it, expect } from 'vitest'
import { mount } from '#vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.text()).toBeTruthy()
})
})
The thing is, using the default slot on the el-table-column and printing directly on the DOM with scope variable is failing the test with this error message:
TypeError: Cannot read properties of undefined (reading 'row')
How can I mock this? I tried doing this:
import { describe, it, expect } from 'vitest'
import { mount } from '#vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
const scope = () => {
return {
row: {}
}
}
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, {
global: {
mocks:{
scope
}
}
})
expect(wrapper.text()).toBeTruthy()
})
})
But seems it's ignoring this mock...
The interesting this is, if I use this scope variable in something else (not printing in the DOM) the test pass, like this:
<template>
<el-table :data="data">
<el-table-column label="id">
<template #default="scope">
<el-button #click="handleClick(scope.$index, scope.row)">
Test
</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
const data = [{ id: 1 }, { id: 2 }, { id: 3 }]
</script>
Well... how can I solve this problem?
Thank you
I have a component that look like this. How would I write a vue jest test to satisfy these conditions?
<template>
<div align="center">
<button #click="Tests()">Tests</button>
<button #click="Benchmark()">Benchmark</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
result: null,
};
},
props: {
msg: String,
},
};
</script>
Using #vue/test-utils, you could shallowMount the component to get a wrapper, and then access smallestInt() and result via the vm property of the wrapper:
// MyComponent.spec.js
import MyComponent from '#/components/MyComponent.vue'
import { shallowMount } from '#vue/test-utils'
describe('MyComponent.smallestInt()', () => {
it('case 1', () => {
const wrapper = shallowMount(MyComponent)
const A = [1, 3, 6, 4, 1, 2]
wrapper.vm.smallestInt(A)
expect(wrapper.vm.result).toBe(5)
})
it('case 2', () => {
const wrapper = shallowMount(MyComponent)
const A = [-1, -3]
wrapper.vm.smallestInt(A)
expect(wrapper.vm.result).toBe(1)
})
})
i trying to post data from ant design React.js into Python Django rest frame work.
so I am using method OnFinish to send data, but its not working.
MY big problem is , i don't know how can i Introduction Data i want to send them data from Form , by using React-redux or something else way , so please Help me .
#react.js Form:
import React, { Component } from "react";
import {
Form,
Input,
Button,
PageHeader,
Select,
DatePicker,
message,
} from "antd";
import "antd/dist/antd.css";
import { connect } from "react-redux";
import axios from "axios";
// defualt setting for django
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
// from layout setting
const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
sm: {
span: 8,
},
},
wrapperCol: {
xs: {
span: 24,
},
sm: {
span: 16,
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
// end fform layout setting
// const onFinish = (values) => {
// console.log(values);
// axios.post("http://127.0.0.1:8000/api/create/", {
// title: values.title,
// manager: values.manager,
// });
// };
// const title = event.target.elements.title.value;
// const manager = event.target.elements.manager.value;
class ExtrashiftForm extends React.Component {
constructor(props) {
super(props);
this.state = {
Extrashifts: [],
};
}
// componentDidMount() {
// this.fetchExtrashift();
// }
handleSubmit = () => {
axios
.post("http://127.0.0.1:8000/api/create", {
data: {
title: this.target.elements.title.value,
manager: this.data.item.manager,
},
})
.then((res) => {
if (res.status == 200) message.success("data successfully updated!");
this.fetchExtrashift();
})
.catch((err) => {
message.error("data profile failed to update ...");
});
};
render() {
return (
<div>
<Form {...formItemLayout} name="update">
<Form.Item label="Title :">
<Input name="title" placeholder="Put a title here" />
</Form.Item>
<Form.Item label="Manager :">
<Input name="manager" placeholder="Enter manager name" />
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button
type="primary"
htmlType="submit"
onFinish={this.handleSubmit}
>
create
</Button>
</Form.Item>
</Form>
</div>
);
}
}
export default ExtrashiftForm;
#back end api/urls.py :
from Extrashift.api.views import ExtrashiftViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'', ExtrashiftViewSet, basename='Extrashift')
urlpatterns = router.urls
#backend : api/views.py:
from rest_framework import viewsets
from Extrashift.models import Extrashift
from .Serializers import ExtrashiftSerializers
class ExtrashiftViewSet(viewsets.ModelViewSet):
serializer_class = ExtrashiftSerializers
queryset = Extrashift.objects.all()
from rest_framework import permissions
from rest_framework.generics import (
ListAPIView,
RetrieveAPIView,
CreateAPIView,
UpdateAPIView,
DestroyAPIView
)
from my back end everything is work but Please help me to i can send only one data from this form.
if is possible please ,change my Code to the Correct code
Nothing spectacular here, you can read the docs
Rather than giving the name as a prop to the Input field.
I've passed it as a prop to Form.Item component
You can check the example here
import React, { Component } from "react";
import {
Form,
Input,
Button,
PageHeader,
Select,
DatePicker,
message,
} from "antd";
import "antd/dist/antd.css";
import axios from "axios";
// defualt setting for django
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
// from layout setting
const formItemLayout = {
labelCol: {
xs: {
span: 24,
},
sm: {
span: 8,
},
},
wrapperCol: {
xs: {
span: 24,
},
sm: {
span: 16,
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
// end fform layout setting
// const onFinish = (values) => {
// console.log(values);
// axios.post("http://127.0.0.1:8000/api/create/", {
// title: values.title,
// manager: values.manager,
// });
// };
// const title = event.target.elements.title.value;
// const manager = event.target.elements.manager.value;
export default class ExtrashiftForm extends React.Component {
constructor(props) {
super(props);
this.state = {
Extrashifts: [],
};
}
// componentDidMount() {
// this.fetchExtrashift();
// }
handleSubmit = (values) => {
console.log(values)
// axios
// .post("http://127.0.0.1:8000/api/create", {
// data: {
// title: this.target.elements.title.value,
// manager: this.data.item.manager,
// },
// })
// .then((res) => {
// if (res.status == 200) message.success("data successfully updated!");
// this.fetchExtrashift();
// })
// .catch((err) => {
// message.error("data profile failed to update ...");
// });
};
render() {
return (
<div>
<Form {...formItemLayout} name="update" onFinish={this.handleSubmit}>
<Form.Item label="Title :" name="title">
<Input placeholder="Put a title here" />
</Form.Item>
<Form.Item label="Manager :" name="manager">
<Input placeholder="Enter manager name" />
</Form.Item>
<Form.Item {...tailFormItemLayout}>
<Button
type="primary"
htmlType="submit"
>
create
</Button>
</Form.Item>
</Form>
</div>
);
}
}
I'm trying to get Apollo gql to load more posts after clicking a button. So it would load the next 15 results, every time you click - load more.
This is my current code
import Layout from "./Layout";
import Post from "./Post";
import client from "./ApolloClient";
import { useQuery } from "#apollo/react-hooks"
import gql from "graphql-tag";
const POSTS_QUERY = gql`
query {
posts(first: 15) {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
const Posts = props => {
let currPage = 0;
const { posts } = props;
const { loading, error, data, fetchMore } = useQuery(
POSTS_QUERY,
{
variables: {
offset: 0,
limit: 15
},
fetchPolicy: "cache-and-network"
});
function onLoadMore() {
fetchMore({
variables: {
offset: data.posts.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
posts: [...prev.posts, ...fetchMoreResult.posts]
});
}
});
}
if (loading) return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
Loading...
</div>
</div>
);
if (error) return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
Oops, there was an error :( Please try again later.
</div>
</div>
);
return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
{data.posts.nodes.length
? data.posts.nodes.map(post => <Post key={post.postId} post={post} />)
: ""}
</div>
<button onClick={() => { onLoadMore() }}>Load More</button>
</div>
);
};
export default Posts;
When you click load more it refreshes the query and console errors
Invalid attempt to spread non-iterable instance
I have been loading for solutions but a lot of the examples are previous or next pages like traditional pagination. Or a cursor based infinite loader which I don't want. I just want more posts added to the list onClick.
Any advise is appreciated, thank you.
Your current POSTS_QUERY it isn't accepting variables, so first you need change this:
const POSTS_QUERY = gql`
query postQuery($first: Int!, $offset: Int!) {
posts(first: $first, offset: $offset) {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
Now, it will use the variables listed in your useQuery and fetchMore.
And to finish the error is because updateQuery isn't correct, change it to:
function onLoadMore() {
fetchMore({
variables: {
offset: data.posts.nodes.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return { posts: { nodes: [...prev.posts.nodes, ...fetchMoreResult.posts.nodes] } };
});
}
});
}
I would suggest useState hook to manage a variable that stores current offset in the dataset, place a useEffect to watch changes to that offset, the offset value in passed as query variable to load data. Remove fetchmore, useEffect hook will do the job.
When user clicks on load more button, you just need to update offset value, that will trigger the query and update data.
const [offset,setOffset] = React.useState(0)
const [results, setResults] = React.useState([])
const { loading, error, data } = useQuery(
POSTS_QUERY,
{
variables: {
offset: offset,
limit: 15
},
fetchPolicy: "cache-and-network"
}
);
React.useEffect(() => {
const newResults = [...results, ...data]
setResults(newResults)
}, [data])
function onLoadMore() {
setOffset(results.data.length)
}
I am working on an app which was created with the Vue loader's webpack template.
I included testing with Karma as an option when creating the project, so it was all set up and I haven't changed any of the config.
The app is a Github user lookup which currently consists of three components; App.vue, Stats.vue and UserForm.vue. The stats and form components are children of the containing app component.
Here is App.vue:
<template>
<div id="app">
<user-form
v-model="inputValue"
#go="submit"
:input-value="inputValue"
></user-form>
<stats
:username="username"
:avatar="avatar"
:fave-lang="faveLang"
:followers="followers"
></stats>
</div>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import _ from 'lodash'
import UserForm from './components/UserForm'
import Stats from './components/Stats'
Vue.use(VueAxios, axios)
export default {
name: 'app',
components: {
UserForm,
Stats
},
data () {
return {
inputValue: '',
username: '',
avatar: '',
followers: [],
faveLang: '',
urlBase: 'https://api.github.com/users'
}
},
methods: {
submit () {
if (this.inputValue) {
const api = `${this.urlBase}/${this.inputValue}`
this.fetchUser(api)
}
},
fetchUser (api) {
Vue.axios.get(api).then((response) => {
const { data } = response
this.inputValue = ''
this.username = data.login
this.avatar = data.avatar_url
this.fetchFollowers()
this.fetchFaveLang()
}).catch(error => {
console.warn('ERROR:', error)
})
},
fetchFollowers () {
Vue.axios.get(`${this.urlBase}/${this.username}/followers`).then(followersResponse => {
this.followers = followersResponse.data.map(follower => {
return follower.login
})
})
},
fetchFaveLang () {
Vue.axios.get(`${this.urlBase}/${this.username}/repos`).then(reposResponse => {
const langs = reposResponse.data.map(repo => {
return repo.language
})
// Get most commonly occurring string from array
const faveLang = _.chain(langs).countBy().toPairs().maxBy(_.last).head().value()
if (faveLang !== 'null') {
this.faveLang = faveLang
} else {
this.faveLang = ''
}
})
}
}
}
</script>
<style lang="stylus">
body
background-color goldenrod
</style>
Here is Stats.vue:
<template>
<div class="container">
<h1 class="username" v-if="username">{{username}}</h1>
<img v-if="avatar" :src="avatar" class="avatar">
<h2 v-if="faveLang">Favourite Language: {{faveLang}}</h2>
<h3 v-if="followers.length > 0">Followers ({{followers.length}}):</h3>
<ul v-if="followers.length > 0">
<li v-for="follower in followers">
{{follower}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'stats',
props: [
'username',
'avatar',
'faveLang',
'followers'
]
}
</script>
<style lang="stylus" scoped>
h1
font-size 44px
.avatar
height 200px
width 200px
border-radius 10%
.container
display flex
align-items center
flex-flow column
font-family Comic Sans MS
</style>
And here is UserForm.vue:
<template>
<form #submit.prevent="handleSubmit">
<input
class="input"
:value="inputValue"
#input="updateValue($event.target.value)"
type="text"
placeholder="Enter a GitHub username..."
>
<button class="button">Go!</button>
</form>
</template>
<script>
export default {
props: ['inputValue'],
name: 'user-form',
methods: {
updateValue (value) {
this.$emit('input', value)
},
handleSubmit () {
this.$emit('go')
}
}
}
</script>
<style lang="stylus" scoped>
input
width 320px
input,
button
font-size 25px
form
display flex
justify-content center
</style>
I wrote a trivial test for UserForm.vue which test's the outerHTML of the <button>:
import Vue from 'vue'
import UserForm from 'src/components/UserForm'
describe('UserForm.vue', () => {
it('should have a data-attribute in the button outerHTML', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(UserForm)
})
expect(vm.$el.querySelector('.button').outerHTML)
.to.include('data-v')
})
})
This works fine; the output when running npm run unit is:
UserForm.vue
✓ should have a data-attribute in the button outerHTML
However, when I tried to write a similarly simple test for Stats.vue based on the documentation, I ran into a problem.
Here is the test:
import Vue from 'vue'
import Stats from 'src/components/Stats'
// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(Stats).$mount()
vm.username = 'foo'
// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
expect(vm.$el.querySelector('.username').textContent).toBe('foo')
done()
})
})
and here is the respective error when running npm run unit:
ERROR LOG: '[Vue warn]: Error when rendering root instance: '
✗ updates the rendered message when vm.message updates
undefined is not an object (evaluating '_vm.followers.length')
I have tried the following in an attempt to get the test working:
Change how the vm is created in the Stats test to be the same as the UserForm test - same error is returned
Test individual parts of the component, for example the textContent of a div in the component - same error is returned
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front? How can I get around this issue to be able to successfully test my component?
(Repo with all code: https://github.com/alanbuchanan/vue-github-lookup-2)
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front?
This piece of code is from the render function that Vue compiled your template into. _vm is a placeholder that gets inserted automatically into all Javascript expressions when vue-loader converts the template into a render function during build - it does that to provide access to the component.
When you do this in your template:
{{followers.length}}
The compiled result in the render function for this piece of code will be:
_vm.followers.length
Now, why does the error happen in the first place? Because you have defined a prop followers on your component, but don't provide any data for it - therefore, the prop's value is undefined
Solution: either you provide a default value for the prop:
// Stats.vue
props: {
followers: { default: () => [] }, // function required to return fresh object
// ... other props
}
Or you propvide acual values for the prop:
// in the test:
const vm = new Vue({
...Stats,
propsData: {
followers: [/* ... actual data*/]
}
}).$mount()