Test url params in react-testing-library without react-router - reactjs

I have a widget that uses React but not the react-router package. One component checks for the existence of a url param via the URLSearchParams API and then opens or closes using styling:
function App() {
const openWidget =
new URLSearchParams(window.location.search)
.get("openWidget")
?.toLowerCase() === "true";
const [show, setShow] = useState(openWidget);
return (<StyledComponent $show={show}>something</StyledComponent>) // set height to 0 or 100vh based on $show
}
However, I can't figure out how to test this in rtl. My current test is like so:
const location = {
...window.location,
search: "openWidget=true",
};
Object.defineProperty(window, "location", {
value: location,
});
customRender(<ChatWindow />, { contextProps: amendedMockContext });
const chatWindow = screen.getByTestId("chat-window");
expect(chatWindow).toHaveStyle("max-height: 100vh");
But when I log out window.location, I get this: { search: '' }. My test throws this error:
- Expected
- max-height: 100vh;
+ max-height: 0px;
What's the right way to test values using search params?
Note: I have other tests using the same customRender and amendedMockContext - it has nothing to do with them.
EDIT: I tried making a minimal reproducible example, and it works just fine: Code Sandbox . But in my actual project it still doesn't work. The code sandbox is set up identically (context, styles, customerRender, everything) yet that one works, and in my own project window.location.search still returns an empty string. I am utterly dumbfounded.

The last comment in this issue on GitHub Jest repository will help you
https://github.com/facebook/jest/issues/5124#issuecomment-914606939
Using history.replaceState should allow you to change URL search parameter.
Check the other replies in the issue I shared, seems that most of the techniques that were working before to change window.location are not working anymore in the last versions of Jest.
Example
Following your comment about the URL parameter, the point to note here is that, for this test in particular, the only thing that is important for you is the query string, so you can use any valid origin as parameter and eventually, if in another test you need a specific origin, you can pass it to the function. Let me show you an example, created starting from the link I shared.
function changeJSDOMURL(search, url = "https://www.example.com/") {
const newURL = new URL(url);
newURL.search = new URLSearchParams(search);
const href = `${window.origin}${newURL.pathname}${newURL.search}${newURL.hash}`;
history.replaceState(history.state, null, href);
}
// This will consider the default URL, it is OK for your test
changeJSDOMURL({ openWidget: "true" });
// If you need to pass an origin different from the default one
changeJSDOMURL({ openWidget: "true" }, "https://www.something.io");

Related

Next.js: What is the best way to solve the warning about different server & client styles based on react-spring?

I'm working with Next.js and using a react-spring library to get an animation for a bottomsheet component. It works, however there is a warning appears:
Warning: Prop style did not match. Server: "transform:translate3d(0,Infinitypx,0)" Client: "transform:translate3d(0,652px,0)"
I've carefully investigated this warning and know that it's about incorrect rendering of the HTML element on the server and on the client side. It's clear that on the server side there is no viewport height and thus react-spring can't calculate normally the final value and Next.js registers it as an one value with Infinity and then blames on the client side when the value is calculated correctly due to available viewport height.
I'm wondering what is the best way to rid of this error?
Unfortunatelly I can't catch the react-spring calculation stage and to set a correct value.Tere is no API to do it and basically I just don't know the user's viewport height.
I've thinking about the using indexOf for the value and check if the Infinity presented and replace it for ex: by 0
however it still doesn't solve a problem as the final value will be different anyway.
Maybe someone has an idea or some link to docs etc. where I could find a solution for that?
Basically it's just a warning but I'd like to fix it anyway.
Here is the example code:
import { a, config, useSpring } from '#react-spring/web';
export function BottomSheet({propsHeight}) {
const finalHeight = propsHeight || height - 62;
const display = y.to((py) => (py < finalHeight ? 'flex' : 'none'));
const [{ y }, api] = useSpring(() => ({ y: finalHeight }));
const open = (dragEvent?: any) => {
const canceled = dragEvent?.canceled;
// when cancel is true, it means that the user passed the upwards threshold
// so need to change the spring config to create a nice wobbly effect
api.start({
y: 0,
immediate: false,
config: canceled ? config.wobbly : config.stiff,
});
};
const close = (velocity = 0) => {
api.start({
y: finalHeight,
immediate: false,
onResolve() {
if (onClose) {
onClose();
}
},
config: { ...config.stiff, velocity },
});
};
useEffect(() => {
// provide open/close actions to parent control
getActions(open, close);
}, []);
// pseudo hmtl. Removed all other markup to simplify things
return (<a.div
style={{
y, // Here is the problem of the server & client incorrect values
}}
/>)
}
I highly appreciate any help!
Kind Regards
The only one solution I've found so far for this use case it's rendering the component on client side only and, basically, it makes sense because this code is based on the browser API. I don't see any other possible solutions
To achieve it you can use dymanic imports and Next.js supports them well: here is the docs
You should disable the SSR in the import options and the component will be rendered on the client side only.
Like this:
import dynamic from 'next/dynamic';
const BottomSheetDynamic = dynamic(
() =>
import('#mylibrary/components/bottomsheet/BottomSheet').then(
//this component doesn't have default import so should do it in this way
(mod) => mod.BottomSheetexport
),
{
ssr: false,
}
);

