/*
  This file is part of KDDockWidgets.

  SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
  Author: Sérgio Martins <sergio.martins@kdab.com>

  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only

  Contact KDAB at <info@kdab.com> for commercial licensing options.
*/

/// @file
/// @brief Here lives the tests that only apply to QtWidgets
/// either because they haven't been ported to QtQuick yet or because they are really
/// QtWidgets specific.

#define NOMINMAX

#include "kddockwidgets/KDDockWidgets.h"
#include "../utils.h"
#include "Config.h"
#include "LayoutSaver.h"
#include "core/Logging_p.h"
#include "core/DragController_p.h"
#include "core/layouting/Item_p.h"
#include "core/layouting/LayoutingSeparator_p.h"
#include "core/WindowBeingDragged_p.h"

#include "qtwidgets/ViewFactory.h"
#include "core/LayoutSaver_p.h"
#include "core/Platform.h"
#include "core/Action_p.h"
#include "core/DropArea.h"
#include "core/Separator.h"
#include "core/Group.h"
#include "core/DockWidget.h"
#include "core/MainWindow.h"
#include "core/SideBar.h"

#include "qtwidgets/views/MDIArea.h"
#include "qtwidgets/views/Stack.h"
#include "qtwidgets/views/MainWindow.h"
#include "qtwidgets/views/ViewWrapper_p.h"
#include "qtwidgets/views/DockWidget.h"
#include "qtwidgets/views/TitleBar.h"
#include "qtcommon/View.h"

#include "ui_mainwindow.h"

#ifdef KDDW_HAS_SPDLOG
#include "../fatal_logger.h"
#endif

#include "../src/qtwidgets/DebugWidgetViewer_p.h"

#include <QObject>
#include <QPushButton>
#include <QMenuBar>
#include <QTabBar>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLineEdit>
#include <QGraphicsProxyWidget>
#include <QMouseEvent>
#include <QtTest/QTest>

using namespace KDDockWidgets;
using namespace KDDockWidgets::Core;
using namespace KDDockWidgets::Tests;

namespace {

// For testing overriding DockWidget::closeEvent()
// You can override the guest's closeEvent() instead. That's already tested elsewhere.
class NonClosableDockWidget : public QtWidgets::DockWidget
{
public:
    using QtWidgets::DockWidget::DockWidget;
    void closeEvent(QCloseEvent *ev) override
    {
        ev->ignore();
    }
};

}

inline Core::DockWidget *createDockWidget(const QString &name, QWidget *w,
                                          DockWidgetOptions options = {},
                                          LayoutSaverOptions layoutSaverOptions = {},
                                          bool show = true, const QString &affinityName = {})
{
    w->setFocusPolicy(Qt::StrongFocus);
    auto dock = newDockWidget(name, options, layoutSaverOptions);
    dock->setAffinityName(affinityName);
    dock->setGuestView(QtWidgets::ViewWrapper::create(w));
    dock->setObjectName(name);
    dock->view()->setGeometry(QRect(0, 0, 400, 400));
    if (show) {
        dock->open();
        dock->dptr()->morphIntoFloatingWindow();
        dock->view()->activateWindow();
        Q_ASSERT(dock->window());
        if (Platform::instance()->tests_waitForWindowActive(dock->view()->window(), 1000)) {
            return dock;
        }
        KDDW_WARN("KDDockWidgets::Tests::createDockWidget: Couldn't activate window");
        return nullptr;
    } else {
        return dock;
    }
}

class EmbeddedWindow : public QWidget
{
public:
    explicit EmbeddedWindow(Core::MainWindow *m)
        : mainWindow(m)
    {
    }

    ~EmbeddedWindow() override;

    Core::MainWindow *const mainWindow;
};
EmbeddedWindow::~EmbeddedWindow() = default;


inline EmbeddedWindow *createEmbeddedMainWindow(QSize sz)
{
    // Tests a MainWindow which isn't a top-level window, but is embedded in another window
    auto mainwindow = createMainWindow(QSize(600, 600), MainWindowOption_HasCentralGroup).release();

    auto window = new EmbeddedWindow(mainwindow);

    auto lay = new QVBoxLayout(window);
    lay->setContentsMargins(100, 100, 100, 100);
    lay->addWidget(dynamic_cast<QWidget *>(mainwindow->view()));

    window->show();
    window->resize(sz);
    return window;
}

class TestQtWidgets : public QObject
{
    Q_OBJECT

public Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();

private Q_SLOTS:
    // TODO: Port these to QtQuick
    void tst_mainWindowAlwaysHasCentralWidget();
    void tst_dockableMainWindows();
    void tst_mdi_mixed_with_docking();
    void tst_mdi_mixed_with_docking2();
    void tst_mdi_mixed_with_docking_setMDISize();
    void tst_deleteOnClose();
    void tstCloseNestedMdi();
    void tstCloseNestedMDIPropagates();
    void tstQGraphicsProxyWidget();

    // But these are fine to be widget only:
    void tst_controllerToView();
    void tst_designerMainWindow();
    void tst_tabsNotClickable();
    void tst_embeddedMainWindow();
    void tst_restoreEmbeddedMainWindow();
    void tst_negativeAnchorPositionWhenEmbedded();
    void tst_negativeAnchorPositionWhenEmbedded_data();
    void tst_closeRemovesFromSideBar();
    void tst_sideBarHidden();
    void tst_restoreSideBar();
    void tst_restoreSideBar2();
    void tst_toggleActionOnSideBar();
    void tst_deleteOnCloseWhenOnSideBar();
    void tst_openWhenOnSideBar();
    void tst_isOpenSideBar();
    void tst_sidebarOverlayShowsAutohide();
    void tst_sidebarOverlayGetsHiddenOnClick();
    void tst_sidebarGrouping();
    void tst_sidebarCrash();
    void tst_sidebarCrash2();
    void tst_sidebarCloseReason();
    void tst_sidebarSide();
    void tst_floatRemovesFromSideBar();
    void tst_overlayedGeometryIsSaved();
    void tst_overlayCrash();
    void tst_setAsCurrentTab();
    void tst_crash326();
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
    // Qt < 6.7 has a crash. See 97d227acc74c603 in qtbase
    void tst_crash661();
#endif
    void tst_restoreWithIncompleteFactory();
    void tst_deleteDockWidget();
    void tst_standaloneTitleBar();
    void tst_titleBarTitle();
    void tst_widgetAddQAction();
    void tst_currentTabChanged();
    void tst_moveTab();
    void tst_moveTab_data();
    void tst_nestedMainWindowToggle();
    void tst_nestedMainWindowFloatButton();
    void tst_nestedMainWindowSaveRestore_data();
    void tst_nestedMainWindowSaveRestore();
    void tst_focusBetweenTabs();
    void addDockWidgetToSide();
    void addDockWidgetToSide2();
    void addDockWidgetToSide3();
    void addDockWidgetToSide4();
    void addDockWidgetToSide5();
    void addDockWidgetToSideCrash();
    void userHiddenButton();
    void tst_tabAsCentralWidget();
    void tst_nonClosable();
    void tst_crashDuringRestore();
    void tst_toggleVsShowHidden();
    void tst_indicatorsNotShowing();
    void tst_neighbourSqueezeStrategy();
    void tst_tabBarIcons();
    void tst_debugWidgetViewer();
    void tst_addDockWidgetToContainingWindowNested();
    void tst_restoreInvalidPercentages();

    // And fix these
    void tst_floatingWindowDeleted();
    void tst_addToSmallMainWindow6();
    void tst_minSizeChanges();
    void tst_maxSizePropagates();
    void tst_maxSizePropagates2();
    void tst_maxSizedFloatingWindow();
    void tst_restoreWithRemapping();
    void tst_restoreResizesLayout();
    void tst_restoreNonRelativeFloatingWindowGeometry();
    void tst_maxSizeHonouredWhenDropped();
    void tst_fixedSizePolicy();
    void tst_maxSizeHonouredWhenAnotherDropped();
    void tst_addToHiddenMainWindow();
    void tst_maximumSizePolicy();
    void tst_complex();
    void tst_restoreFloatingMaximizedState();
    void tst_findAncestor();
#if defined(KDDW_FRONTEND_QT) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    void tst_userData();
#endif
};

void TestQtWidgets::tst_designerMainWindow()
{
    // Tests that a KDDW main window doesn't crash if the user used setupUI() on it.
    // setupUI() triggers a QMainWindow::setCentralWidget() call which overrides KDDW's own central widget
    // solution is for KDDW users to call manualInit() later

    EnsureTopLevelsDeleted e;
    QtWidgets::MainWindow mw("mw1", MainWindowOptions(MainWindowOption_HasCentralWidget) | MainWindowOption_ManualInit);
    auto dock1 = createDockWidget("dock1", new QWidget());
    mw.show();

    QVERIFY(!mw.centralWidget());

    Ui_MainWindow ui; // autogenerated by mainwindow.ui
    ui.setupUi(&mw);

    QVERIFY(mw.centralWidget()->objectName() != "MyCentralWidget");
    mw.manualInit();
    QCOMPARE(mw.centralWidget()->objectName(), "MyCentralWidget");

    mw.mainWindow()->addDockWidget(dock1, KDDockWidgets::Location_OnRight);
    QTest::qWait(1);
    QVERIFY(dock1->isVisible());
}

void TestQtWidgets::tst_tabsNotClickable()
{
    // Well, not a great unit-test, as it's only repro when it's Windows sending the native event
    // Can't repro with fabricated events. Uncomment the WAIT and test different configs manually
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(
        KDDockWidgets::Config::Flag_Default
        | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible);

    auto dock1 = createDockWidget("dock1", new QWidget());
    auto dock2 = createDockWidget("dock2", new QWidget());
    dock1->addDockWidgetAsTab(dock2);

    auto group = dock1->dptr()->group();
    QCOMPARE(group->currentIndex(), 1);

    QTest::qWait(500); // wait for window to get proper geometry

    const QPoint clickPoint = group->tabBar()->mapToGlobal(group->tabBar()->rectForTab(0).center());
    QCursor::setPos(clickPoint); // Just for visual debug when needed

    pressOn(clickPoint, group->tabBar()->view());
    releaseOn(clickPoint, group->tabBar()->view());

    // WAIT // Uncomment for MANUAL test. Also test by adding Flag_AlwaysShowTabs

    QCOMPARE(group->currentIndex(), 0);
}

void TestQtWidgets::tst_mainWindowAlwaysHasCentralWidget()
{
    EnsureTopLevelsDeleted e;

    auto m = createMainWindow();

    QWidget *central = dynamic_cast<QtWidgets::MainWindow *>(m->view())->centralWidget();
    auto dropArea = m->dropArea();
    QVERIFY(dropArea);

    ObjectGuard<Core::Group> centralGroup = Group::fromItem(dropArea->centralFrame());
    QVERIFY(central);
    QVERIFY(dropArea);
    QCOMPARE(dropArea->count(), 1);
    QVERIFY(centralGroup);
    QCOMPARE(centralGroup->dockWidgetCount(), 0);

    // Add a tab
    auto dock = createDockWidget("doc1");
    m->addDockWidgetAsTab(dock);
    QCOMPARE(dropArea->count(), 1);
    QCOMPARE(centralGroup->dockWidgetCount(), 1);

    // Detach tab
    QPoint globalPressPos = dragPointForWidget(centralGroup.data(), 0);
    auto tabBar = centralGroup->tabBar()->view();
    QVERIFY(tabBar);
    drag(tabBar, globalPressPos, m->geometry().bottomRight() + QPoint(30, 30));

    QVERIFY(centralGroup);
    QCOMPARE(dropArea->count(), 1);
    QCOMPARE(centralGroup->dockWidgetCount(), 0);
    QVERIFY(dropArea->checkSanity());
}

void TestQtWidgets::tst_dockableMainWindows()
{
    EnsureTopLevelsDeleted e;

    auto m1 = createMainWindow();
    auto dock1 = createDockWidget("dock1", new QPushButton("foo"));
    m1->addDockWidget(dock1, Location_OnTop);

    auto m2 = new KDDockWidgets::QtWidgets::MainWindow("mainwindow-dockable");
    auto m2Container = createDockWidget("mainwindow-dw", ( View * )m2);
    auto menubar = m2->menuBar();
    menubar->addMenu("File");
    menubar->addMenu("View");
    menubar->addMenu("Help");
    m2Container->open();

    auto dock21 = createDockWidget("dock21", new QPushButton("foo"));
    auto dock22 = createDockWidget("dock22", new QPushButton("foo"));
    m2->mainWindow()->addDockWidget(dock21, Location_OnLeft);
    m2->mainWindow()->addDockWidget(dock22, Location_OnRight);

    auto fw = m2Container->floatingWindow();
    Core::TitleBar *fwTitleBar = fw->titleBar();

    QVERIFY(fw->hasSingleGroup());
    QVERIFY(fw->hasSingleDockWidget());

    // Check that the inner-inner dock widgets have a visible title-bar
    QVERIFY(dock21->titleBar()->isVisible());
    QVERIFY(dock22->titleBar()->isVisible());
    QVERIFY(dock21->titleBar() != fwTitleBar);
    QVERIFY(dock22->titleBar() != fwTitleBar);

    const QPoint startPoint = fwTitleBar->mapToGlobal(QPoint(5, 5));
    const QPoint destination = startPoint + QPoint(20, 20);

    // Check that we don't get the "Refusing to itself" warning. not actually dropping anywhere
    drag(fwTitleBar->view(), startPoint, destination);

    // The FloatingWindow has a single DockWidget, so it shows the title bar, while the Frame
    // doesn't
    QVERIFY(fwTitleBar->isVisible());
    QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible());

    fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
    // Now the FloatingWindow has two dock widgets, so our main window dock widget also shows the
    // title bar
    QVERIFY(fwTitleBar->isVisible());
    QVERIFY(m2Container->dptr()->group()->titleBar()->isVisible());

    // Put it how it was, FloatingWindow is single dock again
    auto group1 = dock1->dptr()->group();
    dock1->close();
    Platform::instance()->tests_waitForDeleted(group1);
    QVERIFY(fwTitleBar->isVisible());
    QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible());

    // Repeat, but instead of closing dock1, we float it
    fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
    QVERIFY(fwTitleBar->isVisible());
    QVERIFY(m2Container->dptr()->group()->titleBar()->isVisible());
    group1 = dock1->dptr()->group();
    group1->titleBar()->onFloatClicked();
    QVERIFY(fwTitleBar->isVisible());

    QVERIFY(!m2Container->dptr()->group()->titleBar()->isVisible());

    fw->dropArea()->addDockWidget(dock1, Location::Location_OnLeft, nullptr);
}

