I am new to React and learning Automated testing for the first time.
I have a very simple application, which essentially uses Dummy Data, and renders a table based on the data.
I also have a search input the user can use to filter the data.
I am trying to write a unit test for my function that filters the array based on the search input and then return the filtered array that is set in State.
My function is as follows,
const onClickHandler = () => {
// Filter state data
const filteredData = originalData.filter((entity) =>
entity.entityType.toLocaleLowerCase().includes(searchInput)
);
setFilterData(filteredData);
};
I am struggling to understand how I would write a unit test that covers this logic.
I have been able to write a test that covers the search input and the resetting of the input if the user clicks a Reset button.
Apologies, I hope I have explained this well enough.
First, you need to define what is a unit in your code, and the extract you pasted misses a lot of contexts.
I will assume:
the unit is a function
that function is "importable/visible" from the test scope (I'm missing the export in export const onClickHandler = ...)
originalData and searchInput are the parameters
So:
// file...
export const onClickHandler = (originalData, searchInput) => {
const filteredData = originalData.filter(({ entityType }) => (
entityType.toLocaleLowerCase().includes(searchInput))
);
setFilterData(filteredData);
};
For any test, I like the AAA approach:
// test...
import { onClickHandler } from 'path/to/file';
describe('path/to/file/and/file/name.ts', () => {
it('should filter values be searchInput', () => {
// ARRANGE
const searchInput = 'fo';
const originalData = [{ entityType: 'FOO' }, { entityType: 'BAR' }]
// ACT
const result = onClickHandler(originalData, searchInput);
// ASSERT
expect([result]).toEqual(originalData[0]);
}
};
If the function setFilterData is defined outside the tested unit, I use a mock instead of the original.
I hope that helps!
Cheers
Hi i am new in jest and unit testing. i want to ask how to set value text input using vue test utils.
shortly I have custom component for text input, its my code
<input
v-model="local_value"
#keyup.enter="submitToParent"
:class="input_class"
:id="id"
:disabled="is_disabled"
:maxlength="max_length"
:placeholder="placeholder"
:autocomplete="(is_autocomplete) ? 'on' : 'off'"
:name="id"
:ref="id"
/>
and it's my test
it("type something on field", async () => {
const wrapper = shallowMount(TextInput, {
propsData: {
id: "my_input",
}
})
// find component (its work properly) and I want to try inserting some text
const input = wrapper.findComponent({ref: "my_input"})
input.element.value = "sample text"
input.setValue("sample text")
// the value still empty string (""), idk what happens with my code
console.log(wrapper.vm.local_value)
expect(wrapper.vm.local_value).toBe("sample text")
please tell me if you known solution for this problem, thank you for your time
As far as I know, setValue is async, so you might need to set it with
await input.setValue('sample text')
If you're setting the vm's local_value value, you'd want to set it as:
const wrapper = shallowMount(TextInput)
await wrapper.setData({
local_value: 'sample text'
})
.... if the local_value is declared in data(), or:
const wrapper = shallowMount(TextInput)
await wrapper.setProps({
local_value: 'sample text'
})
... if the local_value is declared in props() in your component.
Then check it with:
expect(wrapper.vm.local_value).toBe('sample text')
Although that's just the vm value and not rendered value on DOM.
If you'd like to check including the rendered value, you have to declare first the property value on your component for it to be testable:
<input
v-model="local_value"
// ... rest of the properties
:value="local_value"
/>
and test it with:
const input = await wrapper.findComponent({ ref: "my_input" })
expect(input.attributes('value').toBe('sample text')
Ideally, your test will look like this:
it('should have input value' async () => {
const wrapper = shallowMount(TextInput)
await wrapper.setProps({ local_value: 'sample text' })
const input = await wrapper.findComponent({ ref: 'my_input' })
expect(wrapper.vm.local_value).toBe('sample text')
expect(input.attributes('value').toBe('sample text')
})
👍 Some tip:
You can check the rendered attributes input by commenting all expect and put:
expect(input.attributes('').toBe('random chars here to force an error')
Console will display the errors with the expected attributes output
I am doing simple todo app. I have 1 input box and Add button. On typing into input box and clicking Add button the text is displayed in a list below. I am using below constructor:
class Entry extends Component {
constructor() {
super()
this.state = {
st_search_field: '',
st_lists: [
{
list_id: new Date(`enter code here`),
list_name: ''
}
]
}
}
Now I create list_handler = () => { ...... } function to set search_field text into list_name value. I use list_handler method during onClick for Add button. I have started by usingconst join = Object.assign({}, this.state) in list_handler function and tried using this.setState({st_lists.list_name: this.state.st_searh_field}) butst_lists.list_name is marked in red in VS editor. Tried this.state.st_lists.map(li => {}) above setState method but even that gives error.
you can't set state on one of states sub objects do it like this:
const st_lists = this.state.st_lists;
st_lists.list_name = this.state.st_search_field;
this.setState({st_lists});
st_lists.list_name is not a key, you need to add merge the object
this.setState({
st_lists: [
{
...this.state.st_lists,
list_name: this.state.st_searh_field,
},
]
})
I feel like an idiot for having to ask about something so seemingly simple, but I'm trying to figure out how to use "enums" in VueJS. Currently, in a file called LandingPage.js I have this bit of code:
const Form = {
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
};
function main() {
new Vue({
el: "#landing-page",
components: {
LoginForm,
WhoIsBehindSection,
WhatIsSection,
Form,
},
data () {
return {
form: Form.LOGIN,
};
},
template: `
<div>
<LoginForm v-if="form === Form.LOGIN"></LoginForm>
<WhatIsSection></WhatIsSection>
<WhoIsBehindSection></WhoIsBehindSection>
</div>
`
});
}
It is the conditional v-if="form === Form.LOGIN" that is failing with the error messages:
Property or method "Form" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Cannot read property 'LOGIN' of undefined
Just so you guys know without the conditional everything is working, and if I were to put this bit in the template
<p>{{ form }}</p>
it will print 0 on the screen. Though, putting this in the template
<p>{{ Form.LOGIN }}</p>
Will not result in it printing 0 on the screen. So I just cannot for the life of me figure out why it will not accept Form.LOGIN.
The Answer
I did add it to components, but never did I think of adding it to data. Happy that it's working now. :)
data () {
return {
form: Form.LOGIN,
Form, // I had to add this bit
};
},
Thank you MarcRo 👍
If you are using Vue in Typescript, then you can use:
import { TernaryStatus } from '../enum/MyEnums';
export default class MyClass extends Vue {
myVariable: TernaryStatus = TernaryStatus.Started;
TernaryStatus: any = TernaryStatus;
}
and then in Template you can just use
<div>Status: {{ myVariable == TernaryStatus.Started ? "Started It" : "Stopped it" }}</div>
You can use https://stackoverflow.com/a/59714524/3706939.
const State = Object.freeze({ Active: 1, Inactive: 2 });
export default {
data() {
return {
State,
state: State.Active
};
},
methods: {
method() {
return state === State.Active;
}
}
}
You only have access to properties of the Vue instance in your template. Just try accessing window or any global in your template, for example.
Hence, you can access {{ form }} but not {{ Form.LOGIN }}.
A wild guess is that it has something to do with how Vue compiles, but I don't know enough about the internals to answer this.
So just keep declaring all the properties you wish to use in your template in your Vue instance (usually as data).
You can enclose enum into class. All your data, the state, the enum variants would be in one place. The same about behaviours, so you will call form.isLogin() rather than form === Form.LOGIN and form.setLogin() rather than form = Form.Login.
The class to generate enums:
class Fenum {
constructor(start, variants) {
this.state = start;
variants.forEach(value => {
const valueC = value.charAt(0).toUpperCase() + value.slice(1);
this['is' + valueC] = () => this.state === value;
this['set' + valueC] = () => this.state = value;
})
}
}
Example of usage:
function main() {
new Vue({
el: "#landing-page",
components: {
LoginForm,
WhoIsBehindSection,
WhatIsSection,
Form,
},
data () {
return {
form: new Fenum("login", ["login", "signUp", "forgotPassword"]),
};
},
template: `
<div>
<LoginForm v-if="form.isLogin()"></LoginForm>
<WhatIsSection></WhatIsSection>
<WhoIsBehindSection></WhoIsBehindSection>
</div>
`
});
}
Vue observe nested objects, so each call of a set method (from.setLogin(), form.setSignUp(), ...) will trigger updates of the component as it should be.
The generated object from this example:
You can use $options instead of $data https://vuejs.org/v2/api/#vm-options
You can use Proxy to create object which throw runtime errors if someone will read non-defined value or try to add new value - here is createEnum (and use it in data() section)
function createEnum(name,obj) {
return new Proxy(obj, {
get(target, property) {
if (property in target) return target[property];
throw new Error(`ENUM: ${name}.${property} is not defined`);
},
set: (target, fieldName, value) => {
throw new Error(`ENUM: adding new member '${fieldName}' to Enum '${name}' is not allowed.`);
}
});
}
// ---------------
// ----- TEST ----
// ---------------
const Form = createEnum('Form',{
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
});
// enum value exists
console.log(Form.LOGIN);
// enum value not exists
try{ console.log(Form.LOGOUT) } catch(e){ console.log(e.message)}
// try to add new value
try{ Form.EXIT = 5 } catch(e){ console.log(e.message)}
for string-like Enums where values are equal to keys you can use following helper
export function createEnumArr(name='', values=[]) {
let obj = {};
values.forEach(v => obj[v]=v);
return createEnum(name,obj);
}
const Form = createEnumArr('Form',[
"LOGIN",
"SIGN_UP",
"FORGOT_PASSWORD",
]);
The easiest way!
in main.js
const enumInfo = {
SOURCE_TYPE: {
WALLET: 1,
QR: 2
}
}
Vue.prototype.enumInfo = enumInfo
index.vue
{{enumInfo}}
For 2022 and beyond you should probably be using Vue 3 and Typescript.
The easiest way to use an enum is to map it to string values and then simply return it from your setup function.
<template>
...
<div v-if="mode == DarkModes.DARK">
do something for dark mode
</div>
...
</template>
<script lang="ts">
enum DarkModes {
BRIGHT = 'bright',
DARK = 'dark',
}
export default defineComponent({
name: 'MyDarkOrBrightComponent',
setup() {
const mode = ref(DarkModes.BRIGHT);
...
return {
mode,
DarkModes, // <- PASS YOUR ENUM HERE!
}
}
});
</script>
And if you're using the new <script setup> functionality it's just as easy ... all top level imports are automatically accessible from the template (if you want to put your enum in a separate file).
I've this problem, too.
Here my solution, just put this in the first line:
<script setup>
const Form = {
LOGIN: 0,
SIGN_UP: 1,
FORGOT_PASSWORD: 2,
};
</script>
I am writing my first codemod using Jscodeshift.
My current goal is to export a const that is assigned a certain identifier.
So that, if I target every variable named stuff, it will be named-exported after the script runs.
IN:
const stuff = 4;
OUT:
export const stuff = 4;
This is a stripped down version of what I have. It sort of works but it looks very brittle and has a number of drawbacks.
const constName = "stuff";
module.exports = (fileInfo, api) => {
const j = api.jscodeshift;
const root = j(fileInfo.source);
const declaration = root.find(j.VariableDeclaration, {
declarations: [
{
id: {
type: "Identifier",
name: constName
}
}
]
});
declaration.forEach(n => {
n.insertBefore("export");
});
return root.toSource();
};
AST
This will result in (notice the unwanted new line)
export
const stuff = 4;
This also crucially fails if this source is fed to the script.
IN:
// hey
const stuff = 4;
OUT:
export
// hey
const stuff = 4;
I am quite convinced that n.insertBefore("export"); is really the culprit here, and I'd like to build the named export myself using jscodeshift builders but really can't get it work.
Any suggestions here?
.insertBefore is not the right method to use. This is for inserting a whole new node before another node.
How want to replace a VariableDeclaration with an ExportNamedDeclaration. If you look at the AST for export const stuff = 4; you can see that it has a property declaration whose value is a VariableDeclaration node. That makes the transformation easy for us: Find the VariableDeclaration, create a new ExportNamedDeclaration, set it's declaration property to the found node and replace the found node with the new node.
To find out how to build the node we can look at ast-type's ast definitions.
const constName = "stuff";
module.exports = (fileInfo, api) => {
const j = api.jscodeshift;
return j(fileInfo.source)
.find(j.VariableDeclaration, {
declarations: [
{
id: {
type: "Identifier",
name: constName
}
}
]
})
.replaceWith(p => j.exportDeclaration(false, p.node))
.toSource();
};
astexplorer