Should an element's text contents be tested, or only the visibility thereof? I think this is a question of what is an implementation detail.
Example:
it('renders post body', async () => {
getPost.resolves(fakePost)
const { getByTestId } = render(<Post />)
await wait(() => getByTestId('post-body'))
expect(getByTestId('post-body')).toBeVisible()
// Should this next line be included?
expect(getByTestId('post-body')).toHaveTextContent(fakePost.body)
})
I feel like it is an implementation detail regarding how the body text is rendered, that I should only care that something was rendered.
For example, I next I want to store the body text as markdown and render it as HTML. To implement this, I must first change the test, because no longer will the stored text be equal to what is rendered in the DOM.
However, if only testing the visibility of a rendered element, there is no guarantee that element actually contains anything. I feel the test should be safer than that.
expect(getByTestId('post-body')).not.toBeEmpty() comes to mind in the jest-dom api, but that would pass even if the element contained only another element with no actual text contents.
Especially thanks to the guiding principals, I think it’s fair to say if you are testing your component or app the same way you would instruct a human to test it in production, then you are doing it right.
If your component is taking an API call, and formatting it into Markdown, then you should test that it is actually happening correctly. How the component is rendering (and mimicking it in your test) is an example of testing implementation details. Testing what the component renders is not.
I know it’s a fine line, but I think you should include your last line. I also think it’d be great if you could find a way to query by something other than test-id.
Related
I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.
I'm writing some tests for a React app using Testing Library. I want to check that some text appears, but I need to check it appears in a particular place because I know it already appears somewhere else.
The Testing Library documentation for queries says that the getByText query takes a container parameter, which I guessed lets you search within that container. I tried doing this, with the container and text parameters in the order specified in the docs:
const container = getByTestId('my-test-id');
expect(getByText(container, 'some text')).toBeTruthy();
and I get an error: matcher.test is not a function.
If I put the params the other way round:
const container = getByTestId('my-test-id');
expect(getByText('some text', container)).toBeTruthy();
I get a different error: Found multiple elements with the text: some text
Which means it's not searching inside the specified container.
I think I'm not understanding how getByText works. What am I doing wrong?
Better to use within for this sort of things:
render(<MyComponent />)
const { getByText } = within(screen.getByTestId('my-test-id'))
expect(getByText('some text')).toBeInTheDocument()
Another way to do this
import {render, screen} from '#testing-library/react';
...
render(<MyComponent />);
expect(screen.getByTestId('my-test-id')).toHaveTextContent('some text');
Note it is now recommended to use screen instead of the results of render.
(StackOverflow post the points to a KC Dobbs Article explaining why: react-testing-library - Screen vs Render queries)
This way you can be more precise, focusing in specific item:
expect(queryByTestId("helperText")?.textContent).toContain("Help me!");
I'm trying to reinitialize/ redraw all MDC components for form elements when their values change via javascript, but so far my attempts have fallen short. Is there an easy way to achieve this with a built in MDC method that I'm unaware of?
I created a custom way to reload the MDC components with a data-mdc-reload html attribute that fires on click but this isn't quite doing the job.
Here's a codepen showing the issue: https://codepen.io/oneezy/pen/XvMavP
click the UPDATE FORM VALUES button to add data
the VALUE output in red means the component is broke/ blue means it works
click the RESET button to reset data to initial state (this is broke too)
Javascript
// MDC Reload Component
function mdcReload(time = 1) {
var components = mdc.autoInit();
let reloadComponents = document.querySelectorAll('[data-mdc-reload]');
for (const reloadItem of reloadComponents) {
reloadItem.addEventListener("click", async () => {
setTimeout(function() {
components.forEach((c) => c.layout && c.layout());
}, time);
});
}
}
// Initialize MDC Components
mdcReload();
You can use the Foundations and Adapters provided by MDC.
I would suggest you make a MDC instance of every component and use the built in methods in mdc.COMPONENT.foundation_.adapter_.
Here is the modified pen
The issue was that you need to call floatLabel(true) to let the labels float after you set their values.
if (c.foundation_.adapter_ && c.foundation_.adapter_.floatLabel) {
c.foundation_.adapter_.floatLabel(true);
}
Furthermore I changed the select component to
// $(MDCselect).val('Option 3');
// Instantiate the MDC select component.
const mdcSelect = mdc.select.MDCSelect.attachTo(document.querySelector('.mdc-select'));
mdcSelect.foundation_.adapter_.setValue('Option 3');
Hope it helps !
I found that the easiest way to refresh the labels for the MDC components after setting a custom value via javascript is to send a Blur event to the input like this:
yourMDCInput.dispatchEvent(new Event('blur'))
This will leave the MDC component to decide which action it has to take, so it floats the label if there is a value set or resets the label if there is no value set.
It is quite annoying, but without digging into the code of the MDC foundation to find a better solution, I couldn't spot any better solution in the docs (which are incomplete and partly wrong anyways).
If you can, try using the MDC class instance to set your values - in that case, the MDC component is informed about the change and will behave as intended. When using autoInit, please note that the docs say the MDC class instance is attached to the root <div> while it is actually attached to the <label> where the data-mdc-auto-init attribute is set.
Assuming you wrap an MDCTextField in a <div id='my-text-field'>, you could do something like:
document.querySelector('#my-text-field label').MDCTextField.value = 'hello world'
and the field will update as expected.
I'm having a small problem and hope that maybe here i could find a solution.
We have a website that we created with React, we use this package to do server side rendering, and throw the resulting files in S3.
https://github.com/markdalgleish/static-site-generator-webpack-plugin
Then we do ReactDom.hydrate, like
if (typeof document !== 'undefined') {
ReactDOM.hydrate(
<BrowserRouter context={{}} initialEntries={['/']} initialIndex={0}>
{ renderRoutes(Routes) }
</BrowserRouter>,
document.getElementById('root')
);
}
To attach event listeners, and change the content for logged in users, and the sort.
Normally everything is fine, but if the DOM on the SSR result and the new one is too similar it breaks. For example.
<nav>
{!isLoggedin && Login}
{isLoggedin && Options}
</nav>
In a particular case like this, isLoggedIn is always false on SSR, so the login button will render first. Then hydrate will change to display the options button; the options word appears but the href is still the same, which is bad.
I tried using the key attribute, or changing some other properties, they get ignored, but I haven't found a way to prevent that, apart from making the DOM different, but is not always possible, like in that menu example.
Any tips or pointers appreciated!
The program you mentioned is only about 200 lines of code, consider reading it: https://github.com/markdalgleish/static-site-generator-webpack-plugin/blob/master/index.js also, it doesn't appear like "having the same dom" is an issue, since the code doesn't appear to have dom comparison routines at first glance, perhaps the problem is somewhere else, not "having the same dom".
If I defined a ReactJS class in say dialog.js:
var divStyle = {
padding: '15px',
paddingBottom: '12px'
};
var Dialog = React.createClass({
render: function() {
return (
<div style={divStyle}>...</div>
);
}
});
In the above I define a class.. but in every example I see there is also the React.renderComponent(<Dialog/>,document.getElementById('someId'));
The problem I have with this is.. if I want to use this component on different pages with different IDs to render it at, or perhaps for several different IDs, I can't hard-code the id in the actual class it's at. I supposed I can pass in the ID in some manner??
But furthermore, I also want to be able to render the component in say a different JS class, one loaded AFTER the loading of this js class. I have an app.js class for my SPA app, that is loaded last. In it, when a user clicks a link, I may want to only render this component at that time. I do realize in the render method I can control in some way whether or not it's actually rendered.. but my thinking is also to not even bothering inserting it into the DOM unless an action occurs. Sort of like lazy insertion I guess?
I've tried in my app.js:
function () {
React.renderComponent(<Dialog/>,...);
}
but obviously this doesn't work as this JS is not a JSX JS file. So I tried using React.renderComponent(Dialog,...); thinking the Dialog class is globally defined, so it would be available, but that too fails.
So how do I use the Dialog class in another function to render it?
I think you're getting something conceptually wrong here:
but in every example I see there is also the React.renderComponent(<Dialog/>,document.getElementById('someId'));
The fact that the short examples are followed by a renderComponent call is for the trivial reason to show the demos. Once again, keep in mind that in React, <Dialog /> desugars to Dialog(). If you don't call it nothing happens.
So, simply don't call until you need it.
Also, I don't understand what you mean by:
but obviously this doesn't work as this JS is not a JSX JS file
Since you can just process that file through JSX?
If you're having trouble mentally mapping JSX to functions and vice-versa: try the live compiler here: http://facebook.github.io/react/jsx-compiler.html