void TestQtWidgets::tst_mdi_mixed_with_docking()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralWidget);
    auto dock1 = createDockWidget("1", new QPushButton("1"));

    m->addDockWidget(dock1, Location_OnBottom);

    auto mdiArea = new QtWidgets::MDIArea();
    m->setPersistentCentralView(QtWidgets::ViewWrapper::create(mdiArea));

    auto mdiWidget1 = createDockWidget("mdi1", new QPushButton("mdi1"));
    auto mdiWidget2 = createDockWidget("mdi2", new QPushButton("mdi12"));

    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));


    // We don't support LayoutSaver with mdi yet, but it shouldn't crash
    // so test it as well
    LayoutSaver saver;
    saver.serializeLayout();

    Core::Group *groupMDI1 = mdiWidget1->d->group();
    Core::Group *group1 = dock1->d->group();
    QVERIFY(!group1->isMDI());
    QVERIFY(groupMDI1->isMDI());
    QVERIFY(!group1->mdiLayout());
    QVERIFY(groupMDI1->mdiLayout());

    QVERIFY(!dock1->titleBar()->isMDI());

    auto tb1 = mdiWidget1->titleBar();
    QVERIFY(tb1->isMDI());
    QVERIFY(Platform::instance()->tests_waitForEvent(tb1->view(), QEvent::Show));
    QVERIFY(tb1->isVisible());

    // Press the float button
    tb1->onFloatClicked();

    QVERIFY(mdiWidget1->d->lastPosition()->lastItem());
    QVERIFY(mdiWidget1->titleBar()->isVisible());
    QVERIFY(mdiWidget1->isFloating());

    // Dock again, and check it went back
    mdiWidget1->titleBar()->onFloatClicked();
    QVERIFY(!mdiWidget1->isFloating());
}

void TestQtWidgets::tst_mdi_mixed_with_docking2()
{
    // Here, the MDI dock widgets are themselves main windows which will show drop-indicators.
    // It will be super nested: MainWindow -> MDI -> MainWindow

    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
    auto dock1 = createDockWidget("1", new QPushButton("1"));

    m->addDockWidget(dock1, Location_OnBottom);

    auto mdiArea = new QtWidgets::MDIArea();

    m->setPersistentCentralView(QtWidgets::ViewWrapper::create(mdiArea));


    auto createSheet = [](int id) -> Core::DockWidget * {
        auto dock =
            newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable);
        auto btn = new QPushButton(QStringLiteral("Sheet %1").arg(id));
        dock->setGuestView(QtWidgets::ViewWrapper::create(btn));
        dock->setTitle(QStringLiteral("Sheet %1").arg(id));

        return dock;
    };

    auto mdiWidget1 = createSheet(1);
    auto mdiWidget2 = createSheet(2);
    auto mdiWidget3 = createSheet(3);
    /*auto mdiWidget4 = createSheet(4);
    auto mdiWidget5 = createSheet(5);
    auto mdiWidget6 = createSheet(6);*/


    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));

    Core::Group *group1 = mdiWidget1->d->group();
    Core::Group *mdiFrame1 = group1->mdiFrame();

    ObjectGuard<Core::Group> group2 = mdiWidget2->d->group();
    ObjectGuard<Core::Group> mdiFrame2 = group2->mdiFrame();
    ObjectGuard<DropArea> dropArea2 = group2->mdiDropAreaWrapper();

    ObjectGuard<DropArea> dropArea1 = group1->mdiDropAreaWrapper();
    dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);

    QVERIFY(!group1->isMDI());
    QVERIFY(group1->isMDIWrapper());
    QVERIFY(group1->mdiDockWidgetWrapper());
    QVERIFY(dropArea1->isMDIWrapper());
    QVERIFY(dropArea2->isMDIWrapper());
    QVERIFY(!mdiFrame1->isMDIWrapper());
    QVERIFY(mdiFrame1->isMDI());
    QVERIFY(!mdiWidget1->d->isMDIWrapper());

    // Test title bars:

    auto tb1 = mdiWidget1->titleBar();
    auto mdiTb1 = mdiFrame1->titleBar();
    auto mdiTb2 = mdiFrame2->titleBar();

    Platform::instance()->tests_waitForEvent(mdiTb1, QEvent::Show);

    QVERIFY(mdiTb1->isVisible());
    QVERIFY(mdiTb2->isVisible());
    QVERIFY(tb1->isVisible());
    QCOMPARE(tb1->title(), QString("Sheet 1"));
    QCOMPARE(mdiTb1->title(), QString("dockwidgets-unit-tests"));
    QCOMPARE(mdiTb2->title(), QString("Sheet 2"));

    QVERIFY(tb1 != mdiTb1);
    QCOMPARE(mdiWidget2->titleBar(), mdiTb2);

    // Test that closing will delete the wrappers:
    mdiWidget2->close();
    QVERIFY(!mdiWidget2->isOpen());

    Platform::instance()->tests_waitForDeleted(dropArea2);
    QVERIFY(dropArea2.isNull());
    QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame2));
    QVERIFY(!mdiFrame2);

    mdiWidget1->close();
    QVERIFY(!mdiWidget1->isOpen());
    QTest::qWait(500); // wait some event loops to make sure there's no delete later. (There isn't,
                       // but a bug could introduce them)
    QVERIFY(!dropArea1.isNull()); // Not deleted, as sheet3 is still there
    QCOMPARE(dropArea1->visibleCount(), 1);
    QVERIFY(mdiTb1->isVisible());
    QCOMPARE(mdiWidget3->titleBar(), mdiTb1);
    Core::Group *group3 = mdiWidget3->d->group();
    QVERIFY(!group3->titleBar()->isVisible());

    mdiWidget3->close();
    QVERIFY(Platform::instance()->tests_waitForDeleted(dropArea1));
    QVERIFY(!mdiWidget3->isOpen());
    QVERIFY(dropArea1.isNull());

    // Reopen everything again:

    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));
    group1 = mdiWidget1->d->group();
    mdiFrame1 = group1->mdiFrame();
    dropArea1 = group1->mdiDropAreaWrapper();
    dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);

    // Test floating:
    group2 = mdiWidget2->d->group();
    ObjectGuard<Core::DockWidget> dwWrapper2 = group2->mdiDockWidgetWrapper();
    dropArea2 = group2->mdiDropAreaWrapper();
    QVERIFY(mdiWidget2->isVisible());
    QVERIFY(group2->isMDIWrapper());
    QVERIFY(dwWrapper2->d->isMDIWrapper());
    mdiFrame2 = group2->mdiFrame();
    mdiWidget2->setFloating(true);
    QVERIFY(mdiWidget2->isFloating());

    QVERIFY(!mdiWidget2->d->group()->isMDI());
    QVERIFY(!mdiWidget2->d->group()->isMDIWrapper());
    QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame2));

    QVERIFY(dropArea2.isNull());
    QVERIFY(dwWrapper2.isNull());

    auto mdiFrames = mdiArea->groups();
    QCOMPARE(mdiFrames.count(), 1);
    mdiFrame1 = mdiFrames.first();
    QVERIFY(mdiFrame1->isMDI());
    QVERIFY(mdiFrame1->hasNestedMDIDockWidgets());
    auto mdiTitleBar1 = mdiFrame1->titleBar();
    QVERIFY(mdiFrame1->titleBar()->isVisible());
    mdiTitleBar1->makeWindow();

    QVERIFY(Platform::instance()->tests_waitForDeleted(mdiFrame1));
    QCOMPARE(mdiArea->groups().size(), 0);

    // Dock again:
    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));

    group1 = mdiWidget1->d->group();
    dropArea1 = group1->mdiDropAreaWrapper();
    dropArea1->addDockWidget(mdiWidget3, Location_OnLeft, nullptr);

    // Detach an internal dock widget by dragging
    const QPoint globalSrc = mdiWidget1->mapToGlobal(QPoint(5, 5));
    const QPoint globalDest = globalSrc + QPoint(100, 100);

    drag(mdiWidget1->view(), globalDest);

    QCOMPARE(mdiArea->groups().count(), 2);
    auto mdiTitleBar = mdiArea->groups().first()->titleBar();
    QVERIFY(mdiTitleBar->isVisible());

    QVERIFY(!mdiWidget3->isFloating());
    QVERIFY(mdiWidget3->d->lastPosition()->lastItem());
    mdiTitleBar->onFloatClicked();
    QVERIFY(mdiWidget3->isFloating());

    QVERIFY(Platform::instance()->tests_waitForDeleted(mdiArea->groups().constFirst()));
    QCOMPARE(mdiArea->groups().size(), 1);

    QVERIFY(!mdiWidget2->isFloating());
    Core::Group *lastMdiFrame = mdiArea->groups().constFirst();
    QVERIFY(lastMdiFrame->titleBar()->isVisible());
    QVERIFY(!lastMdiFrame->titleBar()->isFloating());
    lastMdiFrame->titleBar()->onFloatClicked();
    QVERIFY(mdiWidget2->isFloating());

    // put it in the MDI area again
    mdiWidget2->titleBar()->onFloatClicked();
    QVERIFY(!mdiWidget2->isFloating());
}

void TestQtWidgets::tst_mdi_mixed_with_docking_setMDISize()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
    auto dock1 = createDockWidget("1", new QPushButton("1"));

    m->addDockWidget(dock1, Location_OnBottom);

    auto mdiArea = new QtWidgets::MDIArea();
    m->setPersistentCentralView(QtWidgets::ViewWrapper::create(mdiArea));

    auto createSheet = [](int id) -> Core::DockWidget * {
        auto dock =
            newDockWidget(QStringLiteral("dw-sheet-%1").arg(id), DockWidgetOption_MDINestable);
        dock->setGuestView(QtWidgets::ViewWrapper::create(
            new QPushButton(QStringLiteral("Sheet %1").arg(id))));
        dock->setTitle(QStringLiteral("Sheet %1").arg(id));

        return dock;
    };

    auto mdiWidget1 = createSheet(1);
    auto mdiWidget2 = createSheet(2);

    mdiArea->addDockWidget(mdiWidget1, QPoint(10, 10));
    mdiArea->addDockWidget(mdiWidget2, QPoint(50, 50));

    Core::Group *group1 = mdiArea->groups().at(0);

    const QSize sz1 = group1->view()->size();
    const QSize increment(200, 200);

    QVERIFY(mdiWidget1->d->mdiLayout());
    mdiWidget1->setMDISize(sz1 + increment);

    QCOMPARE(group1->size(), sz1 + increment);
}

// No need to port to QtQuick
void TestQtWidgets::tst_floatingWindowDeleted()
{
    // Tests a case where the empty floating dock widget wouldn't be deleted
    // Doesn't repro QTBUG-83030 unfortunately, as we already have an event loop running
    // but let's leave this here nonetheless
    class MyMainWindow : public KDDockWidgets::QtWidgets::MainWindow
    {
    public:
        MyMainWindow()
            : KDDockWidgets::QtWidgets::MainWindow("tst_floatingWindowDeleted",
                                                   MainWindowOption_None)
        {
            auto dock1 = newDockWidget(QStringLiteral("DockWidget #1"));
            auto myWidget = new QWidget();
            dock1->setGuestView(QtWidgets::ViewWrapper::create((myWidget)));
            dock1->view()->resize(QSize(600, 600));
            dock1->open();

            auto dock2 = newDockWidget(QStringLiteral("DockWidget #2"));
            myWidget = new QWidget();
            dock2->setGuestView(QtWidgets::ViewWrapper::create(myWidget));
            dock2->view()->resize(QSize(600, 600));
            dock2->open();

            dock1->addDockWidgetAsTab(dock2);
        }
    };

    MyMainWindow m;
}

void TestQtWidgets::tst_addToSmallMainWindow6()
{
    EnsureTopLevelsDeleted e;
    // Test test shouldn't spit any warnings

    QWidget container;
    auto lay = new QVBoxLayout(&container);
    auto m = Platform::instance()->createMainWindow("MyMainWindow_tst_addToSmallMainWindow8", {},
                                                    MainWindowOption_None);
    auto qmainwindow = dynamic_cast<QtWidgets::MainWindow *>(m->view());
    lay->addWidget(qmainwindow);
    container.resize(100, 100);
    Platform::instance()->tests_waitForEvent(&container, QEvent::Resize);
    container.show();
    Platform::instance()->tests_waitForResize(m);
    auto dock1 = createDockWidget(
        "dock1", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) }));
    auto dock2 = createDockWidget(
        "dock2", Platform::instance()->tests_createView({ true, {}, QSize(50, 240) }));
    m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom);
    m->addDockWidget(dock2, KDDockWidgets::Location_OnBottom);
    Platform::instance()->tests_waitForResize(m);
    QVERIFY(m->dropArea()->checkSanity());
    delete m;
}

void TestQtWidgets::tst_sideBarHidden()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
    auto dw1 = newDockWidget(QStringLiteral("1"));
    m1->addDockWidget(dw1, Location_OnBottom);

    auto sb = m1->sideBar(SideBarLocation::South);
    QVERIFY(sb);
    QVERIFY(sb->isEmpty());
    QVERIFY(!sb->isVisible());

    m1->moveToSideBar(dw1);
    QVERIFY(!sb->isEmpty());
    QVERIFY(sb->isVisible());
}

void TestQtWidgets::tst_closeRemovesFromSideBar()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
    auto dw1 = newDockWidget(QStringLiteral("1"));
    auto fw1 = dw1->window();
    m1->addDockWidget(dw1, Location_OnBottom);
    m1->moveToSideBar(dw1);

    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isVisible());
    QVERIFY(dw1->isInSideBar());

    Core::SideBar *sb = m1->sideBarForDockWidget(dw1);
    QVERIFY(sb);

    // Overlay it:
    sb->toggleOverlay(dw1);
    QVERIFY(dw1->isOverlayed());
    QVERIFY(dw1->isVisible());
    QCOMPARE(dw1->sideBarLocation(), sb->location());
    QVERIFY(dw1->isInMainWindow());
    QVERIFY(!dw1->isFloating());

    // Close it while it's overlayed:
    dw1->close();
    QVERIFY(!dw1->isInMainWindow());
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isVisible());
    QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None);
}

