/*
 * Copyright © 2012 Intel Corporation
 * Copyright © 2013 Collabora, Ltd.
 * Copyright © 2018 Canonical Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "helpers.h"
#include "in_process_server.h"
#include "xdg_shell_stable.h"
#include "xdg_shell_v6.h"

#include <gmock/gmock.h>

#include <experimental/optional>

using namespace testing;

int const window_width = 400, window_height = 500;
int const popup_width = 60, popup_height = 40;

namespace
{
uint32_t anchor_stable_to_v6(xdg_positioner_anchor anchor)
{
    switch (anchor)
    {
        case XDG_POSITIONER_ANCHOR_NONE:
            return ZXDG_POSITIONER_V6_ANCHOR_NONE;
        case XDG_POSITIONER_ANCHOR_TOP:
            return ZXDG_POSITIONER_V6_ANCHOR_TOP;
        case XDG_POSITIONER_ANCHOR_BOTTOM:
            return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
        case XDG_POSITIONER_ANCHOR_LEFT:
            return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
        case XDG_POSITIONER_ANCHOR_RIGHT:
            return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
        case XDG_POSITIONER_ANCHOR_TOP_LEFT:
            return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
        case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT:
            return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_LEFT;
        case XDG_POSITIONER_ANCHOR_TOP_RIGHT:
            return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
        case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT:
            return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
        default:
            return ZXDG_POSITIONER_V6_ANCHOR_NONE;
    }
}

uint32_t gravity_stable_to_v6(xdg_positioner_gravity gravity)
{
    switch (gravity)
    {
        case XDG_POSITIONER_GRAVITY_NONE:
            return ZXDG_POSITIONER_V6_GRAVITY_NONE;
        case XDG_POSITIONER_GRAVITY_TOP:
            return ZXDG_POSITIONER_V6_GRAVITY_TOP;
        case XDG_POSITIONER_GRAVITY_BOTTOM:
            return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
        case XDG_POSITIONER_GRAVITY_LEFT:
            return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
        case XDG_POSITIONER_GRAVITY_RIGHT:
            return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
        case XDG_POSITIONER_GRAVITY_TOP_LEFT:
            return ZXDG_POSITIONER_V6_GRAVITY_TOP | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
        case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT:
            return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_LEFT;
        case XDG_POSITIONER_GRAVITY_TOP_RIGHT:
            return ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
        case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT:
            return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
        default:
            return ZXDG_POSITIONER_V6_GRAVITY_NONE;
    }
}

uint32_t constraint_adjustment_stable_to_v6(xdg_positioner_constraint_adjustment ca)
{
    return ca; // the two enums have the same values
}

struct PositionerParams
{
    PositionerParams()
        : popup_size{popup_width, popup_height},
          anchor_rect{{0, 0}, {window_width, window_height}}
    {
    }

    auto with_size(int x, int y) -> PositionerParams& { popup_size = {x, y}; return *this; }
    auto with_anchor_rect(int x, int y, int w, int h) -> PositionerParams& { anchor_rect = {{x, y}, {w, h}}; return *this; }
    auto with_anchor(xdg_positioner_anchor value) -> PositionerParams& { anchor_stable = {value}; return *this; }
    auto with_gravity(xdg_positioner_gravity value) -> PositionerParams& { gravity_stable = {value}; return *this; }
    auto with_constraint_adjustment(xdg_positioner_constraint_adjustment value) -> PositionerParams& { constraint_adjustment_stable = {value}; return *this; }
    auto with_offset(int x, int y) -> PositionerParams& { offset = {{x, y}}; return *this; }

    std::pair<int, int> popup_size; // will default to XdgPopupStableTestBase::popup_(width|height) if nullopt
    std::pair<std::pair<int, int>, std::pair<int, int>> anchor_rect; // will default to the full window rect
    std::experimental::optional<xdg_positioner_anchor> anchor_stable;
    std::experimental::optional<xdg_positioner_gravity> gravity_stable;
    std::experimental::optional<xdg_positioner_constraint_adjustment> constraint_adjustment_stable;
    std::experimental::optional<std::pair<int, int>> offset;
};

struct PositionerTestParams
{
    PositionerTestParams(std::string name, int expected_x, int expected_y, PositionerParams const& positioner)
        : name{name},
          expected_positon{expected_x, expected_y},
          positioner{positioner}
    {
    }

    std::string name;
    std::pair<int, int> expected_positon;
    PositionerParams positioner;
};

std::ostream& operator<<(std::ostream& out, PositionerTestParams const& param)
{
    return out << param.name;
}

class XdgPopupManagerBase
{
public:
    struct State
    {
        int x;
        int y;
        int width;
        int height;
    };

    static int const window_x = 500, window_y = 500;

    XdgPopupManagerBase(wlcs::InProcessServer* const in_process_server)
        : the_server{in_process_server->the_server()},
          client{the_server},
          surface{client}
    {
        surface.add_frame_callback([this](auto) { surface_rendered = true; });
    }

    virtual ~XdgPopupManagerBase() = default;

    void wait_for_frame_to_render()
    {
        surface.attach_buffer(window_width, window_height);
        surface_rendered = false;
        wl_surface_commit(surface);
        client.dispatch_until([this]() { return surface_rendered; });
        the_server.move_surface_to(surface, window_x, window_y);
    }

    void map_popup(PositionerParams const& params)
    {
        popup_surface.emplace(client);
        setup_popup(params);
        wl_surface_commit(popup_surface.value());
        dispatch_until_popup_configure();
        popup_surface.value().attach_buffer(popup_width, popup_height);
        bool surface_rendered{false};
        popup_surface.value().add_frame_callback([&surface_rendered](auto) { surface_rendered = true; });
        wl_surface_commit(popup_surface.value());
        client.dispatch_until([&surface_rendered]() { return surface_rendered; });
    }

    void unmap_popup()
    {
        clear_popup();
        popup_surface = std::experimental::nullopt;
    }

    virtual auto popup_position() const -> std::experimental::optional<std::pair<int, int>> = 0;

    virtual void dispatch_until_popup_configure() = 0;

    wlcs::Server& the_server;

    wlcs::Client client;
    wlcs::Surface surface;
    std::experimental::optional<wlcs::Surface> popup_surface;

    std::experimental::optional<State> state;

protected:
    virtual void setup_popup(PositionerParams const& params) = 0;
    virtual void clear_popup() = 0;

    bool surface_rendered{true};
};

class XdgPopupStableManager : public XdgPopupManagerBase
{
public:
    XdgPopupStableManager(wlcs::InProcessServer* const in_process_server)
        : XdgPopupManagerBase{in_process_server},
          xdg_shell_surface{client, surface},
          toplevel{xdg_shell_surface}
    {
        wait_for_frame_to_render();
    }

    void dispatch_until_popup_configure() override
    {
        client.dispatch_until(
            [prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
            {
                return current_count > prev_count;
            });
    }

    void setup_popup(PositionerParams const& param) override
    {
        wlcs::XdgPositionerStable positioner{client};

        // size must always be set
        xdg_positioner_set_size(positioner, param.popup_size.first, param.popup_size.second);

        // anchor rect must always be set
        xdg_positioner_set_anchor_rect(
            positioner,
            param.anchor_rect.first.first,
            param.anchor_rect.first.second,
            param.anchor_rect.second.first,
            param.anchor_rect.second.second);

        if (param.anchor_stable)
            xdg_positioner_set_anchor(positioner,  param.anchor_stable.value());

        if (param.gravity_stable)
            xdg_positioner_set_gravity(positioner, param.gravity_stable.value());

        if (param.constraint_adjustment_stable)
            xdg_positioner_set_constraint_adjustment(positioner, param.constraint_adjustment_stable.value());

        if (param.offset)
            xdg_positioner_set_offset(positioner, param.offset.value().first, param.offset.value().second);


        popup_xdg_surface.emplace(client, popup_surface.value());
        popup.emplace(popup_xdg_surface.value(), xdg_shell_surface, positioner);

        popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
            {
                xdg_surface_ack_configure(popup_xdg_surface.value(), serial);
                popup_surface_configure_count++;
            });

        popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
            {
                state = State{x, y, width, height};
            });
    }

    void clear_popup() override
    {
        popup = std::experimental::nullopt;
        popup_xdg_surface = std::experimental::nullopt;
    }

    auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
    {
        return std::make_pair(state.value().x, state.value().y);
    }

    wlcs::XdgSurfaceStable xdg_shell_surface;
    wlcs::XdgToplevelStable toplevel;

    std::experimental::optional<wlcs::XdgSurfaceStable> popup_xdg_surface;
    std::experimental::optional<wlcs::XdgPopupStable> popup;

    int popup_surface_configure_count{0};
};

class XdgPopupV6Manager : public XdgPopupManagerBase
{
public:
    XdgPopupV6Manager(wlcs::InProcessServer* const in_process_server)
        : XdgPopupManagerBase{in_process_server},
          xdg_shell_surface{client, surface},
          toplevel{xdg_shell_surface}
    {
        wait_for_frame_to_render();
    }

    void dispatch_until_popup_configure() override
    {
        client.dispatch_until(
            [prev_count = popup_surface_configure_count, &current_count = popup_surface_configure_count]()
            {
                return current_count > prev_count;
            });
    }

    void setup_popup(PositionerParams const& param) override
    {
        wlcs::XdgPositionerV6 positioner{client};

        // size must always be set
        zxdg_positioner_v6_set_size(positioner, param.popup_size.first, param.popup_size.second);

        // anchor rect must always be set
        zxdg_positioner_v6_set_anchor_rect(
            positioner,
            param.anchor_rect.first.first,
            param.anchor_rect.first.second,
            param.anchor_rect.second.first,
            param.anchor_rect.second.second);

        if (param.anchor_stable)
        {
            uint32_t v6_anchor = anchor_stable_to_v6(param.anchor_stable.value());
            zxdg_positioner_v6_set_anchor(positioner, v6_anchor);
        }

        if (param.gravity_stable)
        {
            uint32_t v6_gravity = gravity_stable_to_v6(param.gravity_stable.value());
            zxdg_positioner_v6_set_gravity(positioner, v6_gravity);
        }

        if (param.constraint_adjustment_stable)
        {
            uint32_t v6_constraint_adjustment =
                constraint_adjustment_stable_to_v6(param.constraint_adjustment_stable.value());
            zxdg_positioner_v6_set_constraint_adjustment(positioner, v6_constraint_adjustment);
        }

        if (param.offset)
        {
            zxdg_positioner_v6_set_offset(positioner, param.offset.value().first, param.offset.value().second);
        }


        popup_xdg_surface.emplace(client, popup_surface.value());
        popup.emplace(popup_xdg_surface.value(), xdg_shell_surface, positioner);

        popup_xdg_surface.value().add_configure_notification([&](uint32_t serial)
            {
                zxdg_surface_v6_ack_configure(popup_xdg_surface.value(), serial);
                popup_surface_configure_count++;
            });

        popup.value().add_configure_notification([this](int32_t x, int32_t y, int32_t width, int32_t height)
            {
                state = State{x, y, width, height};
            });
    }

    void clear_popup() override
    {
        popup = std::experimental::nullopt;
        popup_xdg_surface = std::experimental::nullopt;
    }

    auto popup_position() const -> std::experimental::optional<std::pair<int, int>> override
    {
        return std::make_pair(state.value().x, state.value().y);
    }

    wlcs::XdgSurfaceV6 xdg_shell_surface;
    wlcs::XdgToplevelV6 toplevel;

    std::experimental::optional<wlcs::XdgSurfaceV6> popup_xdg_surface;
    std::experimental::optional<wlcs::XdgPopupV6> popup;

    int popup_surface_configure_count{0};
};

}

class XdgPopupPositionerTest:
    public wlcs::StartedInProcessServer,
    public testing::WithParamInterface<PositionerTestParams>
{
};

TEST_P(XdgPopupPositionerTest, xdg_shell_stable_popup_placed_correctly)
{
    auto manager = std::make_unique<XdgPopupStableManager>(this);
    auto const& param = GetParam();

    manager->map_popup(param.positioner);

    ASSERT_THAT(
        manager->popup_position(),
        Ne(std::experimental::nullopt)) << "popup configure event not sent";

    ASSERT_THAT(
        manager->popup_position(),
        Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
}

TEST_P(XdgPopupPositionerTest, xdg_shell_unstable_v6_popup_placed_correctly)
{
    auto manager = std::make_unique<XdgPopupV6Manager>(this);
    auto const& param = GetParam();

    manager->map_popup(param.positioner);

    ASSERT_THAT(
        manager->popup_position(),
        Ne(std::experimental::nullopt)) << "popup configure event not sent";

    ASSERT_THAT(
        manager->popup_position(),
        Eq(std::experimental::make_optional(param.expected_positon))) << "popup placed in incorrect position";
}

INSTANTIATE_TEST_CASE_P(
    Default,
    XdgPopupPositionerTest,
    testing::Values(
        PositionerTestParams{"default values", (window_width - popup_width) / 2, (window_height - popup_height) / 2, PositionerParams()}
    ));

INSTANTIATE_TEST_CASE_P(
    Anchor,
    XdgPopupPositionerTest,
    testing::Values(
        PositionerTestParams{"anchor left", -popup_width / 2, (window_height - popup_height) / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_LEFT)},

        PositionerTestParams{"anchor right", window_width - popup_width / 2, (window_height - popup_height) / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_RIGHT)},

        PositionerTestParams{"anchor top", (window_width - popup_width) / 2, -popup_height / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP)},

        PositionerTestParams{"anchor bottom", (window_width - popup_width) / 2, window_height - popup_height / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM)},

        PositionerTestParams{"anchor top left", -popup_width / 2, -popup_height / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)},

        PositionerTestParams{"anchor top right", window_width - popup_width / 2, -popup_height / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_TOP_RIGHT)},

        PositionerTestParams{"anchor bottom left", -popup_width / 2, window_height - popup_height / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_LEFT)},

        PositionerTestParams{"anchor bottom right", window_width - popup_width / 2, window_height - popup_height / 2,
            PositionerParams().with_anchor(XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)}
    ));

INSTANTIATE_TEST_CASE_P(
    Gravity,
    XdgPopupPositionerTest,
    testing::Values(
        PositionerTestParams{"gravity none", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_NONE)},

        PositionerTestParams{"gravity left", window_width / 2 - popup_width, (window_height - popup_height) / 2,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_LEFT)},

        PositionerTestParams{"gravity right", window_width / 2, (window_height - popup_height) / 2,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_RIGHT)},

        PositionerTestParams{"gravity top", (window_width - popup_width) / 2, window_height / 2 - popup_height,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP)},

        PositionerTestParams{"gravity bottom", (window_width - popup_width) / 2, window_height / 2,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM)},

        PositionerTestParams{"gravity top left", window_width / 2 - popup_width, window_height / 2 - popup_height,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_LEFT)},

        PositionerTestParams{"gravity top right", window_width / 2, window_height / 2 - popup_height,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_TOP_RIGHT)},

        PositionerTestParams{"gravity bottom left", window_width / 2 - popup_width, window_height / 2,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_LEFT)},

        PositionerTestParams{"gravity bottom right", window_width / 2, window_height / 2,
            PositionerParams().with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)}
    ));

INSTANTIATE_TEST_CASE_P(
    AnchorRect,
    XdgPopupPositionerTest,
    testing::Values(
        PositionerTestParams{"explicit defaultPositionerParams anchor rect", (window_width - popup_width) / 2, (window_height - popup_height) / 2,
            PositionerParams().with_anchor_rect(0, 0, window_width, window_height)},

        PositionerTestParams{"upper left anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
            PositionerParams().with_anchor_rect(0, 0, window_width - 40, window_height - 30)},

        PositionerTestParams{"upper right anchor rect", (window_width + 40 - popup_width) / 2, (window_height - 30 - popup_height) / 2,
            PositionerParams().with_anchor_rect(40, 0, window_width - 40, window_height - 30)},

        PositionerTestParams{"lower left anchor rect", (window_width - 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
            PositionerParams().with_anchor_rect(0, 30, window_width - 40, window_height - 30)},

        PositionerTestParams{"lower right anchor rect", (window_width + 40 - popup_width) / 2, (window_height + 30 - popup_height) / 2,
            PositionerParams().with_anchor_rect(40, 30, window_width - 40, window_height - 30)},

        PositionerTestParams{"offset anchor rect", (window_width - 40 - popup_width) / 2, (window_height - 80 - popup_height) / 2,
            PositionerParams().with_anchor_rect(20, 20, window_width - 80, window_height - 120)}
    ));

struct XdgPopupTestParam
{
    std::function<std::unique_ptr<XdgPopupManagerBase>(wlcs::InProcessServer* const)> build;
};

std::ostream& operator<<(std::ostream& out, XdgPopupTestParam const&) { return out; }

class XdgPopupTest:
    public wlcs::StartedInProcessServer,
    public testing::WithParamInterface<XdgPopupTestParam>
{
};

TEST_P(XdgPopupTest, pointer_focus_goes_to_popup)
{
    auto const& param = GetParam();
    auto manager = param.build(this);
    auto pointer = the_server().create_pointer();
    pointer.move_to(manager->window_x + 1, manager->window_y + 1);
    manager->client.roundtrip();

    EXPECT_THAT(manager->client.focused_window(), Eq((wl_surface*)manager->surface));

    auto positioner = PositionerParams{}
        .with_size(30, 30)
        .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
        .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
    manager->map_popup(positioner);
    manager->client.roundtrip();

    pointer.move_to(manager->window_x + 2, manager->window_y + 1);
    manager->client.roundtrip();

    EXPECT_THAT(manager->client.focused_window(), Eq((wl_surface*)manager->popup_surface.value()));
}

TEST_P(XdgPopupTest, popup_gives_up_pointer_focus_when_gone)
{
    auto const& param = GetParam();
    auto manager = param.build(this);
    auto pointer = the_server().create_pointer();

    auto positioner = PositionerParams{}
        .with_size(30, 30)
        .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
        .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
    manager->map_popup(positioner);
    manager->client.roundtrip();

    pointer.move_to(manager->window_x + 2, manager->window_y + 1);
    manager->client.roundtrip();

    EXPECT_THAT(manager->client.focused_window(), Eq((wl_surface*)manager->popup_surface.value()));

    manager->unmap_popup();
    manager->client.roundtrip();
    pointer.move_to(manager->window_x + 3, manager->window_y + 1);
    manager->client.roundtrip();

    EXPECT_THAT(manager->client.focused_window(), Eq((wl_surface*)manager->surface));
}

TEST_F(XdgPopupTest, zero_size_anchor_rect_stable)
{
    auto manager = std::make_unique<XdgPopupStableManager>(this);

    auto positioner = PositionerParams{}
        .with_anchor_rect(window_width / 2, window_height / 2, 0, 0);

    manager->map_popup(positioner);
    manager->client.roundtrip();

    ASSERT_THAT(
        manager->popup_position(),
        Eq(std::experimental::make_optional(
            std::make_pair(
                (window_width - popup_width) / 2,
                (window_height - popup_height) / 2)))) << "popup placed in incorrect position";
}

// regression test for https://github.com/MirServer/mir/issues/836
TEST_P(XdgPopupTest, popup_configure_is_valid)
{
    auto const& param = GetParam();
    auto manager = param.build(this);

    auto positioner = PositionerParams{}
        .with_size(30, 30)
        .with_anchor(XDG_POSITIONER_ANCHOR_TOP_LEFT)
        .with_gravity(XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
    manager->map_popup(positioner);
    manager->client.roundtrip();

    ASSERT_THAT(manager->state, Ne(std::experimental::nullopt));
    EXPECT_THAT(manager->state.value().width, Gt(0));
    EXPECT_THAT(manager->state.value().height, Gt(0));
}

INSTANTIATE_TEST_CASE_P(
    XdgPopupStable,
    XdgPopupTest,
    testing::Values(XdgPopupTestParam{
        [](wlcs::InProcessServer* const server)
        {
            return std::make_unique<XdgPopupStableManager>(server);
        }}));

INSTANTIATE_TEST_CASE_P(
    XdgPopupUnstableV6,
    XdgPopupTest,
    testing::Values(XdgPopupTestParam{
        [](wlcs::InProcessServer* const server)
        {
            return std::make_unique<XdgPopupV6Manager>(server);
        }}));

// TODO: test that positioner is always overlapping or adjacent to parent
// TODO: test that positioner is copied immediately after use
// TODO: test that error is raised when incomplete positioner is used (positioner without size and anchor rect set)
// TODO: test set_size
// TODO: test that set_window_geometry affects anchor rect
// TODO: test set_constraint_adjustment
// TODO: test set_offset
// TODO: test that a zero size anchor rect fails on v6
