Vue jest test for algorithm - unit-testing

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)
})
})

Related

How to remove single component from object in gridstack Vue3

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

Vue3 Element-plus test component that uses el-table (error reading of undefined)

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

Jest - Cannot read properties of undefined (reading 'map') when using i18next

I'm quite new to testing and I'm using i18next in my app but when I try to test components where I use .map() I receive an error of cannot read properties of undefined. How should I write the test so I don't get this error?
Component.js - the myArr is from a json file.
MyComponent = () => {
return(
<div data-testid="comp">
{t("myArr", { returnObjects: true}).map(({ name, href, target }) => (
<div key={name} >
<Link href={href}>
<a
target={target}
onClick={() => handleClick()}>
{name}
</a>
</Link>
</div>
))}
)
}
Component.test.js
import React from "react";
import {render } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import MyComp from ".";
jest.mock("next/router", () => ({
useRouter() {
return {
local: "",
asPath: "",
};
},
}));
describe("MyComp", () => {
it("should render myComponent", () => {
const { getByTestId } = render(
<myComponent menuOpen={false} setBurgerOpen={jest.fn()} t={jest.fn()}/>
);
const myComp = getByTestId("comp");
expect(myComp).toBeVisible();
});
});

How to capture object props in Vue snapshot tests

I always get [object Object] in place of object props due to object to string coercion when I use snapshot testing. How can I fix it? I've tried wrapping element into JSON.stringify(), but it causes "Converting circular structure to JSON" Error.
The example of a resulting snapshot:
exports[`SalesList.vue Снапшот десктоп 1`] = `
<magic-grid-stub
class="sales-list"
cols="[object Object]"
gaps="[object Object]"
usemin="true"
>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-item-stub
class="item"
sale="[object Object]"
/>
<sales-info-stub
class="item"
content="additionalInfo"
/>
</magic-grid-stub>
`;
I have the simple corresponding snapshot tests, like this one:
import { createLocalVue, shallowMount } from '#vue/test-utils'
import SalesList from '#/components/sales/SalesList.vue'
let localVue
const fakeSale = {
code: 'code',
description: 'description',
title: 'title',
image: 'image',
archive: false,
visible: true,
date_to: '2020/08/01',
short_description: 'short_description',
slug: 'slug',
date_from: '2020/06/01',
seo: {
seo_description: 'seo_description',
seo_title: 'seo_title',
seo_keywords: 'seo_keywords',
},
}
function createWrapper(component, options) {
return shallowMount(component, {
localVue,
...options,
})
}
beforeAll(() => {
localVue = createLocalVue()
})
describe('SalesList.vue', () => {
it('Снапшот десктоп', async () => {
expect.assertions(1)
const wrapper = createWrapper(SalesList, {
propsData: {
sales: Array.from({ length: 4 }, (_, index) => ({
...fakeSale,
slug: `slug-${index}`,
})),
additionalInfo: 'additionalInfo',
},
mocks: {
$device: { isDesktop: true },
},
})
expect(wrapper.element).toMatchSnapshot()
})
})
And the component in question itself:
<script lang="ts">
import SalesItem from '#/components/sales/SalesItem.vue'
import MagicGrid from '#/components/MagicGrid.vue'
import SalesInfo from '#/components/sales/SalesInfo.vue'
import Vue from 'vue'
export default Vue.extend({
name: 'SalesList',
components: {
SalesItem,
MagicGrid,
SalesInfo,
},
props: {
sales: {
type: Array,
required: true,
},
additionalInfo: {
type: String,
default: null,
},
},
computed: {
colsAndGaps(): {
cols: { 0: number }
gaps: { 0: number }
} {
return this.$device.isDesktopOrTablet
? {
cols: {0: 2},
gaps: {0: 30},
}
: {
cols: {0: 1},
gaps: {0: 16},
}
},
},
})
</script>
<template>
<magic-grid v-bind="colsAndGaps" class="sales-list">
<sales-item
v-for="sale in sales"
:key="sale.slug"
:sale="sale"
class="item"
/>
<sales-info v-if="additionalInfo" :content="additionalInfo" class="item"/>
</magic-grid>
</template>
You could use a custom jest snapshot serializer.
For VueJs 2 you could use https://github.com/tjw-lint/jest-serializer-vue-tjw - but it doesn't work for VueJs 3 (https://github.com/tjw-lint/jest-serializer-vue-tjw/pull/64).
Example configuration for VueJs 2:
npm install jest-serializer-vue-tjw
// package.json
{
...
"jest": {
"snapshotSerializers": ["jest-serializer-vue-tjw"]
}
}

React + Chai + Enzyme

I'm trying to run some unit test with Chai&Enzyme to my React app.
Enzyme seems to have an issue with props I passed to components. They got undefined in tests (not in app).
main file:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TaskList from './components/task_list';
import AddingInput from './components/input';
const titleHeader = <h1 className="app__title">Simple to-do list:</h1>;
class App extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [
{id: 0, name: 'Complete task'},
{id: 1, name: 'Add task'},
{id: 2, name: 'Delete task'}
],
id: 3,
};
}
(...)
render () {
return (
<div className="app">
{titleHeader}
<TaskList
tasks={this.state.tasks}
deleteMe={this.deleteMe.bind(this)} />
<AddingInput addTodo={this.addTodo.bind(this)} />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Task_list - first component I want to test:
import React from 'react';
import TaskItem from './task_item';
const TaskList = (props) => {
const taskItems = props.tasks.map((task) => {
return (
<TaskItem
key={task.id}
task={task}
deleteMe={props.deleteMe}
/>
);
});
return (
<ol className="ordered-list">
{taskItems}
</ol>
);
};
export default TaskList;
And the second:
import React from 'react';
const TaskItem = React.createClass({
onCompleted: function(e) {
e.target.className += " ordered-list__item--completed"
},
render: function() {
return (
<li><span className="ordered-list__item" onClick={this.onCompleted}>{this.props.task.name}</span>
<span onClick={() => this.props.deleteMe(this.props.task)}
className='btn btn--delete'>Delete</span>
</li>
);
}
});
export default TaskItem;
An here are my tests. Two of them are passed (both component exist) but in other two got undefined on props.tasks:
import React from 'react';
import { expect, assert } from 'chai';
import { shallow, mount } from 'enzyme';
import TaskList from './src/components/task_list';
import TaskItem from './src/components/task_item';
describe('TaskList', () => {
it('should render TaskItem', () => {
const wrapper = shallow(<TaskList />);
expect(wrapper.containsAllMatchingElements([
<TaskItem />,
])).to.equal(true);
});
it('should exists', () => {
assert.isDefined(TaskList)
})
});
describe('TaskItem', () => {
it('should render one item of unordered list', () => {
const item = mount(<TaskItem />);
expect(item).to.contain('li');
});
it('should exists', () => {
assert.isDefined(TaskList)
})
});
Problems:
1) TaskList should render TaskItem:
TypeError: Cannot read property 'map' of undefined
2) TaskItem should render one item of unordered list:
TypeError: Cannot read property 'name' of undefined
Your App component passes a tasks prop:
<TaskList
tasks={this.state.tasks}
deleteMe={this.deleteMe.bind(this)} />
And TaskList expects it to be always present. Similarly, TaskItem expects to always have certain props available (from TaskList).
If you add the necessary props in your unit tests, it should work. For TaskList, this might look something like the following (untested):
const tasks = [
{id: 0, name: 'Complete task'},
{id: 1, name: 'Add task'},
{id: 2, name: 'Delete task'}
];
const wrapper = shallow(<TaskList tasks={tasks}/>);