问题描述
我在对使用 vis-js 时间轴控件的 React 组件进行单元测试时遇到了一些问题。我得到的错误是“无法读取未定义的属性‘比例’”,调试代码会发生错误,因为 timeaxis 对象应该具有 step 属性,但该属性仅在运行 timeaxis 的重绘方法时设置。我怀疑问题在于测试库生成了没有高度或宽度的 DOM。我本来希望设置 timeaxis 选项来解决此问题,但情况似乎并非如此。这是我的测试方法。
const defaultProps: GanttProps = {
rowData: [],data: [],expanded: false,availabilityItems: [],dragLocked: false,isVisualizerForPrint: false,shownestedOverride: false,textItemsFordisplay: ["Name"],options: {
start: moment(0).toDate(),end: moment(0).add(1,"year").toDate(),timeAxis: {scale: 'day',step: 30}
}
};
it("Should call handleClick",async () => {
const handleClick = jest.fn();
const props = {
...defaultProps,click: handleClick,data: mockData,rowData: mockRowData
};
render(<Gantt { ...props } />);
userEvent.click(screen.getByText(/Test Item/i));
expect(handleClick).toHaveBeenCalledTimes(1);
});
import * as React from "react";
import ReactDOM from "react-dom";
import * as visTimeline from "vis-timeline";
import * as typeDefs from "./../common/typeDefs";
import moment from "moment";
import "vis-timeline/styles/vis-timeline-graph2d.min.css";
export type SchedTemplateFunc = (item: typeDefs.TaskItem,element: any) => any;
export type SubgroupStackingLookupBuilder = (items: typeDefs.GanttSubgroupData[])
=> { [subgroupId: string]: boolean };
const DefaultTimeZone = "America/Edmonton";
const _timelineItemTypes = ["background","Box","point","range"];
const MonthDragScale: visTimeline.TimelineOptions =
{ timeAxis: { scale: "month" } };
const YearDragScale: visTimeline.TimelineOptions =
{ timeAxis: { scale: "year" } };
const defaultOptions: visTimeline.TimelineOptions = {
stack: false,stackSubgroups: true,minHeight: 200,editable: false,verticalScroll: true,zoomable: true,zoomMax: 253206145314,zoomMin: 80927257,zoomKey: "ctrlKey",moveable: true,orientation: "top",margin: {
item: { horizontal: 0,vertical: 0 }
},timeAxis: { scale: "day" }
};
type GanttProps = {
data?: typeDefs.TaskItem[] | null;
rowData?: typeDefs.GanttRowData[] | null;
subGroups?: typeDefs.GanttSubgroupData[] | null;
availabilityItems?: typeDefs.AvailabilityItem[] | null;
parentSelector?: string;
dragDirection?: string;
dragLocked?: boolean;
expanded?: boolean;
isVisualizerForPrint?: boolean;
shownestedOverride?: boolean;
timescaleSelector?: string | null;
textItemsFordisplay?: string[];
timezone?: string | undefined;
options?: visTimeline.TimelineOptions | undefined;
timelineRange?: typeDefs.Range | undefined;
click?: (event: string,properties: any) => void;
doubleClick?: (event: React.MouseEvent<HTMLElement>) => void;
changed?: () => void;
select?: (items: visTimeline.IdType[],event?: any) => void;
rangeChange?: (properties?: typeDefs.RangeProperties) => void;
rangeChanged?: (properties?: typeDefs.RangeProperties) => void;
subgroupStackingLookup?: SubgroupStackingLookupBuilder;
focusedItem? : string;
};
const Gantt = (props: GanttProps) => {
const [loaded,setLoaded] = React.useState(false);
const [items,setItems] = React.useState<visTimeline.DataItemCollectionType>([]);
const [groups,setGroups] = React.useState<visTimeline.DataGroupCollectionType>([]);
const [range,setRange] = React.useState<typeDefs.Range>()
let timeline: visTimeline.Timeline | undefined = undefined;
let timelineNode: HTMLdivelement | null = null;
let invisibleInput: HTMLInputElement | null = null;
let hideColumnsTimer: number = 0;
const timelineRef = React.useRef<visTimeline.Timeline | undefined>(timeline);
const deferredLoad = _.debounce(() => setLoaded(true),200);
const createTimeline = (): void => {
if (timelineNode && !timelineRef.current) {
let tlOptions = {
...defaultOptions,...props.options,autoResize: true,moveable: !props.dragLocked
};
if (tlOptions.height === undefined) {
delete tlOptions.height;
}
if (tlOptions.maxHeight === undefined) {
delete tlOptions.maxHeight;
}
timeline = new visTimeline.Timeline(
timelineNode,items ?? [],groups,tlOptions
);
timelineRef.current = timeline;
timelineRef.current.on("doubleClick",doubleClickHandler);
timelineRef.current.on("changed",changedHandler);
timelineRef.current.on("select",selectHandler);
timelineRef.current.on("rangechanged",rangeChangedHandler);
timelineRef.current.on("rangechange",rangeChangeHandler);
timelineRef.current.on("mouseDown",mouseDownHandler);
timelineRef.current.on("mouseUp",mouseUpHandler);
timelineRef.current.on("click",(properties) => {
clickHandler("click",properties);
});
} else if (!loaded) {
deferredLoad();
}
}
/* Start Event Handlers */
const doubleClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
...
}
const clickHandler = (event: string,properties: any): void => {
...
}
const changedHandler = (): void => {
...
};
const rangeChangedHandler = (properties?: typeDefs.RangeProperties): void => {
...
};
const rangeChangeHandler = (properties?: typeDefs.RangeProperties): void => {
...
}
// vanishes the columns on drag so that they don't make dragging choppy
const mouseDownHandler = (event: visTimeline.TimelineEventPropertiesResult )
: boolean =>
{
...
}
const mouseUpHandler = () : boolean => {
...
}
const selectHandler = (params: typeDefs.SelectionEventInfo): void => {
...
}
/* End Event Handlers */
/**
* Update the timeline scale options based on current scale.
* Timeline scale is being switched to auto when the zoom reaches a level where days labels can fit and will revert back to day when zoomed out above that scale level.
* @returns Void
*/
const updateOptions = (prevOptions?: visTimeline.TimelineOptions): void => {
if (!timelineRef.current) {
return;
}
let options: visTimeline.TimelineOptions | null = null;
if (calcScale > 0) {
options = {
showMinorLabels: true
};
}
if (prevOptions && prevOptions!.multiselect !== props.options!.multiselect) {
options = { ...options,multiselect: props.options!.multiselect };
}
if (options) {
timelineRef.current.setoptions(options);
}
}
// ---------- start effect hooks
React.useEffect(() => {
createTimeline();
if(props.timescaleSelector) {
createTimescale();
}
// find the nearest focusable grid target in the parent element
if (timelineNode) {
const parent = timelineNode.parentElement;
if (parent !== null) {
const grandparent = parent.parentElement;
if (grandparent !== null) {
invisibleInput = grandparent.querySelector(
`.form-control[type="text"]`
);
}
}
}
return () => {
if (timelineRef.current) {
timelineRef.current.destroy();
}
}
},[]);
React.useEffect(() => {
setItems(() => [...]);
},[props.data]);
React.useEffect(() => {
setGroups([...]);
},[props.rowData]);
React.useEffect(() => {
removeResizeLeftDiv();
if(timelineRef.current)
timelineRef.current.setItems(items);
},[items]);
React.useEffect(() => {
removeResizeLeftDiv();
if(timelineRef.current)
timelineRef.current.setData({
groups: groups,items: items
});
},[groups]);
React.useEffect(() => {
if(!range) {
return;
}
if (
timelineRef.current && props.options &&
props.options.maxHeight
) {
timelineRef.current.setoptions({
maxHeight: props.options!.maxHeight
});
}
updateOptions();
if(range?.start && range?.end)
{
if(timelineRef.current)
timelineRef.current.setwindow(
range.start,range.end,{
animation: true
});
}
},[range]);
React.useEffect(() => {
if(props?.timelineRange)
{
setRange(props.timelineRange);
}
},[props.timelineRange])
React.useEffect(() => {
if(props.focusedItem && timelineRef.current)
{
timelineRef.current.focus(props.focusedItem,{animation: false,zoom: false});
}
},[props.focusedItem])
// ---------- end effect hooks
return (
<div ref={el => (timelineNode = el)} />
);
}
export default Gantt; export type { GanttProps };
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)