Merging dev into main #49
@@ -33,6 +33,12 @@
|
||||
|
||||
/* Node Styles */
|
||||
|
||||
:global(.react-flow__node.selected) {
|
||||
outline: 1px dashed blue !important;
|
||||
border-radius: 5pt;
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
.default-node {
|
||||
padding: 10px 15px;
|
||||
background-color: canvas;
|
||||
@@ -41,6 +47,8 @@
|
||||
filter: drop-shadow(0 0 0.75rem black);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.node-norm {
|
||||
outline: rgb(0, 149, 25) solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem forestgreen);
|
||||
@@ -76,10 +84,13 @@
|
||||
filter: drop-shadow(0 0 0.25rem plum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.draggable-node {
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: black solid 2pt;
|
||||
filter: drop-shadow(0 0 0.75rem black);
|
||||
}
|
||||
@@ -88,6 +99,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: forestgreen solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem forestgreen);
|
||||
}
|
||||
@@ -96,6 +108,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: yellow solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem yellow);
|
||||
}
|
||||
@@ -104,6 +117,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: teal solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem teal);
|
||||
}
|
||||
@@ -112,6 +126,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: dodgerblue solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem dodgerblue);
|
||||
}
|
||||
@@ -120,6 +135,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: orange solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem orange);
|
||||
}
|
||||
@@ -128,6 +144,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: red solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem red);
|
||||
}
|
||||
@@ -136,6 +153,7 @@
|
||||
padding: 3px 10px;
|
||||
background-color: canvas;
|
||||
border-radius: 5pt;
|
||||
cursor: move;
|
||||
outline: plum solid 2pt;
|
||||
filter: drop-shadow(0 0 0.25rem plum);
|
||||
}
|
||||
@@ -152,4 +170,53 @@
|
||||
|
||||
.bottomRightHandle {
|
||||
left: 60% !important;
|
||||
}
|
||||
|
||||
.node-toolbar-tooltip {
|
||||
background-color: darkgray;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
cursor: help;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.custom-tooltip {
|
||||
pointer-events: none;
|
||||
background-color: Canvas;
|
||||
color: CanvasText;
|
||||
padding: 8px 12px;
|
||||
border-radius: 0 6px 6px 0;
|
||||
outline: CanvasText solid 2px;
|
||||
font-size: 14px;
|
||||
filter: drop-shadow(0 0 0.25rem CanvasText);
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.custom-tooltip-header {
|
||||
pointer-events: none;
|
||||
background-color: CanvasText;
|
||||
color: Canvas;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px 0 0 6px;
|
||||
outline: CanvasText solid 2px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-variant-caps: small-caps;
|
||||
filter: drop-shadow(0 0 0.25rem CanvasText);
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
MarkerType,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import {useEffect} from "react";
|
||||
import {type CSSProperties, useEffect, useState} from "react";
|
||||
import {useShallow} from 'zustand/react/shallow';
|
||||
import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts";
|
||||
import useProgramStore from "../../utils/programStore.ts";
|
||||
@@ -79,7 +79,7 @@ const VisProgUI = () => {
|
||||
endBatchAction,
|
||||
scrollable
|
||||
} = useFlowStore(useShallow(selector)); // instructs the editor to use the corresponding functions from the FlowStore
|
||||
|
||||
const [zoom, setZoom] = useState(1);
|
||||
// adds ctrl+z and ctrl+y support to respectively undo and redo actions
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
@@ -91,7 +91,7 @@ const VisProgUI = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={`${styles.innerEditorContainer} round-lg border-lg`}>
|
||||
<div className={`${styles.innerEditorContainer} round-lg border-lg`} style={({'--flow-zoom': zoom} as CSSProperties)}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -107,6 +107,7 @@ const VisProgUI = () => {
|
||||
onNodeDragStart={beginBatchAction}
|
||||
onNodeDragStop={endBatchAction}
|
||||
preventScrolling={scrollable}
|
||||
onMove={(_, viewport) => setZoom(viewport.zoom)}
|
||||
snapToGrid
|
||||
fitView
|
||||
proOptions={{hideAttribution: true}}
|
||||
|
||||
@@ -3,7 +3,8 @@ import EndNode, {
|
||||
EndConnectionSource,
|
||||
EndDisconnectionTarget,
|
||||
EndDisconnectionSource,
|
||||
EndReduce
|
||||
EndReduce,
|
||||
EndTooltip
|
||||
} from "./nodes/EndNode";
|
||||
import { EndNodeDefaults } from "./nodes/EndNode.default";
|
||||
import StartNode, {
|
||||
@@ -11,7 +12,8 @@ import StartNode, {
|
||||
StartConnectionSource,
|
||||
StartDisconnectionTarget,
|
||||
StartDisconnectionSource,
|
||||
StartReduce
|
||||
StartReduce,
|
||||
StartTooltip
|
||||
} from "./nodes/StartNode";
|
||||
import { StartNodeDefaults } from "./nodes/StartNode.default";
|
||||
import PhaseNode, {
|
||||
@@ -19,7 +21,8 @@ import PhaseNode, {
|
||||
PhaseConnectionSource,
|
||||
PhaseDisconnectionTarget,
|
||||
PhaseDisconnectionSource,
|
||||
PhaseReduce
|
||||
PhaseReduce,
|
||||
PhaseTooltip
|
||||
} from "./nodes/PhaseNode";
|
||||
import { PhaseNodeDefaults } from "./nodes/PhaseNode.default";
|
||||
import NormNode, {
|
||||
@@ -27,7 +30,8 @@ import NormNode, {
|
||||
NormConnectionSource,
|
||||
NormDisconnectionTarget,
|
||||
NormDisconnectionSource,
|
||||
NormReduce
|
||||
NormReduce,
|
||||
NormTooltip
|
||||
} from "./nodes/NormNode";
|
||||
import { NormNodeDefaults } from "./nodes/NormNode.default";
|
||||
import GoalNode, {
|
||||
@@ -35,7 +39,8 @@ import GoalNode, {
|
||||
GoalConnectionSource,
|
||||
GoalDisconnectionTarget,
|
||||
GoalDisconnectionSource,
|
||||
GoalReduce
|
||||
GoalReduce,
|
||||
GoalTooltip
|
||||
} from "./nodes/GoalNode";
|
||||
import { GoalNodeDefaults } from "./nodes/GoalNode.default";
|
||||
import TriggerNode, {
|
||||
@@ -43,10 +48,18 @@ import TriggerNode, {
|
||||
TriggerConnectionSource,
|
||||
TriggerDisconnectionTarget,
|
||||
TriggerDisconnectionSource,
|
||||
TriggerReduce
|
||||
TriggerReduce,
|
||||
TriggerTooltip
|
||||
} from "./nodes/TriggerNode";
|
||||
import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
|
||||
import BasicBeliefNode, { BasicBeliefConnectionSource, BasicBeliefConnectionTarget, BasicBeliefDisconnectionSource, BasicBeliefDisconnectionTarget, BasicBeliefReduce } from "./nodes/BasicBeliefNode";
|
||||
import BasicBeliefNode, {
|
||||
BasicBeliefConnectionSource,
|
||||
BasicBeliefConnectionTarget,
|
||||
BasicBeliefDisconnectionSource,
|
||||
BasicBeliefDisconnectionTarget,
|
||||
BasicBeliefReduce,
|
||||
BasicBeliefTooltip
|
||||
} from "./nodes/BasicBeliefNode";
|
||||
import { BasicBeliefNodeDefaults } from "./nodes/BasicBeliefNode.default";
|
||||
|
||||
/**
|
||||
@@ -173,4 +186,17 @@ export const NodesInPhase = {
|
||||
end: () => false,
|
||||
phase: () => false,
|
||||
basic_belief: () => false,
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the tooltips for all nodeTypes so they can be accessed by the tooltip component
|
||||
*/
|
||||
export const NodeTooltips = {
|
||||
start: StartTooltip,
|
||||
end: EndTooltip,
|
||||
phase: PhaseTooltip,
|
||||
norm: NormTooltip,
|
||||
goal: GoalTooltip,
|
||||
trigger: TriggerTooltip,
|
||||
basic_belief: BasicBeliefTooltip,
|
||||
}
|
||||
@@ -45,9 +45,9 @@ function createNode(id: string, type: string, position: XYPosition, data: Record
|
||||
|
||||
//* Initial nodes, created by using createNode. */
|
||||
// Start and End don't need to apply the UUID, since they are technically never compiled into a program.
|
||||
const startNode = createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}, false)
|
||||
const endNode = createNode('end', 'end', {x: 500, y: 100}, {label: "End"}, false)
|
||||
const initialPhaseNode = createNode(crypto.randomUUID(), 'phase', {x:200, y:100}, {label: "Phase 1", children : [], isFirstPhase: false, nextPhaseId: null})
|
||||
const startNode = createNode('start', 'start', {x: 110, y: 100}, {label: "Start"}, false)
|
||||
const endNode = createNode('end', 'end', {x: 590, y: 100}, {label: "End"}, false)
|
||||
const initialPhaseNode = createNode(crypto.randomUUID(), 'phase', {x:235, y:100}, {label: "Phase 1", children : [], isFirstPhase: false, nextPhaseId: null})
|
||||
|
||||
const initialNodes : Node[] = [startNode, endNode, initialPhaseNode,];
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useReactFlow, type XYPosition } from '@xyflow/react';
|
||||
import { type ReactNode, useCallback, useRef, useState } from 'react';
|
||||
import useFlowStore from '../VisProgStores';
|
||||
import styles from '../../VisProg.module.css';
|
||||
import { NodeDefaults, type NodeTypes } from '../NodeRegistry'
|
||||
import { NodeDefaults, type NodeTypes} from '../NodeRegistry'
|
||||
import {Tooltip} from "./NodeComponents.tsx";
|
||||
|
||||
/**
|
||||
* Props for a draggable node within the drag-and-drop toolbar.
|
||||
@@ -47,14 +48,17 @@ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeP
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}
|
||||
ref={draggableRef}
|
||||
id={`draggable-${nodeType}`}
|
||||
data-testid={`draggable-${nodeType}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
<Tooltip nodeType={nodeType}>
|
||||
<div>
|
||||
<div className={className}
|
||||
ref={draggableRef}
|
||||
id={`draggable-${nodeType}`}
|
||||
data-testid={`draggable-${nodeType}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +137,7 @@ export function DndToolbar() {
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`}>
|
||||
<div className={`flex-col gap-lg padding-md ${styles.innerDndPanel}`} id={"draggable-sidebar"}>
|
||||
<div className="description">
|
||||
You can drag these nodes to the pane to create new nodes.
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { NodeToolbar } from '@xyflow/react';
|
||||
import {NodeToolbar} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import {type JSX, useState} from "react";
|
||||
import {createPortal} from "react-dom";
|
||||
import styles from "../../VisProg.module.css";
|
||||
import {NodeTooltips} from "../NodeRegistry.ts";
|
||||
import useFlowStore from "../VisProgStores.tsx";
|
||||
|
||||
|
||||
/**
|
||||
* Props for the Toolbar component.
|
||||
*
|
||||
@@ -24,14 +29,94 @@ type ToolbarProps = {
|
||||
* @constructor
|
||||
*/
|
||||
export function Toolbar({nodeId, allowDelete}: ToolbarProps) {
|
||||
const {deleteNode} = useFlowStore();
|
||||
const {nodes, deleteNode} = useFlowStore();
|
||||
|
||||
const deleteParentNode = ()=> {
|
||||
|
||||
const deleteParentNode = () => {
|
||||
deleteNode(nodeId);
|
||||
}
|
||||
};
|
||||
|
||||
const nodeType = nodes.find((node) => node.id === nodeId)?.type as keyof typeof NodeTooltips;
|
||||
return (
|
||||
<NodeToolbar>
|
||||
<NodeToolbar className={"flex-row align-center"}>
|
||||
<button className="Node-toolbar__deletebutton" onClick={deleteParentNode} disabled={!allowDelete}>delete</button>
|
||||
<Tooltip nodeType={nodeType}>
|
||||
<div className={styles.nodeToolbarTooltip}>i</div>
|
||||
</Tooltip>
|
||||
</NodeToolbar>);
|
||||
}
|
||||
|
||||
|
||||
type TooltipProps = {
|
||||
nodeType?: keyof typeof NodeTooltips;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A general tooltip component, that can be used as a wrapper for any component
|
||||
* that has a nodeType and a corresponding nodeTooltip.
|
||||
*
|
||||
* currently used to show tooltips for draggable-nodes and nodes inside the editor
|
||||
*
|
||||
* @param {"start" | "end" | "phase" | "norm" | "goal" | "trigger" | "basic_belief" | undefined} nodeType
|
||||
* @param {React.JSX.Element} children
|
||||
* @returns {React.JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export function Tooltip({ nodeType, children }: TooltipProps) {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const [disabled , setDisabled] = useState(false);
|
||||
const [coords, setCoords] = useState({ top: 0, left: 0 });
|
||||
|
||||
const updateTooltipPos = () => {
|
||||
const rect = document.getElementById("draggable-sidebar")!.getBoundingClientRect();
|
||||
setCoords({
|
||||
// Position exactly below the bottom edge of the draggable sidebar (plus a small gap)
|
||||
top: rect.bottom + 10,
|
||||
left: rect.left + rect.width / 2, // Keep it horizontally centered
|
||||
});
|
||||
};
|
||||
|
||||
return nodeType ?
|
||||
(<div>
|
||||
<div
|
||||
onMouseDown={() => {
|
||||
updateTooltipPos();
|
||||
setShowTooltip(false);
|
||||
setDisabled(true);
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setDisabled(false);
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
if (!disabled) {
|
||||
updateTooltipPos();
|
||||
setShowTooltip(true);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={ () => setShowTooltip(false)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{showTooltip && createPortal(
|
||||
<div
|
||||
className={"flex-row"}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
position: 'fixed',
|
||||
top: `${coords.top}px`,
|
||||
left: `${coords.left}px`,
|
||||
transform: 'translateX(-50%)', // Center based on the midpoint
|
||||
}}
|
||||
>
|
||||
<span className={styles.customTooltipHeader}>{nodeType}</span>
|
||||
<span className={styles.customTooltip}>
|
||||
{NodeTooltips[nodeType] || "Available for drag"}
|
||||
</span>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
) : children
|
||||
}
|
||||
|
||||
@@ -31,4 +31,13 @@
|
||||
filter: drop-shadow(0 0 0.25rem green);
|
||||
}
|
||||
|
||||
:global(.react-flow__handle) {
|
||||
width: calc(8px / var(--flow-zoom, 1));
|
||||
height: calc(8px / var(--flow-zoom, 1));
|
||||
transition: width 0.1s ease, height 0.1s ease;
|
||||
min-width: 8px;
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ type Emotion = { type: "emotion", id: string, value: string, label: "Emotion rec
|
||||
|
||||
export type BasicBeliefNode = Node<BasicBeliefNodeData>
|
||||
|
||||
// update the tooltip to reflect newly added connection options for a belief
|
||||
export const BasicBeliefTooltip = `
|
||||
A belief describes a condition that must be met
|
||||
in order for a connected norm to be activated`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target
|
||||
|
||||
@@ -55,6 +55,10 @@ export function EndReduce(node: Node, _nodes: Node[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export const EndTooltip = `
|
||||
The end node signifies the endpoint of your program;
|
||||
the output of the final phase of your program should connect to the end node`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target
|
||||
* @param _thisNode the node of this node type which function is called
|
||||
|
||||
@@ -144,6 +144,11 @@ export function GoalReduce(node: Node, _nodes: Node[]) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const GoalTooltip = `
|
||||
The goal node allows you to set goals that Pepper has to achieve
|
||||
before moving to the next phase of your program`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target
|
||||
* @param _thisNode the node of this node type which function is called
|
||||
|
||||
@@ -114,6 +114,10 @@ export function NormReduce(node: Node, nodes: Node[]) {
|
||||
return result
|
||||
}
|
||||
|
||||
export const NormTooltip = `
|
||||
A norm describes a behavioral rule Pepper must follow during the connected phase(-s),
|
||||
for example: "respond using formal language"`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target
|
||||
* @param _thisNode the node of this node type which function is called
|
||||
|
||||
@@ -118,6 +118,10 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export const PhaseTooltip = `
|
||||
A phase is a single stage of the program, during a phase Pepper will behave
|
||||
in accordance with any connected norms, goals and triggers`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target (phase)
|
||||
* @param _thisNode the node of this node type which function is called
|
||||
|
||||
@@ -53,6 +53,10 @@ export function StartReduce(node: Node, _nodes: Node[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export const StartTooltip = `
|
||||
The start node acts as the starting point for a program,
|
||||
it should be connected to the left handle of the first phase of your program`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target
|
||||
* @param _thisNode the node of this node type which function is called
|
||||
|
||||
@@ -118,6 +118,11 @@ export function TriggerReduce(node: Node, nodes: Node[]) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const TriggerTooltip = `
|
||||
A trigger node is used to make Pepper execute a predefined plan -
|
||||
consisting of one or more actions - when the connected beliefs are met`;
|
||||
|
||||
/**
|
||||
* This function is called whenever a connection is made with this node type as the target
|
||||
* @param _thisNode the node of this node type which function is called
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import {Tooltip} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/NodeComponents.tsx";
|
||||
import {renderWithSidebar} from "../../../../test-utils/test-utils.tsx";
|
||||
|
||||
describe('Tooltip component test', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders and shows tooltip content on hover', () => {
|
||||
renderWithSidebar(
|
||||
<Tooltip nodeType="phase">
|
||||
<div>?</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const trigger = screen.getByText('?');
|
||||
|
||||
// initially hidden
|
||||
expect(
|
||||
screen.queryByText('Phase tooltip text')
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// hover shows tooltip
|
||||
fireEvent.mouseOver(trigger);
|
||||
|
||||
expect(screen.getByText('phase')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('A phase is a single stage of the program, during a phase Pepper will behave in accordance with any connected norms, goals and triggers')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// rendered via portal
|
||||
expect(
|
||||
document.body.contains(
|
||||
screen.getByText('A phase is a single stage of the program, during a phase Pepper will behave in accordance with any connected norms, goals and triggers')
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,48 @@ export function renderWithProviders(
|
||||
}
|
||||
|
||||
|
||||
type SidebarRect = Partial<DOMRect>;
|
||||
|
||||
const defaultRect: DOMRect = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 100,
|
||||
right: 200,
|
||||
width: 200,
|
||||
height: 100,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a component and injects a mock `#draggable-sidebar`
|
||||
* element required by Tooltip positioning logic.
|
||||
*/
|
||||
export function renderWithSidebar(
|
||||
ui: ReactElement,
|
||||
rect: SidebarRect = {},
|
||||
options?: RenderOptions
|
||||
) {
|
||||
const sidebar = document.createElement('div');
|
||||
sidebar.id = 'draggable-sidebar';
|
||||
|
||||
sidebar.getBoundingClientRect = jest.fn(() => ({
|
||||
...defaultRect,
|
||||
...rect,
|
||||
}));
|
||||
|
||||
document.body.appendChild(sidebar);
|
||||
|
||||
const result = render(ui, options);
|
||||
|
||||
return {
|
||||
...result,
|
||||
sidebar,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Re-export everything from testing library
|
||||
//eslint-disable-next-line react-refresh/only-export-components
|
||||
export * from '@testing-library/react';
|
||||
|
||||
Reference in New Issue
Block a user