void TestQtWidgets::tst_restoreSideBar()
{
    SideBarLocation loc;
    QByteArray serialized; // serialization after having 1 sidebar visible
    QByteArray beforeSideBarSerialized; // serialization without any sidebar visible

    {
        LayoutSaver saver;
        EnsureTopLevelsDeleted e;
        KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));
        auto fw1 = dw1->window();
        m1->addDockWidget(dw1, Location_OnBottom);
        beforeSideBarSerialized = saver.serializeLayout();
        QVERIFY(!m1->anySideBarIsVisible());
        m1->moveToSideBar(dw1);
        QVERIFY(m1->anySideBarIsVisible());

        QVERIFY(!dw1->isOverlayed());
        QVERIFY(!dw1->isVisible());
        loc = dw1->sideBarLocation();
        QVERIFY(loc != SideBarLocation::None);

        serialized = saver.serializeLayout();

        m1.reset();
    }

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    auto fw1 = dw1->window();
    m1->addDockWidget(dw1, Location_OnBottom);
    QVERIFY(!m1->anySideBarIsVisible());
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isVisible());
    QVERIFY(!dw1->isFloating());
    QVERIFY(dw1->isInMainWindow());

    LayoutSaver restorer;
    restorer.restoreLayout(serialized);

    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isVisible());
    QVERIFY(!dw1->isInMainWindow());
    QVERIFY(m1->anySideBarIsVisible());

    QCOMPARE(loc, dw1->sideBarLocation());

    restorer.restoreLayout(beforeSideBarSerialized);
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isVisible());
    QVERIFY(!dw1->isFloating());
    QVERIFY(dw1->isInMainWindow());
    QVERIFY(!m1->anySideBarIsVisible());

    QCOMPARE(LayoutSaver::sideBarDockWidgetsInLayout(serialized), { "1" });
}

void TestQtWidgets::tst_restoreSideBar2()
{
    // Tests a case where the layout size would be bigger than the parent DropArea
    EnsureTopLevelsDeleted e;

    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    KDDockWidgets::QtWidgets::MainWindow m1("MyMainWindow");
    m1.resize(QSize(1197, 1197));

    auto dw1 = new KDDockWidgets::QtWidgets::DockWidget(
        QStringLiteral("DockWidget #0"));
    auto dw2 = new KDDockWidgets::QtWidgets::DockWidget(
        QStringLiteral("DockWidget #1"));
    auto dw3 = new KDDockWidgets::QtWidgets::DockWidget(
        QStringLiteral("DockWidget #2"));
    m1.addDockWidget(dw1, KDDockWidgets::Location_OnTop);
    m1.addDockWidget(dw2, KDDockWidgets::Location_OnRight, dw1);
    m1.addDockWidget(dw3, KDDockWidgets::Location_OnLeft);
    m1.show();
    auto layout = m1.mainWindow()->layout();
    QCOMPARE(layout->size().width(), layout->layoutSize().width());
    QCOMPARE(layout->size().height(), layout->layoutSize().height());

    LayoutSaver saver;
    saver.restoreFromFile(":/layouts/sidebar_restore.json");

    QVERIFY(dw1->isOpen());
    QVERIFY(!dw2->isOpen());
    QVERIFY(dw3->dockWidget()->isInSideBar());
    QVERIFY(layout->checkSanity());

    QCOMPARE(layout->size(), layout->layoutSize());
}

void TestQtWidgets::tst_openWhenOnSideBar()
{
    // Calling DW::open() when on sidebar should open it in its previous docked position
    // and remove it from overlay

    {
        EnsureTopLevelsDeleted e;

        KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget("1");
        m1->addDockWidget(dw1, Location_OnBottom);
        dw1->moveToSideBar();
        QTest::qWait(100);

        QVERIFY(!m1->overlayedDockWidget());
        QVERIFY(m1->anySideBarIsVisible());

        QVERIFY(!dw1->dptr()->m_lastPositions->wasFloating());
        dw1->open();

        QVERIFY(!m1->overlayedDockWidget());
        QVERIFY(!m1->anySideBarIsVisible());
        QVERIFY(dw1->isInMainWindow());
    }

    {
        EnsureTopLevelsDeleted e;

        KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget("1");
        m1->addDockWidget(dw1, Location_OnBottom);
        dw1->moveToSideBar();
        QTest::qWait(100);

        QVERIFY(!m1->overlayedDockWidget());
        QVERIFY(m1->anySideBarIsVisible());

        // overlay and deoverlay
        m1->toggleOverlayOnSideBar(dw1);
        m1->toggleOverlayOnSideBar(dw1);

        // open should go to docked position, not floating
        dw1->open();

        QVERIFY(!m1->overlayedDockWidget());
        QVERIFY(!m1->anySideBarIsVisible());

        QVERIFY(dw1->isInMainWindow());
    }
}

void TestQtWidgets::tst_isOpenSideBar()
{
    // Tests that DockWidget::isOpen() toggles when we toggle the overlay
    EnsureTopLevelsDeleted e;

    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget("1");
    m1->addDockWidget(dw1, Location_OnBottom);
    dw1->moveToSideBar();

    QVERIFY(!m1->overlayedDockWidget());
    QVERIFY(m1->anySideBarIsVisible());
    QVERIFY(!dw1->isOpen());

    // overlay it:
    m1->toggleOverlayOnSideBar(dw1);
    QVERIFY(m1->overlayedDockWidget());
    QVERIFY(m1->anySideBarIsVisible());
    QVERIFY(dw1->isOpen());

    // close overlay:
    m1->toggleOverlayOnSideBar(dw1);
    QVERIFY(!m1->overlayedDockWidget());
    QVERIFY(m1->anySideBarIsVisible());

    QVERIFY(!dw1->isOpen());
}

void TestQtWidgets::tst_toggleActionOnSideBar()
{
    // When a dock widget is in the sidebar and we use DockWidget::toggleAction() then it should
    // toggle visibility without removing it from the sidebar

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget("1");
    m1->addDockWidget(dw1, Location_OnBottom);
    dw1->moveToSideBar();

    QVERIFY(!dw1->isVisible());
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isInSideBar());
    QVERIFY(!dw1->isInMainWindow());

    auto action = dw1->toggleAction();
    action->trigger();

    QVERIFY(dw1->isVisible());
    QVERIFY(dw1->isOverlayed());
    QVERIFY(dw1->isInMainWindow());

    QVERIFY(dw1->isInSideBar());
    action->trigger();

    QVERIFY(!dw1->isOverlayed());
    QVERIFY(!dw1->isInMainWindow());

    QVERIFY(dw1->isInSideBar());
    QVERIFY(!dw1->isInMainWindow());


    // Count how many times toggleActions triggers:
    int openCount = 0;
    int closeCount = 0;
    KDBindings::ScopedConnection conn = action->d->toggled.connect([&](bool t) {
        if (t)
            openCount++;
        else
            closeCount++;
    });

    // Make it visible on sidebar:
    m1->toggleOverlayOnSideBar(dw1);
    QCOMPARE(openCount, 1);
    QCOMPARE(closeCount, 0);

    // close it on sidebar
    m1->toggleOverlayOnSideBar(dw1);
    QCOMPARE(openCount, 1);
    QCOMPARE(closeCount, 1);

    // Make visible on sidebar again
    m1->toggleOverlayOnSideBar(dw1);
    QCOMPARE(openCount, 2);
    QCOMPARE(closeCount, 1);

    // Click the unpin button, docks in main window, no toggling should happen
    dw1->titleBar()->onAutoHideClicked();
    QCOMPARE(openCount, 2);
    QCOMPARE(closeCount, 1);
}

void TestQtWidgets::tst_deleteOnCloseWhenOnSideBar()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
    ObjectGuard<Core::DockWidget> dock1 = createDockWidget(
        "dock1", Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }),
        DockWidgetOption_DeleteOnClose);
    m->addDockWidget(dock1, Location_OnLeft);

    dock1->moveToSideBar();
    QVERIFY(dock1);
    QVERIFY(dock1->isInSideBar());

    QTest::qWait(500);
    QVERIFY(dock1);
}

void TestQtWidgets::tst_sidebarOverlayShowsAutohide()
{
    // Tests that overlayed widgets show the "Disable auto-hide" button

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));

    m1->addDockWidget(dw1, Location_OnBottom);
    QVERIFY(dw1->titleBar()->supportsAutoHideButton());

    m1->moveToSideBar(dw1);
    m1->overlayOnSideBar(dw1);

    QVERIFY(dw1->isOverlayed());

    auto titleBar = dw1->titleBar();
    QVERIFY(titleBar->isVisible());
    QVERIFY(titleBar->supportsAutoHideButton());

    delete dw1;
}

void TestQtWidgets::tst_sidebarOverlayGetsHiddenOnClick()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    {
        // Case #1 click on another dockwidget should hide the overlay

        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));
        auto dw2 = newDockWidget(QStringLiteral("2"));

        m1->addDockWidget(dw1, Location_OnBottom);
        m1->addDockWidget(dw2, Location_OnBottom);

        m1->moveToSideBar(dw1);
        m1->overlayOnSideBar(dw1);

        QVERIFY(dw1->isOverlayed());

        Tests::clickOn(dw2->mapToGlobal(dw2->rect().bottomLeft() + QPoint(5, -5)), dw2->view());
        QVERIFY(!dw1->isOverlayed());
        QVERIFY(dw1->isInSideBar());

        auto widget2 = Platform::instance()->tests_createView({ true });
        dw2->setGuestView(widget2->asWrapper());
        m1->overlayOnSideBar(dw1);
        QVERIFY(dw1->isOverlayed());
        Tests::clickOn(widget2->mapToGlobal(widget2->rect().bottomLeft() + QPoint(5, -5)), widget2);
        QVERIFY(!dw1->isOverlayed());

        m1.reset();
        delete dw1;
    }

    {
        // Case #1 click on empty main window space, should hide the overlay

        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));

        m1->addDockWidget(dw1, Location_OnBottom);

        m1->moveToSideBar(dw1);
        m1->overlayOnSideBar(dw1);

        QVERIFY(dw1->isOverlayed());

        const QPoint localPt(100, 250);
        Tests::clickOn(m1->mapToGlobal(m1->rect().topLeft() + localPt),
                       m1->view()->childViewAt(localPt).get());
        QVERIFY(!dw1->isOverlayed());
    }
}

void TestQtWidgets::tst_sidebarGrouping()
{
    // Tests Flag_AutoHideAsTabGroups
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport | KDDockWidgets::Config::Flag_AutoHideAsTabGroups);

    {
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));
        auto dw2 = newDockWidget(QStringLiteral("2"));

        m1->addDockWidget(dw1, Location_OnBottom);
        dw1->addDockWidgetAsTab(dw2);

        dw1->titleBar()->onAutoHideClicked();
        QVERIFY(dw1->isInSideBar());
        QVERIFY(dw2->isInSideBar());
        QVERIFY(!dw1->isOverlayed());
        QVERIFY(!dw2->isOverlayed());

        m1->overlayOnSideBar(dw1);
        QVERIFY(dw1->isOverlayed());
        QVERIFY(!dw2->isOverlayed());

        dw1->titleBar()->onAutoHideClicked();
        QVERIFY(!dw1->isInSideBar());
        QVERIFY(!dw2->isInSideBar());
        QVERIFY(!dw1->isOverlayed());
        QVERIFY(!dw2->isOverlayed());
    }
}

void TestQtWidgets::tst_sidebarCrash()
{
    // Tests a crash that happened when toggling overlay a few times

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    m1->addDockWidget(dw1, Location_OnBottom);

    dw1->titleBar()->onAutoHideClicked();
    QVERIFY(dw1->isInSideBar());

    m1->overlayOnSideBar(dw1);

    dw1->titleBar()->onAutoHideClicked();
    QVERIFY(!dw1->isInSideBar());
    QVERIFY(!dw1->isOverlayed());

    dw1->titleBar()->onAutoHideClicked();
    QVERIFY(dw1->isInSideBar());

    QTest::qWait(1000);

    m1->toggleOverlayOnSideBar(dw1);
}

void TestQtWidgets::tst_sidebarCrash2()
{
    // Tests a crash, only reproduceable with an ASAN build of Qt
    // While unpinning, the QToolButton would be deleted while still on the stack

    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    m1->addDockWidget(dw1, Location_OnBottom);
    m1->moveToSideBar(dw1);
    m1->overlayOnSideBar(dw1);

    auto titleBarWidget = qobject_cast<QtWidgets::TitleBar *>(QtCommon::View_qt::asQWidget(dw1->titleBar()->view()));
    QVERIFY(titleBarWidget && titleBarWidget->isVisible());

    auto button = titleBarWidget->m_autoHideButton;
    QVERIFY(button && button->isVisible());

    auto globalPos = button->mapToGlobal(QPoint(5, 5));

    auto b = QtWidgets::ViewWrapper::create(button);
    Tests::clickOn(globalPos, b.get());
}

void TestQtWidgets::tst_sidebarCloseReason()
{
    {
        EnsureTopLevelsDeleted e;
        KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));
        m1->addDockWidget(dw1, Location_OnBottom);

        CloseReason expectedLastReason = CloseReason::Unspecified;
        KDBindings::ScopedConnection conn = dw1->toggleAction()->d->toggled.connect([&expectedLastReason, dw1](bool open) {
            // We also check that the value is actually set *before* the QAction firing
            // as users connect to the action
            if (!open)
                Q_ASSERT(dw1->lastCloseReason() == expectedLastReason);
        });

        expectedLastReason = CloseReason::MovedToSideBar;
        dw1->titleBar()->onAutoHideClicked();
        QVERIFY(dw1->isInSideBar());
        QVERIFY(!dw1->isOpen());

        QCOMPARE(dw1->lastCloseReason(), expectedLastReason);

        m1->overlayOnSideBar(dw1);
        QVERIFY(dw1->isInSideBar());
        QVERIFY(dw1->isOpen());

        expectedLastReason = CloseReason::OverlayCollapse;
        m1->clearSideBarOverlay();
        QCOMPARE(dw1->lastCloseReason(), expectedLastReason);

        m1->overlayOnSideBar(dw1);
        QVERIFY(dw1->isInSideBar());
        QVERIFY(dw1->isOpen());
        expectedLastReason = CloseReason::TitleBarCloseButton;
        dw1->titleBar()->onCloseClicked();
        QCOMPARE(dw1->lastCloseReason(), expectedLastReason);

        expectedLastReason = CloseReason::MovedToSideBar;
        dw1->open();
        m1->moveToSideBar(dw1);
        QCOMPARE(dw1->lastCloseReason(), expectedLastReason);
    }
}

void TestQtWidgets::tst_sidebarSide()
{
    // Tests which sidebar is chosen

    {
        // Tests that if a dock widget is touching top and bottom border, it will go to
        // left sidebar if it's closer to it.
        EnsureTopLevelsDeleted e;
        KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
        auto dw1 = newDockWidget(QStringLiteral("1"));
        auto dw2 = newDockWidget(QStringLiteral("2"));
        auto dw3 = newDockWidget(QStringLiteral("3"));
        auto dw4 = newDockWidget(QStringLiteral("4"));
        m1->addDockWidget(dw1, Location_OnRight);
        m1->addDockWidget(dw2, Location_OnRight);
        m1->addDockWidget(dw3, Location_OnRight);
        m1->addDockWidget(dw4, Location_OnRight);
        m1->layoutEqually();

        dw2->titleBar()->onAutoHideClicked();

        auto sb = m1->sideBarForDockWidget(dw2);
        QVERIFY(sb);
        QCOMPARE(sb->location(), SideBarLocation::West);
    }
}

