#include "core/Router.h" #include using namespace diag; namespace { bool isOrthogonal(const QList& poly) { for (int i = 0; i + 1 < poly.size(); ++i) { const QPointF a = poly.at(i); const QPointF b = poly.at(i + 1); if (qAbs(a.x() - b.x()) > 1e-6 && qAbs(a.y() - b.y()) > 1e-6) return false; } return true; } } // namespace class TestRouter : public QObject { Q_OBJECT private slots: void route_isOrthogonalAndKeepsEndpoints() { const QList poly = Router::route({63, 37}, Side::Right, {305, 143}, Side::Left, 20); QVERIFY(poly.size() >= 2); QCOMPARE(poly.first(), QPointF(63, 37)); QCOMPARE(poly.last(), QPointF(305, 143)); QVERIFY(isOrthogonal(poly)); } void route_intermediatePointsOnGrid() { const QList poly = Router::route({63, 37}, Side::Right, {305, 143}, Side::Left, 20); // The vertical middle segment must run on a grid line. bool foundGridX = false; for (int i = 1; i + 1 < poly.size(); ++i) { const double x = poly.at(i).x(); if (qAbs(x - Router::snap(x, 20)) < 1e-6) foundGridX = true; } QVERIFY(foundGridX); } void route_leavesPortInSideDirection() { const QList down = Router::route({50, 50}, Side::Bottom, {250, 250}, Side::Top, 20); QVERIFY(down.at(1).y() > down.at(0).y()); QCOMPARE(down.at(1).x(), down.at(0).x()); const QList left = Router::route({300, 50}, Side::Left, {50, 50}, Side::Right, 20); QVERIFY(left.at(1).x() < left.at(0).x()); } void route_mixedAxesSingleCorner() { const QList poly = Router::route({100, 100}, Side::Right, {200, 300}, Side::Top, 20); QVERIFY(isOrthogonal(poly)); QCOMPARE(poly.first(), QPointF(100, 100)); QCOMPARE(poly.last(), QPointF(200, 300)); } void crossings_detectsPerpendicularCross() { const QList horizontal{{0, 100}, {200, 100}}; const QList vertical{{100, 0}, {100, 200}}; const auto hits = Router::crossings(horizontal, vertical); QCOMPARE(hits.size(), 1); QCOMPARE(hits.first(), QPointF(100, 100)); // Symmetric case reports the same point. QCOMPARE(Router::crossings(vertical, horizontal).size(), 1); } void crossings_ignoresParallelAndTouching() { const QList a{{0, 100}, {200, 100}}; const QList parallel{{0, 120}, {200, 120}}; QCOMPARE(Router::crossings(a, parallel).size(), 0); // Vertical segment ending exactly on the line: a T-joint, not a cross. const QList touching{{100, 0}, {100, 100}}; QCOMPARE(Router::crossings(a, touching).size(), 0); } void toPath_arcAddsDetourLength() { const QList poly{{0, 100}, {200, 100}}; const QPainterPath plain = Router::toPath(poly, {}, 6); const QPainterPath hopped = Router::toPath(poly, {QPointF(100, 100)}, 6); QVERIFY(hopped.length() > plain.length() + 1.0); // The arc bulges upward: path bounding box extends above the line. QVERIFY(hopped.boundingRect().top() < 100.0 - 4.0); QCOMPARE(hopped.currentPosition(), QPointF(200, 100)); } void toPath_hopTooCloseToCornerSkipped() { const QList poly{{0, 100}, {200, 100}}; const QPainterPath plain = Router::toPath(poly, {}, 6); const QPainterPath nearEnd = Router::toPath(poly, {QPointF(198, 100)}, 6); QCOMPARE(nearEnd.length(), plain.length()); } void polyline_lengthAndPointAt() { const QList poly{{0, 0}, {100, 0}, {100, 100}}; QCOMPARE(Router::polylineLength(poly), 200.0); QCOMPARE(Router::pointAt(poly, 0.25), QPointF(50, 0)); QCOMPARE(Router::pointAt(poly, 0.75), QPointF(100, 50)); QCOMPARE(Router::pointAt(poly, 1.0), QPointF(100, 100)); } void snap_roundsToGrid() { QCOMPARE(Router::snap(47, 20), 40.0); QCOMPARE(Router::snap(51, 20), 60.0); QCOMPARE(Router::snapPoint({47, 51}, 20), QPointF(40, 60)); } }; QTEST_GUILESS_MAIN(TestRouter) #include "tst_router.moc"