Commit df9e36ee authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

[wayland] Destroy XdgToplevelClient and XdgPopupClient on unmap

There are several ways to handle unmapping of a wl_surface. The first
one is to destroy the associated AbstractClient instance. The second one
is to transition the AbstractClient in a special state.

The problem with the second approach is that it makes animations such as
fade out more difficult to handle since effects in kwin are geared more
towards the first approach (destroying AbstractClient).
parent 31ea780d
......@@ -519,6 +519,8 @@ set(kwin_SRCS
wayland_cursor_theme.cpp
wayland_server.cpp
waylandclient.cpp
waylandshellintegration.cpp
waylandxdgshellintegration.cpp
window_property_notify_x11_filter.cpp
workspace.cpp
x11client.cpp
......
......@@ -388,16 +388,8 @@ void DebugConsoleTest::testWaylandClient()
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
shellSurface.reset();
Test::flushWaylandConnection();
qDebug() << rowsRemovedSpy.count();
QEXPECT_FAIL("wlShell", "Deleting a ShellSurface does not result in the server removing the XdgShellClient", Continue);
QVERIFY(rowsRemovedSpy.wait(500));
surface.reset();
if (rowsRemovedSpy.isEmpty()) {
QVERIFY(rowsRemovedSpy.wait());
}
QVERIFY(rowsRemovedSpy.wait());
QCOMPARE(rowsRemovedSpy.count(), 1);
QCOMPARE(rowsRemovedSpy.first().first().value<QModelIndex>(), waylandTopLevelIndex);
QCOMPARE(rowsRemovedSpy.first().at(1).value<int>(), 0);
......
......@@ -3,7 +3,6 @@ if (XCB_ICCCM_FOUND)
integrationTest(NAME testSlidingPopups SRCS slidingpopups_test.cpp LIBS XCB::ICCCM)
integrationTest(NAME testShadeWobblyWindows SRCS wobbly_shade_test.cpp LIBS XCB::ICCCM)
endif()
integrationTest(NAME testFade SRCS fade_test.cpp)
integrationTest(WAYLAND_ONLY NAME testEffectWindowGeometry SRCS windowgeometry_test.cpp)
integrationTest(NAME testScriptedEffects SRCS scripted_effects_test.cpp)
integrationTest(WAYLAND_ONLY NAME testToplevelOpenCloseAnimation SRCS toplevel_open_close_animation_test.cpp)
......
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "kwin_wayland_test.h"
#include "abstract_client.h"
#include "composite.h"
#include "effects.h"
#include "effectloader.h"
#include "cursor.h"
#include "platform.h"
#include "wayland_server.h"
#include "workspace.h"
#include "effect_builtins.h"
#include <KConfigGroup>
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/surface.h>
using namespace KWin;
using namespace KWayland::Client;
static const QString s_socketName = QStringLiteral("wayland_test_effects_translucency-0");
class FadeTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void init();
void cleanup();
void testWindowCloseAfterWindowHidden_data();
void testWindowCloseAfterWindowHidden();
private:
Effect *m_fadeEffect = nullptr;
};
void FadeTest::initTestCase()
{
qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8());
qRegisterMetaType<KWin::AbstractClient*>();
qRegisterMetaType<KWin::Effect*>();
QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated);
QVERIFY(workspaceCreatedSpy.isValid());
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));
// disable all effects - we don't want to have it interact with the rendering
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
KConfigGroup plugins(config, QStringLiteral("Plugins"));
ScriptedEffectLoader loader;
const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects();
for (QString name : builtinNames) {
plugins.writeEntry(name + QStringLiteral("Enabled"), false);
}
config->sync();
kwinApp()->setConfig(config);
qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1");
kwinApp()->start();
QVERIFY(workspaceCreatedSpy.wait());
QVERIFY(KWin::Compositor::self());
}
void FadeTest::init()
{
QVERIFY(Test::setupWaylandConnection());
// load the translucency effect
EffectsHandlerImpl *e = static_cast<EffectsHandlerImpl*>(effects);
// find the effectsloader
auto effectloader = e->findChild<AbstractEffectLoader*>();
QVERIFY(effectloader);
QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded);
QVERIFY(effectLoadedSpy.isValid());
QVERIFY(!e->isEffectLoaded(QStringLiteral("kwin4_effect_fade")));
QVERIFY(e->loadEffect(QStringLiteral("kwin4_effect_fade")));
QVERIFY(e->isEffectLoaded(QStringLiteral("kwin4_effect_fade")));
QCOMPARE(effectLoadedSpy.count(), 1);
m_fadeEffect = effectLoadedSpy.first().first().value<Effect*>();
QVERIFY(m_fadeEffect);
}
void FadeTest::cleanup()
{
Test::destroyWaylandConnection();
EffectsHandlerImpl *e = static_cast<EffectsHandlerImpl*>(effects);
if (e->isEffectLoaded(QStringLiteral("kwin4_effect_fade"))) {
e->unloadEffect(QStringLiteral("kwin4_effect_fade"));
}
QVERIFY(!e->isEffectLoaded(QStringLiteral("kwin4_effect_fade")));
m_fadeEffect = nullptr;
}
void FadeTest::testWindowCloseAfterWindowHidden_data()
{
QTest::addColumn<Test::XdgShellSurfaceType>("type");
QTest::newRow("xdgWmBase") << Test::XdgShellSurfaceType::XdgShellStable;
}
void FadeTest::testWindowCloseAfterWindowHidden()
{
// this test simulates the showing/hiding/closing of a Wayland window
// especially the situation that a window got unmapped and destroyed way later
QVERIFY(!m_fadeEffect->isActive());
QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded);
QVERIFY(windowAddedSpy.isValid());
QSignalSpy windowHiddenSpy(effects, &EffectsHandler::windowHidden);
QVERIFY(windowHiddenSpy.isValid());
QSignalSpy windowShownSpy(effects, &EffectsHandler::windowShown);
QVERIFY(windowShownSpy.isValid());
QSignalSpy windowClosedSpy(effects, &EffectsHandler::windowClosed);
QVERIFY(windowClosedSpy.isValid());
QScopedPointer<Surface> surface(Test::createSurface());
QFETCH(Test::XdgShellSurfaceType, type);
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellSurface(type, surface.data()));
auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(c);
QTRY_COMPARE(windowAddedSpy.count(), 1);
QTRY_COMPARE(m_fadeEffect->isActive(), true);
QTest::qWait(500);
QTRY_COMPARE(m_fadeEffect->isActive(), false);
// now unmap the surface
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
QVERIFY(windowHiddenSpy.wait());
QCOMPARE(m_fadeEffect->isActive(), true);
QTest::qWait(500);
QTRY_COMPARE(m_fadeEffect->isActive(), false);
// and map again
Test::render(surface.data(), QSize(100, 50), Qt::red);
QVERIFY(windowShownSpy.wait());
QTRY_COMPARE(m_fadeEffect->isActive(), true);
QTest::qWait(500);
QTRY_COMPARE(m_fadeEffect->isActive(), false);
// and unmap once more
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
QVERIFY(windowHiddenSpy.wait());
QCOMPARE(m_fadeEffect->isActive(), true);
QTest::qWait(500);
QTRY_COMPARE(m_fadeEffect->isActive(), false);
// and now destroy
shellSurface.reset();
surface.reset();
QVERIFY(windowClosedSpy.wait());
QCOMPARE(m_fadeEffect->isActive(), false);
}
WAYLANDTEST_MAIN(FadeTest)
#include "fade_test.moc"
......@@ -240,7 +240,7 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
// This test verifies that the idle inhibitor object is not honored by KWin
// when the associated client is unmapped.
// Get reference to the idle interface.
// Get reference to the idle interface.
auto idle = waylandServer()->display()->findChild<IdleInterface *>();
QVERIFY(idle);
QVERIFY(!idle->isInhibited());
......@@ -252,36 +252,56 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
QVERIFY(!shellSurface.isNull());
QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested);
QVERIFY(configureRequestedSpy.isValid());
// Create the inhibitor object.
QScopedPointer<IdleInhibitor> inhibitor(Test::waylandIdleInhibitManager()->createInhibitor(surface.data()));
QVERIFY(inhibitor->isValid());
// Render the client.
auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(c);
// Map the client.
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
Test::render(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(clientAddedSpy.isEmpty());
QVERIFY(clientAddedSpy.wait());
QCOMPARE(clientAddedSpy.count(), 1);
AbstractClient *client = clientAddedSpy.last().first().value<AbstractClient *>();
QVERIFY(client);
QCOMPARE(client->readyForPainting(), true);
// The compositor will respond with a configure event when the surface becomes active.
QVERIFY(configureRequestedSpy.wait());
QCOMPARE(configureRequestedSpy.count(), 1);
// This should inhibit our server object.
QVERIFY(idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 1);
// Unmap the client.
QSignalSpy hiddenSpy(c, &AbstractClient::windowHidden);
QVERIFY(hiddenSpy.isValid());
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
QVERIFY(hiddenSpy.wait());
QVERIFY(Test::waitForWindowDestroyed(client));
// The surface is no longer visible, so the compositor don't have to honor the
// The surface is no longer visible, so the compositor doesn't have to honor the
// idle inhibitor object.
QVERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 2);
// Tell the compositor that we want to map the surface.
surface->commit(Surface::CommitFlag::None);
// The compositor will respond with a configure event.
QVERIFY(configureRequestedSpy.wait());
QCOMPARE(configureRequestedSpy.count(), 2);
// Map the client.
QSignalSpy windowShownSpy(c, &AbstractClient::windowShown);
QVERIFY(windowShownSpy.isValid());
Test::render(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(windowShownSpy.wait());
QVERIFY(clientAddedSpy.wait());
QCOMPARE(clientAddedSpy.count(), 2);
client = clientAddedSpy.last().first().value<AbstractClient *>();
QVERIFY(client);
QCOMPARE(client->readyForPainting(), true);
// The test client became visible again, so the compositor has to honor the idle
// inhibitor object back again.
......@@ -290,7 +310,7 @@ void TestIdleInhibition::testDontInhibitWhenUnmapped()
// Destroy the test client.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(c));
QVERIFY(Test::waitForWindowDestroyed(client));
QTRY_VERIFY(!idle->isInhibited());
QCOMPARE(inhibitedSpy.count(), 4);
}
......
......@@ -80,8 +80,6 @@ private Q_SLOTS:
void testResizeForVirtualKeyboardWithFullScreen();
void testDestroyMoveClient();
void testDestroyResizeClient();
void testUnmapMoveClient();
void testUnmapResizeClient();
private:
KWayland::Client::ConnectionThread *m_connection = nullptr;
......@@ -1111,98 +1109,6 @@ void MoveResizeWindowTest::testDestroyResizeClient()
QCOMPARE(workspace()->moveResizeClient(), nullptr);
}
void MoveResizeWindowTest::testUnmapMoveClient()
{
// This test verifies that active move operation gets cancelled when
// the associated client is unmapped.
// Create the test client.
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
QVERIFY(!shellSurface.isNull());
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(client);
// Start resizing the client.
QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
QVERIFY(clientStartMoveResizedSpy.isValid());
QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
QVERIFY(clientFinishUserMovedResizedSpy.isValid());
QCOMPARE(workspace()->moveResizeClient(), nullptr);
QCOMPARE(client->isMove(), false);
QCOMPARE(client->isResize(), false);
workspace()->slotWindowMove();
QCOMPARE(clientStartMoveResizedSpy.count(), 1);
QCOMPARE(workspace()->moveResizeClient(), client);
QCOMPARE(client->isMove(), true);
QCOMPARE(client->isResize(), false);
// Unmap the client while we're moving it.
QSignalSpy hiddenSpy(client, &AbstractClient::windowHidden);
QVERIFY(hiddenSpy.isValid());
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
QVERIFY(hiddenSpy.wait());
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
QCOMPARE(workspace()->moveResizeClient(), nullptr);
QCOMPARE(client->isMove(), false);
QCOMPARE(client->isResize(), false);
// Destroy the client.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(client));
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
}
void MoveResizeWindowTest::testUnmapResizeClient()
{
// This test verifies that active resize operation gets cancelled when
// the associated client is unmapped.
// Create the test client.
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
QVERIFY(!shellSurface.isNull());
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(client);
// Start resizing the client.
QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
QVERIFY(clientStartMoveResizedSpy.isValid());
QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
QVERIFY(clientFinishUserMovedResizedSpy.isValid());
QCOMPARE(workspace()->moveResizeClient(), nullptr);
QCOMPARE(client->isMove(), false);
QCOMPARE(client->isResize(), false);
workspace()->slotWindowResize();
QCOMPARE(clientStartMoveResizedSpy.count(), 1);
QCOMPARE(workspace()->moveResizeClient(), client);
QCOMPARE(client->isMove(), false);
QCOMPARE(client->isResize(), true);
// Unmap the client while we're resizing it.
QSignalSpy hiddenSpy(client, &AbstractClient::windowHidden);
QVERIFY(hiddenSpy.isValid());
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
QVERIFY(hiddenSpy.wait());
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
QCOMPARE(workspace()->moveResizeClient(), nullptr);
QCOMPARE(client->isMove(), false);
QCOMPARE(client->isResize(), false);
// Destroy the client.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(client));
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
}
}
WAYLANDTEST_MAIN(KWin::MoveResizeWindowTest)
......
......@@ -70,10 +70,8 @@ private Q_SLOTS:
void init();
void cleanup();
void testMapUnmapMap_data();
void testMapUnmapMap();
void testMapUnmap();
void testDesktopPresenceChanged();
void testTransientPositionAfterRemap();
void testWindowOutputs_data();
void testWindowOutputs();
void testMinimizeActiveWindow_data();
......@@ -158,109 +156,68 @@ void TestXdgShellClient::cleanup()
Test::destroyWaylandConnection();
}
void TestXdgShellClient::testMapUnmapMap_data()
void TestXdgShellClient::testMapUnmap()
{
QTest::addColumn<Test::XdgShellSurfaceType>("type");
// This test verifies that the compositor destroys XdgToplevelClient when the
// associated xdg_toplevel surface is unmapped.
QTest::newRow("xdgWmBase") << Test::XdgShellSurfaceType::XdgShellStable;
}
// Create a wl_surface and an xdg_toplevel, but don't commit them yet!
QScopedPointer<Surface> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> shellSurface(
Test::createXdgShellStableSurface(surface.data(), nullptr, Test::CreationSetup::CreateOnly));
void TestXdgShellClient::testMapUnmapMap()
{
// this test verifies that mapping a previously mapped window works correctly
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown);
QVERIFY(effectsWindowShownSpy.isValid());
QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden);
QVERIFY(effectsWindowHiddenSpy.isValid());
QScopedPointer<Surface> surface(Test::createSurface());
QFETCH(Test::XdgShellSurfaceType, type);
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellSurface(type, surface.data()));
QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested);
QVERIFY(configureRequestedSpy.isValid());
// now let's render
Test::render(surface.data(), QSize(100, 50), Qt::blue);
// Tell the compositor that we want to map the surface.
surface->commit(Surface::CommitFlag::None);
QVERIFY(clientAddedSpy.isEmpty());
// The compositor will respond with a configure event.
QVERIFY(configureRequestedSpy.wait());
QCOMPARE(configureRequestedSpy.count(), 1);
// Now we can attach a buffer with actual data to the surface.
Test::render(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(clientAddedSpy.wait());
auto client = clientAddedSpy.first().first().value<AbstractClient *>();
QCOMPARE(clientAddedSpy.count(), 1);
AbstractClient *client = clientAddedSpy.last().first().value<AbstractClient *>();
QVERIFY(client);
QVERIFY(client->isShown(true));
QCOMPARE(client->isHiddenInternal(), false);
QCOMPARE(client->readyForPainting(), true);
QCOMPARE(client->depth(), 32);
QVERIFY(client->hasAlpha());
QCOMPARE(client->icon().name(), QStringLiteral("wayland"));
QCOMPARE(workspace()->activeClient(), client);
QVERIFY(effectsWindowShownSpy.isEmpty());
QVERIFY(client->isMaximizable());
QVERIFY(client->isMovable());
QVERIFY(client->isMovableAcrossScreens());
QVERIFY(client->isResizable());
QVERIFY(client->property("maximizable").toBool());
QVERIFY(client->property("moveable").toBool());
QVERIFY(client->property("moveableAcrossScreens").toBool());
QVERIFY(client->property("resizeable").toBool());
QCOMPARE(client->isInternal(), false);
QVERIFY(client->effectWindow());
QVERIFY(!client->effectWindow()->internalWindow());
QCOMPARE(client->internalId().isNull(), false);
const auto uuid = client->internalId();
QUuid deletedUuid;
QCOMPARE(deletedUuid.isNull(), true);
connect(client, &AbstractClient::windowClosed, this, [&deletedUuid] (Toplevel *, Deleted *d) { deletedUuid = d->internalId(); });
// now unmap
QSignalSpy hiddenSpy(client, &AbstractClient::windowHidden);
QVERIFY(hiddenSpy.isValid());
QSignalSpy windowClosedSpy(client, &AbstractClient::windowClosed);
QVERIFY(windowClosedSpy.isValid());
// When the client becomes active, the compositor will send another configure event.
QVERIFY(configureRequestedSpy.wait());
QCOMPARE(configureRequestedSpy.count(), 2);
// Unmap the xdg_toplevel surface by committing a null buffer.
surface->attachBuffer(Buffer::Ptr());
surface->commit(Surface::CommitFlag::None);
QVERIFY(hiddenSpy.wait());
QCOMPARE(client->readyForPainting(), true);
QCOMPARE(client->isHiddenInternal(), true);
QVERIFY(windowClosedSpy.isEmpty());
QVERIFY(!workspace()->activeClient());
QCOMPARE(effectsWindowHiddenSpy.count(), 1);
QCOMPARE(effectsWindowHiddenSpy.first().first().value<EffectWindow*>(), client->effectWindow());
QVERIFY(Test::waitForWindowDestroyed(client));
QSignalSpy windowShownSpy(client, &AbstractClient::windowShown);
QVERIFY(windowShownSpy.isValid());
Test::render(surface.data(), QSize(100, 50), Qt::blue, QImage::Format_RGB32);
QCOMPARE(clientAddedSpy.count(), 1);
QVERIFY(windowShownSpy.wait());
QCOMPARE(windowShownSpy.count(), 1);
QCOMPARE(clientAddedSpy.count(), 1);
QCOMPARE(client->readyForPainting(), true);
QCOMPARE(client->isHiddenInternal(), false);
QCOMPARE(client->depth(), 24);
QVERIFY(!client->hasAlpha());
QCOMPARE(workspace()->activeClient(), client);
QCOMPARE(effectsWindowShownSpy.count(), 1);
QCOMPARE(effectsWindowShownSpy.first().first().value<EffectWindow*>(), client->effectWindow());
// let's unmap again
surface->attachBuffer(Buffer::Ptr());
// Tell the compositor that we want to re-map the xdg_toplevel surface.
surface->commit(Surface::CommitFlag::None);
QVERIFY(hiddenSpy.wait());
QCOMPARE(hiddenSpy.count(), 2);
// The compositor will respond with a configure event.
QVERIFY(configureRequestedSpy.wait());
QCOMPARE(configureRequestedSpy.count(), 3);
// Now we can attach a buffer with actual data to the surface.
Test::render(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(clientAddedSpy.wait());
QCOMPARE(clientAddedSpy.count(), 2);
client = clientAddedSpy.last().first().value<AbstractClient *>();
QVERIFY(client);
QCOMPARE(client->readyForPainting(), true);
QCOMPARE(client->isHiddenInternal(), true);
QCOMPARE(client->internalId(), uuid);
QVERIFY(windowClosedSpy.isEmpty());
QCOMPARE(effectsWindowHiddenSpy.count(), 2);
QCOMPARE(effectsWindowHiddenSpy.last().first().value<EffectWindow*>(), client->effectWindow());
// The compositor will respond with a configure event.
QVERIFY(configureRequestedSpy.wait());
QCOMPARE(configureRequestedSpy.count(), 4);
// Destroy the test client.
shellSurface.reset();
surface.reset();
QVERIFY(windowClosedSpy.wait());
QCOMPARE(windowClosedSpy.count(), 1);
QCOMPARE(effectsWindowHiddenSpy.count(), 2);
QCOMPARE(deletedUuid.isNull(), false);
QCOMPARE(deletedUuid, uuid);
QVERIFY(Test::waitForWindowDestroyed(client));