void TestQtWidgets::tst_floatRemovesFromSideBar()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    m1->addDockWidget(dw1, Location_OnBottom);

    m1->moveToSideBar(dw1);
    m1->overlayOnSideBar(dw1);

    QVERIFY(dw1->isOverlayed());
    QVERIFY(!dw1->isFloating());
    QVERIFY(dw1->isInMainWindow());

    dw1->setFloating(true);
    QVERIFY(!dw1->isOverlayed());
    QVERIFY(dw1->isFloating());
    QVERIFY(!dw1->isInMainWindow());

    QCOMPARE(dw1->sideBarLocation(), SideBarLocation::None);

    // Also test a crash I got
    m1->addDockWidget(dw1, Location_OnBottom);

    auto tb = dw1->titleBar();
    QVERIFY(tb->isVisible());

    tb->onFloatClicked();
}

void TestQtWidgets::tst_overlayedGeometryIsSaved()
{
    // Tests that after resizing an overlayed widget, and then hide+show, its size is preserved
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    m1->addDockWidget(dw1, Location_OnBottom);

    m1->moveToSideBar(dw1, SideBarLocation::North);
    m1->overlayOnSideBar(dw1);

    Core::Group *group = dw1->dptr()->group();
    QVERIFY(group->isOverlayed());
    QCOMPARE(dw1->sideBarLocation(), SideBarLocation::North);
    QVERIFY(group->height() > 0);

    const int newHeight = group->height() + 300;
    group->view()->setHeight(newHeight);

    m1->toggleOverlayOnSideBar(dw1);
    m1->toggleOverlayOnSideBar(dw1);

    group = dw1->dptr()->group();
    QCOMPARE(group->height(), newHeight);
}

void TestQtWidgets::tst_overlayCrash()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = newDockWidget(QStringLiteral("1"));
    m1->addDockWidget(dw1, Location_OnBottom);

    auto dw2 = newDockWidget(QStringLiteral("2"));
    m1->addDockWidget(dw2, Location_OnBottom);

    m1->moveToSideBar(dw1);
    m1->toggleOverlayOnSideBar(dw1);

    dw1->close();

    auto tb = dw2->titleBar();
    QVERIFY(tb->isVisible());

    pressOn(tb->mapToGlobal(QPoint(5, 5)), tb->view());
}

void TestQtWidgets::tst_embeddedMainWindow()
{
    EnsureTopLevelsDeleted e;
    // Tests a MainWindow which isn't a top-level window, but is embedded in another window
    EmbeddedWindow *window = createEmbeddedMainWindow(QSize(800, 800));

    auto dock1 = createDockWidget("1", new QPushButton("1"));
    window->mainWindow->addDockWidget(dock1, Location_OnTop);
    dock1->setFloating(true);
    auto dropArea = window->mainWindow->dropArea();
    auto fw = dock1->floatingWindow();

    dragFloatingWindowTo(fw, dropArea, DropLocation_Left);

    auto layout = dropArea;
    QVERIFY(Platform::instance()->tests_waitForDeleted(fw));
    QCOMPARE(layout->count(), 2); // 2, as it has the central group
    QCOMPARE(layout->visibleCount(), 2);
    layout->checkSanity();

    delete window;
}

void TestQtWidgets::tst_restoreEmbeddedMainWindow()
{
    EnsureTopLevelsDeleted e;
    // Tests a MainWindow which isn't a top-level window, but is embedded in another window
    EmbeddedWindow *window = createEmbeddedMainWindow(QSize(800, 800));

    auto dock1 = createDockWidget("1", new QPushButton("1"));
    window->mainWindow->addDockWidget(dock1, Location_OnTop);

    const QPoint originalPos(250, 250);
    const QSize originalSize = window->size();
    window->move(originalPos);

    LayoutSaver saver;
    QByteArray saved = saver.serializeLayout();
    QVERIFY(!saved.isEmpty());

    window->resize(555, 555);
    const QPoint newPos(500, 500);
    window->move(newPos);
    QVERIFY(saver.restoreLayout(saved));

    QCOMPARE(window->pos(), originalPos);
    QCOMPARE(window->size(), originalSize);
    window->mainWindow->layout()->checkSanity();

    delete window;
}

void TestQtWidgets::tst_negativeAnchorPositionWhenEmbedded_data()
{
    QTest::addColumn<bool>("embedded");

    QTest::newRow("false") << false;
    QTest::newRow("true") << true;
}

void TestQtWidgets::tst_negativeAnchorPositionWhenEmbedded()
{
    QFETCH(bool, embedded);
    EnsureTopLevelsDeleted e;

    MainWindow *m = nullptr;
    if (embedded) {
        auto em = createEmbeddedMainWindow(QSize(500, 500));
        m = em->mainWindow;
    } else {
        m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralGroup).release();
        m->view()->resize(QSize(500, 500));
        m->show();
    }
    auto layout = m->multiSplitter();

    auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
    auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
    auto d1 = createDockWidget("1", w1);
    auto d2 = createDockWidget("2", w2);
    auto d3 = createDockWidget("3", w2);

    m->addDockWidget(d1, Location_OnLeft);
    m->addDockWidget(d2, Location_OnLeft);
    m->addDockWidget(d3, Location_OnLeft);

    layout->checkSanity();

    delete static_cast<QtWidgets::ViewWrapper *>(m->window().get())->widget();
}

// For #443
void TestQtWidgets::tst_restoreWithRemapping()
{
    EnsureTopLevelsDeleted e;
    QByteArray savedState;

    // 1. Create a saved state and immediately delete the main window and dock widgets
    auto m = createMainWindow(QSize(500, 500), MainWindowOption_None, "mainWindow1");
    auto dock1 = createDockWidget("1", new QPushButton("1"));
    m->addDockWidget(dock1, Location_OnLeft);
    auto dock2 = createDockWidget("2", new QPushButton("2"));
    m->addDockWidget(dock2, Location_OnRight);

    LayoutSaver saver;
    savedState = saver.serializeLayout();

    dock1->setUniqueName("2");
    dock2->setUniqueName("1");

    // 2. Restore the dock widgets via factory
    LayoutSaver restorer;
    restorer.restoreLayout(savedState);

    // dock 1 is now on the right of dock 2
    QVERIFY(dock1->dptr()->group()->view()->x() > dock2->dptr()->group()->view()->x());
}

void TestQtWidgets::tst_restoreResizesLayout()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(500, 500), MainWindowOption_None);
    auto dock1 = createDockWidget("1", new QPushButton("1"));
    m->addDockWidget(dock1, Location_OnLeft);

    LayoutSaver saver;
    QVERIFY(saver.saveToFile("layout_tst_restoreResizesLayout.json"));

    // Now resize the window, and then restore. The layout should have the new size

    auto layout = m->multiSplitter();
    m->view()->resize(QSize(1050, 1050));
    QCOMPARE(m->size(), QSize(1050, 1050));

    LayoutSaver restorer(RestoreOption_RelativeToMainWindow);
    QVERIFY(restorer.restoreFromFile("layout_tst_restoreResizesLayout.json"));
    QVERIFY(layout->checkSanity());

    QCOMPARE(m->dropArea()->size(), layout->layoutSize());
    QVERIFY(layout->checkSanity());
}

void TestQtWidgets::tst_restoreNonRelativeFloatingWindowGeometry()
{
    // Tests that disabling RelativeFloatingWindowGeometry works

    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(500, 500), MainWindowOption_None);
    auto dock1 = createDockWidget("1", new QPushButton("1"));

    // Also test that invisible dock doesn't change size
    auto dock2 = createDockWidget("2", new QPushButton("2"), {}, {}, /*show=*/false);

    LayoutSaver saver(RestoreOption_RelativeToMainWindow);
    saver.dptr()->m_restoreOptions.setFlag(InternalRestoreOption::RelativeFloatingWindowGeometry,
                                           false);

    const QByteArray saved = saver.serializeLayout();

    const QSize floatingWindowSize = dock1->window()->size();
    const QSize floatingWindowSize2 = dock2->window()->size();

    m->view()->resize(QSize(m->width() * 2, m->height()));
    saver.restoreLayout(saved);

    QVERIFY(dock2->isFloating());
    QVERIFY(!dock2->isOpen());

    QCOMPARE(dock1->window()->size(), floatingWindowSize);
    QCOMPARE(dock2->window()->size(), floatingWindowSize2);
}

void TestQtWidgets::tst_maximumSizePolicy()
{
    EnsureTopLevelsDeleted e;

    const int maxHeight = 250;
    auto widget =
        Platform::instance()->tests_createView({ true, QSize(250, maxHeight), QSize(200, 200) });
    QtCommon::View_qt::asQWidget(widget)->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);

    auto dock1 = createDockWidget("dock1", widget);
    dock1->open();
    dock1->window()->resize(QSize(500, 500));
    auto window1 = dock1->window();
    dock1->close();
    dock1->open();
    auto oldFw2 = dock1->window();

    const int tolerance = 50;
    QVERIFY(dock1->window()->height() <= maxHeight + tolerance); // +tolerance as the floating
                                                                 // window is a bit bigger, due to
                                                                 // margins etc.
    QVERIFY(dock1->height() <= maxHeight);

    auto m1 = createMainWindow();
    auto dock2 = createDockWidget("dock2", new QPushButton("foo"));
    m1->addDockWidget(dock2, Location_OnTop);
    m1->view()->resize(QSize(2000, 3000));

    // Make the floating window big, and see if the suggested highlight is still small
    dock1->window()->resize(QSize(dock1->width(), 800));

    {
        WindowBeingDragged wbd1(dock1->floatingWindow());
        const QRect highlightRect =
            m1->multiSplitter()->rectForDrop(&wbd1, Location_OnBottom, nullptr);
        QVERIFY(highlightRect.height() <= maxHeight + tolerance);
    }

    // Now drop it, and check too
    m1->addDockWidget(dock1, Location_OnBottom);
    QVERIFY(dock1->height() <= maxHeight);
}

void TestQtWidgets::tst_minSizeChanges()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(600, 600), MainWindowOption_None);
    m->show();
    auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
    auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });

    auto d1 = newDockWidget("1");
    d1->setGuestView(w1->asWrapper());
    auto d2 = newDockWidget("2");
    d2->setGuestView(w2->asWrapper());

    m->addDockWidget(d1, Location_OnTop);
    m->addDockWidget(d2, Location_OnTop, nullptr, InitialVisibilityOption::StartHidden);
    auto layout = m->multiSplitter();

    // 1. d2 is a placeholder, let's change its min size before showing it
    w2->setMinimumSize(QSize(800, 800));
    d2->open();

    Item *item2 = layout->itemForGroup(d2->dptr()->group());

    QVERIFY(layout->checkSanity());

    Platform::instance()->tests_waitForResize(m->view());

    QVERIFY(item2->width() >= 800);
    QVERIFY(item2->height() >= 800);
    QVERIFY(m->height() >= 1200);

    // 2. d1 is visible, let's change its min size
    w1->setMinimumSize(QSize(800, 800));

    Platform::instance()->tests_waitForResize(m->view());
    layout->checkSanity();

    QVERIFY(m->height() >= 1600);

    // add a small one to the middle
    auto w3 = Platform::instance()->tests_createView({ true, {}, QSize(100, 100) });
    auto d3 = newDockWidget("3");
    d3->setGuestView(w3->asWrapper());
    m->addDockWidget(d3, Location_OnTop, d1);
}

void TestQtWidgets::tst_maxSizePropagates()
{
    // Tests that the DockWidget gets the min and max size of its guest widget
    EnsureTopLevelsDeleted e;
    auto dock1 = newDockWidget("dock1");

    auto w = Platform::instance()->tests_createView({ true, {}, QSize(200, 200) });
    w->setMinimumSize(QSize(120, 120));
    w->setMaximumSize(QSize(500, 500));
    dock1->setGuestView(w->asWrapper());
    dock1->open();


    QCOMPARE(dock1->view()->minSize(), w->minSize());
    QCOMPARE(dock1->view()->maxSizeHint(), w->maxSizeHint());

    w->setMinimumSize(QSize(121, 121));
    w->setMaximumSize(QSize(501, 501));

    Platform::instance()->tests_waitForEvent(w, QEvent::LayoutRequest);

    QCOMPARE(dock1->view()->minSize(), w->minSize());
    QCOMPARE(dock1->view()->maxSizeHint(), w->maxSizeHint());

    // Now let's see if our Frame also has proper size-constraints
    Core::Group *group = dock1->dptr()->group();
    QCOMPARE(group->view()->maxSizeHint().expandedTo(w->maxSizeHint()),
             group->view()->maxSizeHint());
}

void TestQtWidgets::tst_maxSizedFloatingWindow()
{
    // Tests that FloatingWindows get a proper max-size, if its dock widget has one
    EnsureTopLevelsDeleted e;
    auto dock1 = newDockWidget("dock1");
    auto dock2 = newDockWidget("dock2");
    auto w = Platform::instance()->tests_createView({ true });
    w->setMinimumSize(QSize(120, 100));
    w->setMaximumSize(QSize(300, 300));
    dock1->setGuestView(w->asWrapper());

    dock1->open();
    dock2->open();

    auto window1 = dock1->window();
    auto window2 = dock2->window();
    Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);

    QVERIFY(window1->maxSizeHint().width() < 500);
    QVERIFY(window1->maxSizeHint().height() < 500);
    QVERIFY(window2->maxSizeHint().width() > 500);
    QVERIFY(window2->maxSizeHint().height() > 500);

    auto hasMax = [&window1] {
        const QSize max = window1->maxSizeHint();
        return max.width() < 500 && max.height() < 500;
    };

    // Adding side-by-side, we don't honour max size (yet)
    dock1->addDockWidgetToContainingWindow(dock2, Location_OnBottom);
    Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
    QVERIFY(window1->maxSizeHint().width() > 500);
    QVERIFY(window1->maxSizeHint().height() > 500);

    // Close dw2, we have a single dock widget again, we honour max-size
    dock2->close();
    Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
    QVERIFY(hasMax());

    dock1->addDockWidgetAsTab(dock2);
    Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
    QVERIFY(!hasMax());

    dock2->close();
    Platform::instance()->tests_waitForEvent(window1.get(), QEvent::LayoutRequest);
    QVERIFY(hasMax());
}

