UNPKG

testeranto

Version:

the AI powered BDD test framework for typescript projects

316 lines (315 loc) 17.3 kB
/* 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 }))); };