Files
hrynco-kswitcher/tests/integration.cpp
T

193 lines
6.6 KiB
C++

#include "app.h"
#include <iostream>
namespace
{
ks::App *app{};
HWND host{}, field{};
ks::Clip saved;
bool captured = false, failed = false;
int stage = 0, ru = -1, en = -1;
ULONGLONG deadline{};
std::wstring sentinel = L"KSwitcher integration clipboard sentinel";
void require(bool ok, const char *name)
{
if (!ok)
throw std::runtime_error(name);
std::cout << "PASS " << name << '\n';
}
std::wstring text()
{
wchar_t buffer[2048]{};
GetWindowTextW(field, buffer, 2048);
return buffer;
}
void prepare(const wchar_t *value, bool select)
{
app->resetInput();
SetWindowTextW(field, value);
ks::activateWindow(host);
SetFocus(field);
int end = GetWindowTextLengthW(field);
SendMessageW(field, EM_SETSEL, select ? 0 : end, end);
if (GetForegroundWindow() != host)
throw std::runtime_error("Test editor could not acquire foreground; no input was sent");
}
void record(const std::wstring &keys)
{
std::lock_guard lock(app->stateMutex);
for (wchar_t c : keys)
app->run.record(c, host, app->layouts[en].handle, GetTickCount64());
}
void finish()
{
KillTimer(host, 1);
if (captured)
{
try
{
app->ownSequence = ks::writeClipboard(app->window, saved);
}
catch (...)
{
failed = true;
}
}
DestroyWindow(host);
PostMessageW(app->window, WM_CLOSE, 0, 0);
}
LRESULT CALLBACK procedure(HWND w, UINT msg, WPARAM wp, LPARAM lp)
{
if (msg == WM_TIMER)
{
try
{
if (GetTickCount64() > deadline)
throw std::runtime_error("Native integration timed out");
if (app->busy)
return 0;
switch (stage++)
{
case 0:
saved = ks::readClipboard(app->window, 64 * 1024 * 1024);
captured = true;
app->ownSequence = ks::writeClipboard(app->window, ks::textClip(sentinel));
prepare(L"ghbdtn", true);
app->transform(ks::Direct + ru);
break;
case 1:
std::cout << "Observed editor: " << ks::utf8(text()) << "; busy=" << app->busy
<< "; foreground=" << (GetForegroundWindow() == host) << "\n";
require(text() == L"привет", "selection conversion through actual Ctrl+C / Ctrl+V");
require(ks::readClipboard(app->window).text() == sentinel, "clipboard restored");
app->transform(ks::Undo);
break;
case 2:
require(text() == L"ghbdtn", "verified undo");
prepare(L"hello world", true);
app->transform(ks::Case);
break;
case 3:
require(text() == L"Hello World", "case conversion");
app->transform(ks::Case);
break;
case 4:
require(text() == L"HELLO WORLD", "consecutive command reselects verified previous result");
prepare(L"OLDghbdtn", false);
record(L"ghbdtn");
app->transform(ks::Direct + ru);
break;
case 5:
std::cout << "Recent observed: " << ks::utf8(text()) << "\n";
require(text() == L"OLDпривет", "recent-input conversion preserves adjacent older text");
prepare(L"OLDabcdef", false);
record(L"ghbdtn");
app->transform(ks::Direct + ru);
break;
case 6:
require(text() == L"OLDabcdef", "recent-input mismatch refuses replacement");
app->settings.snippets = {{L";addr", L"LINE1\r\n{cursor}LINE2"}};
prepare(L"prefix ;addr", false);
record(L";addr");
app->transform(0, 0);
break;
case 7:
{
require(text() == L"prefix LINE1\r\nLINE2", "multiline snippet expansion");
DWORD start{}, end{};
SendMessageW(field, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
require(start == end && start == 14, "verified snippet cursor placement");
require(ks::readClipboard(app->window).text() == sentinel,
"clipboard restored after repeated copy transactions");
finish();
break;
}
}
}
catch (const std::exception &e)
{
failed = true;
std::cerr << "FAIL " << e.what() << '\n';
finish();
}
return 0;
}
if (msg == WM_CLOSE)
{
failed = true;
finish();
return 0;
}
return DefWindowProcW(w, msg, wp, lp);
}
} // namespace
int main()
{
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
auto root = std::filesystem::temp_directory_path() /
(L"KSwitcher-integration-" + std::to_wstring(GetCurrentProcessId()));
try
{
{
ks::App instance(GetModuleHandleW(nullptr), false, root);
app = &instance;
app->settings.history = false;
app->settings.switchLayout = false;
for (int i = 0; i < (int)app->layouts.size(); ++i)
{
auto language = LOWORD((uintptr_t)app->layouts[i].handle);
if (language == 0x409)
en = i;
if (language == 0x419)
ru = i;
}
if (en < 0 || ru < 0)
throw std::runtime_error(
"Integration test requires installed English US and Russian layouts");
WNDCLASSW cls{};
cls.hInstance = GetModuleHandleW(nullptr);
cls.lpfnWndProc = procedure;
cls.lpszClassName = L"KSwitcher.TestEditor";
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClassW(&cls);
host = CreateWindowW(cls.lpszClassName, L"KSwitcher — automated test editor",
WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 650, 320, nullptr, nullptr,
cls.hInstance, nullptr);
field =
CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_WANTRETURN,
20, 20, 590, 230, host, nullptr, cls.hInstance, nullptr);
deadline = GetTickCount64() + 30000;
SetTimer(host, 1, 100, nullptr);
app->execute();
}
std::filesystem::remove_all(root);
}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
failed = true;
}
CoUninitialize();
return failed ? 1 : 0;
}