void TestQtWidgets::tst_maxSizeHonouredWhenAnotherDropped()
{
    // dock1 is docked, and has small max-height.
    // When dropping dock2, which is small too, dock2 should occupy all the height except dock1's
    // max-height i.e. dock2 should expand and eat all available space

    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
    auto dock1 = newDockWidget("dock1");

    auto w = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
    w->setMinimumSize(QSize(120, 100));
    w->setMaximumSize(QSize(300, 150));
    dock1->setGuestView(w->asWrapper());
    m1->addDockWidget(dock1, Location_OnLeft);

    auto dock2 = newDockWidget("dock2");
    m1->addDockWidget(dock2, Location_OnBottom);

    auto root = m1->multiSplitter()->rootItem();
    auto separator = root->separators().constFirst();
    const int min1 = root->minPosForSeparator_global(separator);
    const int max2 = root->maxPosForSeparator_global(separator);

    QVERIFY(separator->position() >= min1);
    QVERIFY(separator->position() <= max2);
    const int item1MaxHeight = dock1->dptr()->group()->view()->maxSizeHint().height();
    QVERIFY(dock1->dptr()->group()->view()->height() <= item1MaxHeight);
    root->dumpLayout();
    QCOMPARE(dock2->dptr()->group()->view()->height(),
             root->height() - item1MaxHeight - Item::layoutSpacing);
}

void TestQtWidgets::tst_addToHiddenMainWindow()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, {}, false);
    auto w1 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
    auto w2 = Platform::instance()->tests_createView({ true, {}, QSize(400, 400) });
    auto d1 = createDockWidget("1", w1);
    auto d2 = createDockWidget("2", w2);

    m->addDockWidget(d1, Location_OnTop);
    m->addDockWidget(d2, Location_OnTop);

    QVERIFY(!m->isVisible());
    d1->setFloating(true);
    d2->setFloating(false);
    m->layout()->checkSanity();
}

void TestQtWidgets::tst_maxSizePropagates2()
{
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None);
    auto dock1 = newDockWidget("dock1");

    auto w = Platform::instance()->tests_createView({ true, {}, QSize(200, 200) });
    w->setMinimumSize(QSize(120, 120));
    w->setMaximumSize(QSize(300, 500));
    dock1->setGuestView(w->asWrapper());
    dock1->open();

    auto dock2 = newDockWidget("dock2");
    auto dock3 = newDockWidget("dock3");
    auto dock4 = newDockWidget("dock4");
    m1->addDockWidget(dock2, Location_OnLeft);
    m1->addDockWidget(dock3, Location_OnRight);
    m1->addDockWidget(dock4, Location_OnBottom, dock3);
    m1->addDockWidget(dock1, Location_OnLeft, dock4);

    Core::Group *group1 = dock1->dptr()->group();

    Core::ItemBoxContainer *root = m1->multiSplitter()->rootItem();
    Item *item1 = root->itemForView(group1->asLayoutingGuest());
    auto vertSep1 = root->separators().constFirst();
    const int min1 = root->minPosForSeparator_global(vertSep1);

    ItemBoxContainer *container1 = item1->parentBoxContainer();
    auto innerVertSep1 = container1->separators().constFirst();
    const int minInnerSep = container1->minPosForSeparator_global(innerVertSep1);
    const int maxInnerSep = container1->maxPosForSeparator_global(innerVertSep1);

    root->requestSeparatorMove(vertSep1, -(vertSep1->position() - min1));
    QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());

    container1->requestSeparatorMove(innerVertSep1, -(innerVertSep1->position() - minInnerSep));
    QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());

    container1->requestSeparatorMove(innerVertSep1, maxInnerSep - innerVertSep1->position());
    QVERIFY(group1->width() <= group1->view()->maxSizeHint().width());
}

void TestQtWidgets::tst_maxSizeHonouredWhenDropped()
{
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow();
    auto dock1 = newDockWidget("dock1");
    auto dock2 = newDockWidget("dock2");
    m1->addDockWidget(dock1, Location_OnTop);
    m1->view()->resize(QSize(2000, 2000));

    auto w2 = Platform::instance()->tests_createView({ true, {}, { 0, 0 } });
    dock2->setGuestView(w2->asWrapper());
    const int maxWidth = 200;
    w2->setMaximumSize(QSize(maxWidth, 200));
    m1->addDockWidget(dock2, Location_OnLeft);
    const int droppedWidth = dock2->dptr()->group()->width();
    QVERIFY(droppedWidth < maxWidth + 50); // +50 to cover any margins and waste by QTabWidget

    // Try again, but now dropping a multisplitter
    dock2->setFloating(true);
    auto fw = dock2->floatingWindow();

    m1->dropArea()->drop(fw->view(), Location_OnLeft, nullptr);
    QCOMPARE(dock2->dptr()->group()->width(), droppedWidth);
}

void TestQtWidgets::tst_fixedSizePolicy()
{
    // tests that KDDW also takes into account QSizePolicy::Fixed for calculating the max size hint.
    // Since QPushButton for example doesn't set QWidget::maximumSize(), but instead uses sizeHint()
    // + QSizePolicy::Fixed.
    EnsureTopLevelsDeleted e;
    auto button = new QPushButton("one");
    auto dock1 = createDockWidget("dock1", button);
    Core::Group *group = dock1->dptr()->group();

    // Just a precondition from the test. If QPushButton ever changes, replace with a QWidget and
    // set fixed size policy
    QCOMPARE(button->sizePolicy().verticalPolicy(), QSizePolicy::Fixed);

    const int buttonMaxHeight = button->sizeHint().height();

    QWidget *dockWidget1 = QtCommon::View_qt::asQWidget(dock1->view());
    QCOMPARE(dockWidget1->sizePolicy().verticalPolicy(),
             button->sizePolicy().verticalPolicy());
    QCOMPARE(dockWidget1->sizePolicy().horizontalPolicy(),
             button->sizePolicy().horizontalPolicy());

    QCOMPARE(group->view()->maxSizeHint().height(),
             qMax(buttonMaxHeight, Core::Item::hardcodedMinimumSize.height()));
}

void TestQtWidgets::tst_restoreFloatingMaximizedState()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarHasMaximizeButton);
    auto dock1 = createDockWidget("dock1", Platform::instance()->tests_createView({ true }));
    const QRect originalNormalGeometry = dock1->floatingWindow()->view()->normalGeometry();
    dock1->floatingWindow()->view()->showMaximized();

    QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), WindowState::Maximized);

    LayoutSaver saver;
    const QByteArray saved = saver.serializeLayout();

    saver.restoreLayout(saved);
    QCOMPARE(dock1->floatingWindow()->view()->window()->windowState(), WindowState::Maximized);

    QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry);

    dock1->floatingWindow()->view()->showNormal();
    QCOMPARE(dock1->floatingWindow()->view()->normalGeometry(), originalNormalGeometry);
}

void TestQtWidgets::tst_complex()
{
    // Tests some anchors out of bounds I got

    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(600, 500), MainWindowOption_None);
    auto layout = m->multiSplitter();
    m->view()->resize(QSize(3266, 2239));
    m->show();

    Core::DockWidget::List docks;

    QVector<KDDockWidgets::Location> locations = {
        Location_OnLeft, Location_OnLeft, Location_OnLeft, Location_OnRight,
        Location_OnRight, Location_OnRight, Location_OnRight, Location_OnBottom,
        Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom,
        Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom,
        Location_OnBottom, Location_OnBottom, Location_OnBottom, Location_OnBottom,
        Location_OnBottom
    };

    QVector<KDDockWidgets::InitialVisibilityOption> options = {
        InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartVisible,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartVisible,
        InitialVisibilityOption::StartVisible, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden, InitialVisibilityOption::StartHidden,
        InitialVisibilityOption::StartHidden
    };

    QVector<bool> floatings = { true, false, true, false, false, false, false,
                                false, false, false, false, false, true, false,
                                false, true, true, true, true, true, false };

    QVector<QSize> minSizes = { QSize(316, 219), QSize(355, 237), QSize(293, 66), QSize(158, 72),
                                QSize(30, 141), QSize(104, 143), QSize(104, 105), QSize(84, 341),
                                QSize(130, 130), QSize(404, 205), QSize(296, 177), QSize(914, 474),
                                QSize(355, 237), QSize(104, 104), QSize(104, 138), QSize(1061, 272),
                                QSize(165, 196), QSize(296, 177), QSize(104, 104), QSize(355, 237),
                                QSize(104, 138) };

    const int num = 21;
    for (int i = 0; i < num; ++i) {
        auto widget = Platform::instance()->tests_createView({ true, {}, minSizes.at(i) });
        auto dw = newDockWidget(QString::number(i));
        dw->setGuestView(widget->asWrapper());
        docks << dw;
    }

    for (int i = 0; i < num; ++i) {
        m->addDockWidget(docks[i], locations[i], nullptr, options[i]);
        layout->checkSanity();
        docks[i]->setFloating(floatings[i]);
        layout->checkSanity();
    }

    m->show();

    // Cleanup
    qDeleteAll(docks);
    qDeleteAll(DockRegistry::self()->groups());
}


void TestQtWidgets::tst_deleteOnClose()
{
    // QtQuick doesn't support autohide/pin/sidebar yet.
    // Tests that restoring the side-bar-overlay will call the users dock widget factory in case
    // the dock widget was deleted already
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_AutoHideSupport);
    KDDockWidgets::Config::self().setDockWidgetFactoryFunc([](const QString &name) {
        return createDockWidget(
            name, Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }),
            DockWidgetOption_DeleteOnClose);
    });

    auto m = createMainWindow(QSize(800, 500), MainWindowOption_None);
    ObjectGuard<Core::DockWidget> dock1 =
        createDockWidget("1", Platform::instance()->tests_createView({ true, {}, QSize(400, 400) }),
                         DockWidgetOption_DeleteOnClose);
    m->addDockWidget(dock1, Location_OnLeft);
    m->moveToSideBar(dock1);
    m->overlayOnSideBar(dock1);
    LayoutSaver saver;
    const QByteArray saved = saver.serializeLayout();

    QVERIFY(dock1->isVisible());
    dock1->close();
    QVERIFY(Platform::instance()->tests_waitForDeleted(dock1));
    QVERIFY(!dock1.data());

    saver.restoreLayout(saved);
}

void TestQtWidgets::tstCloseNestedMdi()
{
    // Tests a bug where closing a mdi dock widget would close its main window too
    EnsureTopLevelsDeleted e;

    auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
    ObjectGuard<Core::MainWindow> p = m.get();

    auto mdi = new QtWidgets::MDIArea();
    m->setPersistentCentralView(mdi->asWrapper());

    auto dock1 = new QtWidgets::DockWidget(QStringLiteral("MyDock1"));
    dock1->setWidget(new QPushButton("1"));

    mdi->addDockWidget(dock1, {});

    dock1->dockWidget()->titleBar()->onCloseClicked();
    QVERIFY(p);
    QVERIFY(m->isVisible());
}

void TestQtWidgets::tstCloseNestedMDIPropagates()
{
    auto m = createMainWindow(QSize(1000, 500), MainWindowOption_HasCentralWidget);
    ObjectGuard<Core::MainWindow> p = m.get();

    auto mdi = new QtWidgets::MDIArea();
    m->setPersistentCentralView(mdi->asWrapper());

    auto dock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock1"));
    auto nonClosableWidget = Platform::instance()->tests_createNonClosableView();
    dock1->dockWidget()->setGuestView(nonClosableWidget->asWrapper());
    mdi->addDockWidget(dock1, {});

    auto dock2 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock2"));
    auto nonClosableWidget2 = Platform::instance()->tests_createNonClosableView();
    dock2->dockWidget()->setGuestView(nonClosableWidget2->asWrapper());
    dock2->open();

    Platform::instance()->tests_waitForEvent(dock1->controller(), QEvent::Show);
    QVERIFY(dock1->isVisible());
    QVERIFY(dock2->isVisible());

    m->close();

    QVERIFY(dock2->isVisible());
    QVERIFY(dock1->isVisible());
}

void TestQtWidgets::tst_setAsCurrentTab()
{
    EnsureTopLevelsDeleted e;

    // For issue #312, which is QtWidget only. The controller has the correct current index but
    // not the QTabBar.
    auto dw = Config::self().viewFactory()->createDockWidget("dw1")->asDockWidgetController();
    auto dw2 = Config::self().viewFactory()->createDockWidget("dw2")->asDockWidgetController();
    auto dw3 = Config::self().viewFactory()->createDockWidget("dw3")->asDockWidgetController();

    dw->view()->show();
    dw->addDockWidgetAsTab(dw2);
    dw->addDockWidgetAsTab(dw3);

    Core::Group *group = dw->d->group();
    auto tabBar = group->tabBar();
    QTabBar *tabBarWidget = qobject_cast<QTabBar *>(QtCommon::View_qt::asQWidget(tabBar->view()));

    QVERIFY(!dw->isCurrentTab());
    QVERIFY(!dw2->isCurrentTab());
    QVERIFY(dw3->isCurrentTab());
    QCOMPARE(tabBarWidget->currentIndex(), 2);

    dw->setAsCurrentTab();

    QVERIFY(dw->isCurrentTab());
    QVERIFY(!dw2->isCurrentTab());
    QVERIFY(!dw3->isCurrentTab());

    QCOMPARE(group->currentIndex(), 0);
    QCOMPARE(group->currentDockWidget(), dw);

    QCOMPARE(tabBar->currentIndex(), 0);

    QCOMPARE(tabBarWidget->currentIndex(), 0);
}

void TestQtWidgets::initTestCase()
{
    KDDockWidgets::Core::Platform::instance()->installMessageHandler();
}

void TestQtWidgets::cleanupTestCase()
{
    KDDockWidgets::Core::Platform::instance()->uninstallMessageHandler();
}

