Why calling fit() is rezing the window wrongly? - terminal

I'm getting this resize error when I get out the tab then get back:
I did notice that this happens when I call FitAddon.Fit() inside my ResizeObserver. I was using onSize to call .fit() but I did notice that when I used <Resizable>, the onSize event didn't fire so I went to use ResizeObserver and called .fit() from ResizeObserver's callback so the resize error begun.
my code look like this:
import { useEffect, useRef } from "react";
import { Terminal as TerminalType } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import 'xterm/css/xterm.css';
export const Terminal = () => {
const id = 'xterm-container';
useEffect(() => {
const container = document.getElementById(id) as HTMLElement;
const terminal = new TerminalType({
cursorBlink: true,
cursorStyle: window.api.isWindows ? "bar" : "underline",
cols: DefaultTerminalSize.cols,
rows: DefaultTerminalSize.rows
});
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
terminal.loadAddon(new WebLinksAddon());
const resizeObserver = new ResizeObserver(entries => {
fitAddon.fit();
});
resizeObserver.observe(container);
terminal.open(container);
terminal.onData(key => {
window.api.send('terminal.data', key);
});
terminal.onResize(size => {
window.api.send('terminal.resize', {
cols: size.cols,
rows: size.rows
});
});
fitAddon.fit();
return () => {
resizeObserver.unobserve(container);
}
}, []);
return (
<div
id={id}
style={{height: '100%',
width: '100%',
}}
>
</div>
)
}

Related

Why does this test code fail (It works in the application) - using testing-library/react-hooks

I'm having trouble writing test code for this application. Specifically, the code works in the app - it works exactly the way I want it to. But with all the crazy mocks and abstractions in the codebase I have to work with, for some reason the tests never seem to pass.
Here is the code I'm hoping to test:
// useLandAccessHistory.ts
import { RequestState, RootState, useAppDispatch, useAppSelector } from '../../../../../store';
import { fetchLandAccessHistory } from '../../../../../extension/LandAccess/module/thunks';
import { LAND_ACCESS_SLICE_NAME } from '../../../../../extension/LandAccess/module/constants';
import { models } from '#h2know-how/moata-land-management-sdk';
import { useEffect, useMemo } from 'react';
const landAccessHistoryTableSelector = (state: RootState) => {
const landAccess = state[LAND_ACCESS_SLICE_NAME];
const byTitleId = (titleId: number): models.LandAccessHistory[] | undefined =>
landAccess.landAccessHistories[titleId];
const { status, error } = landAccess.apiStatus.fetchLandAccessHistory;
return { status, error, byTitleId };
};
export const useLandAccessHistory = ({
projectId,
titleId,
}: {
projectId: number;
titleId: number;
}): { historyData?: models.LandAccessHistory[] } & RequestState => {
const dispatch = useAppDispatch();
const { status, error, byTitleId } = useAppSelector(landAccessHistoryTableSelector);
useEffect(() => {
const req = dispatch(
fetchLandAccessHistory({
projectId,
titleId,
})
);
return req.abort;
}, [dispatch, projectId, titleId]);
const historyData = useMemo(() => byTitleId(titleId), [byTitleId, titleId]);
return { historyData, status, error };
};
And here is the test suite that is failing.
// useLandAccessHistory.spec.ts
import React from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import { renderHook } from '#testing-library/react-hooks';
import { waitFor } from '#testing-library/react';
import { useLocation } from 'react-router-dom';
import { useLandAccessHistory } from './useLandAccessHistory';
import { useAppDispatch, useAppSelector } from '../../../../../store';
import { landAccessSlice } from '../../../../../extension/LandAccess/module/landAccess';
import { LAND_ACCESS_SLICE_NAME } from '../../../../../extension/LandAccess/module/constants';
import { useUrlParams, useUrlSearchParams } from '../../../../../utils/hooks';
import { mockLandAccessHistories } from '../../../../../extension/LandAccess/test-helpers/factories';
import { landAccessAPI } from '../../../../../extension/LandAccess/api/landAccessAPI';
const mockHistories = mockLandAccessHistories({ projectId: 123, titleId: 345 }, 2);
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
}));
jest.mock('../../../../../store');
jest.mock('../../../../../utils/hooks', () => ({
...jest.requireActual('../../../../../utils/hooks'),
useUrlParams: jest.fn(),
useUrlSearchParams: jest.fn(),
}));
jest.mock('../../../../../extension/LandAccess/api/landAccessAPI', () => ({
getLandAccessHistory: jest.fn(() => Promise.resolve(mockHistories)),
}));
describe('useLandAccessHistory', () => {
let dispatch: jest.Mock;
let abort: jest.Mock;
let store: any;
let wrapper: any;
beforeEach(() => {
(useLocation as jest.Mock).mockReturnValue({ pathname: '/route/123' });
store = configureStore({
reducer: { [LAND_ACCESS_SLICE_NAME]: landAccessSlice.reducer },
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
});
dispatch = jest.fn((...params) => store.dispatch(...params));
abort = jest.fn();
dispatch.mockReturnValue({ abort });
wrapper = ({ children }) => <Provider store={store}>{children}</Provider>;
(useUrlParams as jest.Mock).mockReturnValue({ projectId: 123 });
(useUrlSearchParams as jest.Mock).mockReturnValue({ feature_id: 345 });
(useAppDispatch as jest.Mock).mockReturnValue(dispatch);
(useAppSelector as jest.Mock).mockImplementation((selectorFunction) => selectorFunction(store.getState()));
});
it('fetches the land access history', async () => {
const { result, rerender } = renderHook(() => useLandAccessHistory({ projectId: 123, titleId: 345 }), { wrapper });
await waitFor(() => {
expect(dispatch).toHaveBeenCalled(); // this line runs.
});
rerender();
await waitFor(() => {
expect(store.getState().landAccessHistories).toEqual(mockHistories); // the store does not contain the data
expect(result.current).toEqual({ error: null, status: 'fulfilled', historyData: mockHistories }); // the status is idle and history data is undefined
});
});
});
Here's what I've tried so far: so far as I can tell, dispatch runs, but it doesn't seem to actually call the mocked (or real) API function. It could be that dispatch has been called but has not yet resolved, but then wouldn't await waitFor and rerender() help solve that problem?

