How to test anchor's href with react-testing-library - reactjs

I am trying to test my anchor tag. Once I click it, I want to see if the window.location.href is what I expect.
I've tried to render the anchor, click it, and then test window.location.href:
test('should navigate to ... when link is clicked', () => {
const { getByText } = render(Click Me);
const link = getByText('Click Me');
fireEvent.click(link);
expect(window.location.href).toBe("https://www.test.com/");
});
I am expecting the test to pass, but instead the window.location.href is just "http://localhost/" meaning it is not getting updated for whatever reason. I even tried wrapping my expect with await wait, but that didn't work either. I can't find much information about testing anchors with react-testing-library. Maybe there is even a better way to test them than what I am doing. 🤷‍♂ī¸

Jest uses jsdom to run its test. jsdom is simulating a browser but it has some limitations. One of these limitations is the fact that you can't change the location. If you want to test that your link works I suggest to check the href attribute of your <a>:
expect(screen.getByText('Click Me').closest('a')).toHaveAttribute('href', 'https://www.test.com/')

I found a solution that may help others. The <a> element is considered a link role by React Testing Library. This should work:
expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com');

If you are using screen which should be the preferred way, by RTL authors:
const linkEl = screen.getByRole('link', { name: 'Click Me' });
expect(linkEl).toHaveAttribute('href', '...')
Similar, without screen (name can be string or RegExp):
const linkEl = getByRole('link', { name: 'Click Me' });

You can simply use this instead:
expect(getByText("Click Me").href).toBe("https://www.test.com/")

simple and easy.
try this
it('should be a link that have href value to "/login"', () => {
render(<SigningInLink />);
const link = screen.getByRole('link', { name: /log in/i });
expect(link.getAttribute('href')).toBe('/login');
});

This is what I use:
const linkToTest = screen.getByRole("link", { name: /link to test/i })
// I used regex here as a value of name property which ignores casing
expect(linkToTest.getAttribute("href")).toMatchInlineSnapshot();
and then run the test, brackets of toMatchInlineSnapshot will be filled with the value that's there in your code.
This has the advantage of not hard coding it, and maintaining this will be easier.
For eg: it will be filled like this:
expect(linkToTest.getAttribute("href")).toMatchInlineSnapshot(`"link/to/somewhere"`);
and next time, suppose you change this link to something else in your codebase, the runner will ask you if you want to update, press u and it will be updated. (Note, that you need to check that this update is correct).
Know more about inline snapshots on Jest Docs

Maybe its overtly engineered in this case. But you can also use data-testid attribute. This guarantees that you are getting the a tag. I think there are benefit to this for more complex components.
test('should navigate to ... when link is clicked', () => {
const { getByTestId } = render(<a data-testid='link' href="https://test.com">Click Me</a>);
expect(getByTestId('link')).toHaveAttribute('href', 'https://test.com');
});

You may have several links to check on a page. I found these suggestions very useful. What I did to adapt it to checking 5 links on the same page -
query the link on the page with the screen() method best practice - it is more reliable
assert the link is on the page
assign the link to a variable
call event on the link
ensure the url toHaveAttribute method rather than navigating with the window object - In react with the virtual DOM, if you try and navigate to another page the link directs to http://localhost/link rather than testing the redirect
test('should navigate to url1 when link is clicked', () => {
const componentName = render(<Component/>)
const url1 = getByText("https://www.test.com/")
expect(ur1).toBeInTheDocument()
screen.userEvent.click(url1);
expect(url1).toHaveAttribute('href', 'https://www.test.com/')
});

This is what worked for me:
expect(screen.getByText('Click Me').closest('a')?.href).toEqual('https://test.com');

Here is what I did, works for me using testing-library/react
import { render, screen } from '#testing-library/react';
import {HomeFeature} from '../Components/HomeFeature.tsx';
let imp_content = [
{
title: "Next.js",
link: "https://nextjs.org/",
},
{
title: "React",
link: "https://reactjs.org/",
},
{
title: "Firebase",
link: "https://firebase.google.com/",
},
{
title: "Tailwind",
link: "https://tailwindcss.com/",
},
{
title: "TypeScript",
link: "https://www.typescriptlang.org/",
},
{
title: "Jest.js",
link: "https://jestjs.io/",
},
{
title: "testing-library/react",
link: "https://testing-library.com/",
}
];
imp_content.map((content) => {
test(`${content.title} contains ${content.link}`, () => {
render(<HomeFeature />);
expect(screen.getByText(content.title).closest('a')).toHaveAttribute('href', content.link);
})
})

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,
}
);

How to refresh code lenses in monaco editor

I adding custom CodeLenseProvider to my React Monaco Editor, which works great in general:
{
provideCodeLenses(model: monaco.editor.ITextModel, token: monaco.CancellationToken): monaco.languages.ProviderResult<monaco.languages.CodeLensList> {
const lenses: monaco.languages.CodeLens[] = [];
lenses.push(...getLenses());
return {
lenses,
dispose: () => {},
};
},
}
The problem is that my method getLenses is time consuming and asynchronous. Ideally I'd like to e.g. fetch some data when provideCodeLenses is called, or even better, I'd like to be able to call provideCodeLenses manually refresh the lenses.
The steps would be:
I have onChange listetener on
<MonacoEditor onChange={onChange} .../>
this onChange is doing some async action
once the action is finished, I'd like to refresh code lenses
How can I achieve that? I know code lense provider has onDidChange property but it is not really clear to me how to use it or if it helps my case

Component-testing #lexical/react editor: How to assert against HTML rendered by Cypress?