void TestQtWidgets::tst_crash326()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(500, 500), MainWindowOption_HasCentralWidget);
    auto dock1 = createDockWidget("1", new QPushButton("1"), {}, {}, false);
    m->addDockWidget(dock1, KDDockWidgets::Location_OnBottom);
    ObjectGuard<Group> originalGroup = dock1->d->group();
    dock1->close();
    QVERIFY(dock1->parent() == nullptr);
    QVERIFY(originalGroup != dock1->d->group());
    QVERIFY(originalGroup->beingDeletedLater());

    dock1->show();
    QVERIFY(originalGroup != dock1->d->group());
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
void TestQtWidgets::tst_crash661()
{
    EnsureTopLevelsDeleted e;

    auto flags = KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible
        | KDDockWidgets::Config::Flag_AlwaysShowTabs
        | KDDockWidgets::Config::Flag_TabsHaveCloseButton
        | KDDockWidgets::Config::Flag_ShowButtonsOnTabBarIfTitleBarHidden;
    KDDockWidgets::Config::self().setFlags(flags);

    auto m = createMainWindow(QSize(800, 600), MainWindowOption_HasCentralWidget);
    auto dock1 = createDockWidget("DockWidget1", new QWidget(), {}, {}, false);
    m->addDockWidget(dock1, KDDockWidgets::Location_OnLeft);
    QVERIFY(dock1->isOpen());

    auto group = dock1->dptr()->group();
    QWidget *groupWidget = QtCommon::View_qt::asQWidget(group->view());
    auto buttons = groupWidget->findChildren<QToolButton *>();
    auto it = std::find_if(buttons.cbegin(), buttons.cend(), [](auto button) {
        return button->property("KDDW_BUTTON_TYPE").template value<TitleBarButtonType>() == TitleBarButtonType::Float;
    });

    QVERIFY(it != buttons.cend());
    QVERIFY((*it)->isVisible());

    QMouseEvent pressEvent(QEvent::MouseButtonPress, QPoint(5, 5), {}, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QCoreApplication::sendEvent(*it, &pressEvent);

    QMouseEvent releaseEvent(QEvent::MouseButtonRelease, QPoint(5, 5), {}, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
    QCoreApplication::sendEvent(*it, &releaseEvent);
}
#endif

void TestQtWidgets::tst_restoreWithIncompleteFactory()
{
    EnsureTopLevelsDeleted e;
    SetExpectedWarning ignoreWarning("Couldn't find dock widget");
    KDDockWidgets::Config::self().setDockWidgetFactoryFunc([](const QString &name) -> KDDockWidgets::Core::DockWidget * {
        if (name.contains(QStringLiteral("centralDockWidget")))
            return nullptr;

        auto w = new KDDockWidgets::QtWidgets::DockWidget(name);
        w->setWidget(new QWidget());
        return w->asDockWidgetController();
    });

    auto m = createMainWindow(QSize(500, 500), MainWindowOption_None, "MainWindow1");

    LayoutSaver saver;
    saver.restoreFromFile(":/layouts/restoreWithIncompleteFactory.json");

    auto layout = m->multiSplitter();
    QCOMPARE(layout->separators().size(), 0);
}

void TestQtWidgets::tst_deleteDockWidget()
{
    // Tests deleting the dock widget directly
    // The layout should readjust

    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = Config::self().viewFactory()->createDockWidget("dw1")->asDockWidgetController();
    m1->addDockWidget(dw1, Location_OnBottom);
    auto dw3 = Config::self().viewFactory()->createDockWidget("dw3")->asDockWidgetController();
    m1->addDockWidget(dw3, Location_OnTop);

    auto dw2 = Config::self().viewFactory()->createDockWidget("dw2")->asDockWidgetController();

    m1->addDockWidget(dw2, KDDockWidgets::Location_OnLeft, dw1);
    dw2->setFloating(true);

    // There's one separator, separating dock 1 from dock 3
    QCOMPARE(m1->multiSplitter()->separators().size(), 1);

    delete dw1;

    // Frame is deleted delayed
    QTest::qWait(1000);

    // Dock3 now occupies everything, separator was deleted
    QVERIFY(m1->multiSplitter()->separators().isEmpty());
}

void TestQtWidgets::tst_findAncestor()
{
    EnsureTopLevelsDeleted e;
    QVERIFY(KDDockWidgets::findAncestor<QWidget>(nullptr) == nullptr);

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dw1 = Config::self().viewFactory()->createDockWidget("dw1")->asDockWidgetController();
    m1->addDockWidget(dw1, Location_OnBottom);

    auto mainWindow = QtCommon::View_qt::asQWidget(m1->view());
    auto dockWidget = QtCommon::View_qt::asQWidget(dw1->view());

    QCOMPARE(mainWindow, KDDockWidgets::findAncestor<QMainWindow>(mainWindow));
    QCOMPARE(mainWindow, KDDockWidgets::findAncestor<QMainWindow>(dockWidget));
}

#if defined(KDDW_FRONTEND_QT) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void TestQtWidgets::tst_userData()
{
    QByteArray saved;

    QVariantMap userData1;
    userData1["key1"] = "value1";
    userData1["key2"] = 123;
    userData1["key3"] = true;

    QVariantMap userData2;
    userData2["name"] = "Test Dock";
    userData2["priority"] = 5;
    userData2["nested"] = QVariantMap { { "subkey", "subvalue" } };

    {
        EnsureTopLevelsDeleted e;
        auto m = createMainWindow({}, {}, "mw1");

        auto dock1 = createDockWidget("dock1");
        dock1->setUserData(userData1);

        auto dock2 = createDockWidget("dock2");
        dock2->setUserData(userData2);

        QCOMPARE(dock1->userData(), userData1);
        QCOMPARE(dock2->userData(), userData2);

        LayoutSaver saver;
        saved = saver.serializeLayout();
    }

    EnsureTopLevelsDeleted e;

    QVERIFY(!saved.isEmpty());

    auto m = createMainWindow({}, {}, "mw1");

    auto dock1 = createDockWidget("dock1");
    auto dock2 = createDockWidget("dock2");

    LayoutSaver restorer;

    QVERIFY(restorer.restoreLayout(saved));
    QCOMPARE(dock1->userData(), userData1);
    QCOMPARE(dock2->userData(), userData2);
}
#endif

void TestQtWidgets::tst_standaloneTitleBar()
{
    QWidget window;

    QtWidgets::TitleBar titleBar(&window);
    titleBar.asTitleBarController()->setTitle("some title");

    auto lay = new QVBoxLayout(&window);
    lay->addWidget(&titleBar);
    lay->addStretch();

    window.show();

    QVERIFY(window.isVisible());
    titleBar.asTitleBarController()->onCloseClicked();
    QVERIFY(!window.isVisible());
}

void TestQtWidgets::tst_titleBarTitle()
{
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock1"));
    m1->addDockWidget(dock1->asDockWidgetController(), Location_OnBottom);

    auto tb = dock1->actualTitleBar();
    QCOMPARE(dock1->asDockWidgetController()->title(), "MyDock1");
    QCOMPARE(tb->title(), "MyDock1");

    dock1->setTitle("Other");
    QCOMPARE(tb->title(), "Other");
}

void TestQtWidgets::tst_widgetAddQAction()
{
    auto dock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock1"));

    QWidget w;
    w.addAction(dock1->toggleAction());

    delete dock1;
}

void TestQtWidgets::tst_currentTabChanged()
{
    // Tests that QtWidgets::DockWidget::isCurrentTabConnection is emitted

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock1"));
    auto dock2 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock2"));

    int count1 = 0;
    int count2 = 0;
    connect(dock1, &QtWidgets::DockWidget::isCurrentTabChanged, [&count1](bool isCurrent) {
        if (isCurrent)
            count1++;
        else
            count1--;
    });

    connect(dock2, &QtWidgets::DockWidget::isCurrentTabChanged, [&count2](bool isCurrent) {
        if (isCurrent)
            count2++;
        else
            count2--;
    });

    m1->addDockWidget(dock1->asDockWidgetController(), Location_OnBottom);
    QCOMPARE(count1, 1);
    QCOMPARE(count2, 0);

    dock1->addDockWidgetAsTab(dock2);
    QCOMPARE(count1, 0);
    QCOMPARE(count2, 1);

    // already current
    dock2->dockWidget()->setAsCurrentTab();
    QCOMPARE(count1, 0);
    QCOMPARE(count2, 1);

    dock1->dockWidget()->setAsCurrentTab();
    QCOMPARE(count1, 1);
    QCOMPARE(count2, 0);

    dock2->dockWidget()->setAsCurrentTab();
    QCOMPARE(count1, 0);
    QCOMPARE(count2, 1);
}

void TestQtWidgets::tst_moveTab_data()
{
    QTest::addColumn<bool>("moveViaController");

    QTest::newRow("true") << true;
    QTest::newRow("false") << false;
}

void TestQtWidgets::tst_moveTab()
{
    QFETCH(bool, moveViaController);

    EnsureTopLevelsDeleted e;
    Config::self().setFlags(Config::Flag_AllowReorderTabs);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dockA = Config::self().viewFactory()->createDockWidget("dwA")->asDockWidgetController();
    auto dockB = Config::self().viewFactory()->createDockWidget("dwB")->asDockWidgetController();

    auto qwidgetA = QtCommon::View_qt::asQWidget(dockA->view());
    auto qwidgetB = QtCommon::View_qt::asQWidget(dockB->view());

    m1->addDockWidget(dockA, Location_OnBottom);
    dockA->addDockWidgetAsTab(dockB);

    Core::TabBar *tb = dockA->d->group()->tabBar();
    QVERIFY(tb);
    tb->setCurrentIndex(0);
    QCOMPARE(tb->currentDockWidget(), dockA);
    auto qtabbar = qobject_cast<QTabBar *>(QtCommon::View_qt::asQWidget(tb->view()));
    auto qtabwidget = qobject_cast<QTabWidget *>(QtCommon::View_qt::asQWidget(tb->stack()->view()));
    QVERIFY(qtabbar);
    QVERIFY(qtabwidget);
    QCOMPARE(qtabbar->currentIndex(), 0);
    QCOMPARE(qtabwidget->currentIndex(), 0);
    QCOMPARE(qtabwidget->widget(0), qwidgetA);
    QCOMPARE(qtabwidget->widget(1), qwidgetB);

    if (moveViaController) {
        tb->moveTabTo(0, 1);
    } else {
        qtabbar->moveTab(0, 1);
    }

    QCOMPARE(qtabbar->currentIndex(), 1);
    QCOMPARE(qtabwidget->widget(0), qwidgetB);
    QCOMPARE(qtabwidget->widget(1), qwidgetA);

    QCOMPARE(tb->currentIndex(), 1);
    QCOMPARE(tb->currentDockWidget(), dockA);
}

void TestQtWidgets::tst_nestedMainWindowSaveRestore_data()
{
    QTest::addColumn<bool>("nestFurther");

    QTest::newRow("true") << true; // parented to DockWidget
    QTest::newRow("false") << false; // parented to intermediate container
}

void TestQtWidgets::tst_nestedMainWindowSaveRestore()
{
    // This is for #508 . Do not change this configuration.
    // tests that a save/restore of tabbed nested main windows does not
    // hide the main window


    QFETCH(bool, nestFurther);
    EnsureTopLevelsDeleted e;

    auto mainWindow = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto nestedMainWindow1 = createMainWindow(QSize(500, 500), MainWindowOption_None, "MW1.1");
    nestedMainWindow1->setAffinities({ "foo1" });
    auto nestedMainWindow2 = createMainWindow(QSize(500, 500), MainWindowOption_None, "MW1.2");
    nestedMainWindow2->setAffinities({ "foo2" });

    auto containerDock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested MainWindow Dock container1"));
    auto containerDock2 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested MainWindow Dock container2"));
    auto nestedMainWindowQWidget1 = static_cast<QMainWindow *>(QtCommon::View_qt::asQWidget(nestedMainWindow1->view()));
    auto nestedMainWindowQWidget2 = static_cast<QMainWindow *>(QtCommon::View_qt::asQWidget(nestedMainWindow2->view()));


    if (nestFurther) {
        auto container1 = new QWidget();
        auto container2 = new QWidget();
        auto lay1 = new QVBoxLayout(container1);
        auto lay2 = new QVBoxLayout(container2);
        lay1->addWidget(nestedMainWindowQWidget1);
        lay2->addWidget(nestedMainWindowQWidget2);

        containerDock1->setWidget(container1);
        containerDock2->setWidget(container2);
    } else {
        containerDock1->setWidget(nestedMainWindowQWidget1);
        containerDock2->setWidget(nestedMainWindowQWidget2);
    }

    mainWindow->addDockWidget(containerDock1->asDockWidgetController(), KDDockWidgets::Location_OnRight);
    mainWindow->addDockWidget(containerDock2->asDockWidgetController(), KDDockWidgets::Location_OnRight);

    containerDock1->addDockWidgetAsTab(containerDock2);
    containerDock1->setAsCurrentTab();

    LayoutSaver saver;
    QVERIFY(saver.restoreLayout(saver.serializeLayout()));
    QVERIFY(mainWindow->isVisible());
}

void TestQtWidgets::tst_nestedMainWindowFloatButton()
{
    // Bug 1 reported in #464:
    // Dockwidget, inside MainWindow inside MainWindow.
    // Float inner main window
    // DockWidget's float button stops working
    EnsureTopLevelsDeleted e;
    auto mainWindow = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto nestedMainWindow = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW2");
    mainWindow->setObjectName("root main window");

    auto containerDock = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested MainWindow Dock container"));
    auto nestedMainWindowQWidget = static_cast<QMainWindow *>(QtCommon::View_qt::asQWidget(nestedMainWindow->view()));
    containerDock->setWidget(nestedMainWindowQWidget);
    mainWindow->addDockWidget(containerDock->asDockWidgetController(), Location_OnBottom);

    auto nestedDock = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested Dock"));
    nestedMainWindow->addDockWidget(nestedDock->asDockWidgetController(), Location_OnBottom);
    nestedMainWindowQWidget->menuBar()->addMenu("Inner menu");
    nestedMainWindowQWidget->setObjectName("nested main window");

    QVERIFY(!containerDock->isFloating());
    QVERIFY(!nestedDock->isFloating());
    QVERIFY(containerDock->dockWidget()->isInMainWindow());
    QVERIFY(nestedDock->dockWidget()->isInMainWindow());

    containerDock->setFloating(true);

    QVERIFY(containerDock->isFloating());
    QVERIFY(!nestedDock->isFloating());
    QVERIFY(!containerDock->dockWidget()->isInMainWindow());
    QVERIFY(nestedDock->dockWidget()->isInMainWindow());
}

void TestQtWidgets::tst_nestedMainWindowToggle()
{
    EnsureTopLevelsDeleted e;
    auto mainWindow = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto nestedMainWindow = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW2");
    mainWindow->setObjectName("root main window");

    auto dock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested MainWindow Dock container"));
    auto nestedMainWindowQWidget = static_cast<QMainWindow *>(QtCommon::View_qt::asQWidget(nestedMainWindow->view()));
    dock1->setWidget(nestedMainWindowQWidget);
    mainWindow->addDockWidget(dock1->asDockWidgetController(), Location_OnBottom);

    auto nestedDock = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested Dock"));
    nestedMainWindow->addDockWidget(nestedDock->asDockWidgetController(), Location_OnBottom);
    nestedMainWindowQWidget->menuBar()->addMenu("Just a visual cue, to notice the nesting");
    nestedMainWindowQWidget->setObjectName("nested main window");
    QPointer<QObject> guard = nestedMainWindowQWidget;

    // Close dock1:
    QVERIFY(dock1->isOpen());
    QVERIFY(nestedDock->isOpen());

    dock1->close();
    QVERIFY(guard); // For some reason nested main window was being deleted
    QVERIFY(!dock1->isOpen());
    QVERIFY(!nestedDock->isOpen());

    // Reopen dock1, the nested dockwidgets should also reopen

    dock1->open();

    QVERIFY(guard);
    QVERIFY(dock1->isOpen());
    QVERIFY(dock1->isVisible());
    QVERIFY(nestedMainWindowQWidget->isVisible());

    QEXPECT_FAIL("", "Bug #360, to be fixed", Continue);
    QVERIFY(nestedDock->isOpen());
}

void TestQtWidgets::tstQGraphicsProxyWidget()
{
    // Tests that using a QGraphicsProxyWidget doesn't crash

    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "MW1");
    auto dock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("MyDock1"));
    m1->addDockWidget(dock1->asDockWidgetController(), Location_OnBottom);

    auto qgv = new QGraphicsView();
    dock1->setWidget(qgv);
    qgv->setVisible(true);

    auto scene = new QGraphicsScene();
    qgv->setScene(scene);

    auto lineEdit = new QLineEdit();
    auto proxyWidget = new QGraphicsProxyWidget();
    proxyWidget->setWidget(lineEdit);
    lineEdit->setText("Test");

    QVERIFY(lineEdit->graphicsProxyWidget());
    QVERIFY(lineEdit->isWindow());

    scene->addItem(proxyWidget);
}

