Files
eepp/src/tests/unit_tests/uicodeeditor_test.cpp

312 lines
10 KiB
C++

#include "utest.h"
#include <eepp/scene/node.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
#include <eepp/ui/uiapplication.hpp>
#include <eepp/ui/uicodeeditor.hpp>
using namespace EE;
using namespace EE::UI;
using namespace EE::UI::Doc;
using namespace EE::Scene;
static const std::string userCode = R"objcpp(#import "common.h"
#import <cmath>
#import <gdiplus.h>
#import <iostream>
#import <vector>
#import <windows.h>
@interface test () {
struct DrawSineWave {
struct AppState {
int width = 800;
int height = 600;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
};
static void Draw(HDC hdc, int width, int height) {
if (width <= 0 || height <= 0)
return;
using namespace Gdiplus;
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
// Background
SolidBrush bgBrush(Color(255, 255, 255, 255));
graphics.FillRectangle(&bgBrush, 0, 0, width, height);
// Axis
Pen axisPen(Color(200, 200, 200), 1.0f);
graphics.DrawLine(&axisPen, 0, height / 2, width, height / 2);
// Sine wave parameters
double amplitude = (height - 20) / 2.0;
double midY = height / 2.0;
double period = width;
double twoPi = 6.283185307179586;
// Build points
std::vector<PointF> pts;
double step = 0.25; // smaller step for smoother curve
for (double x = 0; x < width; x += step) {
double t = x / period;
double y = midY - amplitude * std::sin(twoPi * t);
pts.push_back(PointF(static_cast<REAL>(x), static_cast<REAL>(y)));
}
// Draw sine wave
Pen sinePen(Color(0, 120, 215), 2.0f);
if (!pts.empty()) {
graphics.DrawLines(&sinePen, pts.data(), static_cast<INT>(pts.size()));
}
}
}
@end
OF_APPLICATION_DELEGATE(test)
@implementation test
- (void)applicationDidFinishLaunching:(OFNotification *)notification {
HINSTANCE hInstance = GetModuleHandle(nullptr);
DrawSineWave::Setup(hInstance, SW_SHOW);
[OFApplication terminate];
}
@end
)objcpp";
#define VERIFY_CONSISTENCY( editor ) \
{ \
const DocumentView& view = editor->getDocumentView(); \
TextDocument& doc = editor->getDocument(); \
if ( !view.isOneToOne() ) { \
EXPECT_EQ( (size_t)doc.linesCount(), view.getDocLineToVisibleIndex().size() ); \
EXPECT_EQ( (size_t)doc.linesCount(), view.getVisibleLinesOffset().size() ); \
size_t expectedVisibleCount = 0; \
for ( Int64 i = 0; i < (Int64)doc.linesCount(); i++ ) { \
if ( view.isLineVisible( i ) ) { \
EXPECT_NE( (Int64)VisibleIndex::invalid, (Int64)view.toVisibleIndex( i ) ); \
Int64 startIdx = (Int64)view.toVisibleIndex( i ); \
Int64 endIdx = (Int64)view.toVisibleIndex( i, true ); \
expectedVisibleCount += ( endIdx - startIdx + 1 ); \
} else { \
EXPECT_EQ( (Int64)VisibleIndex::invalid, (Int64)view.toVisibleIndex( i ) ); \
} \
} \
EXPECT_EQ( expectedVisibleCount, view.getVisibleLinesCount() ); \
} \
}
UTEST( UICodeEditor, DocumentViewStressTest ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Stress Test", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
auto* editor = UICodeEditor::New();
editor->setParent( (Node*)app.getUI() );
editor->setPixelsSize( 800, 600 );
editor->getDocument().setSyntaxDefinition(
SyntaxDefinitionManager::instance()->getByLanguageName( "C++" ) );
auto resetEditor = [&]() {
editor->getDocument().selectAll();
editor->getDocument().deleteSelection();
editor->getDocument().textInput( userCode );
editor->getDocument().getFoldRangeService().findRegionsNative();
editor->unfoldAll();
};
// --- SCENARIO 1: Folding Only (No Wrap) ---
editor->setLineWrapMode( LineWrapMode::NoWrap );
resetEditor();
// Fold everything
editor->foldAll();
VERIFY_CONSISTENCY( editor );
// Delete while folded (should unfold affected)
editor->getDocument().setSelection( { { 2, 0 }, { 10, 0 } } );
editor->getDocument().deleteSelection();
VERIFY_CONSISTENCY( editor );
editor->getDocument().undo();
VERIFY_CONSISTENCY( editor );
// Insert text in the middle of a folded region
editor->foldAll();
editor->getDocument().setSelection( { { 5, 5 }, { 5, 5 } } );
editor->getDocument().textInput( "STRESS_TEST" ); // Should unfold line 5
VERIFY_CONSISTENCY( editor );
// --- SCENARIO 2: Wrapping Only (No Folds) ---
resetEditor();
editor->setLineWrapMode( LineWrapMode::Letter );
editor->setPixelsSize( 100, 600 ); // Force lots of wraps
VERIFY_CONSISTENCY( editor );
// Multi-line delete with wraps
editor->getDocument().setSelection( { { 1, 5 }, { 4, 2 } } );
editor->getDocument().deleteSelection();
VERIFY_CONSISTENCY( editor );
editor->getDocument().undo();
VERIFY_CONSISTENCY( editor );
// --- SCENARIO 3: Folding + Wrapping ---
resetEditor();
editor->setLineWrapMode( LineWrapMode::Letter );
editor->setPixelsSize( 100, 600 );
editor->foldAll();
VERIFY_CONSISTENCY( editor );
// Delete range straddling multiple folded regions with wraps
// userCode has folds starting at lines: 1, 2, 3, 7
editor->getDocument().setSelection( { { 0, 0 }, { 12, 0 } } );
editor->getDocument().deleteSelection();
VERIFY_CONSISTENCY( editor );
editor->getDocument().undo();
VERIFY_CONSISTENCY( editor );
// Random heavy operations
resetEditor();
editor->setLineWrapMode( LineWrapMode::Word );
editor->setPixelsSize( 200, 600 );
for ( int i = 0; i < 5; i++ ) {
editor->foldAll();
editor->getDocument().setSelection( { { i * 2, 0 }, { i * 2 + 1, 5 } } );
editor->getDocument().textInput( "RANDOM_INSERTION\nMORE_LINES\n" );
VERIFY_CONSISTENCY( editor );
editor->unfoldAll();
VERIFY_CONSISTENCY( editor );
}
// Final check: Delete everything
editor->getDocument().selectAll();
editor->getDocument().deleteSelection();
VERIFY_CONSISTENCY( editor );
editor->getDocument().undo();
VERIFY_CONSISTENCY( editor );
}
UTEST( UICodeEditor, FoldingCrashReproduction ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Reproduce Crash", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ) );
auto* editor = UICodeEditor::New();
editor->setParent( (Node*)app.getUI() );
editor->setPixelsSize( 800, 600 );
editor->getDocument().setSyntaxDefinition(
SyntaxDefinitionManager::instance()->getByLanguageName( "C++" ) );
editor->getDocument().textInput( userCode );
// Wait for folding regions to be updated
editor->getDocument().getFoldRangeService().findRegionsNative();
// Try to reproduce the sequence that crashed:
// 1. Fold regions
editor->foldAll();
// 2. Select everything and delete
editor->getDocument().selectAll();
editor->getDocument().deleteSelection();
// 3. Undo
editor->getDocument().undo();
// 4. Unfold all
editor->unfoldAll();
// Another sequence: select range straddling folded region and delete
editor->getDocument().textInput( userCode );
editor->getDocument().getFoldRangeService().findRegionsNative();
auto regions = editor->getDocument().getFoldRangeService().getFoldingRegions();
if ( !regions.empty() ) {
auto firstRegionLine = regions.begin()->first;
auto firstRegionRange = regions.begin()->second;
editor->fold( firstRegionLine );
// Select from before the folded region to after
TextRange sel( { firstRegionLine, 0 }, { firstRegionRange.end().line() + 1, 0 } );
editor->getDocument().setSelection( sel );
editor->getDocument().deleteSelection();
editor->getDocument().undo();
}
}
UTEST( UICodeEditor, ReproduceFoldingCrash ) {
UIApplication app(
WindowSettings( 800, 600, "eepp - Reproduce Crash", WindowStyle::Default,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ) );
auto* editor = UICodeEditor::New();
editor->setParent( (Node*)app.getUI() );
editor->setPixelsSize( 800, 600 );
auto languages = SyntaxDefinitionManager::instance()->getLanguageNames();
editor->getDocument().setSyntaxDefinition(
SyntaxDefinitionManager::instance()->getByLanguageName( "C++" ) );
editor->getDocument().textInput( userCode );
// Wait for folding regions to be updated
editor->getDocument().getFoldRangeService().findRegionsNative();
auto regions = editor->getDocument().getFoldRangeService().getFoldingRegions();
// Brute force: Try all combinations of folded regions
size_t numRegions = regions.size();
if ( numRegions > 8 )
numRegions = 8; // Limit for speed
for ( size_t i = 0; i < ( (size_t)1 << numRegions ); ++i ) {
editor->getDocument().resetUndoRedo();
editor->getDocument().resetSelection();
editor->unfoldAll();
size_t idx = 0;
for ( auto const& [line, range] : regions ) {
if ( ( i >> idx ) & 1 ) {
editor->fold( line );
}
if ( ++idx >= numRegions )
break;
}
// Try various selections and deletions
idx = 0;
for ( auto const& [line, range] : regions ) {
// Selection from before the folded region to after
TextRange sel( { line, 0 }, { range.end().line() + 1, 0 } );
editor->getDocument().setSelection( sel );
editor->getDocument().deleteSelection();
editor->getDocument().undo();
// Selection starting inside the folded region
if ( range.end().line() > line ) {
TextRange sel2( { line + 1, 0 }, { range.end().line() + 1, 0 } );
editor->getDocument().setSelection( sel2 );
editor->getDocument().deleteSelection();
editor->getDocument().undo();
}
if ( ++idx >= numRegions )
break;
}
// Also try foldAll then select everything and delete
editor->foldAll();
editor->getDocument().selectAll();
editor->getDocument().deleteSelection();
editor->getDocument().undo();
}
}