I want to component-test a controlled #lexical/react based input in Cypress. An example scenario:
Given I have a controlled input with initial state "Hello world"
When I click into the input
And I press "! key" + "Home key" + "Arrow right key" + "Arrow right key" + "n key"
Then input content is "Henlo world!"
How can I get the editor's text content, so I can assert against it?
I was hoping I could attach a newly-created editor instance to the rendered content to gain access to native lexical methods:
it('Works as intended', () => {
const Test: FC = () => {
const [state, setState] = useState('Hello world')
return <MyInput value={state} setValue={setState} />
}
mount(<Test />)
cy.get('[contenteditable="true"]').should(editorRoot => {
const editorHTML = editorRoot.get()[0]
const editorHtmlClone = editorHTML.cloneNode(true)
const editor = createEditor()
editor.setRootElement(editorHtmlClone as any)
editor.getEditorState().read(() => {
const textValue = $getRoot().getTextContent()
// Now I can access the editor's value
// but unfortunately it's been overwritten
})
})
})
But unfortunately this fails, because editor.setRootElement clears RootElement's contents.
I would really love to find a way to tap into Lexical's API, so I can test my functionality via the API without having to mirror it's exact implementation in my tests (for example defining exact expected html - it can change, doesn't mean much outside of Lexical and is a worse at describing my desired state).
Are there any options/workarounds to achieve this?
You can write the instance on the element or the window itself on your test environment. That's what we do for the TreeView (debugger)!
useEffect(() => {
const element = treeElementRef.current;
if (element !== null) {
element.__lexicalEditor = editor;
return () => {
element.__lexicalEditor = null;
};
}
}, [editor]);
That said, I would argue that your expectations fit a unit test more than an E2E test. An E2E test should validate the DOM, the HTML (via innerHTML) and selection (via getDOMSelection()).
If you want to learn more about how we do E2E testing you can check Lexical's.
ℹī¸ We're planning to ship a separate testing package for users to be able to leverage all the utilities we have built on top of Playwright over the time rather than having to reimplement them on their own.

How to detect if the React app is actually being viewed

So I have created an app that shows data in realtime obtaining it from devices.
However, I want to make my server not obtain data when nobody is viewing the app.
So essentially I need some way to determine whether the app is currently being viewed, regardless of if it's desktop or mobile, this includes tab is on focus where the app is opened and that is what the user is currently viewing, and there is nothing on top of the browser, so browser opened on the correct tab, but user has explorer on top of it doing something entirely different, this for my case should be false, and for mobile, the same thing including if device is locked (screen off).
The reason for trying to do that, is to reduce the load on the devices, so that data is being requested, only when there is someone to view it.
From what I have researched I found out about the focus and blur events, but I was unable to make it work, and I don't even know if that is the correct approach, but what I have tried is:
Adding event listeners to the window in the App component:
function App() {
useEffect(() => {
window.addEventListener("focus", () => { console.log("viewed")});
window.addEventListener("blur", () => { console.log("hidden")});
})
}
Adding them as props to the App component within index.js:
<App onFocus={() => {console.log("viewed")}} onBlur={() => {console.log("hidden")}}/>
Neither had any kind of effect, I didn't get either of the console outputs.
Is that even the correct approach?
I would add a socket connection to the app. Then the server would be able to know if there are at least X persons connected and act accordingly.
I would suggest you to try socket for this kind of connection tested, but since you also wanna reduce the load for the user, testing if the user is focused in the browser is the way to go.
To achieve it, I won't add this code inside the React component because of the nature of React as all of its components are rendered inside the <div id="root"></div>, other parts of the html page will still be unaffected by this mechanism. So what you probably want to do is to add the code in the index.html and use window.userFocused to pass the value into React from the index
Edit: added focus/blur script
<script>
window.addEventListener("focus", function(event)
{
console.log("window is focused");
window.userFocused = true;
}, false);
window.addEventListener("blur", function(event)
{
console.log("window is blurred");
window.userFocused = false;
}, false);
</script>
So I ended up solving it with pretty much the same code as initiallywith a few slight modifications, I still used an useEffect hook:
const onFocusFunction = () => {
// do whatever when focus is gained
};
const onBlurFunction = () => {
// do whatever when focus is lost
};
useEffect(() => {
onFocusFunction();
window.addEventListener("focus", onFocusFunction);
window.addEventListener("blur", onBlurFunction);
return () => {
onBlurFunction();
window.removeEventListener("focus", onFocusFunction);
window.removeEventListener("blur", onBlurFunction);
};
}, []);
The best way is to use document.
document.onvisibilitychange = () => {
console.log(document.hidden)
}

Clicking a Checkbox in an Enzyme test

I am trying to simulate a click on a material-ui checkbox. I have tried
selectAllCheckbox.simulate("change", { target: { checked: true } });
and
act(() => {
selectAllCheckbox.props().onClick();
});
I have tried re-finding the item and updating the wrapper, and I cannot get the checked prop to change.
I feel like I missing something fundamental.
I have a codesandbox here: https://codesandbox.io/s/enzymetestformaterialuitable-t1ruq
the sandbox has a material-ui table (lifted from their demos page).
TIA
I got a hand from a few sources including https://github.com/airbnb/enzyme/issues/216
complete sandbox https://codesandbox.io/s/enzymetestformaterialuitable-updb9 with tests passing
check the 4th checkbox on the table:
let innerInputElement5 = wrapper
.find('[role="checkbox"]')
.hostNodes()
.at(4);
innerInputElement5.simulate("click");
check the indeterminate checkbox in the header:
let selectAllCheckboxInHeader = wrapper
.find(TableHead)
.find('input[type="checkbox"]')
.simulate("change", { target: { checked: true } });

Resources