void TestQtWidgets::tst_focusBetweenTabs()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarIsFocusable);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_None, "mw1");

    auto floatingDock = new QtWidgets::DockWidget(QStringLiteral("floatingDock"));
    auto leFloating = new QLineEdit("floating");
    floatingDock->setWidget(leFloating);
    floatingDock->show();

    auto dock1 = new QtWidgets::DockWidget(QStringLiteral("MyDock1"));
    auto le1 = new QLineEdit("text1");
    dock1->setWidget(le1);

    auto dock2 = new QtWidgets::DockWidget(QStringLiteral("MyDock2"));
    auto le2 = new QLineEdit("text2");
    dock2->setWidget(le2);

    auto dock3 = new QtWidgets::DockWidget(QStringLiteral("MyDock3"));
    auto le3 = new QLineEdit("text3");
    dock3->setWidget(le3);

    m1->addDockWidget(dock1->asDockWidgetController(), Location_OnLeft);
    m1->addDockWidget(dock2->asDockWidgetController(), Location_OnRight);
    dock2->addDockWidgetAsTab(dock3);

    // 1. Setup: Give focus to le2 and le3, just so our focus scope remembers they were focused.
    le1->setFocus();
    leFloating->setFocus();
    QVERIFY(dock3->dockWidget()->isCurrentTab());
    m1->view()->activateWindow();
    QTest::qWait(1);
    le3->setFocus();

    QVERIFY(le3->hasFocus());
    dock2->setAsCurrentTab();
    QVERIFY(dock2->dockWidget()->isCurrentTab());
    le2->setFocus();
    QTest::qWait(1);
    QVERIFY(le2->hasFocus());

    // 2. Now, cycling tabs should toggle focus between le2 and le3
    dock3->setAsCurrentTab();
    QVERIFY(le3->hasFocus());
    QVERIFY(!le2->hasFocus());

    dock2->setAsCurrentTab();
    QVERIFY(le2->hasFocus());
    QVERIFY(!le3->hasFocus());

    // 3. Check that focusing titlebar focuses the previous focused line edit as well
    auto titlebar1 = dock1->actualTitleBar()->view();
    titlebar1->setFocus(Qt::MouseFocusReason);
    QVERIFY(le1->hasFocus());
    QVERIFY(!le2->hasFocus());
    QVERIFY(!le3->hasFocus());

    // 4. Test with a floating window as well
    floatingDock->rootView()->activateWindow();
    QVERIFY(Platform::instance()->tests_waitForWindowActive(floatingDock->window(), 1000));
    QVERIFY(floatingDock->window()->isActive());
    auto titlebarFloating = floatingDock->actualTitleBar()->view();
    QVERIFY(titlebarFloating->isVisible());
    titlebarFloating->setFocus(Qt::MouseFocusReason);
    QTest::qWait(1000);

    QVERIFY(leFloating->hasFocus());
    QVERIFY(!le1->hasFocus());
    QVERIFY(!le2->hasFocus());
    QVERIFY(!le3->hasFocus());
}

void TestQtWidgets::addDockWidgetToSide()
{
    EnsureTopLevelsDeleted e;
    KDDockWidgets::Config::self().setFlags(KDDockWidgets::Config::Flag_TitleBarIsFocusable);
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");

    auto d1 = new QtWidgets::DockWidget("d1");
    auto d2 = new QtWidgets::DockWidget("d2");
    auto d3 = new QtWidgets::DockWidget("d3");
    auto d4 = new QtWidgets::DockWidget("d4");
    auto dTop = new QtWidgets::DockWidget("dTop");
    auto dTop2 = new QtWidgets::DockWidget("dTop2");
    QVERIFY(!d1->isOpen());
    QVERIFY(!d1->toggleAction()->isChecked());
    m1->addDockWidgetToSide(d1->asDockWidgetController(), KDDockWidgets::Location_OnLeft);
    QVERIFY(d1->isOpen());
    QVERIFY(d1->toggleAction()->isChecked());
    m1->addDockWidgetToSide(d2->asDockWidgetController(), KDDockWidgets::Location_OnLeft);
    m1->addDockWidgetToSide(d3->asDockWidgetController(), KDDockWidgets::Location_OnRight);
    m1->addDockWidgetToSide(dTop->asDockWidgetController(), KDDockWidgets::Location_OnTop);
    m1->addDockWidgetToSide(dTop2->asDockWidgetController(), KDDockWidgets::Location_OnTop);
    m1->addDockWidgetToSide(d4->asDockWidgetController(), KDDockWidgets::Location_OnRight);

    auto root = m1->layout()->rootItem()->asBoxContainer();
    QVERIFY(root->isVertical());

    auto topContainer = root->childItems()[0]->asBoxContainer();
    QVERIFY(!topContainer->isVertical());
    QCOMPARE(topContainer->numChildren(), 2);

    QCOMPARE(Group::fromItem(topContainer->childItems()[0])->objectName(), "dTop");
    QCOMPARE(Group::fromItem(topContainer->childItems()[1])->objectName(), "dTop2");

    auto bottomContainer = root->childItems()[1]->asBoxContainer();
    QVERIFY(!bottomContainer->isVertical());
    QCOMPARE(bottomContainer->numChildren(), 3);

    auto leftContainer = bottomContainer->childItems()[0]->asBoxContainer();
    auto rightContainer = bottomContainer->childItems()[2]->asBoxContainer();
    QVERIFY(leftContainer->isVertical());
    QVERIFY(rightContainer->isVertical());
    QCOMPARE(leftContainer->numChildren(), 2);
    QCOMPARE(rightContainer->numChildren(), 2);

    QCOMPARE(Group::fromItem(leftContainer->childItems()[0])->objectName(), "d1");
    QCOMPARE(Group::fromItem(leftContainer->childItems()[1])->objectName(), "d2");
    QCOMPARE(Group::fromItem(rightContainer->childItems()[0])->objectName(), "d3");
    QCOMPARE(Group::fromItem(rightContainer->childItems()[1])->objectName(), "d4");
}

void TestQtWidgets::addDockWidgetToSide2()
{
    // Tests adding relative to a StartHidden

    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");

    auto d1 = new QtWidgets::DockWidget("d1");
    auto d2 = new QtWidgets::DockWidget("d2");

    QVERIFY(!d1->isOpen());
    QVERIFY(!d1->toggleAction()->isChecked());
    m1->addDockWidgetToSide(d1->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);
    QVERIFY(!d1->isOpen());
    QVERIFY(!d1->toggleAction()->isChecked());

    m1->addDockWidgetToSide(d2->asDockWidgetController(), KDDockWidgets::Location_OnRight);

    d1->show();

    auto root = m1->layout()->rootItem()->asBoxContainer();
    QVERIFY(root->isHorizontal());
    QCOMPARE(root->numChildren(), 2);
}

void TestQtWidgets::addDockWidgetToSide3()
{
    // Tests adding relative to a StartHidden but the new one is also hidden
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");

    auto d1 = new QtWidgets::DockWidget("d1");
    auto d2 = new QtWidgets::DockWidget("d2");
    m1->addDockWidgetToSide(d1->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);
    m1->addDockWidgetToSide(d2->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);

    auto root = m1->layout()->rootItem()->asBoxContainer();
    QVERIFY(root->isHorizontal());
    QCOMPARE(root->numChildren(), 2);

    d1->show();
    d2->show();

    auto inner = root->childItems()[1]->asBoxContainer();
    QVERIFY(inner);
    QVERIFY(inner->isVertical());
    QCOMPARE(inner->numChildren(), 2);
    QCOMPARE(inner->numVisibleChildren(), 2);
}

void TestQtWidgets::addDockWidgetToSide4()
{
    // Tests a case where generated layout would be off by a few pixels
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");

    auto dummy1 = new QtWidgets::DockWidget("dummy1");
    auto dummy2 = new QtWidgets::DockWidget("dummy2");
    m1->addDockWidget(dummy1->asDockWidgetController(), KDDockWidgets::Location_OnRight, nullptr, InitialVisibilityOption::StartHidden);
    m1->addDockWidget(dummy2->asDockWidgetController(), KDDockWidgets::Location_OnLeft, nullptr, InitialVisibilityOption::StartHidden);

    auto leftVisible = new QtWidgets::DockWidget("leftVisible");
    auto leftHidden = new QtWidgets::DockWidget("leftHidden");
    auto bottomHidden = new QtWidgets::DockWidget("bottomHidden");
    auto rightVisible = new QtWidgets::DockWidget("rightVisible");
    auto leftVisible2 = new QtWidgets::DockWidget("leftVisible2");
    auto bottomHidden2 = new QtWidgets::DockWidget("bottomHidden2");
    auto rightHidden = new QtWidgets::DockWidget("rightHidden");
    auto rightHidden2 = new QtWidgets::DockWidget("rightHidden2");

    m1->addDockWidgetToSide(leftVisible->asDockWidgetController(), KDDockWidgets::Location_OnLeft);
    m1->addDockWidgetToSide(leftHidden->asDockWidgetController(), KDDockWidgets::Location_OnLeft, InitialVisibilityOption::StartHidden);
    m1->addDockWidgetToSide(bottomHidden->asDockWidgetController(), KDDockWidgets::Location_OnBottom, InitialVisibilityOption::StartHidden);

    m1->addDockWidgetToSide(leftVisible2->asDockWidgetController(), KDDockWidgets::Location_OnLeft);

    m1->addDockWidgetToSide(bottomHidden2->asDockWidgetController(), KDDockWidgets::Location_OnBottom, InitialVisibilityOption::StartHidden);
    m1->addDockWidgetToSide(rightHidden->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);
    m1->addDockWidgetToSide(rightHidden2->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);

    leftVisible->open();

    QVERIFY(m1->layout()->checkSanity());
    m1->addDockWidgetToSide(rightVisible->asDockWidgetController(), KDDockWidgets::Location_OnRight);
    QVERIFY(m1->layout()->checkSanity());
}

void TestQtWidgets::addDockWidgetToSide5()
{
    // Tests a case where generated layout would be off by a few pixels
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1002, 1002), MainWindowOption_HasCentralGroup, "mw1");

    auto rightVisible = new QtWidgets::DockWidget("rightVisible");
    auto rightHidden = new QtWidgets::DockWidget("rightHidden");
    auto rightHidden2 = new QtWidgets::DockWidget("rightHidden2");

    m1->addDockWidgetToSide(rightHidden->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);
    m1->addDockWidgetToSide(rightHidden2->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);

    QVERIFY(m1->layout()->checkSanity());
    m1->addDockWidgetToSide(rightVisible->asDockWidgetController(), KDDockWidgets::Location_OnRight);
    QVERIFY(m1->layout()->checkSanity());
}

void TestQtWidgets::addDockWidgetToSideCrash()
{
    // There used to be a crash when adding this layout setup.
    // This test just ensures it doesn't regress
    {
        EnsureTopLevelsDeleted e;
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");

        auto bottom1 = new QtWidgets::DockWidget("bottom1");
        auto left1 = new QtWidgets::DockWidget("left1");
        auto bottom2 = new QtWidgets::DockWidget("bottom2");
        auto right1 = new QtWidgets::DockWidget("right1");

        m1->addDockWidgetToSide(bottom1->asDockWidgetController(), KDDockWidgets::Location_OnBottom, InitialVisibilityOption::StartHidden);
        m1->addDockWidgetToSide(left1->asDockWidgetController(), KDDockWidgets::Location_OnLeft);
        m1->addDockWidgetToSide(bottom2->asDockWidgetController(), KDDockWidgets::Location_OnBottom, InitialVisibilityOption::StartHidden);

        QVERIFY(m1->layout()->checkSanity());
        bottom1->open();
        bottom2->open();
        QVERIFY(m1->layout()->checkSanity());

        auto centralGroup = m1->dropArea()->centralGroup();
        auto centralItem = centralGroup->layoutItem();
        Core::Item *neighbor = centralItem->outermostNeighbor(KDDockWidgets::Location_OnRight, /*visibleOnly=*/false);
        QVERIFY(!neighbor);

        m1->addDockWidgetToSide(right1->asDockWidgetController(), KDDockWidgets::Location_OnRight);
    }

    {
        EnsureTopLevelsDeleted e;
        auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");

        auto dummy1 = new QtWidgets::DockWidget("dummy1");
        auto dummy2 = new QtWidgets::DockWidget("dummy2");

        auto left1 = new QtWidgets::DockWidget("left1");
        auto bottom1 = new QtWidgets::DockWidget("bottom1");
        auto left2 = new QtWidgets::DockWidget("left2");

        m1->addDockWidgetToSide(dummy1->asDockWidgetController(), KDDockWidgets::Location_OnLeft, InitialVisibilityOption::StartHidden);
        m1->addDockWidgetToSide(dummy2->asDockWidgetController(), KDDockWidgets::Location_OnRight, InitialVisibilityOption::StartHidden);
        m1->addDockWidgetToSide(left1->asDockWidgetController(), KDDockWidgets::Location_OnLeft, InitialVisibilityOption::StartHidden);
        m1->addDockWidgetToSide(bottom1->asDockWidgetController(), KDDockWidgets::Location_OnBottom, InitialVisibilityOption::StartHidden);
        m1->addDockWidgetToSide(left2->asDockWidgetController(), KDDockWidgets::Location_OnLeft, InitialVisibilityOption::StartVisible);
    }
}

