enzyme mount component has error in i18n - reactjs

I use i18n in my component and wrapped in I18nextProvider like this:
const wrapper = shallow(
<I18nextProvider i18n={mockI18n}>
<Component initState={initData} />
</I18nextProvider>
);
The mockI18n is:
const mockI18n = {
t(k) {
return k;
},
on() {
},
getFixedT(k) {
return (k) => k;
},
loadNamespaces(arg) {
return (arg) => arg;
}
};
This works fine when I use shallow. However, I want to test a onChange function, so I need to find the element and simulate click event in a nested sub component. The shallow is not able to find the element, so I want to try out mount, but wen I use mount it gives me error:
ReferenceError: t is not defined
16 | name="user_id"
> 17 | label={t('User ID')}
18 | type="text"
19 | value={this.state.user_id}
What should I do to apply i18n to sub component? Or how to test this.props.onChange when use shallow?

I think inside the component you need to get the t function inside the component that uses the translation.
const { t } = props;
...
Only then you can call the t function.
label={t('User ID')}
https://react.i18next.com/getting-started.html

Related

React createProtal called outsite a JSX component not updating the DOM

I am trying to render a dynamically generated react component in a react app using createProtal.
When I call createProtal from a class the component is not rendered.
Handler.ts the class the contains the business logic
export class Handler {
private element: HTMLElement | null;
constructor(selector: string) {
this.element = document.getElementById(selector);
}
attachedEvent() {
this.element?.addEventListener("mouseenter", () => {
let cancel = setTimeout(() => {
if (this.element != null)
this.attachUi(this.element)
}, 1000)
this.element?.addEventListener('mouseleave', () => {
clearTimeout(cancel)
})
})
}
attachUi(domNode: HTMLElement) {
createPortal(createElement(
'h1',
{className: 'greeting'},
'Hello'
), domNode);
}
}
Main.tsx the react component that uses Handler.ts
const handler = new Handler("test_comp");
export default function Main() {
useEffect(() => {
// #ts-ignore
handler.useAddEventListeners();
});
return (
<>
<div id="test_comp">
<p>Detect Mouse</p>
</div>
</>
)
}
However when I repleace attachUi function with the function below it works
attachUi(domNode: HTMLElement) {
const root = createRoot(domNode);
root.render(createElement(
'h1',
{className: 'greeting'},
'Hello'
));
}
What am I missing?
React uses something called Virtual DOM. Only components that are included in that VDOM are displayed to the screen. A component returns something that React understands and includes to the VDOM.
createPortal(...) returns exactly the same as <SomeComponent ... />
So if you just do: const something = <SomeComponent /> and you don't use that variable anywhere, you can not display it. The same is with createPortal. const something = createPortal(...). Just use that variable somewhere if you want to display it. Add it to VDOM, let some of your components return it.
Your structure is
App
-children
-grand children
-children2
And your portal is somewhere else, that is not attached to that VDOM. You have to include it there, if you want to be displayed.
In your next example using root.render you create new VDOM. It is separated from your main one. This is why it is displayed

Test component methods on CRA

I'm working with create-react-app and I have the following component:
const Desktop = () => {
const onProjects = () => { };
return (
<Icon
title="Proyectos"
icon="projects"
onClick={onProjects}
testId="projects-testid"
/>
);
};
In which I want to test the component's onProject method to increase my coverage value:
File
% Stmts
% Branch
% Funcs
% Lines
Uncovered Line #s
index.tsx
100
100
25
100
It shows 25% cause I have many other methods, but I have decided to copy a simplified version to save space.
I used to pass those methods as props to the Desktop component, which is quite easy to test like this:
const onProjects = jest.fn();
describe("Components/Desktop", () => {
it("Calls the onClick handlers", () => {
const { getByTestId } = render(<Desktop onProjects={onProjects} />);
fireEvent.click(getByTestId("projects-testid"));
expect(onProjects).toHaveBeenCalledTimes(1);
});
});
Now that I don't want to pass those methods as props but handle them inside the Desktop component... I can't seem to find a way to test them. Is there a way to do so? Thanks in advance.

React ForwardRef: Property 'current' does not exist on type 'ForwardedRef<HTMLElement>'

