Clearing a Draft.js textarea in automated testing - reactjs

In my react app I am using a component that is building on top of draft.js.
Unfortunately I can not interact with it as I would with a normal textarea when using react-testing.
I want to input text, clear that text and then input new text. With an empty draft.js editor it is easy enough:
const textarea = screen.getByRole('textbox');
const event = createEvent.paste(textarea, {
clipboardData: {
types: ['text/plain'],
getData: () => 'MyText',
},
});
fireEvent(textarea, event);
I now want to clear the input in order to paste a new input. If I just invoke a second paste event, it will paste the data before what is already in the textarea so:
const textarea = screen.getByRole('textbox');
const event = createEvent.paste(textarea, {
clipboardData: {
types: ['text/plain'],
getData: () => 'MyText',
},
});
fireEvent(textarea, event);
fireEvent(textarea, event);
will result in the content of the textbox being MyTextMyText.
I tried using the change event to clear the textbox:
const textarea = screen.getByRole('textbox');
const changeEvent = createEvent.change(textarea, {
target: {
value: ''
},
});
fireEvent(textarea, changeEvent);
resulting in: The given element does not have a value setter
So my question is: What is the correct way to clear a draft.js textarea between pasting new content into it?

Related

How can I grab the text value from content in tiptap react (typescript) wysiwyg?

I am trying to grab the text that is typed into the wysiwig to store it into my database.
I have tried a lot of different stuff and this was my most recent iteration:
<EditorContent editor={editor} className="contentInput" style={{ backgroundColor:'lightgrey' }} value={state.content} onChange={(evt) => setState({ ...state, content: evt.target.value })}/>
and with that I am getting the error that value is not a property on target. I believe this is because it is no longer an HTML input element, but I am not sure how I would now grab that content to put in the database?
const editor = useEditor({
// ...
onUpdate({ editor }) {
setState(editor.getJSON());
},
});
First you need to create a state with useState hook, then create the editor with useEditor Hook, inside it you're going to to add event onUpdate and set the value of the editorContent state on every update on the editor.
const [editorContent, setEditorContent] = useState("");
const editor = useEditor({
extensions: [StarterKit],
content: "<p>Hello World! šŸŒŽļø</p>",
onUpdate({ editor }) {
setEditorContent(editor.getHTML());
},
});
const msg = editor?.getText();
console.log("msg", msg);
In console you'll get the normal text which is written in tiptap input field.
Use optional chaining because editor initially return null and you might get error of not reading value of undefined.

Trouble on testing onChange property from ant design

Iā€™m having trouble in testing the behaviour of an ant design component.
I use Text component from ant design with ā€˜editableā€™ and ā€˜onChangeā€™ properties for editing a comment. For saving the new content I have to click anywhere on the page or to press enter and the onChange function will be triggered. I tested manually and everything works fine.
In tests, I manage to edit the content of the comment, but when I simulate the enter pressing (or the clicking on any DOM element) the onChange function is not called.
What should I do in order to trigger the onChange function?
Here's my component:
<Text editable={{ onChange: editComment }}
id={"edit-comment-" + props.commentIndex}>
{props.body}
</Text>
Here's my test:
the test includes both methods of triggering the onChange function, but I did not use both of them at the same time
test('Edit comment request', async () => {
const fakeResponse = {
success: 1
};
jest.spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve(fakeResponse)
})
);
const editButton = container.querySelector("span[aria-label='edit']");
await fireEvent.click(editButton);
// Edit the comment content
fireEvent.change(screen.getByRole('textbox'), { target: { value: "edited comment" } });
// Save the comment content by pressing enter
await fireEvent.keyPress(screen.queryByText(/edited comment/i),
{ key: "Enter", code: "Enter", keyCode:13, charCode: 13 });
// Save the comment content by clicking on a random DOM element
await fireEvent.click(container.querySelector('.ant-comment-content-author'));
await wait(() => expect(global.fetch).toHaveBeenCalled());
});
Try to set _defaultValue property before dispatch the onChange event:
const textbox = screen.getByRole('textbox');
textbox.value = "edited comment";
textbox._valueTracker.setValue(null);
fireEvent.change(textbox, {bubbles: true});
To React the value is still unchanged. Check this issue: https://github.com/facebook/react/issues/11488

Use toHaveBeenCalledWith to check EventTarget

I've built a custom Input React component (think wrapper) that renders an HTML input element. When a value is entered the change is passed to the parent component like this:
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
setCurrentValue(event.target.value);
props.onChange(event);
};
Everything works as expected but now I want to write a test for it:
it('Should render a Text Input', () => {
const onChange = jest.fn();
const { queryByTestId } = renderDom(
<Input type={InputTypesEnum.TEXT} name='car' onChange={onChange} />
);
const textInput = queryByTestId('text-input');
expect(textInput).toBeTruthy();
const event = fireEvent.change(textInput, { target: { value: 'Ford' }});
expect(onChange).toHaveBeenCalledTimes(1);
});
This works fine too except that I want to add a final expect using toHaveBeenCalledWith. I've tried several things but can't figure out how to do it. Any ideas?
Update: I've been reading this: https://reactjs.org/docs/events.html#event-pooling. It appears that if I change handleChange like this:
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
event.persist();
setCurrentValue(event.target.value);
props.onChange(event);
};
then the received object from onChange does change to include my test data. That said, I don't like the idea of altering an important feature of production code (in this case, event pooling) simply to accommodate a test.
You can do something like this with toHaveBeenCalledWith
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.objectContaining({
value: "Ford"
})
})
);