void TestQtWidgets::userHiddenButton()
{
    EnsureTopLevelsDeleted e;
    auto d1 = new QtWidgets::DockWidget("d1");
    d1->show();
    auto tb = d1->actualTitleBar();
    auto tbWidget = QtCommon::View_qt::asQWidget(tb->view());
    auto buttons = tbWidget->findChildren<QAbstractButton *>();
    QCOMPARE(buttons.size(), 5);

    const auto numVisible = std::count_if(buttons.cbegin(), buttons.cend(), [](auto button) {
        return button->isVisible();
    });

    QCOMPARE(numVisible, 2);

    // Hide float and close:
    tb->setUserHiddenButtons(TitleBarButtonTypes(TitleBarButtonType::Close) | TitleBarButtonType::Float);

    // 0 visible buttons now:
    QCOMPARE(std::count_if(buttons.cbegin(), buttons.cend(), [](auto button) {
                 return button->isVisible();
             }),
             0);
}

void TestQtWidgets::tst_tabAsCentralWidget()
{
    EnsureTopLevelsDeleted e;
    auto m = createMainWindow(QSize(600, 600), MainWindowOption_HasCentralWidget);
    auto tabWidget = new QTabWidget();
    auto tab1 = new QWidget();
    auto tab2 = new QWidget();
    tabWidget->addTab(tab1, "tab1");
    tabWidget->addTab(tab2, "tab2");

    m->setPersistentCentralView(QtWidgets::ViewWrapper::create(tabWidget));

    auto d1 = new QtWidgets::DockWidget("d1");
    auto d2 = new QtWidgets::DockWidget("d2");
    auto d3 = new QtWidgets::DockWidget("d3");
    m->addDockWidget(d1->dockWidget(), KDDockWidgets::Location_OnRight);
    m->addDockWidget(d2->dockWidget(), KDDockWidgets::Location_OnRight);
    m->addDockWidget(d3->dockWidget(), KDDockWidgets::Location_OnRight);
}

void TestQtWidgets::tst_nonClosable()
{
    EnsureTopLevelsDeleted e;
    auto d1 = NonClosableDockWidget("d1");
    d1.open();
    QVERIFY(d1.isOpen());
    d1.close();
    QVERIFY(d1.isOpen());
    d1.forceClose();
    QVERIFY(!d1.isOpen());
}

void TestQtWidgets::tst_crashDuringRestore()
{
    // Contrived case where if dockWidget->open() was called during restore it would crash

    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");
    auto d1 = new QtWidgets::DockWidget("d1");
    auto d3 = new QtWidgets::DockWidget("d3");
    m1->addDockWidget(d1->asDockWidgetController(), Location_OnRight);
    d1->addDockWidgetAsTab(d3);

    connect(d3->toggleAction(), &QAction::toggled, this, [d3](bool) {
        d3->open();
    });

    LayoutSaver saver;
    const QByteArray saved = saver.serializeLayout();

    saver.restoreLayout(saved);
}

void TestQtWidgets::tst_toggleVsShowHidden()
{
    // Tests that the QAction doesn't fire when adding as hidden
    EnsureTopLevelsDeleted e;
    auto m1 = createMainWindow(QSize(1000, 1000), MainWindowOption_HasCentralGroup, "mw1");
    auto d1 = new QtWidgets::DockWidget("d1");
    QVERIFY(!d1->toggleAction()->isChecked());
    QVERIFY(!d1->isOpen());
    int count = 0;
    connect(d1->toggleAction(), &QAction::toggled, this, [&count](bool) {
        count++;
    });

    m1->addDockWidget(d1->asDockWidgetController(), Location_OnRight, nullptr, KDDockWidgets::InitialVisibilityOption::StartHidden);
    QVERIFY(!d1->toggleAction()->isChecked());
    QVERIFY(!d1->isOpen());

    QCOMPARE(count, 0);
}

void TestQtWidgets::tst_neighbourSqueezeStrategy()
{
    // Tests NeighbourSqueezeStrategy::ImmediateNeighboursFirst
    {
        EnsureTopLevelsDeleted e;
        InitialOption::s_defaultNeighbourSqueezeStrategy = NeighbourSqueezeStrategy::ImmediateNeighboursFirst;
        auto m1 = createMainWindow(QSize(1000, 500), {}, "mw1");

        auto d1 = new QtWidgets::DockWidget("d1");
        auto d2 = new QtWidgets::DockWidget("d2");
        auto d3 = new QtWidgets::DockWidget("d3");
        auto d4 = new QtWidgets::DockWidget("d4");

        m1->addDockWidget(d1->dockWidget(), KDDockWidgets::Location_OnRight);
        m1->addDockWidget(d2->dockWidget(), KDDockWidgets::Location_OnRight);
        m1->addDockWidget(d3->dockWidget(), KDDockWidgets::Location_OnBottom);
        InitialOption opt = QSize(100, 0);
        opt.visibility = InitialVisibilityOption::StartHidden;
        m1->addDockWidget(d4->dockWidget(), KDDockWidgets::Location_OnRight, d2->dockWidget(), opt);
        m1->layoutEqually();

        auto group1 = d1->dockWidget()->dptr()->group();
        const auto sz1 = group1->size();
        d4->open();
        QCOMPARE(sz1, group1->size());
    }
}

namespace {

class Stack464 : public QtWidgets::Stack
{
public:
    using QtWidgets::Stack::Stack;
    void init() override
    {
        m_stack->setHideDisabledButtons(TitleBarButtonType::Close);

        QtWidgets::Stack::init(); // important
    }
};

class ViewFactory464 : public QtWidgets::ViewFactory
{
public:
    Core::View *createStack(Core::Stack *controller, Core::View *parent) const override
    {
        return new Stack464(controller, QtCommon::View_qt::asQWidget(parent));
    }
};

}

void TestQtWidgets::tst_debugWidgetViewer()
{
    // Tests that DebugWidgetViewer builds and can be created destroyed
    // not worth doing fancy tests as it's a debugging aid
    DebugWidgetViewer viewer;
}

void TestQtWidgets::tst_addDockWidgetToContainingWindowNested()
{
    {
        EnsureTopLevelsDeleted e;

        auto m1 = createMainWindow(QSize(1000, 500), {}, "mw1");
        auto d1 = new QtWidgets::DockWidget("d1");
        auto d2 = new QtWidgets::DockWidget("d2");

        m1->addDockWidget(d1->dockWidget(), Location_OnRight);
        d1->dockWidget()->addDockWidgetToContainingWindow(d2->dockWidget(), KDDockWidgets::Location_OnLeft);

        QVERIFY(d1->dockWidget()->isInMainWindow());
        QVERIFY(d2->dockWidget()->isInMainWindow());
    }

    {
        // Tests using addDockWidgetToContainingWindow when dock is nested
        EnsureTopLevelsDeleted e;

        auto m1 = createMainWindow(QSize(1000, 500), {}, "mw1");
        auto nestedMainWindow = createMainWindow(QSize(1000, 500), {}, "mw2");

        auto d1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("Nested MainWindow Dock container"));
        auto nestedMainWindowQWidget = static_cast<QMainWindow *>(QtCommon::View_qt::asQWidget(nestedMainWindow->view()));
        d1->setWidget(nestedMainWindowQWidget);
        m1->addDockWidget(d1->asDockWidgetController(), Location_OnBottom);

        auto d2 = new QtWidgets::DockWidget("d2");
        nestedMainWindow->addDockWidget(d2->dockWidget(), Location_OnRight);

        auto d3 = new QtWidgets::DockWidget("d3");
        d2->dockWidget()->addDockWidgetToContainingWindow(d3->dockWidget(), Location_OnRight);

        QVERIFY(d1->dockWidget()->isInMainWindow());
        QVERIFY(d2->dockWidget()->isInMainWindow());
        QVERIFY(d3->dockWidget()->isInMainWindow());

        QCOMPARE(d1->dockWidget()->mainWindow(), m1.get());
        QCOMPARE(d2->dockWidget()->mainWindow(), nestedMainWindow.get());
        QCOMPARE(d3->dockWidget()->mainWindow(), nestedMainWindow.get());
    }
}

void TestQtWidgets::tst_tabBarIcons()
{
    {
        EnsureTopLevelsDeleted e;
        auto &config = Config::self();
        config.setFlags(config.flags()
                        | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible
                        | KDDockWidgets::Config::Flag_TabsHaveCloseButton
                        | KDDockWidgets::Config::Flag_CloseOnlyCurrentTab
                        | KDDockWidgets::Config::Flag_ShowButtonsOnTabBarIfTitleBarHidden);

        auto d1 = new QtWidgets::DockWidget("d1", KDDockWidgets::DockWidgetOption_NotClosable);
        auto d2 = new QtWidgets::DockWidget("d2", KDDockWidgets::DockWidgetOption_NotClosable);

        d1->addDockWidgetAsTab(d2);

        auto stack = d1->dockWidget()->dptr()->group()->stack();
        auto tabWidget = qobject_cast<QtWidgets::Stack *>(QtCommon::View_qt::asQWidget(stack->view()));
        QVERIFY(tabWidget);

        auto closeButton = tabWidget->button(TitleBarButtonType::Close);
        QVERIFY(closeButton);
        QVERIFY(closeButton->isVisible());
        QVERIFY(!closeButton->isEnabled());
    }

    {
        EnsureTopLevelsDeleted e;
        auto &config = Config::self();
        config.setFlags(config.flags()
                        | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible
                        | KDDockWidgets::Config::Flag_TabsHaveCloseButton
                        | KDDockWidgets::Config::Flag_CloseOnlyCurrentTab
                        | KDDockWidgets::Config::Flag_ShowButtonsOnTabBarIfTitleBarHidden);
        config.setViewFactory(new ViewFactory464());

        auto d1 = new QtWidgets::DockWidget("d1", KDDockWidgets::DockWidgetOption_NotClosable);
        auto d2 = new QtWidgets::DockWidget("d2", KDDockWidgets::DockWidgetOption_NotClosable);

        d1->addDockWidgetAsTab(d2);

        auto stack = d1->dockWidget()->dptr()->group()->stack();
        auto tabWidget = qobject_cast<QtWidgets::Stack *>(QtCommon::View_qt::asQWidget(stack->view()));
        QVERIFY(tabWidget);

        auto closeButton = tabWidget->button(TitleBarButtonType::Close);
        QVERIFY(closeButton);
        QVERIFY(!closeButton->isVisible());
        QVERIFY(!closeButton->isEnabled());
    }
}

void TestQtWidgets::tst_indicatorsNotShowing()
{
    // Tests bug #474, where wrong top level window is detected under cursor
    EnsureTopLevelsDeleted e;
    const Point globalPos = { 600, 300 };
    QCursor::setPos(globalPos);

    auto m1 = createMainWindow(QSize(1000, 500), {}, "mw1");
    auto m2 = createMainWindow(QSize(1000, 500), {}, "mw2");
    // m2->setAffinities({ "m2" });
    auto d2 = new QtWidgets::DockWidget("d2");
    // d2->setAffinities({ "m2" });
    d2->show();
    QTest::qWait(200);
    m2->view()->move(0, 0);
    m1->view()->move(500, 250);
    m1->view()->raiseAndActivate();
    m1->view()->setWindowTitle("m1");

    auto mainWindow1 = dynamic_cast<QtWidgets::MainWindow *>(m1->view());
    QVERIFY(QTest::qWaitForWindowActive(mainWindow1));

    auto dc = DragController::instance();

    dc->programmaticStartDrag(d2->dockWidget()->titleBar(), globalPos, { 10, 5 });

    auto tlw = dc->qtTopLevelUnderCursor();
    QVERIFY(tlw);

    /// Fixed on windows and Linux. Other platforms don't have the required API
    /// to get correct z-order of windows under cursor
    const bool supportsZOrderedWindowSearch = KDDockWidgets::isWindows() || (KDDockWidgets::isXCB() && KDDockWidgets::linksToXLib());
    if (!supportsZOrderedWindowSearch)
        QEXPECT_FAIL("", "Not supported on this platform", Continue);

    QCOMPARE(tlw->controller(), m1.get());

    dc->programmaticStopDrag();
}

void TestQtWidgets::tst_restoreInvalidPercentages()
{
    EnsureTopLevelsDeleted e;

    auto m = createMainWindow(Size(500, 500), MainWindowOption_HasCentralWidget, "mainWindowId1");

    createDockWidget("_kddw_internal_dummy", Platform::instance()->tests_createView({ true }));
    createDockWidget("_kddw_internal_dummy2", Platform::instance()->tests_createView({ true }));
    for (int i = 0; i <= 8; ++i) {
        new QtWidgets::DockWidget(QStringLiteral("dockwidget_tests_%1").arg(i));
    }

    bool ok = false;
    LayoutSaver restorer;
    const QByteArray data = Platform::instance()->readFile(":/layouts/invalidPercentages.json", /*by-ref*/ ok);
    QVERIFY(ok);
    QVERIFY(restorer.restoreLayout(data));

    LayoutSaver saver;
    QVERIFY(!saver.serializeLayout().isEmpty());
}


void TestQtWidgets::tst_controllerToView()
{
    EnsureTopLevelsDeleted e;
    auto dw = createDockWidget("dw1", Platform::instance()->tests_createView({ true }));
    auto dwView = KDDockWidgets::controllerToView<QtWidgets::DockWidget>(dw);

    std::vector<Core::DockWidget *> dws = { dw };
    auto dwViews = KDDockWidgets::controllersToViews<QtWidgets::DockWidget>(dws);

    QCOMPARE(dw->view(), dwView);
    QCOMPARE(dws[0]->view(), dwView);
}

int main(int argc, char *argv[])
{
#ifdef KDDW_HAS_SPDLOG
    FatalLogger::create();
#endif

    // Might be disabled by env var
    const auto frontends = Platform::frontendTypes();
    if (std::find(frontends.cbegin(), frontends.cend(), FrontendType::QtWidgets)
        == frontends.cend())
        return 0;

    Platform::tests_initPlatform(argc, argv, FrontendType::QtWidgets);

    TestQtWidgets test;
    const int exitCode = QTest::qExec(&test, argc, argv);
    Platform::tests_deinitPlatform();

    return exitCode;
}

#include "tst_qtwidgets.moc"
