diff --git a/binaries/data/mods/_test.gui/gui/regainFocus/emptyPage.js b/binaries/data/mods/_test.gui/gui/regainFocus/emptyPage.js
new file mode 100644
index 0000000000..4d9ae217c5
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/regainFocus/emptyPage.js
@@ -0,0 +1,4 @@
+function init()
+{
+ return new Promise(closePageCallback => { globalThis.closePageCallback = closePageCallback; });
+}
diff --git a/binaries/data/mods/_test.gui/gui/regainFocus/emptyPage.xml b/binaries/data/mods/_test.gui/gui/regainFocus/emptyPage.xml
new file mode 100644
index 0000000000..13a791f3dc
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/regainFocus/emptyPage.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/binaries/data/mods/_test.gui/gui/regainFocus/page_emptyPage.xml b/binaries/data/mods/_test.gui/gui/regainFocus/page_emptyPage.xml
index 0bd853ba33..33fad458e4 100644
--- a/binaries/data/mods/_test.gui/gui/regainFocus/page_emptyPage.xml
+++ b/binaries/data/mods/_test.gui/gui/regainFocus/page_emptyPage.xml
@@ -1,4 +1,5 @@
common/styles.xml
+ regainFocus/emptyPage.xml
diff --git a/binaries/data/mods/_test.gui/gui/regainFocus/pushWithPopOnInit.js b/binaries/data/mods/_test.gui/gui/regainFocus/pushWithPopOnInit.js
index 8181d15209..14f188ba39 100644
--- a/binaries/data/mods/_test.gui/gui/regainFocus/pushWithPopOnInit.js
+++ b/binaries/data/mods/_test.gui/gui/regainFocus/pushWithPopOnInit.js
@@ -1 +1,4 @@
-Engine.PushGuiPage("regainFocus/page_emptyPage.xml").then(Engine.PopGuiPage);
+function init()
+{
+ return Engine.PushGuiPage("regainFocus/page_emptyPage.xml");
+}
diff --git a/binaries/data/mods/_test.gui/gui/resolveReject/page_resolveReject.xml b/binaries/data/mods/_test.gui/gui/resolveReject/page_resolveReject.xml
new file mode 100644
index 0000000000..fe22796f27
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/resolveReject/page_resolveReject.xml
@@ -0,0 +1,5 @@
+
+
+ common/styles.xml
+ resolveReject/resolveReject.xml
+
diff --git a/binaries/data/mods/_test.gui/gui/resolveReject/resolveReject.js b/binaries/data/mods/_test.gui/gui/resolveReject/resolveReject.js
new file mode 100644
index 0000000000..051185de33
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/resolveReject/resolveReject.js
@@ -0,0 +1,5 @@
+async function init(reject)
+{
+ if (reject)
+ throw new Error();
+}
diff --git a/binaries/data/mods/_test.gui/gui/resolveReject/resolveReject.xml b/binaries/data/mods/_test.gui/gui/resolveReject/resolveReject.xml
new file mode 100644
index 0000000000..9ced6051e2
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/resolveReject/resolveReject.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/binaries/data/mods/_test.gui/gui/sequential/page_sequential.xml b/binaries/data/mods/_test.gui/gui/sequential/page_sequential.xml
new file mode 100644
index 0000000000..bf740c6a74
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/sequential/page_sequential.xml
@@ -0,0 +1,5 @@
+
+
+ common/styles.xml
+ sequential/sequential.xml
+
diff --git a/binaries/data/mods/_test.gui/gui/sequential/sequential.js b/binaries/data/mods/_test.gui/gui/sequential/sequential.js
new file mode 100644
index 0000000000..e87c9984bf
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/sequential/sequential.js
@@ -0,0 +1,5 @@
+async function init()
+{
+ await Engine.PushGuiPage("regainFocus/page_emptyPage.xml");
+ await Engine.PushGuiPage("regainFocus/page_emptyPage.xml");
+}
diff --git a/binaries/data/mods/_test.gui/gui/sequential/sequential.xml b/binaries/data/mods/_test.gui/gui/sequential/sequential.xml
new file mode 100644
index 0000000000..90c93a6bc0
--- /dev/null
+++ b/binaries/data/mods/_test.gui/gui/sequential/sequential.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/source/gui/tests/test_GuiManager.h b/source/gui/tests/test_GuiManager.h
index fc8a021bbd..1ea3fa205b 100644
--- a/source/gui/tests/test_GuiManager.h
+++ b/source/gui/tests/test_GuiManager.h
@@ -26,6 +26,7 @@
#include "ps/GameSetup/GameSetup.h"
#include "ps/Hotkey.h"
#include "ps/XML/Xeromyces.h"
+#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptRequest.h"
#include "scriptinterface/ScriptInterface.h"
@@ -35,6 +36,14 @@
#include
#include
+#if MSC_VERSION
+# pragma warning(push, 1)
+#endif
+#include "js/Promise.h"
+#if MSC_VERSION
+# pragma warning(pop)
+#endif
+
class TestGuiManager : public CxxTest::TestSuite
{
std::unique_ptr configDB;
@@ -205,37 +214,74 @@ public:
static void CloseTopmostPage()
{
- g_GUI->PopPage(JS::NullHandleValue);
+ ScriptRequest rq{g_GUI->GetActiveGUI()->GetScriptInterface()};
+ JS::RootedValue global{rq.cx, rq.globalValue()};
+ TS_ASSERT(ScriptFunction::CallVoid(rq, global, "closePageCallback"));
+ // Check whether promises are settled in the page stack and flush the stack.
g_GUI->TickObjects();
}
void test_PageRegainedFocusEvent()
{
- // Load up a test page.
ScriptRequest rq{g_GUI->GetScriptInterface()};
- JS::RootedValue val(rq.cx);
- Script::CreateObject(rq, &val);
+ const Script::StructuredClone undefined{
+ Script::WriteStructuredClone(rq, JS::UndefinedHandleValue)};
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 0);
- Script::StructuredClone data = Script::WriteStructuredClone(rq, JS::NullHandleValue);
- g_GUI->PushPage(L"regainFocus/page_emptyPage.xml", data);
-
- const ScriptInterface& pageScriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface());
- ScriptRequest prq(pageScriptInterface);
- JS::RootedValue global(prq.cx, prq.globalValue());
-
- TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 1);
- g_GUI->PushPage(L"regainFocus/page_emptyPage.xml", data);
- TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 2);
- CloseTopmostPage();
+ g_GUI->PushPage(L"regainFocus/page_emptyPage.xml", undefined);
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 1);
// This page instantly pushes an empty page with a callback that pops another page again.
- g_GUI->PushPage(L"regainFocus/page_pushWithPopOnInit.xml", data);
+ g_GUI->PushPage(L"regainFocus/page_pushWithPopOnInit.xml", undefined);
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 3);
- // Pop the empty page
+ // Pop the empty page and execute the continuation.
CloseTopmostPage();
TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 1);
+
+ CloseTopmostPage();
+ TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 0);
+ }
+
+ void test_ResolveReject()
+ {
+ TestLogger logger;
+ constexpr std::array, 2> testSteps{{
+ {false, JS::PromiseState::Fulfilled},
+ {true, JS::PromiseState::Rejected}}};
+
+ const ScriptRequest rq{g_GUI->GetScriptInterface()};
+
+ const Script::StructuredClone undefined{
+ Script::WriteStructuredClone(rq, JS::UndefinedHandleValue)};
+ g_GUI->PushPage(L"regainFocus/page_emptyPage.xml", undefined);
+
+
+ for (const auto& [reject, result] : testSteps)
+ {
+ const JS::RootedValue value{rq.cx, JS::BooleanValue(reject)};
+ const Script::StructuredClone clonedValue{Script::WriteStructuredClone(rq, value)};
+
+ const JS::RootedValue promise{rq.cx,
+ g_GUI->PushPage(L"resolveReject/page_resolveReject.xml", clonedValue)};
+
+ // Check whether promises are settled in the page stack and flush the stack.
+ g_GUI->TickObjects();
+ const JS::RootedObject promiseObject{rq.cx, &promise.toObject()};
+ TS_ASSERT_EQUALS(JS::GetPromiseState(promiseObject), result);
+ }
+ }
+
+ void test_Sequential()
+ {
+ const ScriptRequest rq{g_GUI->GetScriptInterface()};
+ const Script::StructuredClone undefined{
+ Script::WriteStructuredClone(rq, JS::UndefinedHandleValue)};
+ g_GUI->PushPage(L"sequential/page_sequential.xml", undefined);
+ TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 2);
+ CloseTopmostPage();
+ TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 2);
+ CloseTopmostPage();
+ TS_ASSERT_EQUALS(g_GUI->GetPageCount(), 0);
}
};