# noqa: PLC0302
import unittest
from pathlib import Path
from typing import Any, Dict, List

import carb
import carb.settings
import omni.graph.core as og
import omni.graph.core.tests as ogts
import omni.kit.test
import omni.usd
from omni.kit import ui_test
from omni.kit.ui_test.input import (
    emulate_mouse_drag_and_drop,
    emulate_mouse_move,
    emulate_mouse_move_and_click,
    emulate_mouse_scroll,
)
from omni.kit.ui_test.vec2 import Vec2
from omni.ui.tests.test_base import OmniUiTest

EXT_PATH = Path(carb.tokens.get_tokens_interface().resolve("${omni.graph.ui_nodes}"))


class TestUINodes(OmniUiTest):
    def __init__(self, tests=()):
        super().__init__(tests)
        self.settings = carb.settings.get_settings()

        # The first value for each setting is what we want to set it to, the second is where we will store
        # its original value.
        self.viewport_settings: Dict[str, List[Any, Any]] = {
            "/persistent/app/viewport/displayOptions": [0, None],
            "/app/viewport/grid/enabled": [False, None],
            "/persistent/app/viewport/{vp_id}/guide/grid/visible": [False, None],
            "/persistent/app/viewport/{vp_id}/guide/axis/visible": [False, None],
            # This doesn't currently work while the test is running so we set it in the test args instead.
            # "/persistent/app/viewport/{vp_id}/hud/visible": [False, None],
        }

        self.TEST_GRAPH_PATH = "/World/TestGraph"
        self.wait_frames_for_visual_update = 5

        # It takes 6 frames for the nodes and connections to all draw in their proper positions.
        self.wait_frames_for_graph_draw = 6

        # Wait 10 frames for commands to execute and the graph to update
        self.wait_frames_for_commands = 10

    async def setUp(self):
        """Set up test environment, to be torn down when done"""
        await super().setUp()
        self.viewport_name, self.viewport_id = self.get_viewport_name_and_id()
        self._golden_img_dir = EXT_PATH.absolute().resolve().joinpath("data/golden_images")

        # Make sure the viewport looks the same as when we generated the golden images.
        for setting, values in self.viewport_settings.items():
            key = setting.format(vp_id=self.viewport_id)
            values[1] = self.settings.get(key)
            self.settings.set(key, values[0])

        await omni.usd.get_context().new_stage_async()
        self.stage = omni.usd.get_context().get_stage()

    async def tearDown(self):
        self._golden_img_dir = None
        await super().tearDown()

        # Restore viewport settings.
        for setting, (values) in self.viewport_settings.items():
            key = setting.format(vp_id=self.viewport_id)
            if values[1] is None:
                self.settings.destroy_item(key)
            else:
                self.settings.set(key, values[1])
                values[1] = None

        omni.usd.get_context().close_stage()  # Activate OnClosing node

    def get_viewport_name_and_id(self):
        try:
            import omni.kit.viewport.window as vp

            viewport = next(vp.get_viewport_window_instances())
            return (viewport.name, viewport.viewport_api.id)
        except StopIteration as exc:
            raise og.OmniGraphError("Legacy viewport not supported for UI nodes.") from exc

    def create_graph(self) -> og.Graph:
        """Create an execution graph."""
        return og.Controller.create_graph({"graph_path": self.TEST_GRAPH_PATH, "evaluator_name": "execution"})

    async def edit_ui_execution_graph(self, graph_path: str, graph_node_data: dict, error_thrown=False):
        """Edit graph with nodes, connections and values common to all tests."""

        keys = og.Controller.Keys
        data = og.Controller.edit(
            graph_path,
            {
                keys.CREATE_NODES: [
                    ("onLoaded", "omni.graph.action.OnLoaded"),
                    ("viewport", "omni.graph.ui_nodes.SetViewportMode"),
                    ("viewportClose", "omni.graph.ui_nodes.SetViewportMode"),
                    ("onClosing", "omni.graph.action.OnClosing"),
                ]
                + graph_node_data.get(keys.CREATE_NODES, []),
                keys.CONNECT: [
                    ("onLoaded.outputs:execOut", "viewport.inputs:execIn"),
                    ("onClosing.outputs:execOut", "viewportClose.inputs:execIn"),
                ]
                + graph_node_data.get(keys.CONNECT, []),
                keys.SET_VALUES: [
                    ("viewport.inputs:viewport", self.viewport_name),
                    ("viewportClose.inputs:viewport", self.viewport_name),
                    ("viewport.inputs:mode", 1),
                    ("viewport.inputs:enablePicking", True),
                ]
                + graph_node_data.get(keys.SET_VALUES, []),
            },
        )

        # There's no graph window so we're not actually waiting for it to draw, but we still need
        # to give some time for all the connections to resolve.
        if error_thrown:
            with ogts.ExpectedError():
                await ui_test.wait_n_updates(self.wait_frames_for_graph_draw)
        else:
            await ui_test.wait_n_updates(self.wait_frames_for_graph_draw)

        return data

    def get_attribute(self, attribute, node):
        return og.Controller.get(og.Controller.attribute(attribute, node))

    def get_attribute_and_assert_equals(self, attribute, node, expected):
        attr = self.get_attribute(attribute, node)
        self.assertEquals(attr, expected)

    def get_attribute_and_assert_list_equals(self, attribute, node, expected):
        attr = self.get_attribute(attribute, node).tolist()
        self.assertListEqual(attr, expected)

    def get_attribute_and_assert_list_almost_equals(self, attribute, node, expected):
        attr = self.get_attribute(attribute, node).tolist()
        for a, e in zip(attr, expected):
            self.assertAlmostEquals(a, e)

    def assert_compute_message(self, node, error_message, severity=og.Severity.ERROR):
        error = node.get_compute_messages(severity)[0].split("\n")[0]
        self.assertEquals(error, error_message)

    async def test_button_ui(self):
        """Test UI button"""

        graph = self.create_graph()
        widget_identifier = "testbutton"
        keys = og.Controller.Keys
        (_, (_, viewport, _, _, button, click, counter), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("click", "omni.graph.ui_nodes.OnWidgetClicked"),
                    ("counter", "omni.graph.action.Counter"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("click.outputs:clicked", "counter.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("click.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "THIS IS A TEST BUTTON"),
                    ("button.inputs:style", '{"background_color": "green"}'),
                ],
            },
        )

        # Click the button
        await emulate_mouse_move_and_click(Vec2(50, 55))

        self.get_attribute_and_assert_equals(
            "inputs:parentWidgetPath", button, self.get_attribute("outputs:widgetPath", viewport)
        )
        self.get_attribute_and_assert_equals(
            "inputs:create", button, self.get_attribute("outputs:scriptedMode", viewport)
        )
        self.get_attribute_and_assert_equals("inputs:widgetIdentifier", button, widget_identifier)
        self.get_attribute_and_assert_equals("inputs:widgetIdentifier", click, widget_identifier)

        # Test button click event
        self.get_attribute_and_assert_equals("outputs:count", counter, 1)

        await self.finalize_test_no_image()

    async def test_set_viewport_mode(self):
        """Test SetViewportMode UI node"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, viewport, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.SET_VALUES: [("viewport.inputs:mode", 0)],
            },
        )

        self.get_attribute_and_assert_equals("outputs:scriptedMode", viewport, 0)
        self.get_attribute_and_assert_equals("outputs:defaultMode", viewport, 1)

        await self.finalize_test_no_image()

    async def test_on_viewport_clicked(self):
        """Test OnViewportClicked and ReadViewportClickState UI nodes"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, on_viewport_clicked, viewport_click_state, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_viewport_clicked", "omni.graph.ui_nodes.OnViewportClicked"),
                    ("viewport_click_state", "omni.graph.ui_nodes.ReadViewportClickState"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport_click_state.outputs:position", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                    ("on_viewport_clicked.outputs:clicked", "print.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("viewport.inputs:enableViewportMouseEvents", True),
                    ("on_viewport_clicked.inputs:viewport", self.viewport_name),
                    ("viewport_click_state.inputs:viewport", self.viewport_name),
                    ("on_viewport_clicked.inputs:onlyPlayback", False),
                ],
            },
        )

        # Click inside viewport
        await emulate_mouse_move_and_click(Vec2(85, 255))
        position = [72.40223463687151, 158.88268156424587]  # Translated (x, y) position fo the mouse click

        self.get_attribute_and_assert_equals("inputs:gesture", on_viewport_clicked, "Left Mouse Click")
        self.get_attribute_and_assert_equals("inputs:gesture", viewport_click_state, "Left Mouse Click")
        self.get_attribute_and_assert_list_almost_equals("outputs:position", on_viewport_clicked, position)
        self.get_attribute_and_assert_list_almost_equals("outputs:position", viewport_click_state, position)
        self.get_attribute_and_assert_equals("outputs:isValid", viewport_click_state, True)

        await self.finalize_test_no_image()

    async def test_on_viewport_pressed(self):
        """Test OnViewportPressed UI node"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (
            _,
            (_, _, _, _, on_viewport_pressed, viewport_press_state, _, _, _, _),
            _,
            _,
        ) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_viewport_pressed", "omni.graph.ui_nodes.OnViewportPressed"),
                    ("viewport_press_state", "omni.graph.ui_nodes.ReadViewportPressState"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("to_string2", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                    ("print2", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport_press_state.outputs:pressPosition", "to_string.inputs:value"),
                    ("viewport_press_state.outputs:releasePosition", "to_string2.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                    ("to_string2.outputs:converted", "print2.inputs:text"),
                    ("on_viewport_pressed.outputs:pressed", "print.inputs:execIn"),
                    ("on_viewport_pressed.outputs:released", "print2.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("viewport.inputs:enableViewportMouseEvents", True),
                    ("on_viewport_pressed.inputs:viewport", self.viewport_name),
                    ("viewport_press_state.inputs:viewport", self.viewport_name),
                    ("on_viewport_pressed.inputs:onlyPlayback", False),
                ],
            },
        )

        # Click inside viewport
        await emulate_mouse_move_and_click(Vec2(85, 255))
        position = [72.40223463687151, 158.88268156424587]  # Translated (x, y) position fo the mouse click

        self.get_attribute_and_assert_equals("inputs:gesture", on_viewport_pressed, "Left Mouse Press")
        self.get_attribute_and_assert_equals("inputs:gesture", viewport_press_state, "Left Mouse Press")
        self.get_attribute_and_assert_list_equals("outputs:pressPosition", on_viewport_pressed, position)
        self.get_attribute_and_assert_list_equals("outputs:releasePosition", on_viewport_pressed, position)
        self.get_attribute_and_assert_list_equals("outputs:pressPosition", viewport_press_state, position)
        self.get_attribute_and_assert_list_equals("outputs:releasePosition", viewport_press_state, position)
        self.get_attribute_and_assert_equals("outputs:isReleasePositionValid", viewport_press_state, True)
        self.get_attribute_and_assert_equals("outputs:isPressed", viewport_press_state, False)
        self.get_attribute_and_assert_equals("outputs:isValid", viewport_press_state, True)

        await self.finalize_test_no_image()

    async def test_on_viewport_scrolled(self):
        """Test OnViewportScrolled UI node"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, on_viewport_scrolled, viewport_scroll_state, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_viewport_scrolled", "omni.graph.ui_nodes.OnViewportScrolled"),
                    ("viewport_scroll_state", "omni.graph.ui_nodes.ReadViewportScrollState"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport_scroll_state.outputs:position", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                    ("on_viewport_scrolled.outputs:scrolled", "print.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("viewport.inputs:enableViewportMouseEvents", True),
                    ("on_viewport_scrolled.inputs:viewport", self.viewport_name),
                    ("viewport_scroll_state.inputs:viewport", self.viewport_name),
                    ("on_viewport_scrolled.inputs:onlyPlayback", False),
                ],
            },
        )

        # Click inside viewport
        await emulate_mouse_move(Vec2(85, 255))
        await emulate_mouse_scroll(Vec2(0, 900))  # (x, y) make a variable
        position = [72.40223463687151, 158.88268156424587]

        self.get_attribute_and_assert_equals("outputs:scrolled", on_viewport_scrolled, 1)
        self.get_attribute_and_assert_list_equals("outputs:position", on_viewport_scrolled, position)
        self.get_attribute_and_assert_list_equals("outputs:position", viewport_scroll_state, position)
        self.get_attribute_and_assert_equals("outputs:scrollValue", on_viewport_scrolled, 1)
        self.get_attribute_and_assert_equals("outputs:scrollValue", viewport_scroll_state, 1)
        self.get_attribute_and_assert_equals("outputs:isValid", viewport_scroll_state, True)

        await self.finalize_test_no_image()

    async def test_on_viewport_hovered(self):
        """Test OnViewportHovered UI node"""

        graph = self.create_graph()
        await emulate_mouse_move(Vec2(0, 0))  # Move the cursor away from final position

        keys = og.Controller.Keys
        (_, (_, _, _, _, on_viewport_hover, viewport_hover_state, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_viewport_hover", "omni.graph.ui_nodes.OnViewportHovered"),
                    ("viewport_hover_state", "omni.graph.ui_nodes.ReadViewportHoverState"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport_hover_state.outputs:position", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                    ("on_viewport_hover.outputs:began", "print.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("viewport.inputs:enableViewportMouseEvents", True),
                    ("on_viewport_hover.inputs:viewport", self.viewport_name),
                    ("viewport_hover_state.inputs:viewport", self.viewport_name),
                    ("on_viewport_hover.inputs:onlyPlayback", False),
                ],
            },
        )

        # Click inside viewport
        await emulate_mouse_move(Vec2(85, 255))
        position = [72.40223463687151, 158.88268156424587]

        self.get_attribute_and_assert_equals("outputs:began", on_viewport_hover, 1)
        self.get_attribute_and_assert_equals("outputs:isHovered", viewport_hover_state, True)
        self.get_attribute_and_assert_equals("outputs:isValid", viewport_hover_state, True)
        self.get_attribute_and_assert_list_equals("outputs:position", viewport_hover_state, position)

        await self.finalize_test_no_image()

    async def test_on_viewport_dragged(self):
        """Test OnViewportDrag UI node"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, on_viewport_dragged, viewport_drag_state, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_viewport_dragged", "omni.graph.ui_nodes.OnViewportDragged"),
                    ("viewport_drag_state", "omni.graph.ui_nodes.ReadViewportDragState"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport_drag_state.outputs:currentPosition", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                    ("on_viewport_dragged.outputs:ended", "print.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("viewport.inputs:enableViewportMouseEvents", True),
                    ("on_viewport_dragged.inputs:viewport", self.viewport_name),
                    ("viewport_drag_state.inputs:viewport", self.viewport_name),
                    ("on_viewport_dragged.inputs:onlyPlayback", False),
                ],
            },
        )

        # Click inside viewport
        await emulate_mouse_drag_and_drop(Vec2(85, 255), Vec2(100, 455))
        start_position = [72.40223463687151, 158.88268156424587]
        end_position = [85.81005586592177, 337.6536312849162]

        self.get_attribute_and_assert_equals("inputs:gesture", on_viewport_dragged, "Left Mouse Drag")
        self.get_attribute_and_assert_list_equals("outputs:initialPosition", on_viewport_dragged, start_position)
        self.get_attribute_and_assert_list_equals("outputs:finalPosition", on_viewport_dragged, end_position)
        self.get_attribute_and_assert_list_equals("outputs:currentPosition", viewport_drag_state, end_position)
        self.get_attribute_and_assert_list_equals("outputs:initialPosition", viewport_drag_state, start_position)
        self.get_attribute_and_assert_list_equals("outputs:velocity", viewport_drag_state, [0, 0])
        self.get_attribute_and_assert_equals("outputs:isValid", viewport_drag_state, True)
        self.get_attribute_and_assert_equals("outputs:isDragInProgress", viewport_drag_state, False)

        await self.finalize_test_no_image()

    async def test_on_viewport_dragged_began(self):
        """Test OnViewportDrag UI node where we begin the drag but don't complete it."""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, on_viewport_dragged, viewport_drag_state, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_viewport_dragged", "omni.graph.ui_nodes.OnViewportDragged"),
                    ("viewport_drag_state", "omni.graph.ui_nodes.ReadViewportDragState"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport_drag_state.outputs:currentPosition", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                    ("on_viewport_dragged.outputs:began", "print.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("viewport.inputs:enableViewportMouseEvents", True),
                    ("on_viewport_dragged.inputs:viewport", self.viewport_name),
                    ("viewport_drag_state.inputs:viewport", self.viewport_name),
                    ("on_viewport_dragged.inputs:onlyPlayback", False),
                ],
            },
        )

        # Click inside viewport
        await emulate_mouse_drag_and_drop(Vec2(85, 255), Vec2(100, 455))
        start_position = [72.40223463687151, 158.88268156424587]
        velocity = [0.8938547486033599, 22.346368715083777]

        self.get_attribute_and_assert_equals("inputs:gesture", on_viewport_dragged, "Left Mouse Drag")
        self.get_attribute_and_assert_list_equals("outputs:initialPosition", on_viewport_dragged, start_position)
        self.get_attribute_and_assert_list_equals("outputs:initialPosition", viewport_drag_state, start_position)
        self.get_attribute_and_assert_list_equals("outputs:velocity", viewport_drag_state, velocity)
        self.get_attribute_and_assert_equals("outputs:isValid", viewport_drag_state, True)
        self.get_attribute_and_assert_equals("outputs:isDragInProgress", viewport_drag_state, True)

        await self.finalize_test_no_image()

    async def test_read_window_size_viewport_connection(self):
        """Test ReadWindowSize UI node with the viewport/window widget path connection"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, read_window_size, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("read_window_size", "omni.graph.ui_nodes.ReadWindowSize"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:scriptedMode", "print.inputs:execIn"),
                    ("viewport.outputs:widgetPath", "read_window_size.inputs:widgetPath"),
                    ("read_window_size.outputs:height", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                ],
                keys.SET_VALUES: [
                    ("read_window_size.inputs:isViewport", True),
                ],
            },
        )

        self.get_attribute_and_assert_equals("outputs:width", read_window_size, 1440)
        self.get_attribute_and_assert_equals("outputs:height", read_window_size, 900)

        await self.finalize_test_no_image()

    async def test_read_window_size(self):
        """Test ReadWindowSize UI node with the viewport/window name"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, read_window_size, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("read_window_size", "omni.graph.ui_nodes.ReadWindowSize"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:scriptedMode", "print.inputs:execIn"),
                    ("read_window_size.outputs:height", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                ],
                keys.SET_VALUES: [
                    ("read_window_size.inputs:isViewport", True),
                    ("read_window_size.inputs:name", self.viewport_name),
                ],
            },
        )

        self.get_attribute_and_assert_equals("outputs:width", read_window_size, 1440)
        self.get_attribute_and_assert_equals("outputs:height", read_window_size, 900)

        await self.finalize_test_no_image()

    async def test_read_window_size_no_window(self):
        """Test ReadWindowSize UI node without window (error flow)"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, read_window_size, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("read_window_size", "omni.graph.ui_nodes.ReadWindowSize"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:scriptedMode", "print.inputs:execIn"),
                    ("read_window_size.outputs:height", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                ],
                keys.SET_VALUES: [
                    ("read_window_size.inputs:isViewport", True),
                ],
            },
            error_thrown=True,
        )

        warning = "OmniGraph Warning: No window name or widgetPath provided."
        self.assert_compute_message(read_window_size, warning, og.Severity.WARNING)

        await self.finalize_test_no_image()

    async def test_read_window_size_invalid_window(self):
        """Test ReadWindowSize UI node with invalid window name (error flow)"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, read_window_size, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("read_window_size", "omni.graph.ui_nodes.ReadWindowSize"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:scriptedMode", "print.inputs:execIn"),
                    ("read_window_size.outputs:height", "to_string.inputs:value"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                ],
                keys.SET_VALUES: [
                    ("read_window_size.inputs:isViewport", True),
                    ("read_window_size.inputs:name", "invalid-viewport"),
                ],
            },
            error_thrown=True,
        )

        error = "OmniGraph Error: No viewport named 'invalid-viewport' found."
        self.assert_compute_message(read_window_size, error, og.Severity.ERROR)

        await self.finalize_test_no_image()

    async def test_write_widget_property(self):
        """Test WriteWidgetProperty UI node"""

        graph = self.create_graph()

        widget_identifier = "testbutton1"

        keys = og.Controller.Keys
        (_, (_, viewport, _, _, button, click, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("click", "omni.graph.ui_nodes.OnWidgetClicked"),
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("click.outputs:clicked", "write.inputs:write"),
                    ("string.inputs:value", "write.inputs:value"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("click.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "THIS IS A TEST BUTTON"),
                    ("button.inputs:style", '{"background_color": "green"}'),
                    ("string.inputs:value", "NEW BUTTON"),
                    ("write.inputs:widgetIdentifier", widget_identifier),
                    ("write.inputs:propertyName", "text"),
                ],
            },
        )

        # Click the button to trigger property write
        await emulate_mouse_move_and_click(Vec2(50, 55))

        self.get_attribute_and_assert_equals(
            "inputs:parentWidgetPath", button, self.get_attribute("outputs:widgetPath", viewport)
        )
        self.get_attribute_and_assert_equals(
            "inputs:create", button, self.get_attribute("outputs:scriptedMode", viewport)
        )
        self.get_attribute_and_assert_equals("inputs:widgetIdentifier", button, widget_identifier)
        self.get_attribute_and_assert_equals("inputs:widgetIdentifier", click, widget_identifier)

        await self.finalize_test(
            golden_img_dir=self._golden_img_dir,
            golden_img_name="omni.graph.ui_nodes.test_write_widget_property.png",
        )

    async def test_read_widget_property(self):
        """Test ReadWidgetProperty UI node"""

        graph = self.create_graph()

        widget_identifier = "testbutton1"

        keys = og.Controller.Keys
        (_, (_, _, _, _, button, read, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("read", "omni.graph.ui_nodes.ReadWidgetProperty"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("button.outputs:created", "print.inputs:execIn"),
                    ("read.outputs:value", "print.inputs:text"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "THIS IS A TEST BUTTON"),
                    ("button.inputs:style", '{"background_color": "green"}'),
                    ("read.inputs:widgetIdentifier", widget_identifier),
                    ("read.inputs:propertyName", "text"),
                ],
            },
        )

        self.get_attribute_and_assert_equals("inputs:text", button, self.get_attribute("outputs:value", read))

        await self.finalize_test_no_image()

    async def test_write_widget_property_with_widget_path(self):
        """Test WriteWidgetProperty UI node"""

        graph = self.create_graph()

        widget_identifier = "testbutton1"

        keys = og.Controller.Keys
        await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("click", "omni.graph.ui_nodes.OnWidgetClicked"),
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("click.outputs:clicked", "write.inputs:write"),
                    ("string.inputs:value", "write.inputs:value"),
                    ("button.outputs:widgetPath", "write.inputs:widgetPath"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("click.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "THIS IS A TEST BUTTON"),
                    ("button.inputs:style", '{"background_color": "green"}'),
                    ("string.inputs:value", "NEW BUTTON"),
                    ("write.inputs:propertyName", "text"),
                ],
            },
        )

        # Click the button to trigger property write
        await emulate_mouse_move_and_click(Vec2(50, 55))

        await self.finalize_test(
            golden_img_dir=self._golden_img_dir,
            golden_img_name="omni.graph.ui_nodes.test_write_widget_property_with_widget_path.png",
        )

    async def test_write_widget_style(self):
        """Test WriteWidgetProperty UI node"""

        graph = self.create_graph()

        widget_identifier = "testbutton1"

        keys = og.Controller.Keys
        await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("write", "omni.graph.ui_nodes.WriteWidgetStyle"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("button.outputs:created", "write.inputs:write"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "A BUTTON"),
                    ("write.inputs:widgetIdentifier", widget_identifier),
                    ("write.inputs:style", '{"background_color": "blue"}'),
                ],
            },
        )

        # Click the viewport to see it
        await emulate_mouse_move_and_click(Vec2(80, 255))

        await self.finalize_test(
            golden_img_dir=self._golden_img_dir,
            golden_img_name="omni.graph.ui_nodes.test_write_widget_style.png",
        )

    async def test_write_widget_style_with_widget_path(self):
        """Test WriteWidgetProperty UI node"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("write", "omni.graph.ui_nodes.WriteWidgetStyle"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("button.outputs:created", "write.inputs:write"),
                    ("button.outputs:widgetPath", "write.inputs:widgetPath"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:text", "A BUTTON"),
                    ("write.inputs:style", '{"background_color": "blue"}'),
                ],
            },
        )

        # Click the viewport to see it
        await emulate_mouse_move_and_click(Vec2(80, 255))

        await self.finalize_test(
            golden_img_dir=self._golden_img_dir,
            golden_img_name="omni.graph.ui_nodes.test_write_widget_style_with_widget_path.png",
        )

    async def test_placer(self):
        """Test Placer UI node"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("placer", "omni.graph.ui_nodes.Placer"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "placer.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "placer.inputs:create"),
                    ("placer.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("placer.outputs:created", "button.inputs:create"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:text", "THIS IS A PLACED BUTTON"),
                    ("button.inputs:style", '{"background_color": "blue"}'),
                    ("placer.inputs:position", (20.0, 250.0)),
                ],
            },
        )

        # Click the viewport
        await emulate_mouse_move_and_click(Vec2(50, 55))

        await self.finalize_test(
            golden_img_dir=self._golden_img_dir, golden_img_name="omni.graph.ui_nodes.test_placer.png"
        )

    async def test_vstack_and_spacer(self):
        """Test VStack and Spacer UI nodes"""

        buttons = 3
        colors = ["green", "blue", "orange"]
        stack = [f"button{b}" for b in range(buttons)]
        stack.insert(1, "spacer")

        # To ensure a consistent ordering of the children within the stack, the creation of the stack triggers the
        # creation of its first child widget and each child then triggers the creation of the next one.
        widget_creation_connections = [("stack.outputs:created", f"{stack[0]}.inputs:create")]
        for i in range(len(stack) - 1):
            widget_creation_connections += [(f"{stack[i]}.outputs:created", f"{stack[i+1]}.inputs:create")]

        graph = self.create_graph()
        keys = og.Controller.Keys
        await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("stack", "omni.graph.ui_nodes.VStack"),
                    ("spacer", "omni.graph.ui_nodes.Spacer"),
                ]
                + [(f"button{b}", "omni.graph.ui_nodes.Button") for b in range(buttons)],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "stack.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "stack.inputs:create"),
                ]
                + widget_creation_connections
                + [("stack.outputs:widgetPath", f"{widget}.inputs:parentWidgetPath") for widget in stack],
                keys.SET_VALUES: [("spacer.inputs:height", 100)]
                + [(f"button{b}.inputs:text", f"Button {b+1}") for b in range(buttons)]
                + [(f"button{b}.inputs:style", '{"background_color": "' + colors[b] + '"}') for b in range(buttons)],
            },
        )

        # Click the viewport
        await emulate_mouse_move_and_click(Vec2(50, 55))

        await self.finalize_test(
            golden_img_dir=self._golden_img_dir, golden_img_name="omni.graph.ui_nodes.test_vstack_and_spacer.png"
        )

    async def test_button_negative_size(self):
        """Test UI button error flow - negative size"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, button), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [("button", "omni.graph.ui_nodes.Button")],
                keys.CONNECT: [("viewport.outputs:scriptedMode", "button.inputs:create")],
                keys.SET_VALUES: [("button.inputs:size", (-1, -1))],  # Negative size
            },
            error_thrown=True,
        )

        self.assert_compute_message(button, "OmniGraph Error: The size of the widget cannot be negative!")

        await self.finalize_test_no_image()

    async def test_button_negative_no_parent_widget(self):
        """Test UI button error flow - no parent widget"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, button), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [("button", "omni.graph.ui_nodes.Button")],
                keys.CONNECT: [("viewport.outputs:scriptedMode", "button.inputs:create")],
            },
            error_thrown=True,
        )

        self.assert_compute_message(button, "OmniGraph Error: No parentWidgetPath supplied.")

        await self.finalize_test_no_image()

    async def test_button_negative_invalid_style(self):
        """Test UI button error flow - style not valid"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, button), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [("button", "omni.graph.ui_nodes.Button")],
                keys.CONNECT: [
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                ],
                keys.SET_VALUES: [("button.inputs:style", "bad style")],
            },
            error_thrown=True,
        )

        # The expected error message contains the text of a Python SyntaxError exception, which is
        # different depending on the Python version. So rather than do an exact match on the entire message
        # we'll just check for some identifying text that is under our control.
        msg = button.get_compute_messages(og.Severity.ERROR)[0].split("\n")[0]
        self.assertTrue("OmniGraph Error: 'inputs:style': Invalid style syntax" in msg, "expected syntax error")

        await self.finalize_test_no_image()

    async def test_write_widget_property_invalid_widget_identifier(self):
        """Test WriteWidgetProperty UI node error flow - invalid widget identifier"""

        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, write, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                    ("impulse", "omni.graph.action.OnImpulseEvent"),
                ],
                keys.CONNECT: [
                    ("impulse.outputs:execOut", "write.inputs:write"),
                    ("string.inputs:value", "write.inputs:value"),
                ],
                keys.SET_VALUES: [
                    ("write.inputs:widgetIdentifier", "invalid-id"),
                    ("string.inputs:value", "new style"),
                    ("impulse.state:enableImpulse", True),
                    ("impulse.inputs:onlyPlayback", False),
                ],
            },
        )

        warning = "OmniGraph Warning: No widget with identifier 'invalid-id' found in this graph."
        self.assert_compute_message(write, warning, og.Severity.WARNING)

        await self.finalize_test_no_image()

    async def test_write_widget_property_invalid_widget_path(self):
        graph = self.create_graph()

        widget_path = "Viewport//Frame/ZStack[0]/Frame[3]/OG_overlay/Placer[2]/testbutton11"

        keys = og.Controller.Keys
        (_, (_, _, _, _, write, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                    ("impulse", "omni.graph.action.OnImpulseEvent"),
                ],
                keys.CONNECT: [
                    ("impulse.outputs:execOut", "write.inputs:write"),
                    ("string.inputs:value", "write.inputs:value"),
                ],
                keys.SET_VALUES: [
                    ("write.inputs:widgetPath", widget_path),
                    ("string.inputs:value", "new style"),
                    ("impulse.state:enableImpulse", True),
                    ("impulse.inputs:onlyPlayback", False),
                ],
            },
        )

        warning = f"OmniGraph Warning: No widget found at path '{widget_path}'."
        self.assert_compute_message(write, warning, og.Severity.WARNING)

        await self.finalize_test_no_image()

    async def test_write_widget_property_invalid_widget(self):
        graph = self.create_graph()

        keys = og.Controller.Keys
        (_, (_, _, _, _, write, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                    ("impulse", "omni.graph.action.OnImpulseEvent"),
                ],
                keys.CONNECT: [
                    ("impulse.outputs:execOut", "write.inputs:write"),
                    ("string.inputs:value", "write.inputs:value"),
                ],
                keys.SET_VALUES: [
                    ("string.inputs:value", "new style"),
                    ("impulse.state:enableImpulse", True),
                    ("impulse.inputs:onlyPlayback", False),
                ],
            },
        )

        warning = "OmniGraph Warning: No widgetIdentifier or widgetPath provided."
        self.assert_compute_message(write, warning, og.Severity.WARNING)

        await self.finalize_test_no_image()

    async def test_write_widget_property_no_property(self):
        graph = self.create_graph()

        widget_identifier = "testbutton1"

        keys = og.Controller.Keys
        (_, (_, _, _, _, _, write, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("button.outputs:created", "write.inputs:write"),
                    ("button.outputs:widgetPath", "write.inputs:widgetPath"),
                    ("string.inputs:value", "write.inputs:value"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "TEST BUTTON"),
                    ("button.inputs:style", '{"background_color": "green"}'),
                    ("string.inputs:value", "NEW BUTTON"),
                ],
            },
            error_thrown=True,
        )

        error = "OmniGraph Error: No property provided."
        self.assert_compute_message(write, error)

        await self.finalize_test_no_image()

    async def test_write_widget_property_invalid_property(self):
        graph = self.create_graph()

        widget_identifier = "testbutton1"

        keys = og.Controller.Keys
        (_, (_, _, _, _, _, write, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("button", "omni.graph.ui_nodes.Button"),
                    ("write", "omni.graph.ui_nodes.WriteWidgetProperty"),
                    ("string", "omni.graph.nodes.ConstantString"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:widgetPath", "button.inputs:parentWidgetPath"),
                    ("viewport.outputs:scriptedMode", "button.inputs:create"),
                    ("button.outputs:created", "write.inputs:write"),
                    ("string.inputs:value", "write.inputs:value"),
                ],
                keys.SET_VALUES: [
                    ("button.inputs:widgetIdentifier", widget_identifier),
                    ("write.inputs:widgetIdentifier", widget_identifier),
                    ("button.inputs:text", "TEST BUTTON"),
                    ("button.inputs:style", '{"background_color": "green"}'),
                    ("string.inputs:value", "NEW BUTTON"),
                    ("write.inputs:propertyName", "invalid-property"),
                ],
            },
            error_thrown=True,
        )

        error = f"OmniGraph Error: Widget '{widget_identifier}' has no property 'invalid-property'."
        self.assert_compute_message(write, error)

        await self.finalize_test_no_image()

    async def test_get_set_active_viewport_camera(self):
        graph = self.create_graph()

        prim_path = "/OmniverseKit_Front"

        keys = og.Controller.Keys
        (_, (_, _, _, _, _, get_camera, _, _), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("set_camera", "omni.graph.ui_nodes.SetActiveViewportCamera"),
                    ("get_camera", "omni.graph.ui_nodes.GetActiveViewportCamera"),
                    ("to_string", "omni.graph.nodes.ToString"),
                    ("print", "omni.graph.ui_nodes.PrintText"),
                ],
                keys.CONNECT: [
                    ("viewport.outputs:scriptedMode", "set_camera.inputs:execIn"),
                    ("get_camera.outputs:camera", "to_string.inputs:value"),
                    ("set_camera.outputs:execOut", "print.inputs:execIn"),
                    ("to_string.outputs:converted", "print.inputs:text"),
                ],
                keys.SET_VALUES: [
                    ("set_camera.inputs:viewport", self.viewport_name),
                    ("set_camera.inputs:primPath", prim_path),
                ],
            },
        )

        self.get_attribute_and_assert_equals("outputs:camera", get_camera, prim_path)

        await self.finalize_test_no_image()

    # Using emulate_mouse_move_and_click() doesn't trigger the OnPicked node, even when done through the
    # Script Editor in an interactive session.
    @unittest.skip("OM-77263: Not working.")
    async def test_on_picked(self):

        graph = self.create_graph()
        keys = og.Controller.Keys
        (_, (_, _, _, _, _, counter), _, _) = await self.edit_ui_execution_graph(
            graph,
            {
                keys.CREATE_NODES: [
                    ("on_picked", "omni.graph.ui_nodes.OnPicked"),
                    ("counter", "omni.graph.action.Counter"),
                ],
                keys.CONNECT: [
                    ("on_picked.outputs:picked", "counter.inputs:execIn"),
                ],
                keys.SET_VALUES: [
                    ("on_picked.inputs:viewport", self.viewport_name),
                    ("on_picked.inputs:onlyPlayback", False),
                ],
            },
        )

        await emulate_mouse_move_and_click(Vec2(85, 255))

        cube = ogts.create_cube(self.stage, "my_cube", (1, 1, 1))
        attr = cube.GetAttribute("size")
        attr.Set(10.0)
        await ui_test.wait_n_updates(20)

        self.get_attribute_and_assert_equals("outputs:count", counter, 1)

        await self.finalize_test()

    async def test_pass_clicks_thru(self):
        await self.create_test_area(width=1200, height=800, block_devices=False)

        selection: omni.usd.Selection = omni.usd.get_context().get_selection()

        # Load some geometry so that we have something which can be selected with a mouse click.
        # The pxr renderer (aka "Storm") has problems selecting implicit geometry such as that generated by
        # the Cube prim, so we need to use a mesh.
        (result, error) = await ogts.load_test_file("mesh-cube.usd", use_caller_subdirectory=True)
        self.assertTrue(result, error)

        cube_path = "/World/Cube"
        cube_pos = Vec2(700, 400)

        # Create an execution graph with a SetViewportMode node and a variable to control its passClicksThru.
        # An OnVariableChange node triggers the SetViewportMode node whenever the variable's value changes.
        graph = self.create_graph()
        keys = og.Controller.Keys

        controller = og.Controller()
        controller.edit(
            graph,
            {
                keys.CREATE_VARIABLES: [("passThruMode", "bool", False)],
                keys.CREATE_NODES: [
                    ("readVariable", "omni.graph.core.ReadVariable"),
                    ("viewportMode", "omni.graph.ui_nodes.SetViewportMode"),
                    ("variableChanged", "omni.graph.action.OnVariableChange"),
                    # These are needed to ensure that the viewport is set back to default mode when the test is done.
                    ("viewportClose", "omni.graph.ui_nodes.SetViewportMode"),
                    ("onClosing", "omni.graph.action.OnClosing"),
                ],
                keys.SET_VALUES: [
                    ("readVariable.inputs:variableName", "passThruMode"),
                    ("viewportMode.inputs:viewport", self.viewport_name),
                    ("viewportMode.inputs:mode", 1),
                    ("variableChanged.inputs:variableName", "passThruMode"),
                    ("variableChanged.inputs:onlyPlayback", False),
                    ("viewportClose.inputs:viewport", self.viewport_name),
                ],
                keys.CONNECT: [
                    ("readVariable.outputs:value", "viewportMode.inputs:passClicksThru"),
                    ("variableChanged.outputs:changed", "viewportMode.inputs:execIn"),
                    ("onClosing.outputs:execOut", "viewportClose.inputs:execIn"),
                ],
            },
        )

        variable = graph.find_variable("passThruMode")

        # Make sure nothing is selected and give the viewport some time to settle down.
        selection.clear_selected_prim_paths()
        await ui_test.wait_n_updates(2)

        # Enable passClicksThru and click on the cube. It should be selected.
        variable.set(graph.get_context(), True)
        await ui_test.wait_n_updates(2)

        await emulate_mouse_move_and_click(cube_pos)
        await ui_test.wait_n_updates(8)

        self.assertTrue(cube_path in selection.get_selected_prim_paths(), "Selection with passClicksThru True.")

        # Disable passClicksThru and click on the cube. It should not be selected.
        selection.clear_selected_prim_paths()
        variable.set(graph.get_context(), False)
        await ui_test.wait_n_updates(2)

        await emulate_mouse_move_and_click(cube_pos)
        await ui_test.wait_n_updates(8)

        self.assertFalse(cube_path in selection.get_selected_prim_paths(), "Selection with passClicksThru False.")

        await self.finalize_test_no_image()
