testeranto
Version:
the AI powered BDD test framework for typescript projects
316 lines (315 loc) • 17.3 kB
JavaScript
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Container, Row, Col } from "react-bootstrap";
import { Editor } from "@monaco-editor/react";
import { TestPageNavbar } from "./TestPageNavbar";
import { TestPageMainContent } from "./TestPageMainContent";
import { TestPageLeftContent } from "./TestPageLeftContent";
import { MagicRobotModal } from "../components/pure/MagicRobotModal";
import { ToastNotification } from "../components/pure/ToastNotification";
export const TestPageView = ({ projectName, testName, decodedTestPath, runtime, testsExist, errorCounts, logs, isWebSocketConnected, }) => {
var _a;
const navigate = useNavigate();
const [showAiderModal, setShowAiderModal] = useState(false);
const [messageOption, setMessageOption] = useState("default");
const [customMessage, setCustomMessage] = useState(typeof logs["message.txt"] === "string"
? logs["message.txt"]
: "make a script that prints hello");
const [showToast, setShowToast] = useState(false);
const [toastMessage, setToastMessage] = useState("");
const [toastVariant, setToastVariant] = useState("success");
const [expandedSections, setExpandedSections] = useState({
standardLogs: true,
runtimeLogs: true,
sourceFiles: true,
buildErrors: true,
});
// const [isNavbarCollapsed, setIsNavbarCollapsed] = useState(false);
// // Extract build errors and warnings relevant to this test
// const [buildErrors, setBuildErrors] = useState<{
// errors: any[];
// warnings: any[];
// }>({ errors: [], warnings: [] });
// useEffect(() => {
// const metafile = logs.build_logs?.metafile;
// if (!metafile) {
// setBuildErrors({ errors: [], warnings: [] });
// return;
// }
// const sourceFilesSet = new Set<string>();
// // Collect all input files from metafile outputs related to this test
// Object.entries(metafile.outputs || {}).forEach(([outputPath, output]) => {
// // Normalize paths for comparison
// const normalizedTestName = testName.replace(/\\/g, "/");
// const normalizedEntryPoint = output.entryPoint
// ? output.entryPoint.replace(/\\/g, "/")
// : "";
// if (normalizedEntryPoint.includes(normalizedTestName)) {
// Object.keys(output.inputs || {}).forEach((inputPath) => {
// sourceFilesSet.add(inputPath.replace(/\\/g, "/"));
// });
// }
// });
// // Filter errors and warnings to those originating from source files of this test
// const filteredErrors = (logs.build_logs?.errors || []).filter(
// (err: any) => {
// if (!err.location || !err.location.file) return false;
// return sourceFilesSet.has(err.location.file.replace(/\\/g, "/"));
// }
// );
// const filteredWarnings = (logs.build_logs?.warnings || []).filter(
// (warn: any) => {
// if (!warn.location || !warn.location.file) return false;
// return sourceFilesSet.has(warn.location.file.replace(/\\/g, "/"));
// }
// );
// setBuildErrors({ errors: filteredErrors, warnings: filteredWarnings });
// }, [logs, testName]);
// // Update customMessage when logs change
// useEffect(() => {
// if (typeof logs["message.txt"] === "string" && logs["message.txt"].trim()) {
// setCustomMessage(logs["message.txt"]);
// }
// }, [logs]);
// // Fetch projects data
// useEffect(() => {
// const fetchProjects = async () => {
// try {
// // First try to fetch from the API endpoint
// try {
// const response = await fetch('/api/projects/list');
// if (response.ok) {
// const projectsData = await response.json();
// setProjects(projectsData);
// return;
// }
// // If response is not ok, throw to trigger the catch block
// throw new Error(`HTTP ${response.status}: ${response.statusText}`);
// } catch (apiError) {
// console.warn('API endpoint failed, trying projects.json:', apiError);
// }
// // Fall back to projects.json
// const response = await fetch('/projects.json');
// if (response.ok) {
// const projectsData = await response.json();
// setProjects(projectsData);
// } else {
// // Throw an error if projects.json doesn't exist
// throw new Error('projects.json not found');
// }
// } catch (error) {
// console.error('Error fetching projects:', error);
// // Set projects to empty array to show appropriate UI
// setProjects([]);
// }
// };
// fetchProjects();
// }, []);
// // Use the centralized WebSocket from App context
// const wsContext = useWebSocket();
// const ws = wsContext?.ws;
const [activeTab, setActiveTab] = React.useState("tests.json");
const [openFiles, setOpenFiles] = useState([]);
const [activeFileIndex, setActiveFileIndex] = useState(-1);
const [selectedSourcePath, setSelectedSourcePath] = useState(null);
const [editorTheme, setEditorTheme] = useState("vs-dark");
// Add state for projects
const [projects, setProjects] = useState([]);
// Reference to the editor instance and model
const [editorRef, setEditorRef] = useState(null);
const [modelRef, setModelRef] = useState(null);
// Function to open a file in a new tab
const openFile = (file) => {
// Check if the file is already open
const existingIndex = openFiles.findIndex(f => f.path === file.path);
if (existingIndex !== -1) {
setActiveFileIndex(existingIndex);
return;
}
// Add the file to open files and set it as active
setOpenFiles(prev => [...prev, file]);
setActiveFileIndex(openFiles.length);
};
// Function to close a tab
const closeFile = (index) => {
setOpenFiles(prev => prev.filter((_, i) => i !== index));
// Adjust active index if needed
if (activeFileIndex === index) {
// If closing the active tab, activate the next one or previous one
if (openFiles.length > 1) {
setActiveFileIndex(Math.min(index, openFiles.length - 2));
}
else {
setActiveFileIndex(-1);
}
}
else if (activeFileIndex > index) {
setActiveFileIndex(activeFileIndex - 1);
}
};
// Update selectedFile when activeFileIndex changes
const selectedFile = activeFileIndex >= 0 ? openFiles[activeFileIndex] : null;
// Debug: track selectedFile changes
// useEffect(() => {
// console.log('selectedFile changed:', {
// path: selectedFile?.path,
// contentLength: selectedFile?.content?.length,
// language: selectedFile?.language
// });
// }, [selectedFile]);
// Update editor content when selectedFile changes
useEffect(() => {
// console.log('selectedFile useEffect triggered', {
// selectedFile: selectedFile,
// editorRefExists: !!editorRef,
// modelRefExists: !!modelRef
// });
if (selectedFile && editorRef) {
console.log('Updating editor for selected file:', selectedFile.path);
const monaco = window.monaco;
if (monaco) {
// Get current model or create a new one if needed
let currentModel = editorRef.getModel();
if (!currentModel) {
// Create a new model if none exists
currentModel = monaco.editor.createModel(selectedFile.content || "// No content", selectedFile.language || "plaintext");
editorRef.setModel(currentModel);
setModelRef(currentModel);
console.log('New model created and set');
}
else {
// Update existing model's value and language if needed
const currentValue = currentModel.getValue();
const newValue = selectedFile.content || "// No content";
// Only update if content has changed
if (currentValue !== newValue) {
currentModel.setValue(newValue);
console.log('Model value updated');
}
// Update language if needed
const currentLanguage = currentModel.getLanguageId();
const newLanguage = selectedFile.language || "plaintext";
if (currentLanguage !== newLanguage) {
monaco.editor.setModelLanguage(currentModel, newLanguage);
console.log('Model language updated to:', newLanguage);
}
}
// Always reveal the beginning of the file
editorRef.revealPosition({ lineNumber: 1, column: 1 });
}
}
else {
console.log('No selectedFile or editorRef available');
}
}, [selectedFile, editorRef, modelRef]);
return (React.createElement(Container, { fluid: true, className: "px-0" },
React.createElement(TestPageNavbar, { decodedTestPath: decodedTestPath, projectName: projectName, runtime: runtime, setShowAiderModal: setShowAiderModal, isWebSocketConnected: isWebSocketConnected }),
React.createElement(MagicRobotModal, { customMessage: customMessage, isWebSocketConnected: isWebSocketConnected, messageOption: messageOption, navigate: navigate, projectName: projectName, runtime: runtime, setCustomMessage: setCustomMessage, setMessageOption: setMessageOption, setShowAiderModal: setShowAiderModal, setShowToast: setShowToast, setToastMessage: setToastMessage, setToastVariant: setToastVariant, showAiderModal: showAiderModal, testName: testName }),
React.createElement(Row, { className: "g-0 flex-nowrap overflow-x-auto" },
React.createElement(Col, { sm: 3, style: {
height: "calc(100vh - 56px)",
overflowY: "auto",
} },
React.createElement(TestPageLeftContent, { projects: projects, setExpandedSections: setExpandedSections, expandedSections: expandedSections, logs: logs, setActiveTab: setActiveTab, setSelectedFile: openFile, runtime: runtime, selectedSourcePath: selectedSourcePath, activeTab: activeTab, setSelectedSourcePath: setSelectedSourcePath, projectName: projectName, testName: testName })),
React.createElement(Col, { sm: 8, style: {
height: "calc(100vh - 56px)",
overflowY: "auto",
display: "flex",
flexDirection: "column",
} },
openFiles.length > 0 && (React.createElement("div", { style: {
display: "flex",
backgroundColor: "#1e1e1e",
borderBottom: "1px solid #3c3c3c"
} }, openFiles.map((file, index) => (React.createElement("div", { key: file.path, style: {
padding: "8px 16px",
cursor: "pointer",
backgroundColor: index === activeFileIndex ? "#2d2d30" : "transparent",
color: index === activeFileIndex ? "#ffffff" : "#cccccc",
borderRight: "1px solid #3c3c3c",
display: "flex",
alignItems: "center",
}, onClick: () => setActiveFileIndex(index) },
React.createElement("span", { style: { marginRight: "8px" } }, file.path.split('/').pop()),
React.createElement("button", { onClick: (e) => {
e.stopPropagation();
closeFile(index);
}, style: {
background: "none",
border: "none",
color: "#cccccc",
cursor: "pointer",
padding: "0",
width: "16px",
height: "16px",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}, onMouseOver: (e) => {
e.currentTarget.style.backgroundColor = '#e81123';
e.currentTarget.style.color = '#ffffff';
}, onMouseOut: (e) => {
e.currentTarget.style.backgroundColor = 'transparent';
e.currentTarget.style.color = '#cccccc';
} }, "\u00D7")))))),
React.createElement("div", { style: { height: "100%", width: "100%", flexGrow: 1 }, id: "editor-wrapper" },
React.createElement(Editor, { height: "100%" // Fill the container
, path: (selectedFile === null || selectedFile === void 0 ? void 0 : selectedFile.path) || "empty", defaultLanguage: (selectedFile === null || selectedFile === void 0 ? void 0 : selectedFile.language) || "plaintext", theme: editorTheme, options: {
minimap: { enabled: false },
fontSize: 14,
wordWrap: "on",
automaticLayout: true,
readOnly: !((_a = selectedFile === null || selectedFile === void 0 ? void 0 : selectedFile.path) === null || _a === void 0 ? void 0 : _a.includes("source_files")),
// Configure scrollbars to allow horizontal scrolling to bubble up when appropriate
scrollbar: {
vertical: 'auto',
horizontal: 'auto',
verticalScrollbarSize: 8,
horizontalScrollbarSize: 8,
handleMouseWheel: true,
// This is the key option: don't always consume mouse wheel events
alwaysConsumeMouseWheel: false
},
mouseWheelScrollSensitivity: 1,
fastScrollSensitivity: 1,
scrollBeyondLastColumn: 0,
scrollBeyondLastLine: true,
}, onMount: (editor, monaco) => {
console.log('Editor mounted', {
selectedFileAtMount: selectedFile,
monacoAvailable: !!monaco
});
// Store editor reference
setEditorRef(editor);
// Create initial model if selectedFile is available
if (selectedFile) {
const model = monaco.editor.createModel(selectedFile.content || "// Select a file to view its contents", selectedFile.language || "plaintext");
editor.setModel(model);
setModelRef(model);
console.log('Initial model created and set');
}
console.log('Editor reference set');
// Clean up when editor is unmounted
return () => {
// Dispose of the model if it exists
const currentModel = editor.getModel();
if (currentModel) {
currentModel.dispose();
}
// Clear the references
setEditorRef(null);
setModelRef(null);
};
} }))),
React.createElement(Col, { sm: 8, style: {
height: "calc(100vh - 56px)",
overflowY: "auto",
} },
React.createElement(TestPageMainContent, { selectedFile: selectedFile, buildErrors: buildErrors, projectName: projectName, testName: testName, runtime: runtime })),
React.createElement(Col, { sm: 3, style: { height: "calc(100vh - 56px)", overflowY: "auto" } }, "atttibute editor here"),
React.createElement(Col, { sm: 3, style: { height: "calc(100vh - 56px)", overflowY: "auto" } }, "tree editor here")),
React.createElement(ToastNotification, { showToast: showToast, setShowToast: setShowToast, toastVariant: toastVariant, toastMessage: toastMessage })));
};