s.indexOf is not a function at Function.n.fromString

I have no idea why I'm getting this error or from where is coming from because I think I'm not using that ?
I'm doing a firebase update after an user update a row from DataGrid MUI and I'm doing the update as I would normally do nothing different at all and it just jumps into that error.
I'm not sure if is an React error, JS error, Firebase error, MUI error. but I THINK is a firebase error because the path says so
This is what I was trying to do:
const [editRowsModel, setEditRowsModel] = React.useState({});
const [editRowData, setEditRowData] = React.useState({});
const handleEditRowsModelChange = React.useCallback(
(model) => {
const editedIds = Object.keys(model);
if (editedIds.length === 0) {
console.log(editRowData)
console.log(editRowData.uid)
console.log(editRowData.nombre)
console.log(editRowData.colegio)
console.log(editRowData.grado)
const temporalQuery = db.collection("usuarios").doc(user.uid).collection("estudiantes").doc(editRowData.uid);
temporalQuery.update({
nombre: editRowData.nombre,
colegio: editRowData.colegio,
grado: editRowData.grado
})
} else {
setEditRowData(model[editedIds[0]]);
}
setEditRowsModel(model);
},
[editRowData]
);
This is what the console.log shows up. I honestly don't see any error in the way I code it that's how I always do it, never had an issue before. First time I update from a nested collection though
This is how it looks in the firebase
And yes the user.uid also comes correctly
Found the issue is because the data comes with a whole string of information instead of just the value as I though it was coming.
All I had to do was to call the field + the value when I was using it.
Ej:
if I wanted the uid I had to call it editRowData.uid.value
the .doc(name) only accepts strings, so check if your user.uid is a string

Changing an input parameter within React component