RematchJS Refresh Issue

I am new at using RematchJS and have managed to display hardcoded Objects from an array. The dispatcher (addTopicAsync) however does not seems to update the Array, when I try to add a new Object.
The Array is briefly updated and the topic is flashes on the screen, but the Array is empty shortly afterwards.
My model code:
import { createModel } from "#rematch/core";
import { RootModel } from ".";
export interface Topic {
topic: String
}
export interface TopicsList {
list: Array<Topic>
}
const TOPICS_LIST_STATE = {
list: []
}
export const topics = createModel<RootModel>()({
state: TOPICS_LIST_STATE as TopicsList,
reducers: {
addTopic: (state, topic) => {
return { list: [...state.list, { topic }] }
},
clearTopics: () => {
return { list: [] }
}
},
effects: (dispatch) => ({
async addTopicAsync(topic: string) {
await dispatch.topics.addTopic(topic)
},
async clearTopicsAsync() {
await dispatch.topics.clearTopics()
}
})
});
My application code:
// eslint-disable-next-line #typescript-eslint/no-unused-vars
import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, Dispatch } from '#vanilla/data'
import { Topic } from 'libs/data/src/lib/models/topics';
export function App() {
const [topic, setTopic] = useState("aaa")
const topicsState = useSelector((state: RootState) => state.topics)
const dispatch = useDispatch<Dispatch>()
const topicChange = (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault()
setTopic(event.target.value)
}
const updateTopicList = async () => {
await dispatch.topics.addTopicAsync(topic)
console.log('topicsState : ', topicsState.list)
}
return (
<>
<h3>Topics</h3>
<form>
<input type='text' value={topic} onChange={topicChange} />
<button onClick={() => { updateTopicList() }}> Add Topic</button>
</form>
<div className="container">
{topicsState.list.map((topicRecord: Topic, index: number) => (
<h5 key={index}>
{topicRecord.topic}
</h5>
))}
</div>
</>
)
}
export default (App)

Implement force-directed graph in next js

