const DomNodeData = () => {
useEffect(() => {
const domNode = document.getElementById('visitDate')
if (domNode) {
//do something
// this needs to be tested
}
}, [])
return (
<div id="visitDate">Data</div>
)
}
describe('DemoData', () => {
it('Render dom node', () => {
render(<DomNodeData />)
})
})
After rendering the component in the test case, I cannot get the dom node, it's null. How can this be implemented in test?
Related
I can only access ipcRenderer in preload.js ( i disabled nodeIntegration ) so how do i display the output line by line whenever i get the output in preload.js
main.js
function execShellCommands(commands) {
let shellProcess = spawn("powershell.exe", [commands[0]])
shellProcess.stdout.on("data", (data) => {
mainWindow.webContents.send("sendToRenderer/shell-output", data.toString())
})
shellProcess.stderr.on("data", (data) => {
mainWindow.webContents.send("sendToRenderer/shell-output", "stderr: " + data.toString())
})
shellProcess.on("exit", () => {
mainWindow.webContents.send("sendToRenderer/shell-output", "shell-exited")
commands.shift()
if (0 < commands.length) {
execShellCommands(commands)
}
})
}
ipcMain.on("sendToElectron/execShellCommands", (event, args) => {
execShellCommands(args)
})
preload.js
let API = {
execShellCommands: (action) => ipcRenderer.send("sendToElectron/execShellCommands", action)
}
contextBridge.exposeInMainWorld("ElectronAPI", API)
ipcRenderer.on("sendToRenderer/shell-output", (event, output) => {
console.log(output)
})
react's App.jsx
ElectronAPI.execShellCommands(["spicetify apply"])
The output in printed one by one in the console but how do i display the output in react DOM (App.jsx) one by one in a p tag?
First, you need to expose the shell-output listener in you API so your components can access it:
shellOutput: (callback) => {
const channel = "sendToRenderer/shell-output";
const subscription = (_event, output) => callback(output);
ipcRenderer.on(channel , subscription);
return () => {
ipcRenderer.removeListener(channel, subscription);
};
}
On your components, you can now access this function with window.ElectronAPI.shellOutput. To log the outputs, you can create a state to store them, and set the listener with a useEffect():
const [outputs, setOutputs] = useState([]);
useEffect(() => {
const removeOutputListener = window.ElectronAPI.shellOutput(output => {
setOutputs(previousOutputs => [
...previousOutputs,
output
]);
});
return removeOutputListener;
}, []);
return (
<div>
{outputs.map((output, i) => (
<p key={i}>{output}</p>
))}
</div>
);
I am passing functions to my child component. And I am using React.memo to restrict compoenent from re-rendering. But My component rerenders when parent re-renders. I tried to check why this is happening by using useEffect on all the props and I get to this point that my functions are causing compoenent to re-renders.
// my functions
const scrollToView = (index) => {
if (scrollRef && scrollRef.current && scrollRef.current[index]) {
scrollRef.current[index].scrollIntoView({ behavior: 'smooth' });
}
};
const scrollToReportView = (reportIndex) => {
if (scrollToReportRef && scrollToReportRef.current &&
scrollToReportRef.current[reportIndex]) {
scrollToReportRef.current[reportIndex].scrollIntoView({
behavior: 'smooth' });
}
}
.......
function LeftNav({
scrollToView, //function
scrollToReportView, //function
reports, //object
}) {
useEffect(() => {
console.log('scrollToView')
}, [scrollToView])
useEffect(() => {
console.log('scrollToReportView')
}, [scrollToReportView])
useEffect(() => {
console.log('reports')
}, [reports])
return (
<div>{'My Child Component'}</div>
);
}
export default memo(LeftNav);
And this is how my left nav is being called
<LeftNav
scrollToView={(index) => scrollToView(index)}
scrollToReportView={(repIndex)=> scrollToReportView(repIndex)}
reports={reports}
/>
With
<LeftNav
scrollToView={(index) => scrollToView(index)}
scrollToReportView={(repIndex)=> scrollToReportView(repIndex)}
reports={reports}
/>
you're creating new anonymous functions every time you render the LeftNav component, so memoization does absolutely nothing.
Just
<LeftNav
scrollToView={scrollToView}
scrollToReportView={scrollToReportView}
reports={reports}
/>
instead (assuming those functions are stable by identity (e.g. are declared outside the component or are properly React.useCallbacked or React.useMemoed).
In other words, if your component is currently
function Component() {
// ...
const scrollToView = (index) => {
if (scrollRef && scrollRef.current && scrollRef.current[index]) {
scrollRef.current[index].scrollIntoView({ behavior: "smooth" });
}
};
const scrollToReportView = (reportIndex) => {
if (scrollToReportRef && scrollToReportRef.current && scrollToReportRef.current[reportIndex]) {
scrollToReportRef.current[reportIndex].scrollIntoView({
behavior: "smooth",
});
}
};
return (
<LeftNav
scrollToView={(index) => scrollToView(index)}
scrollToReportView={(repIndex) => scrollToReportView(repIndex)}
reports={reports}
/>,
);
}
it needs to be something like
function Component() {
// ...
const scrollToView = React.useCallback((index) => {
if (scrollRef?.current?.[index]) {
scrollRef.current[index].scrollIntoView({ behavior: "smooth" });
}
}, []);
const scrollToReportView = React.useCallback((reportIndex) => {
if (scrollToReportRef?.current?.[reportIndex]) {
scrollToReportRef.current[reportIndex].scrollIntoView({
behavior: "smooth",
});
}
}, []);
return (<LeftNav scrollToView={scrollToView} scrollToReportView={scrollToReportView} reports={reports} />);
}
so the scrollToView and scrollToReportView functions have stable identities.
I have the following react hook which brings focus to a given ref and on unmount returns the focus to the previously focused element.
export default function useFocusOnElement(elementRef: React.RefObject<HTMLHeadingElement>) {
const documentExists = typeof document !== 'undefined';
const [previouslyFocusedEl] = useState(documentExists && (document.activeElement as HTMLElement));
useEffect(() => {
if (documentExists) {
elementRef.current?.focus();
}
return () => {
if (previouslyFocusedEl) {
previouslyFocusedEl?.focus();
}
};
}, []);
}
Here is the test I wrote for it.
/**
* #jest-environment jsdom
*/
describe('useFocusOnElement', () => {
let ref: React.RefObject<HTMLDivElement>;
let focusMock: jest.SpyInstance;
beforeEach(() => {
ref = { current: document.createElement('div') } as React.RefObject<HTMLDivElement>;
focusMock = jest.spyOn(ref.current as HTMLDivElement, 'focus');
});
it('will call focus on passed ref after mount ', () => {
expect(focusMock).not.toHaveBeenCalled();
renderHook(() => useFocusOnElement(ref));
expect(focusMock).toHaveBeenCalled();
});
});
I would like to also test for the case where document is undefined as we also do SSR. In the hook I am checking for the existence of document and I would like to test for both cases.
JSDOM included document so I feel I'd need to remove it and some how catch an error in my test?
First of all, to simulate document as undefined, you should mock it like:
jest
.spyOn(global as any, 'document', 'get')
.mockImplementationOnce(() => undefined);
But to this work in your test, you will need to set spyOn inside renderHook because looks like it also makes use of document internally, and if you set spyOn before it, you will get an error.
Working test example:
it('will NOT call focus on passed ref after mount', () => {
expect(focusMock).not.toHaveBeenCalled();
renderHook(() => {
jest
.spyOn(global as any, 'document', 'get')
.mockImplementationOnce(() => undefined);
useFocusOnElement(ref);
});
expect(focusMock).not.toHaveBeenCalled();
});
You should be able to do this by creating a second test file with a node environment:
/**
* #jest-environment node
*/
describe('useFocusOnElement server-side', () => {
...
});
I ended up using wrapWithGlobal and wrapWithOverride from https://github.com/airbnb/jest-wrap.
describe('useFocusOnElement', () => {
let ref: React.RefObject<HTMLDivElement>;
let focusMock: jest.SpyInstance;
let activeElMock: unknown;
let activeEl: HTMLDivElement;
beforeEach(() => {
const { window } = new JSDOM();
global.document = window.document;
activeEl = document.createElement('div');
ref = { current: document.createElement('div') };
focusMock = jest.spyOn(ref.current as HTMLDivElement, 'focus');
activeElMock = jest.spyOn(activeEl, 'focus');
});
wrapWithOverride(
() => document,
'activeElement',
() => activeEl,
);
describe('when document present', () => {
it('will focus on passed ref after mount and will focus on previously active element on unmount', () => {
const hook = renderHook(() => useFocusOnElement(ref));
expect(focusMock).toHaveBeenCalled();
hook.unmount();
expect(activeElMock).toHaveBeenCalled();
});
});
describe('when no document present', () => {
wrapWithGlobal('document', () => undefined);
it('will not call focus on passed ref after mount nor on previously active element on unmount', () => {
const hook = renderHook(() => useFocusOnElement(ref));
expect(focusMock).not.toHaveBeenCalled();
hook.unmount();
expect(activeElMock).not.toHaveBeenCalled();
});
});
});
I am learning to test React app using jest and Enzyme . I have created a component and using redux to maintain and update the state . The component code is below .
Now i want to write the test to check initial value of prodOverviewAccordion which we are setting as true in context file.
I have tried writing , but getting error . Sharing the test code also . Please help
const ProdOverview = () => {
const {
productState,
setProdOverviewAccordion
} = React.useContext(ProductContext);
const {prodOverviewAccordion } = productState;
const [completeStatusprod, setCompleteStatusprod] = useState(false);
return (
<div onClick={toggleTriggerProd}>
<s-box>
<Collapsible
trigger={
<Accordion
name={ProductConfig.accordionTriggerLabels.prodOverviewLabel}
completeStatusIcon={completeStatusprod ? 'check-circle' : 'alert-triangle'}
completeStatus={completeStatusprod}
/>
}
easing='ease-out'
handleTriggerClick={() => {
if (!prodOverviewAccordion) {
setProdOverviewAccordion(true);
} else {
setProdOverviewAccordion(false);
}
}}
open={prodOverviewAccordion}
data-test='prodOverViewCollapsible'
>
<p>Test</p>
</Collapsible>
</s-box>
</div>
);
};
export default ProdOverview;
const prodsetup = (props = {}) => {
return shallow(<ProdOverview />);
};
describe('Product Overview panel Test', () => {
const mockSetCurrentGuess = jest.fn();
beforeEach(() => {
mockSetCurrentGuess.mockClear();
});
test('should render Collapsible panel', () => {
const wrapper = prodsetup();
const component = findByTestAttr(wrapper, 'prodOverViewCollapsible');
expect(component.length).toBe(1);
});
test('Product Overview Panel should be in open state', () => {
const wrapper = prodsetup();
expect(wrapper.state().prodOverviewAccordion.to.equal(true));
});
});
I'm using nextJs and jest enzyme for testing of component. I have a component file like below, where I get items array from props and loop to it calling setRef inside useEffect.
[itemRefs, setItemRefs] = React.useState([]);
setRef = () => {
const refArr = [];
items.forEach(item => {
const setItemRef = RefItem => {
refArr.push(RefItem)
}
if(item && item.rendItem){
item.rendItem = item.rendItem.bind(null, setItemRef);
}
}
setItemRefs(refArr);
}
React.useEffect(() => {
setRef();
},[])
My test file is like below :
const items = [
{
original: 'mock-picture-link',
thumbnail: 'mock-thumb-link',
renderItem: jest.fn().mockReturnValue({ props: { children: {} } })
}
];
beforeEach(() => {
setItemRefs = jest.fn(),
jest.spyOn(React, 'useEffect').mockImplementationOnce(fn => fn());
})
it('mocks item references', ()=>{
jest.spyOn(React, 'useState').mockImplementationOnce(() => [items, setItemRefs]);
wrapper = shallow(<Component />).dive();
expect(setItemRefs).toHaveBeenCalledWith(items);
})
The test case fails with expected as items array BUT received a blank array. The console.log inside if(item && item.rendItem) works but console.log inside const setItemRef = RefItem => {... doesn't work I doubt the .bind is not getting mocked in jest.
Any help would be fine.