I am going through the code of a project and I see the following code:
export const FileLink = React.memo(({ url, data, ext, linkContent }) => {
...
...
if (!url.includes('?')) {
url += '?'
}
if (!url.endsWith('?')) {
url += '&'
}
return <a href={`${url}file_format=${ext}`}>{linkContent}</a>
})
It is working fine and no bugs appear in app behavior. But url is a passed parameter and it is changed within the FileLink: from what I read React components should be pure functions. So, I wonder whether its ok to do that, under which circumstances, and if not - why? What can go wrong? Any examples of how it could mess up the app?
(If interested to see the full code: https://github.com/broadinstitute/seqr/blob/8b4419285dfac9365c5c500bbb87b89808c0cedd/ui/shared/components/buttons/ExportTableButton.jsx#L37)
url is a local variable. Reassigning that variable, which is all this code is doing to it, has no possibility of affecting code outside of this function call. It doesn't make the function impure.
Now, if you were passed in an object, and you started mutating that object, then that would break purity. Because if the component that passed you this object is still using it, then it can "see" that change. For example:
const Example = ({ someObjectProp }) => {
someObjectProp.name = 'bob';
}

How does document.getSelection work under reactjs environment?

I was working on a electron-react project for epub file. Right now, I am planning to make the app capable of selecting text field and highlight it.
To achieve it, I was trying to use web's Window.getSelection api. However, there are some really weird things come up like in this.
In short, I am unable to capture the Selection object. It seems like even if I log the Selection object, this object could somehow jump to something else. Also, I cannot even serialize the Selection object with JSON.stringfy. This is super surprising, and this is my first time seeing something like this (I will get empty object to stringfy the Selection object).
So how to use Window.getSelection properly under react-electron environment? Or this api will not work well for text content which is generated by react's dangerouslySetInnerHTML?
Looks like the window.getSelection api needs to toString the selected object.
const getSelectionHandler = () => {
const selection = window.getSelection();
console.log("Got selection", selection.toString());
};
Super simple textarea and button to get selection react demo
this solution might help someone, catch last selection
const selection = useRef('');
const handleSelection = () => {
const text = document.getSelection().toString();
if (text) {
selection.current = text;
}
}
useEffect(() => {
document.addEventListener('selectionchange', handleSelection);
return () => {
document.removeEventListener('selectionchange', handleSelection);
};
});

Not allow to require by using variable? [duplicate]

I have read several posts about issues that people are having with React Native and the require() function when trying to require a dynamic resource such as:
Dynamic (fails):
urlName = "sampleData.json";
data = require('../' + urlName);
vs. Static (succeeds):
data = require('../sampleData.json');
I have read on some threads that this is a bug in React Native and in others that this is a feature.
Is there a new way to require a dynamic resource within a function?
Related Posts (all fairly old in React time):
Importing Text from local json file in React native
React Native - Dynamically List/Require Files In Directory
React Native - Image Require Module using Dynamic Names
React Native: how to use require(path) with dynamic urls?
As i have heard of, react's require() only uses static url not variables, that means that you have to do require('/path/file'), take a look at this issue on github and this one for more alternative solutions, there are a couple of other ways to do it!
for e.g
const images = {
profile: {
profile: require('./profile/profile.png'),
comments: require('./profile/comments.png'),
},
image1: require('./image1.jpg'),
image2: require('./image2.jpg'),
};
export default images;
then
import Images from './img/index';
render() {
<Image source={Images.profile.comments} />
}
from this answer
Here is my solution.
Setup
File structure:
app
|--src
|--assets
|--images
|--logos
|--small_kl_logo.png
|--small_a1_logo.png
|--small_kc_logo.png
|--small_nv_logo.png
|--small_other_logo.png
|--index.js
|--SearchableList.js
In index.js, I have this:
const images = {
logos: {
kl: require('./logos/small_kl_logo.png'),
a1: require('./logos/small_a1_logo.png'),
kc: require('./logos/small_kc_logo.png'),
nv: require('./logos/small_nv_logo.png'),
other: require('./logos/small_other_logo.png'),
}
};
export default images;
In my SearchableList.js component, I then imported the Images component like this:
import Images from './assets/images';
I then created a new function imageSelect in my component:
imageSelect = network => {
if (network === null) {
return Images.logos.other;
}
const networkArray = {
'KL': Images.logos.kl,
'A1': Images.logos.a1,
'KC': Images.logos.kc,
'NV': Images.logos.nv,
'Other': Images.logos.other,
};
return networkArray[network];
};
Then in my components render function I call this new imageSelect function to dynamically assign the desired Image based on the value in the this.state.network:
render() {
<Image source={this.imageSelect(this.state.network)} />
}
The value passed into the imageSelect function could be any dynamic string. I just chose to have it set in the state first and then passed in.
I hope this answer helps. :)
For anyone reading this that cannot work with the existing answers, I have an alternative.
First I'll explain my scenario. We have a mono repo with a number of packages (large react-native app). I want to dynamically import a bunch of locale files for i18n without having to keep a central registry in some magic file. There could be a number of teams working in the same monorepo and the DX we want is for package developers to be able to just add their local files in a known folder {{packageName}}/locales/en.json and have our core i18n functionality pick up their strings.
After several less than ideal solutions, I finally landed on https://github.com/kentcdodds/babel-plugin-preval as an ideal solution for us. This is how I did it:
const packageEnFiles = preval`
const fs = require('fs');
const path = require('path');
const paths = [];
const pathToPackages = path.join(__dirname, '../../../../packages/');
fs.readdirSync(pathToPackages)
.filter(name => fs.lstatSync(path.join(pathToPackages, name)).isDirectory())
.forEach(dir => {
if (fs.readdirSync(path.join(pathToPackages, dir)).find(name => name === 'locales')) {
const rawContents = fs.readFileSync(path.join(pathToPackages, dir, 'locales/en.json'), 'utf8');
paths.push({
name: dir,
contents: JSON.parse(rawContents),
});
}
});
module.exports = paths;
`;
Then I can just iterate over this list and add the local files to i18next:
packageEnFiles.forEach(file => {
i18n.addResourceBundle('en', file.name, file.contents);
});
If you need to switch between multiple locally stored images, you can also use this way:
var titleImg;
var textColor;
switch (this.props.data.title) {
case 'Футбол':
titleImg = require('../res/soccer.png');
textColor = '#76a963';
break;
case 'Баскетбол':
titleImg = require('../res/basketball.png');
textColor = '#d47b19';
break;
case 'Хоккей':
titleImg = require('../res/hockey.png');
textColor = '#3381d0';
break;
case 'Теннис':
titleImg = require('../res/tennis.png');
textColor = '#d6b031';
break;
}
In this snippet I change variables titleImg and textColor depending of the prop. I have put this snippet directly in render() method.
I have found that a dynamic path for require() works when it starts with a static string. For example require("./" + path) works, whereas require(path) doesn't.
Simple to dynamic images (using require)
Example array(into state)
this.state={
newimage: require('../../../src/assets/group/kids_room.png'),
randomImages=[
{
image:require('../../../src/assets/group/kids_room.png')
},
{
image:require('../../../src/assets/group/kids_room2.png')
}
,
{
image:require('../../../src/assets/group/kids_room3.png')
}
]
}
Trigger image( like when press button(i select image random number betwenn 0-2))
let setImage=>(){
this.setState({newimage:this.state.randomImages[Math.floor(Math.random() * 3)];
})
}
view
<Image
style={{ width: 30, height: 30 ,zIndex: 500 }}
source={this.state.newimage}
/>
Hey lads I rounded another way to require It's ugly but works. Images dynamically. Instead of storing your URL in the state you store the entire JSX. For an example:
state = {
image: []
};
Instead of
let imageURL = `'../assets/myImage.png'`
this.state.image = imageURL
You use
let greatImage = (<Image source={require(../assets/myImage.png)}></Image>)
this.state.image = greatImage
To render in the JSX
{this.state.image}
You can style your image in the variable too. I had to use some if statements to render some images dynamically and after break my head for 2 hours this was the way that solved my problem. Like I said It's ugly and probably wrong.
Are you using a module bundler like webpack?
If so, you can try require.ensure()
See: https://webpack.js.org/guides/code-splitting/#dynamic-imports
Reading through the docs, I've found a working answer and I'm able to use dynamic images, in the docs they refer to it as Network Images here
https://facebook.github.io/react-native/docs/images#network-images
Not sure if this can be applied to other file types, but as they list require with non image types
You would need to use the uri: call
data = {uri: urlName}
For me I got images working dynamically with this
<Image source={{uri: image}} />
Try the solution mentioned in this thread for Android. This solves the issue but unfortunately, it's only for android.
But make sure to run react-native run-android after every update. Else, the added images won't appear in the app.
This seems to work :
const {
messages,
} = require(`../${dynamicPath}/messages.specific`);

Resources