I'm trying to create a force-directed graph for mapping the interactions between courses in an institution. Using Next JS + TypeScript for my frontend.
Have tried several attempts at charting this out using react-flow, dagre, vis-network but am getting either a window : undefined error or just the damn alignment of nodes not being force-directed inside the box I have defined.
Before I move on with implementing d3-force right out of the box, can someone please recommend any alternative solution to this ?
Here's what my nodes & edges look like :
Here's my attempt with reactflow & dagre :
import React, { useCallback, useEffect, useState } from 'react';
import ReactFlow, {
addEdge,
useNodesState,
useEdgesState,
Edge,
Node,
Position,
ConnectionLineType,
ReactFlowProvider,
MiniMap,
Controls,
Background,
} from 'react-flow-renderer';
import dagre from 'dagre';
import { NodeData, useCourseNodes } from 'src/hooks/useCourseNodes';
import { useDepartment } from '#contexts/ActiveDepartmentContext';
import {
useUpdateActiveCourse,
} from '#contexts/ActiveCourseContext';
import { useDrawerOpen, useUpdateDrawerOpen } from '#contexts/DrawerContext';
const dagreGraph = new dagre.graphlib.Graph({directed:true});
dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 10.2;
const nodeHeight = 6.6;
const getLayoutedElements = (
nodes: Node[],
edges:Edge[],
) => {
// const isHorizontal = direction === 'LR';
dagreGraph.setGraph( {width:900, height:900, nodesep:20, ranker:'longest-path' });
nodes.forEach((node: Node) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
});
edges.forEach((edge: Edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
dagre.layout(dagreGraph);
nodes.forEach((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
// node.targetPosition = isHorizontal ? Position.Left : Position.Top;
// node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
node.targetPosition = Position.Top;
node.sourcePosition = Position.Bottom;
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
node.position = {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2,
};
console.log(nodeWithPosition)
return node;
})
return { layoutedNodes:nodes, layoutedEdges:edges };
};
const LayoutFlow = () => {
const activeDept = useDepartment();
const setActiveCourse = useUpdateActiveCourse();
const setDrawerOpen = useUpdateDrawerOpen()
const drawerOpen = useDrawerOpen();
const {courseList, edgeList} = useCourseNodes()
const { layoutedNodes, layoutedEdges } = getLayoutedElements(courseList, edgeList)
const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
const [edges, setEdges,onEdgesChange] = useEdgesState(layoutedEdges);
console.log(layoutedNodes)
const onConnect = useCallback(
(params) =>
setEdges((eds) =>
addEdge({ ...params, type: ConnectionLineType.SimpleBezier, animated: true }, eds),
),
[],
);
// ? For switching between layouts (horizontal & vertical) for phone & desktop
// const onLayout = useCallback(
// (direction) => {
// const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
// nodes,
// edges,
// direction
// );
// setNodes([...layoutedNodes]);
// setEdges([...layoutedEdges]);
// },
// [nodes, edges]
// );
// ? M1 - for force re-rendering react flow graph on state change - https://github.com/wbkd/react-flow/issues/1168
// ? M2 - (Applied currently in useEffect block below)for force re-rendering react flow graph on state change - https://github.com/wbkd/react-flow/issues/1168
useEffect(() => {
const {layoutedNodes, layoutedEdges} = getLayoutedElements(courseList, edgeList)
setNodes([...layoutedNodes]);
setEdges([...layoutedEdges]);
}, [activeDept, drawerOpen]);
return (
<div style={{ width: '100%', height: '100%' }} className="layoutflow">
<ReactFlowProvider>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeClick={(e: React.MouseEvent, node: Node<NodeData>) => {
e.preventDefault();
// created a copy of the node since we're only deleting the "label" property from the node object to conveniently map the rest of the data to the "data" property of the active course
const nodeCopy = JSON.parse(JSON.stringify(node))
const { data } = nodeCopy;
const { label } = data
delete data.label
setActiveCourse({
courseId: label,
data
});
setDrawerOpen(true);
}}
connectionLineType={ConnectionLineType.SimpleBezier}
fitView
>
<MiniMap />
<Controls />
{/* <Background /> */}
</ReactFlow>
</ReactFlowProvider>
<div className="controls">
{/* <button onClick={() => onLayout('TB')}>vertical layout</button>
<button onClick={() => onLayout('LR')}>horizontal layout</button> */}
</div>
</div>
);
};
export default LayoutFlow;
Here's my attempt with vis-network : (note : I did slightly modify edges to have from-to instead of source-target when working with this)
import { useCourseNodes } from "#hooks/useCourseNodes";
import React, { useEffect, useRef } from "react";
import { Network } from "vis-network";
const GraphLayoutFour: React.FC = () => {
const {courseList:nodes, edgeList:edges} = useCourseNodes()
// Create a ref to provide DOM access
const visJsRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const network =
visJsRef.current &&
new Network(visJsRef.current, { nodes, edges } );
// Use `network` here to configure events, etc
}, [visJsRef, nodes, edges]);
return typeof window !== "undefined" ? <div ref={visJsRef} /> : <p>NOT AVAILABLE</p>;
};
export default GraphLayoutFour;
Here's my attempt with react-sigma
import React, { ReactNode, useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { UndirectedGraph } from "graphology";
import erdosRenyi from "graphology-generators/random/erdos-renyi";
import randomLayout from "graphology-layout/random";
import chroma from "chroma-js";
import { Attributes } from "graphology-types";
import { ControlsContainer, ForceAtlasControl, SearchControl, SigmaContainer, useLoadGraph, useRegisterEvents, useSetSettings, useSigma, ZoomControl } from "react-sigma-v2/lib/esm";
interface MyCustomGraphProps {
children?: ReactNode;
}
export const MyCustomGraph: React.FC<MyCustomGraphProps> = ({ children }) => {
const sigma = useSigma();
const registerEvents = useRegisterEvents();
const loadGraph = useLoadGraph();
const setSettings = useSetSettings();
const [hoveredNode, setHoveredNode] = useState<any>(null);
useEffect(() => {
// Create the graph
const graph = erdosRenyi(UndirectedGraph, { order: 100, probability: 0.2 });
randomLayout.assign(graph);
graph.nodes().forEach(node => {
graph.mergeNodeAttributes(node, {
label: "label",
size: Math.max(4, Math.random() * 10),
color: chroma.random().hex(),
});
});
loadGraph(graph);
// Register the events
registerEvents({
enterNode: event => setHoveredNode(event.node),
leaveNode: () => setHoveredNode(null),
});
}, []);
useEffect(() => {
setSettings({
nodeReducer: (node, data) => {
const graph = sigma.getGraph();
const newData: Attributes = { ...data, highlighted: data.highlighted || false };
if (hoveredNode) {
//TODO : add type safety
if (node === hoveredNode || (graph as any).neighbors(hoveredNode).includes(node)) {
newData.highlighted = true;
} else {
newData.color = "#E2E2E2";
newData.highlighted = false;
}
}
return newData;
},
edgeReducer: (edge, data) => {
const graph = sigma.getGraph();
const newData = { ...data, hidden: false };
//TODO : add type safety
if (hoveredNode && !(graph as any).extremities(edge).includes(hoveredNode)) {
newData.hidden = true;
}
return newData;
},
});
}, [hoveredNode]);
return <>{children}</>;
};
ReactDOM.render(
<React.StrictMode>
<SigmaContainer>
<MyCustomGraph />
<ControlsContainer position={"bottom-right"}>
<ZoomControl />
<ForceAtlasControl autoRunFor={2000} />
</ControlsContainer>
<ControlsContainer position={"top-right"}>
<SearchControl />
</ControlsContainer>
</SigmaContainer>
</React.StrictMode>,
document.getElementById("root"),
);
import { useCourseNodes } from '#hooks/useCourseNodes'
import dynamic from 'next/dynamic';
import React from 'react'
import { useSigma } from 'react-sigma-v2/lib/esm';
const GraphLayoutThree = () => {
const isBrowser = () => typeof window !== "undefined"
const { courseList, edgeList } = useCourseNodes()
const sigma = useSigma();
if(isBrowser) {
const SigmaContainer = dynamic(import("react-sigma-v2").then(mod => mod.SigmaContainer), {ssr: false});
const MyGraph = dynamic(import("./CustomGraph").then(mod => mod.MyCustomGraph), {ssr: false});
return (
<SigmaContainer style={{ height: "500px", width: "500px" }} >
<MyGraph/>
</SigmaContainer>
)
}
else return (<p>NOT AVAILABLE</p>)
}
export default GraphLayoutThree
Here's my attempt with react-force-graph (note : I did slightly modify edges to have from-to instead of source-target when working with this)
import dynamic from "next/dynamic";
const GraphLayoutTwo = () => {
const isBrowser = () => typeof window !== "undefined"
if(isBrowser) {
const MyGraph = dynamic(import("./CustomGraphTwo").then(mod => mod.default), {ssr: false});
return (
<MyGraph/>
)
}
else return (<p>NOT AVAILABLE</p>)
}
export default GraphLayoutTwo
import dynamic from "next/dynamic";
const GraphLayoutTwo = () => {
const isBrowser = () => typeof window !== "undefined"
if(isBrowser) {
const MyGraph = dynamic(import("./CustomGraphTwo").then(mod => mod.default), {ssr: false});
return (
<MyGraph/>
)
}
else return (<p>NOT AVAILABLE</p>)
}
export default GraphLayoutTwo
To implement something similar we use react-graph-vis inside a nextjs application.
If you have the window is not defined error, just wrap the component and import it with dynamic
// components/graph.tsx
export const Graph = ({data, options, events, ...props}) => {
return (
<GraphVis
graph={transformData(data)}
options={options}
events={events}
/>
)
}
then in your page
// pages/index.ts
const Graph = dynamic(() => (import("../components/graph").then(cmp => cmp.Graph)), { ssr: false })
const Index = () => {
return (
<>
<Graph data={...} .... />
</>
)
}
export default Index;

What should I expect for navigate buttons in react using react-testing library

I don't understand what should I expect for navigate button in the below code. can any one help me with this. Thank you.
code:
import react from 'react';
const HomeButton = (props) => {
const history = props.history;
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick} data-testid="goToHome">
Go home
</button>
);
}
export default HomeButton;
This is the test code I have been trying for the above component
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
import GoToHome from '../GoToHome';
describe('Read only text', () => {
const history = createMemoryHistory();
it('text came from props', () => {
const { container } = render(<GoToHome history={history} />);
const goToHome = screen.getByTestId('goToHome')
fireEvent.click(goToHome, jest.fn())
expect(container).
});
});
you can check my passing the history as a prop with push property assigned to jest.fn as mock function and check if it is getting called when you press to navigate button
describe('Read only text', () => {
const mockPush = jest.fn()
const history = {
push: mockPush()
}
it('text came from props', () => {
const { container } = render(<GoToHome history={history} />);
const goToHome = screen.getByTestId('goToHome')
fireEvent.click(goToHome)
expect(mockPush ).toBeCalled()
});
});

