Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ versioned baseline.
before.
- `gui\src\ASWUnitTests_GUI_Sources.cmake`, which lists the GUI runners' shared units as
`ASWUNITTESTS_GUI_SOURCES`.
- A dark theme in the VCL GUI runner, with **View > Theme** to choose it: **Use Windows Setting**
(the default) follows Windows' app mode setting, switching as soon as it changes, and **Light**
and **Dark** override it. The choice applies without a restart and is saved in the layout file;
`--layout-ignore` and `--layout-reset` cover it as they do the layout. Dark is RAD Studio's Carbon
VCL style, so a project of your own that runs its tests in the VCL GUI runner needs Carbon among
its custom styles (Project > Options > Application > Appearance) for it, and a `WinMain()` that
applies the saved theme as `vcl\gui\rad370\ASWUnitTests_VCL_GUI.cpp` does.
- A `TCLIParser::ParseArguments()` overload that takes the arguments as a
`std::vector<std::string>`, the program name first, as in `argv`.
- `UTF8ToWide()` in `ASWUnitTests_Utils.h`, which converts UTF-8 to wide text (UTF-16 on Windows,
Expand Down
8 changes: 8 additions & 0 deletions Deploy.bat
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ call :CopyFile "gui\src\ASWUnitTests_GUI_TestList.cpp" "gui\src\ASWUnitTests_GUI
if errorlevel 1 exit /b 1
call :CopyFile "gui\src\ASWUnitTests_GUI_TestList.h" "gui\src\ASWUnitTests_GUI_TestList.h"
if errorlevel 1 exit /b 1
call :CopyFile "gui\src\ASWUnitTests_GUI_Theme.cpp" "gui\src\ASWUnitTests_GUI_Theme.cpp"
if errorlevel 1 exit /b 1
call :CopyFile "gui\src\ASWUnitTests_GUI_Theme.h" "gui\src\ASWUnitTests_GUI_Theme.h"
if errorlevel 1 exit /b 1
call :CopyFile "gui\src\ASWUnitTests_GUI_Tree.cpp" "gui\src\ASWUnitTests_GUI_Tree.cpp"
if errorlevel 1 exit /b 1
call :CopyFile "gui\src\ASWUnitTests_GUI_Tree.h" "gui\src\ASWUnitTests_GUI_Tree.h"
Expand Down Expand Up @@ -117,6 +121,10 @@ call :CopyFile "gui\tests\Test_ASWUnitTests_GUI_TestList.cpp" "gui\tests\Test_AS
if errorlevel 1 exit /b 1
call :CopyFile "gui\tests\Test_ASWUnitTests_GUI_TestList.h" "gui\tests\Test_ASWUnitTests_GUI_TestList.h"
if errorlevel 1 exit /b 1
call :CopyFile "gui\tests\Test_ASWUnitTests_GUI_Theme.cpp" "gui\tests\Test_ASWUnitTests_GUI_Theme.cpp"
if errorlevel 1 exit /b 1
call :CopyFile "gui\tests\Test_ASWUnitTests_GUI_Theme.h" "gui\tests\Test_ASWUnitTests_GUI_Theme.h"
if errorlevel 1 exit /b 1
call :CopyFile "gui\tests\Test_ASWUnitTests_GUI_Tree.cpp" "gui\tests\Test_ASWUnitTests_GUI_Tree.cpp"
if errorlevel 1 exit /b 1
call :CopyFile "gui\tests\Test_ASWUnitTests_GUI_Tree.h" "gui\tests\Test_ASWUnitTests_GUI_Tree.h"
Expand Down
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,15 @@ Reset Layout** restores the default layout right away; `--layout-ignore` and `--
(below) control this from the command line. Since the file is named after the executable, the two
GUI runners, and each project's own build of them, keep separate layouts.

In the VCL GUI runner, **View > Theme** chooses a light or dark look. **Use Windows Setting**, the
default, follows Windows' app mode setting (Settings > Personalization > Colors), and switches as
soon as it changes, but stays light while high contrast is on, so the window shows high contrast's
own colors. **Light** and **Dark** keep that look whatever Windows is set to. Dark is RAD Studio's
Carbon VCL style, and light the standard Windows look. A choice applies right away and is saved in
the layout file at once, so the next session starts with it; **View > Reset Layout** leaves it as
it is. The Theme commands are disabled during a run, and a change of the Windows setting during one
applies when it ends. The Win32 GUI runner always has the standard Windows look, for now.

The window also remembers which tests are checked, in `%APPDATA%\ASWUnitTests\<exe name>.selection`,
as long as the command line doesn't choose them itself (no `--run`, `--filter`, or partition
options). What it saves is what Run Selected would run, the checked tests that are shown; the filter
Expand Down Expand Up @@ -421,11 +430,12 @@ these differences:
- `--exit` (GUI-only, requires `--run`) closes the window when that run finishes, and exits with the
same code the console runner would; a run ended with Stop exits with `1`. Without `--exit`, the
GUI always exits with `0`.
- `--layout-ignore` (GUI-only) neither loads nor saves the window layout, so the window opens with
the defaults and the saved layout is left as it is.
- `--layout-reset` (GUI-only) deletes the saved window layout and opens with the defaults, which is
a way to recover if a saved layout ever causes a problem. The layout is saved again when the
window closes.
- `--layout-ignore` (GUI-only) neither loads nor saves the window layout, or the VCL GUI runner's
theme, so the window opens with the defaults and the saved ones are left as they are. A theme
chosen in the View menu then lasts only until the window closes.
- `--layout-reset` (GUI-only) deletes the saved window layout, and with it the VCL GUI runner's
theme, and opens with the defaults, which is a way to recover if a saved layout ever causes a
problem. The layout is saved again when the window closes.

A GUI process doesn't make a Windows command prompt wait for it, so a script that needs `--exit`'s
exit code should start it with `start /wait` (or its own language's equivalent; Bash, including Git
Expand Down Expand Up @@ -453,8 +463,10 @@ To run your own tests in it, create a RAD Studio VCL application that defines
`ASWUNITTESTS_RTL_EXCEPTIONS`, and add to it: the framework's `src` files except `main.cpp`; the
files in `gui\src` (shared by the GUI runners) and `vcl\gui\src`, including
`ASWUnitTests_GUI_MainForm.dfm`, with both folders on the include path; your own test modules; and
a `WinMain()` modeled on `vcl\gui\rad370\ASWUnitTests_VCL_GUI.cpp`, which parses the command line
and starts the main form.
a `WinMain()` modeled on `vcl\gui\rad370\ASWUnitTests_VCL_GUI.cpp`, which parses the command line,
applies the saved theme and starts the main form. For the dark theme, also add RAD Studio's Carbon
style to the application (Project > Options > Application > Appearance, which this repository's
project has as `Custom_Styles`); without it, Dark shows the light look.

### Win32 GUI Runner

Expand Down
1 change: 1 addition & 0 deletions cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ set(ASWUNITTESTS_SELF_TEST_SOURCES
../gui/tests/Test_ASWUnitTests_GUI_Selection.cpp
../gui/tests/Test_ASWUnitTests_GUI_Shuffle.cpp
../gui/tests/Test_ASWUnitTests_GUI_TestList.cpp
../gui/tests/Test_ASWUnitTests_GUI_Theme.cpp
../gui/tests/Test_ASWUnitTests_GUI_Tree.cpp
../gui/tests/Test_ASWUnitTests_GUI_View.cpp
../tests/Test_ASWTools_Random.cpp
Expand Down
8 changes: 5 additions & 3 deletions gui/src/ASWUnitTests_GUI_CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ std::string TGUICommandLine::GUIOptionsHelp()
" returning the same exit code the console runner would.\n"
" Requires --run.\n"
" --layout-ignore Neither load nor save the window layout (its size,\n"
" position, and panel sizes); open with the defaults.\n"
" --layout-reset Delete the saved window layout and open with the\n"
" defaults; the layout is saved again on closing.\n"
" position, and panel sizes) or the VCL GUI's theme;\n"
" open with the defaults.\n"
" --layout-reset Delete the saved window layout (and the VCL GUI's\n"
" theme) and open with the defaults; the layout is saved\n"
" again on closing.\n"
"\n"
"Ignored by the GUI: --list, --pause, and the color options.\n";
}
Expand Down
1 change: 1 addition & 0 deletions gui/src/ASWUnitTests_GUI_Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ TGUICommandStates GUICommandStates(TGUICommandContext const& context)
states.ShuffleOrder = idle;
states.ShuffleSeed = idle;
states.Stop = context.Running;
states.Theme = idle;

return states;
}
Expand Down
8 changes: 6 additions & 2 deletions gui/src/ASWUnitTests_GUI_Commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ struct TGUICommandContext
//
// Whether each of a GUI runner's commands is enabled, one member per
// command, named as in the VCL GUI runner's actions (e.g. RunSelected for
// Act_RunSelected), plus the filter box.
// Act_RunSelected), plus the filter box. Theme is the VCL GUI runner's
// View > Theme menu, its three actions together; the Win32 GUI runner has no
// themes yet, so it has no command that uses it.
/////////////////////////////////////////////////////////////////////////////
struct TGUICommandStates
{
Expand All @@ -80,6 +82,7 @@ struct TGUICommandStates
bool ShuffleOrder = false;
bool ShuffleSeed = false;
bool Stop = false;
bool Theme = false;
};

//---------------------------------------------------------------------------
Expand All @@ -90,7 +93,8 @@ struct TGUICommandStates
// Details, Expand All, Collapse All, and Reset Layout) are enabled. Tests run on the GUI's main thread, so a
// modal dialog (About, Command Line Options, Shuffle Seed, Export JUnit Report) would hold up the run until
// it closed; the run's tests and order are already decided, so checking tests and changing the shuffle
// would only mislead; and changing the filter would rebuild the tree the run is updating.
// would only mislead; changing the filter would rebuild the tree the run is updating; and changing the theme
// would recreate the windows the run is updating.
// - Run Selected and Run Failed stay disabled once a run has been aborted.
// - Run Failed and Select Failed need a failed test, Export JUnit Report a finished run's report, Copy Details a
// selected node, and Expand All and Collapse All a tree with nodes.
Expand Down
1 change: 1 addition & 0 deletions gui/src/ASWUnitTests_GUI_Sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ set(ASWUNITTESTS_GUI_SOURCES
"${ASWUNITTESTS_GUI_SOURCE_DIR}/ASWUnitTests_GUI_Selection.cpp"
"${ASWUNITTESTS_GUI_SOURCE_DIR}/ASWUnitTests_GUI_Shuffle.cpp"
"${ASWUNITTESTS_GUI_SOURCE_DIR}/ASWUnitTests_GUI_TestList.cpp"
"${ASWUNITTESTS_GUI_SOURCE_DIR}/ASWUnitTests_GUI_Theme.cpp"
"${ASWUNITTESTS_GUI_SOURCE_DIR}/ASWUnitTests_GUI_Tree.cpp"
"${ASWUNITTESTS_GUI_SOURCE_DIR}/ASWUnitTests_GUI_View.cpp"
)
117 changes: 117 additions & 0 deletions gui/src/ASWUnitTests_GUI_Theme.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* **************************************************************************
ASWUnitTests_GUI_Theme.cpp
Author: Anthony S. West - ASW Software

See header for info.

Copyright 2026 ASW Software

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

************************************************************************** */

//---------------------------------------------------------------------------
// Module header
#include "ASWUnitTests_GUI_Theme.h"
//---------------------------------------------------------------------------
#include <algorithm>
#include <optional>
#include <string>
//---------------------------------------------------------------------------

namespace
{

char const ThemeKey[] = "Theme";
char const ViewSection[] = "View";

//---------------------------------------------------------------------------

bool SameText(std::string const& a, std::string const& b);
char ToLowerASCII(char c);

//---------------------------------------------------------------------------
/*
SameText

Whether 'a' and 'b' are the same, ignoring ASCII case, as the INI document matches section and key names.
*/
bool SameText(std::string const& a, std::string const& b)
{
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char x, char y)
{
return ToLowerASCII(x) == ToLowerASCII(y);
});
}
//---------------------------------------------------------------------------
char ToLowerASCII(char c)
{
return (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
}
//---------------------------------------------------------------------------

} // namespace

//---------------------------------------------------------------------------

namespace ASWUnitTests
{

//---------------------------------------------------------------------------
bool GUIThemeIsDark(EGUITheme theme, bool windowsAppsUseDark, bool highContrast)
{
switch (theme)
{
case EGUITheme::Dark:
return true;

case EGUITheme::Light:
return false;

case EGUITheme::Windows:
break;
}

return windowsAppsUseDark && !highContrast;
}
//---------------------------------------------------------------------------
EGUITheme LoadGUITheme(TGUIINIDocument const& ini)
{
std::optional<std::string> const name = ini.ReadString(ViewSection, ThemeKey);

if (name.has_value())
{
if (SameText(*name, "Dark"))
return EGUITheme::Dark;

if (SameText(*name, "Light"))
return EGUITheme::Light;
}

return EGUITheme::Windows;
}
//---------------------------------------------------------------------------
void SaveGUITheme(TGUIINIDocument& ini, EGUITheme theme)
{
char const* name = "Windows";

if (theme == EGUITheme::Dark)
name = "Dark";
else if (theme == EGUITheme::Light)
name = "Light";

ini.WriteString(ViewSection, ThemeKey, name);
}
//---------------------------------------------------------------------------

} // namespace ASWUnitTests
62 changes: 62 additions & 0 deletions gui/src/ASWUnitTests_GUI_Theme.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* **************************************************************************
ASWUnitTests_GUI_Theme.h
Author: Anthony S. West - ASW Software

Copyright 2026 ASW Software

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

************************************************************************** */

//---------------------------------------------------------------------------
#ifndef ASWUnitTests_GUI_ThemeH
#define ASWUnitTests_GUI_ThemeH
//---------------------------------------------------------------------------
#include "ASWUnitTests_GUI_INIDocument.h"
//---------------------------------------------------------------------------

namespace ASWUnitTests
{

/////////////////////////////////////////////////////////////////////////////
// EGUITheme
//
// A GUI runner's theme, as chosen in the VCL GUI runner's View > Theme menu
// and saved in its layout file. Contains no UI code, so it's unit tested
// from the console runners (see gui/tests/Test_ASWUnitTests_GUI_Theme.cpp).
// The Win32 GUI runner has no themes yet.
/////////////////////////////////////////////////////////////////////////////
enum class EGUITheme
{
Windows, // Follows Windows' setting for apps, light or dark; the default.
Light,
Dark
};

//---------------------------------------------------------------------------

// Whether 'theme' is dark: Light and Dark as chosen, and Windows as 'windowsAppsUseDark' (Windows' app mode
// setting) says, except that it's light while 'highContrast' is on, so the window shows high contrast's own
// colors.
bool GUIThemeIsDark(EGUITheme theme, bool windowsAppsUseDark, bool highContrast);
// Reads the theme SaveGUITheme() wrote to 'ini', its [View] section's Theme key, ignoring (ASCII) case.
// Windows when it's missing or isn't a theme's name.
EGUITheme LoadGUITheme(TGUIINIDocument const& ini);
// Writes 'theme' to 'ini', as "Windows", "Light" or "Dark". The file itself is written from the document's
// Text() afterward.
void SaveGUITheme(TGUIINIDocument& ini, EGUITheme theme);

} // namespace ASWUnitTests

//---------------------------------------------------------------------------
#endif // #ifndef ASWUnitTests_GUI_ThemeH
7 changes: 4 additions & 3 deletions gui/tests/Test_ASWUnitTests_GUI_Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ std::string EnabledCommands(TGUICommandStates const& states)
add(states.ShuffleOrder, "ShuffleOrder");
add(states.ShuffleSeed, "ShuffleSeed");
add(states.Stop, "Stop");
add(states.Theme, "Theme");

return text;
}
Expand Down Expand Up @@ -141,7 +142,7 @@ void TTest_ASWUnitTests_GUI_Commands::Test_GUICommandStates_AbortedRunCannotRunA

// Act and Assert
CheckEquals(std::string("About CommandLineOptions Exit ExportJUnitReport FilterBox FocusFilter ResetLayout "
"SelectAll SelectFailed SelectNone ShuffleOrder ShuffleSeed"), EnabledCommands(GUICommandStates(context)),
"SelectAll SelectFailed SelectNone ShuffleOrder ShuffleSeed Theme"), EnabledCommands(GUICommandStates(context)),
__func__, __LINE__, "neither Run Selected nor Run Failed, but everything else that doesn't run tests");
}
//---------------------------------------------------------------------------
Expand All @@ -153,15 +154,15 @@ void TTest_ASWUnitTests_GUI_Commands::Test_GUICommandStates_FailuresEnableRunAnd

// Act and Assert
CheckEquals(std::string("About CommandLineOptions Exit FilterBox FocusFilter ResetLayout RunFailed RunSelected "
"SelectAll SelectFailed SelectNone ShuffleOrder ShuffleSeed"), EnabledCommands(GUICommandStates(context)),
"SelectAll SelectFailed SelectNone ShuffleOrder ShuffleSeed Theme"), EnabledCommands(GUICommandStates(context)),
__func__, __LINE__, "Run Failed and Select Failed, once a test has failed");
}
//---------------------------------------------------------------------------
void TTest_ASWUnitTests_GUI_Commands::Test_GUICommandStates_IdleWithNothingRunYet()
{
// Act and Assert: as the window opens, before its tree is built.
CheckEquals(std::string("About CommandLineOptions Exit FilterBox FocusFilter ResetLayout RunSelected SelectAll "
"SelectNone ShuffleOrder ShuffleSeed"), EnabledCommands(GUICommandStates(TGUICommandContext())), __func__,
"SelectNone ShuffleOrder ShuffleSeed Theme"), EnabledCommands(GUICommandStates(TGUICommandContext())), __func__,
__LINE__, "nothing that needs a failure, a report, a selection, or tree nodes, and not Stop");
}
//---------------------------------------------------------------------------
Expand Down
Loading
Loading