I am trying to create a component that will track the vertical scroll. The catch is – the actual scroll container is not easily predictable (in this specific case it is neither window, document nor body – it is div#__next, due to CSS overflow rules).
I want to keep the component flexible and self-contained. So I've created a ref with DOM selector as an argument. I know it is far from idiomatic (to say the least), but it suprisingly seems to be working:
// Parent component
import { useRef } from "react"
const Article = (props) => {
const scrollContainerRef = useRef<HTMLElement | null>(
document.querySelector("#__next") // <-- the scroll container reference
)
return (
<SomeContent>
<ScrollToTop treshold={640} ref={scrollContainerRef} />
</SomeContent>
)
// ScrollToTop
const ScrollToTop = forwardRef(
({ treshold }, ref) => {
const [visible, setVisible] = useState(false)
useEffect(() => {
if (ref?.current) {
ref.current.addEventListener("scroll", throttle(toggleVisible, 300))
return () => {
ref.current.removeEventListener("scroll", throttle(toggleVisible, 300))
}
}
}, [])
// …
So what's the problem? the current one is Typescript. I've spent hours trying to get the types right, but to no avail. The parent component is red squigly lines free (unless I pass globalThis, which seems to work at least in CodeSandbox), but the ScrollToTop is compaining whenever I am accessing current property:
Property 'current' does not exist on type 'ForwardedRef<HTMLElement>'.
I've tried to use React.MutableRefObject<HTMLElement | null /* or other T's */>, both in parent and in child, but it didn't help.
Any ideas how to get the types to match? Or is this a silly idea from the beginning?
CodeSandbox demo
Refs might be objects with a .current property, but they might also be functions. So you can't assume that a forwarded ref has a .current property.
I think it's a mistake to use forwardRef at all here. The purpose of forwardRef is to allow a parent component to get access to an element in a child component. But instead, the parent is the one finding the element, and then you're passing it to the child for it to use. I would use a regular state and prop for that:
const Article = (props) => {
const [scrollContainer, setScrollContainer] = useState<HTMLElement | null>(() => {
return document.querySelector("#__next");
});
return (
<SomeContent>
<ScrollToTop treshold={640} scrollContainer={scrollContainer} />
</SomeContent>
)
interface ScrollToTopProps {
treshold: number;
scrollContainer: HTMLElement | null;
}
const ScrollToTop = ({ treshold, scrollContainer }: ScrollToTopProps) => {
const [visible, setVisible] = useState(false);
useEffect(() => {
if (scrollContainer) {
const toggle = throttle(toggleVisible, 300);
scrollContainer.addEventListener("scroll", toggle);
return () => {
scrollContainer.removeEventListener("scroll", toggle);
}
}
}, [scrollContainer]);
// ...
}

Jest/Enzyme Shallow testing RFC - not firing jest.fn()

I'm trying to test the onChange prop (and the value) of an input on an RFC. On the tests, trying to simulate the event doesn't fire the jest mock function.
The actual component is connected (with redux) but I'm exporting it also as an unconnected component so I can do a shallow unit test. I'm also using some react-spring hooks for animation.
I've also tried to mount instead of shallow the component but I still get the same problem.
MY Component
export const UnconnectedSearchInput: React.FC<INT.IInputProps> = ({ scrolled, getUserInputRequest }): JSX.Element => {
const [change, setChange] = useState<string>('')
const handleChange = (e: InputVal): void => {
setChange(e.target.value)
}
const handleKeyUp = (): void => {
getUserInputRequest(change)
}
return (
<animated.div
className="search-input"
data-test="component-search-input"
style={animateInputContainer}>
<animated.input
type="text"
name="search"
className="search-input__inp"
data-test="search-input"
style={animateInput}
onChange={handleChange}
onKeyUp={handleKeyUp}
value={change}
/>
</animated.div>
)
}
export default connect(null, { getUserInputRequest })(UnconnectedSearchInput);
My Tests
Here you can see the test that is failing. Commented out code is other things that I-ve tried so far without any luck.
describe('test input and dispatch action', () => {
let changeValueMock
let wrapper
const userInput = 'matrix'
beforeEach(() => {
changeValueMock = jest.fn()
const props = {
handleChange: changeValueMock
}
wrapper = shallow(<UnconnectedSearchInput {...props} />).dive()
// wrapper = mount(<UnconnectedSearchInput {...props} />)
})
test('should update input value', () => {
const input = findByTestAttr(wrapper, 'search-input').dive()
// const component = findByTestAttr(wrapper, 'search-input').last()
expect(input.name()).toBe('input')
expect(changeValueMock).not.toHaveBeenCalled()
input.props().onChange({ target: { value: userInput } }) // not geting called
// input.simulate('change', { target: { value: userInput } })
// used with mount
// act(() => {
// input.props().onChange({ target: { value: userInput } })
// })
// wrapper.update()
expect(changeValueMock).toBeCalledTimes(1)
// expect(input.prop('value')).toBe(userInput);
})
})
Test Error
Nothing too special here.
expect(jest.fn()).toBeCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
71 | // wrapper.update()
72 |
> 73 | expect(changeValueMock).toBeCalledTimes(1)
Any help would be greatly appreciated since it's been 2 days now and I cn't figure this out.
you don't have to interact with component internals; instead better use public interface: props and render result
test('should update input value', () => {
expect(findByTestAttr(wrapper, 'search-input').dive().props().value).toEqual('');
findByTestAttr(wrapper, 'search-input').dive().props().onChange({ target: {value: '_test_'} });
expect(findByTestAttr(wrapper, 'search-input').dive().props().value).toEqual('_test_');
}
See you don't need to check if some internal method has been called, what's its name or argument. If you get what you need - and you require to have <input> with some expected value - it does not matter how it happened.
But if function is passed from the outside(through props) you will definitely want to verify if it's called at some expected case
test('should call getUserInputRequest prop on keyUp event', () => {
const getUserInputRequest = jest.fn();
const mockedEvent = { target: { key: 'A' } };
const = wrapper = shallow(<UnconnectedSearchInput getUserInputRequest={getUserInputRequest } />).dive()
findByTestAttr(wrapper, 'search-input').dive().props().onKeyUp(mockedEvent)
expect(getUserInputRequest).toHaveBeenCalledTimes(1);
expect(getUserInputRequest).toHaveBeenCalledWith(mockedEvent);
}
[UPD] seems like caching selector in interm variable like
const input = findByTestAttr(wrapper, 'search-input').dive();
input.props().onChange({ target: {value: '_test_'} });
expect(input.props().value).toEqual('_test_');
does not pass since input refers to stale old object where value does not update.
At enzyme's github I've been answered that it's expected behavior:
This is intended behavior in enzyme v3 - see https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#calling-props-after-a-state-change.
So yes, exactly - everything must be re-found from the root if anything has changed.

React/ Enzyme - test failing in component that renders content with a map function

I have a React project where I have a component that maps throught an array of objects and render other components, like this:
return (
historyParts.map((historyPart, historyPartIndex) =>
(<div key={`historyPart${historyPartIndex}` // eslint-disable-line react/no-array-index-key
}
>
<div>
{historyPart.link &&
<Element>
<NavLink
to={createLocationForHistoryItems(handlingLocation, historyPart.link.code)}
>
{findLinkText(historyPart[0].link, intl)}
</NavLink>
</Element>
}
<BubbleText
bodyText={findText(historyPart.summary)}
className="bubble-panel__tekst"
/>
</div>
</div>
)));
This is a test that I wrote for this component:
import React from 'react';
import { shallowWithIntl, intlMock } from 'testHelpers/intl-enzyme-test-helper';
import { expect } from 'chai';
import { HistoryDescriptionType9} from './HistorikkMalType9';
const historyPart = {
cause: null,
actionPoint: null,
summary: 'adsadsd',
link: {
code: 'UTTAK',
codeType: 'SKJERMLENKE_TYPE',
name: 'Uttak',
},
};
const historyParts = [historyPart , historyPart ];
const handlingLocation = {};
describe('HistoryDescriptionType9', () => {
it('should render HistoryDescriptionType9', () => {
const wrapper = shallowWithIntl(<HistoryDescriptionType9
historyParts ={historyParts }
handlingLocation={handlingLocation}
intl={intlMock}
/>);
const bubbleText = wrapper.find('BubbleText');
expect(bubbleText).to.have.length(historyParts.length);
});
});
So, since I am mapping an array with 2 objects, there should be 2 BubbleText components rendered. But, I get a message that the test fails:
AssertionError: expected { length: 0 } to have a length of 2 but got 0
+ expected - actual
I have also tried with importing the component and using it in find function explicitly, like this:
import BubbleText from './bubbleText';
const bubbleText = wrapper.find(BubbleText);
But, I got the same error message.
I assume that the test is failing because of the map function. How can I fix this?
idk how your BubbleText component looks like but this issue is maybe because you are passing inside tests summary: 'adsadsd' instead of "BubbleText"

Resources