Fetch request in React: How do I Map through JSON array of objects, setState() & append?

This API returns a JSON array of objects, but I'm having trouble with setState and appending. Most documentation covers JSON objects with keys. The error I get from my renderItems() func is:
ItemsList.js:76 Uncaught TypeError: Cannot read property 'map' of undefined
in ItemsList.js
import React, { Component } from "react";
import NewSingleItem from './NewSingleItem';
import { findDOMNode } from 'react-dom';
const title_URL = "https://www.healthcare.gov/api/index.json";
class ItemsList extends Component {
constructor(props) {
super(props);
this.state = {
// error: null,
// isLoaded: false,
title: [],
url: [],
descrip: []
};
}
componentDidMount() {
fetch(title_URL)
.then(response => {
return response.json();
})
.then((data) => {
for (let i = 0; i < data.length; i++) {
this.setState({
title: data[i].title,
url: data[i].url,
descrip: data[i].bite,
});
console.log(data[i])
}
})
.catch(error => console.log(error));
}
renderItems() {
return this.state.title.map(item => {
<NewSingleItem key={item.title} item={item.title} />;
});
}
render() {
return <ul>{this.renderItems()}</ul>;
}
}
export default ItemsList;
Above, I'm trying to map through the items, but I'm not quite sure why I cant map through the objects I set in setState(). Note: even if in my setState() I use title: data.title, it doesnt work. Can someone explain where I'm erroring out? Thanks.
in App.js
import React, { Component } from "react";
import { hot } from "react-hot-loader";
import "./App.css";
import ItemsList from './ItemsList';
class App extends Component {
render() {
return (
<div className="App">
<h1> Hello Healthcare </h1>
<ItemsList />
<article className="main"></article>
</div>
);
}
}
export default App;
in NewSingleItem.js
import React, { Component } from "react";
const NewSingleItem = ({item}) => {
<li>
<p>{item.title}</p>
</li>
};
export default NewSingleItem;
The problem is this line:
this.setState({
title: data[i].title,
url: data[i].url,
descrip: data[i].bite,
});
When you state this.state.title to data[i].title, it's no longer an array. You need to ensure it stays an array. You probably don't want to split them up anyway, just keep them all in a self contained array:
this.state = {
// error: null,
// isLoaded: false,
items: [],
};
...
componentDidMount() {
fetch(title_URL)
.then(response => {
return response.json();
})
.then((data) => {
this.setState({
items: data.map(item => ({
title: item.title,
url: item.url,
descrip: item.bite,
})
});
console.log(data[i])
}
})
...
renderItems() {
return this.state.items.map(item => {
<NewSingleItem key={item.title} item={item.title} />;
});
}

Resources