How to set slate value based on the plain string value(serialized)

I am building a rich text editor with slate js on React and I need to parse URL from the content(for example: www.website.me should be transformed to www.website.me).
I have already implemented the function to parse URL from plain text and then wrap the content inside the correct tag.
However, the problem is that I need to display the parsed value as soon as the user is typing on the editor(on onChange event). Here is what I have :
onChange = ({ value }) => {
const { isFocused } = value.selection
if (isFocused !== this.state.isFocused) {
this.setState({ isFocused })
}
const string = this.state.html.serialize(value)
const linkifiedString = linkifyHtml(string) || ''
if (value.document !== this.state.value.document) {
const { onChange } = this.props
onChange && onChange(linkifiedString)
}
// this won't work and will call the onChange method infinitely
this.setState({ value: this.state.html.deserialize(linkifiedString) })
}
Thanks for your help.
Finally, I have found the solution, you have to create your own slate plugin or use this one: https://github.com/enzoferey/slate-instant-replace

Highcharts checkboxes multiply when window resize event fires

I have an app which contains checkbox at the bottom of the chart (by the legend item).
When I resize the app window, event listener (which logs 'resize' events) changes the chart width accordingly and a new checkbox appears on the chart.
A new checkbox also appears as well when the chart theme is changed.
This makes me believe that whenever Highcharts are re-rendered (I use React), a new checkbox is created.
Initially, there is only one checkbox:
After single resize of the page:
After multiple resizes:
Also, there is always one checkbox at the top right corner of the chart (appears after the first resize):
The checkBox is added to the chart only once, at the componendDidMount() cycle. It is added to a single series using this option:
showCheckbox: true
The first and the only time the function that generates charts and sets this option is called in the main components cycle componentDidMount:
Main Container
import Charts from './Charts';
import * as highcharts from 'highcharts';
class MainComponent exteds React.Component<Props, State> {
public async componentDidMount() {
try {
// Here the charts are generated
const initialOpts = Charts.generateCharts();
const min = 1000;
const max = 5000;
highcharts.charts.map(chart => {
chart.xAxis[0].setExtremes(min, max, undefined, false)
});
} catch { ... }
this.chartDimensionsHandler();
window.addEventListener('resize', this.chartDimensionsHandler);
}
private chartDimensionsHandler() {
const chartHeight: number = ZoomService.getChartHeight();
this.setState({ chartHeight });
// When the state is set, component re-renders and causes the issue
}
/*
Theme is passed as props to the main component thus whenever theme changes,
new props arrive and component along with higcharts is also
re-rendered what causes a new checkbox to appear on top right of the chart
*/
}
Charts class
export class ChartService {
public generateCharts() {
return this.charts.map((char) => this.generateDataChart(chart));
}
private generateDataChart(chart: Chart): Options {
const { slices } = chart;
const xValues = slices.map(slice =>
Number(slice[chart.xAxis.name].value)
);
const xAxis = this.generateXAxisOptions(chart);
const yAxis = [ ... ];
const options = { xAxis, yAxis };
// Create line series
try {
const lineSeries = this.constructDataSeries(chart, xValues);
// Create events series
const eventsCharts = this.charts.filter(
x => x.type === 'events'
);
const eventsSeries = eventsCharts.map((evChart: Chart) => {
// Here I call a function which sets 'showCheckbox' to true
const allEvents = this.createEventSeries(evChart!)[0];
allEvents.yAxis = 1;
return allEvents;
});
const otherSeries = ...; // Irrelevant code
// Add all series
const series = [...otherSeries, ...eventsSeries];
return Object.assign(options, { series });
} catch (e) {
return Object.assign(
options,
{ series: [] },
{
lang: {
noData: `Fail.`
}
}
);
}
}
private constructEventSeries(chart) {
const { slices, yAxis, xAxis } = chart;
const xValues = slices.map(slice =>
Number(slice[xAxis.name].value)
);
return yAxis.map((item) => ({
name: item.label,
type: 'line',
data: this.convertEventData(slices, xValues),
lineWidth: 0,
showCheckbox: true, // THE PLACE WHERE I SET showCheckbox to true
marker: {
enabled: true,
radius: 10,
symbol : 'circle'
},
meta: {
type: 'events'
}
}));
}
}
Does anyone have an idea why a new checkbox is added to the chart every single render of it (no matter if the chart is resized or not)?
I found out that whenever resize event is logged, Highcharts automatically calls chart.reflow() function which caused the checkmarks to be multiplied. Component setState function made the situation even worse.
By changing the function which is called by the event from setting component's state into setting chart's dimensions directly with higcharts function chart.setSize(width, height, animation: boolean).
Then in the CSS I set the display of the second checkbox to none by using nth child property (because resize event always creates the second checkbox, there is no way to shut it (probably a bug from highcharts)).
This was my solution.

Resources