For testing an input it works like this:
it('test input', () => {
const { getByTestId, getByLabelText } = render(<MyComponent />);
const myButton = getByTestId('submit-button');
expect(myButton).toBeInTheDocument();
fireEvent.change(getByLabelText('first-name'), {
target: { value: 'ted' }
});
// do something
});
the above test is setting the value of first-name input to "ted" and moves forward.
I want to do something similar but for a drop-down selector.
This is the component:
<SelectInput
label='my-dropdown'
onChange={onChange}
options={myOptions}
/>;
myOptions is an array of this shape:
const myOptions = [
{ id: '0', name: 'zero' },
{ id: '1', name: 'one' },
{ id: '2', name: 'two' }
];
it works fine in the application, no errors from this part.
Here comes the testing of it, I did something but it doesn't work:
it('test dropdpwn', () => {
const { getByTestId, getByLabelText } = render(<MyComponent />);
const saveButton = getByTestId('submit-button');
expect(saveButton).toBeInTheDocument();
fireEvent.change(getByLabelText('my-dropdown'), {
target: { value: { id: '0', name: 'zero' } }
});
// do something
});
the above code doesn't work, it doesn't set the dropdown with that value.
Any ideas on how to solve this? Not sure if it's important, but all those inputs are inside a react-hook-form and at the end it should test that the onSubmit is working (it works only if all inputs are set).
So, since the select behavior is being achieved using a button and spans.
You need to first click the button this would bring all the options on the screen and then you need to click one of those options.
And then you can finally test that the selected option is now on the screen.
it("test dropdpwn", async () => {
const { getByTestId, getByLabelText } = renderWithClientInstance(
<MapSignalModal title={title} open={true} toggle={toggle} />
);
userEvent.click(screen.getAllByTestId("selectButton")[0]);
userEvent.click(screen.getByText("sensor pool 1"));
expect(
await screen.findByText(screen.getByText("sensor pool 1"))
).toBeInTheDocument();
});
Also, to be really sure you can try the following, this should fail because "sensor pool 1" option is not initially on the screen.
And it should pass when the text is changed to "sensor pool 0" because that's there on the screen initially.
it("test dropdpwn", async () => {
const { getByTestId, getByLabelText } = renderWithClientInstance(
<MapSignalModal title={title} open={true} toggle={toggle} />
);
expect(screen.getByText("sensor pool 1")).toBeInTheDocument();
// if you replace the above text to "sensor pool 0", it should work
});
Related
I want to filter items by filter method and I did it but it doesn't work in UI but
when I log it inside console it's working properly
I don't know where is the problem I put 2 images
Explanation of this code:
Looping inside currencies_info by map method and show them when I click on it and this completely working then I want filter the list when user enter input inside it I use filter method and this completely working in console not in UI
import React, { useState } from "react";
// Artificial object about currencies information
let currencies_info = [
{
id: 1,
name: "بیت کوین (bitcoin)",
icon: "images/crypto-logos/bitcoin.png",
world_price: 39309.13,
website_price: "3000",
balance: 0,
in_tomans: 0
},
{
id: 2,
name: "اتریوم (ethereum)",
icon: "images/crypto-logos/ethereum.png",
world_price: 39309.13,
website_price: "90",
balance: 0,
in_tomans: 0
},
{
id: 3,
name: "تتر (tether)",
icon: "images/crypto-logos/tether.png",
world_price: 39309.13,
website_price: "5",
balance: 0,
in_tomans: 0
},
{
id: 4,
name: "دوج کوین (dogecoin)",
icon: "images/crypto-logos/dogecoin.png",
world_price: 39309.13,
website_price: "1000000",
balance: 0,
in_tomans: 0
},
{
id: 5,
name: "ریپل (ripple)",
icon: "images/crypto-logos/xrp.png",
world_price: 39309.13,
website_price: "1,108",
balance: 0,
in_tomans: 0
}
];
export default function Buy() {
// States
let [api_data, set_api_data] = useState(currencies_info);
const [currency_icon, set_currency_icon] = useState("");
const [currency_name, set_currency_name] = useState("");
const [currency_price, set_currency_price] = useState(0);
const [dropdown, set_drop_down] = useState(false);
let [search_filter, set_search_filter] = useState("");
// States functions
// this function just toggle dropdown list
const toggle_dropdown = () => {
dropdown ? set_drop_down(false) : set_drop_down(true);
};
// this function shows all currencies inside dropdown list and when click on each item replace
// the currency info and hide dropdown list
const fetch_currency = (e) => {
set_drop_down(false);
currencies_info.map((currency) => {
if (e.target.id == currency.id) {
set_currency_name(currency.name);
set_currency_icon(currency.icon);
set_currency_price(currency.website_price);
}
});
};
// this function filter items base on user input value
const filter_currency = (e) => {
set_search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
api_data = set_search_filter;
console.log(api_data);
};
return (
<div className="buy-page-input" onClick={toggle_dropdown}>
{/* currency logo */}
<div className="currency-logo">
<img src={currency_icon} width="30px" />
</div>
{/* currency name in persian */}
<span className="currency-name">{currency_name}</span>
{/* currency dropdown icon */}
<div className="currency-dropdown">
<img className={dropdown ? "toggle-drop-down-icon" : ""}
src="https://img.icons8.com/ios-glyphs/30/000000/chevron-up.png"
/>
</div>
</div>
{/* Drop down list */}
{dropdown ? (
<div className="drop-down-list-container">
{/* Search box */}
<div className="search-box-container">
<input type="search" name="search-bar" id="search-bar"
placeholder="جستجو بر اساس اسم..."
onChange={(e) => {
filter_currency(e);
}}/>
</div>
{api_data.map((currency) => {
return (<div className="drop-down-list" onClick={(e) => {
fetch_currency(e);}} id={currency.id}>
<div class="right-side" id={currency.id}>
<img src={currency.icon} width="20px" id={currency.id} />
<span id={currency.id}>{currency.name}</span>
</div>
<div className="left-side" id={currency.id}>
<span id={currency.id}>قیمت خرید</span>
<span className="buy-price" id={currency.id}>
{currency.website_price}تومان</span>
</div>
</div>);})}
</div>) : ("")});}
Your search_filter looks redundant to me.
Try to change the filter_currency function like this:
const filter_currency = (e) => {
const search = e.target.value;
const filtered = currencies_info.filter((currency) => {
return currency.name.includes(search);
});
set_api_data(filtered);
};
It looks like you are never setting the api_data after you set the filter state.
Change the following
api_data = set_search_filter
console.log(api_data)
to
api_data = set_search_filter
set_api_data(api_data)
However, it then looks like set_search_filter is never used and only set so to improve this further you could remove that state and just have it set the api_data direct. Something like this:
const filter_currency = (e) => {
const search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1
})
set_api_data(search_filter)
}
Change your state value from string to array of the search_filter like this -
let [search_filter, set_search_filter] = useState([]);
and also it should be like this -
const filter_currency = (e) => {
const filterValues = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
set_search_filter(filtervalues);
set_api_data(filterValues);
console.log(filterValues);
};
and use useEffect with search_filter as dependency, so that every time search_filter value is being set, useEffect will trigger re render, for eg:-
useEffect(()=>{
//every time search_filter value will change it will update the dom.
},[search_filter])
I'm building a text editor using React with Typescript.
The component hierarchy looks like this: TextEditor -> Blocks -> Block -> ContentEditable.
The ContentEditable is an npm package https://www.npmjs.com/package/react-contenteditable.
What i want it to do
The behavior I'm after is similar to Medium or Notions text editor. When a user writes in a block and hits enter on their keyboard, a new block should be created after the current block.
What it does
The behavior right now is strange to me. If I press enter and add one block, it works fine. But if I press enter again it overrides the previous block instead of creating a new one.
However, if I press enter and add a block, then puts the carrot (focusing) on the new block and press enter again, a new block is added after as expected.
Sandbox
Here is a sandbox with the complete code: https://codesandbox.io/s/texteditor-mxgbey?file=/src/components/Block.tsx:81-557
TextEditor
export default function TextEditor(props) {
const [blocks, setBlocks] = useState([
{ id: "1", tag: "h1", html: "Title1" },
{ id: "2", tag: "p", html: "Some text" }
]);
function handleAddBlock(id: string) {
const index = blocks.findIndex((b) => b.id === id);
let copiedBlocks = [...blocks];
let newBlock = { id: nanoid(), tag: "p", html: "New block..." };
copiedBlocks.splice(index + 1, 0, newBlock);
setBlocks(copiedBlocks);
}
return <Blocks injectedBlocks={blocks} handleAddBlock={handleAddBlock} />;
}
Blocks
export default function Blocks(props) {
const { injectedBlocks, handleAddBlock } = props;
return (
<>
{injectedBlocks.map((b) => {
return (
<Block
key={b.id}
id={b.id}
tag={b.tag}
html={b.html}
handleAddBlock={handleAddBlock}
/>
);
})}
</>
);
}
Block
export default function Block(props) {
const { id, tag, html, handleAddBlock } = props;
function handleChange(e: React.SyntheticEvent) {}
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === "Enter") {
console.log("Enter pressed on: ", id);
e.preventDefault();
handleAddBlock(id);
}
}
return (
<ContentEditable
tagName={tag}
html={html}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
}
State value not give the updated value while handleAddBlock function calls.
So use like this,
setBlocks((p) => {
let copiedBlocks = [...p];
let newBlock = { id: nanoid(), tag: "p", html: "New block..." };
copiedBlocks.splice(index + 1, 0, newBlock);
return copiedBlocks;
});
This will gives the updated state value immediately.
I have the following code to test the value of the Select element after changing:
it("changes value after selecting another field", () => {
doSetupWork();
let field = screen.getByLabelText("MySelectField");
expect(field).toHaveValue("");
fireEvent.change(field, { target: { value: "1" } });
// Insert one of two options from below
});
However, when I insert the following at the bottom, it does not work:
field = screen.getByLabelText("MySelectField");
expect(field).toHaveValue("1");
and gives the following error message:
Expected the element to have value: 1
Received:
But, when I wrap it in a setTimeout with just 1ms delay, it does work:
setTimeout(() => {
field = screen.getByLabelText("MySelectField");
expect(field).toHaveValue("1");
}, 1);
It feels like there should be a more elegant way of writing this without setTimeout. Any advice?
When I am using react-testing-library I tend to use render when I have events to interact with.
For instance:
In my App.js I have this code on the return method
const handleChoice = () => {};
const attributes = [
{ label: "One", value: "1" },
{ label: "Two", value: "2" },
{ label: "Three", value: "3" }
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<select onChange={handleChoice} data-testid="MySelectField">
<option value="0">Zero</option>
{attributes.map((item) => {
return (
<option key={item.value} value={item.value}>
{item.label}
</option>
);
})}
</select>
</div>
);
then my test would be something like this:
import { fireEvent, render } from "#testing-library/react";
import "#testing-library/jest-dom";
import App from "./App";
it("changes value after selecting another field", () => {
const { getByTestId } = render(<App />);
let field = getByTestId("MySelectField");
expect(field).toHaveValue("0");
fireEvent.change(field, { target: { value: "1" } });
expect(field.value).toBe("1");
fireEvent.change(field, { target: { value: "3" } });
expect(field.value).toBe("3");
// Insert one of two options from below
});
Take a look in this sandbox to see it working.
https://codesandbox.io/s/ecstatic-https-hzi5n?file=/src/App.spec.js
You could try using waitFor instead of setTimeout:
import {waitFor} from '#testing-library/react'
...
await waitFor(() => screen.getByLabelText("MySelectField").toHaveValue("1"))
What you're seeing is that it takes a finite amount of time for your app to react (excuse the pun) to the change that has occurred - specifically, it needs to re-render.
And yes, there is a nicer way - the waitFor function from testing-library/react:
import { screen, waitFor } from '#testing-library/react';
...
it("changes value after selecting another field", async () => {
...
fireEvent.change(field, { target: { value: "1" } });
await waitFor(async () => {
field = screen.getByLabelText("MySelectField");
expect(field).toHaveValue("1");
});
}
Note that the entire test body (i.e. after the test's name) has to be declared async in order to be able to await the new waitFor block.
I am currently writing tests for a Vue Component which implements a Vuetify Switch. As part of the testing I want to check the functionality of the vuetify switch. I am having troubling triggering a click on the switch to then verify that the switches value has changed (and once I have done that I will verify that the value bound to the switch has changed as well)
I have looked at the API docs for Vuetify and there are no methods to directly set the state of a Vuetify switch which is bewildering in my opinion. Because of this I am trying to perform a click on the VSwitch component using wrapper.find().trigger('click') but this isn't changing the switch value, leading me to believe the click isn't doing anything at all.
Below are two tests
the first checks that the switch has the correct state on creation, which is passing
The second tries to perform a click event and check that the state has changed, which is failing
Any help in resolving this problem would be greatly appreciated.
switch.vue
<template>
<v-row>
<v-col>
<label class="label-text" :for="`${fieldLabel}`">{{labelText}}</label>
<v-row>
<label class="left-label">{{toggleLeftText}}</label>
<v-switch
:id="`${fieldLabel}`"
v-model="toggleState"
class="ma-0 pa-0"
:data-qa="`${fieldLabel}Checkbox`"
>
</v-switch>
<label class="right-label">{{toggleRightText}}</label>
</v-row>
<!--Hidden input field includes switch value in form when submitted-->
<input type="hidden" :value="toggleState" :name="`${fieldLabel}`">
</v-col>
</v-row>
</template>
<script>
export default {
name: "Switch",
props: {
fieldLabel: {
type: String,
required: true
},
labelText: {
type: String,
required: true
},
toggleLeftText: {
type: String,
required: true
},
toggleRightText: {
type: String,
required: true
},
toggleValue: {
type: Boolean,
required: true
},
},
data: function () {
return {
toggleState: this.toggleValue
}
}
}
</script>
switch.spec.js
describe('Switch', () => {
const toggleState = true;
const localVue = createLocalVue();
localVue.use(Vuetify, {
components: {
VRow,
VCol,
VSwitch,
InputError
}
});
const wrapperFactory = () => {
return shallowMount(Switch, {
localVue,
vuetify: new Vuetify(),
propsData: testProps,
});
};
const testProps = {
labelText: "Test Label",
fieldLabel: "testLabel",
toggleLeftText: "No",
toggleRightText: "Yes",
toggleValue: toggleState
};
let wrapper;
beforeEach(() => {
wrapper = wrapperFactory(testProps);
});
afterEach(() => {
wrapper.destroy();
});
it("should have correct toggle value", () => {
const vSwitch = wrapper.find(VSwitch);
expect(vSwitch.vm.value).toBe(toggleState);
});
it("should have correct toggle value after click", async () => {
const vSwitch = wrapper.find(VSwitch);
await vSwitch.trigger('click');
expect(vSwitch.vm.value).toBe(!toggleState);
});
});
I might be a bit late for answering your question, but this way you should be able to get your v-switch.
const vSwitch = wrapper.find({ name: 'v-switch' });
and then trigger the event with
vSwitch.$emit('change', <true or false>);, depending on what you're testing.
The limit with this approach is that if you have multiple v-switches in your code, you would need to target them with a data-test-id, for example like this:
<v-switch data-test-id="my-switch-1"> ... </v-switch>;
<v-switch data-test-id="my-switch-2"> ... </v-switch>;
and then I defined a helper function on top of my test file, like so:
const getSwitchComponent = (wrapper: Wrapper<Vue>, testId: string): Wrapper<Vue> => {
const switches = wrapper.findAll({ name: 'v-switch' });
const component = switches.wrappers.find(wrapper =>
wrapper.contains(`[data-test-id="${testId}"]`),
);
if (!component) {
throw Error(`Element not found: ${testId}`);
}
return component;
};
which will let you do something like this:
const mySwitch1 = getSwitchComponent(wrapper, 'my-switch-1');
const mySwitch2 = getSwitchComponent(wrapper, 'my-switch-2');
and trigger the change event so:
mySwitch1.vm.$emit('change', false);
mySwitch2.vm.$emit('change', true);
I have a function named toggleFilter() in a react component which looks like this:
toggleFilter = (filterType, filterName) => {
const filterApplied = this.state.appliedFilterList[filterType].includes(filterName);
if (filterApplied) {
//Remove the applied filter
this.setState(prevState => ({
appliedFilterList: {
...prevState.appliedFilterList,
[filterType]: prevState.appliedFilterList[filterType].filter(filter => filter !== filterName)
}
}));
} else {
//Add the filter
this.setState(prevState => ({
appliedFilterList: {
...prevState.appliedFilterList,
[filterType]: [...prevState.appliedFilterList[filterType], filterName]
}
}));
}
};
This function is being passed to the child components as :
<ChildComponent toggleFilter={this.toggleFilter} />
So, i am trying to test this toggleFilter() function like this:
it("checks for the function calls", () => {
const toggleFilterMockFn = jest.fn();
const component = shallow(
<ProductList
headerText="Hello World"
productList={data}
paginationSize="10"
accessFilters={["a 1", "a 2"]}
bandwidthFilters={["b 1", "b 2"]}
termsFilters={["t 1", "t 2"]}
appliedFilterList={appliedFilter}
toggleFilter={toggleFilterMockFn}
/>
);
component.find(FilterDropdownContent).prop("toggleFilter")({ target: { value: "someValue" } });
});
But I get the error saying :
TypeError: Cannot read property 'includes' of undefined
What may be causing the issue? Can someone please help me with this.
EDIT 1: I tried the below test case:
expect(toggleFilterMockFn).toHaveBeenCalledWith(appliedFilter, "access");
But I get the below error :
expect(jest.fn()).toHaveBeenCalledWith(expected)
Expected mock function to have been called with:
[{"access": ["Access Type Of The Service"], "bandwidth": ["the allowed band width ", "the allowed band width"], "term": ["term associated with the service"]}, "access"]
But it was not called.
You can't render a parent and test a child function like that. Instead, you should render <FilterDropdownContent /> directly, and then write a test that simulates an event (like click) and checks to see if the function was called.
Something like this for example:
import React from 'react';
import { shallow } from 'enzyme';
describe('<FilterDropdownContent />', () => {
let wrapper, toggleFilter;
beforeEach(() => {
toggleFilter = jest.fn();
wrapper = shallow(
<FilterDropdownContent
toggleFilter={toggleFilter}
/>
);
});
describe('when clicking the .toggle-filter button', () => {
it('calls `props.toggleFilter()` with the correct data', () => {
wrapper.find('.toggle-filter').simulate('click');
expect(toggleFilter).toHaveBeenCalledWith({ target: { value: 'someValue' } });
});
}):
});
In this example, clicking a link with the .toggle-filter class calls the function, but you should be able to adapt this to your specific implementation.