From f3c0eec96a24b8334272307e98d747f12561e1ea Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 19:11:59 -0400 Subject: [PATCH 1/8] XUI's notes say what thirteen of Alchemy's widgets are for xui_notes.xml gives each tag the one sentence the Library and the generated xui.xsd show. Thirteen of Alchemy's widgets had none: angle_dial, choice_list, code_editor, color_table_panel, corner_field, diff_bar, diff_view, find_bar, image_field, offset_pad, output_view, pane_list and text_view. Each now has one, in the file's sorted run of Alchemy's own tags. alxuischema's "every tag says what it is for" did not see them: its TUT executable linked only the parts of llui its own file reached, and a widget registers itself from its own object, so the schema it read had none of these thirteen in it. In one binary with the rest of llui's tests, as in the viewer, every widget registers, and both alxuischema cases that hold the notes to the schema failed on them. Co-Authored-By: Claude Opus 5.5 --- indra/newview/skins/default/xui/en/xui_notes.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/indra/newview/skins/default/xui/en/xui_notes.xml b/indra/newview/skins/default/xui/en/xui_notes.xml index d2c70d42a2..066f08213b 100644 --- a/indra/newview/skins/default/xui/en/xui_notes.xml +++ b/indra/newview/skins/default/xui/en/xui_notes.xml @@ -154,16 +154,29 @@ + + + + + + + + + + + + + From 2899a2a8475c7d485b923b2516c1672ba011780c Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 19:11:59 -0400 Subject: [PATCH 2/8] XUI Studio opens at the width its panes add up to, and its canvas row fits Its window was 1620 wide, but its three panes are authored at 400, 744 and 500, which with the bars between them come to a stack 1664 wide: a window of 1680. The navigator and the inspector keep their widths, so the canvas got what was left, 684. The row of controls under it is laid out from the left at fixed widths and needed 748, more than even the 744 it was authored in: its last align buttons sat past its edge. The window, its header bar, its stack and its status line are now the width the panes were authored for, so the canvas opens at 744; and Reload is 54 wide, as Show and Hide beside it are, so the row is 744 too. alxuifill's "every stack fills what it is in" did not see it. Its TUT executable linked only the parts of llui its own file reached, and a widget registers from its own object, so the row was built without the canvas_zoom spinner and the canvas_backdrop menu button, 102 pixels narrower. In one binary with the rest of llui's tests, as in the viewer, the spinner is there and the case failed; with the menu button linked in as well, the row now ends at the canvas's edge. Co-Authored-By: Claude Opus 5.5 --- .../skins/default/xui/en/floater_xui_studio.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_xui_studio.xml b/indra/newview/skins/default/xui/en/floater_xui_studio.xml index ef67ce347a..95a341179d 100644 --- a/indra/newview/skins/default/xui/en/floater_xui_studio.xml +++ b/indra/newview/skins/default/xui/en/floater_xui_studio.xml @@ -11,7 +11,7 @@ save_rect="true" single_instance="true" title="XUI Studio" - width="1620"> + width="1680"> No editor: set the ExternalEditor setting or the LL_XUI_EDITOR environment variable, with %s for the file and %L for the line. Select a file. The window would not give a snapshot. @@ -600,7 +600,7 @@ mouse_opaque="false" name="header_bar" top="40" - width="1612" /> + width="1672" /> + width="54" /> + width="1664" /> From afb2ae340bf2d44b287ef5a1fa17877ae3e93dbd Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 13 Oct 2026 20:45:10 -0400 Subject: [PATCH 3/8] llui's tests are Catch2 binaries, converted by tut2catch.py TM23, the sixth wave of stage 3: llui's 117 TUT files, 1,541 tests, become two Catch2 binaries, each file still run in a process of its own: - llui_tests, 114 files with no GL: templates tested whole in their headers, text and XUI below the view, a file of llui's each, what comparing two texts is made of, and every widget above LLView, built over one headless UI on the source tree's skins and fonts; - llui_gl_tests, the three that draw into llrender's hidden window and read the pixels back: the text view, a surface and its tab strip, and a comparison. LLUI_TEST_APP_DIR is defined for every file of each. The fakes llui asks the viewer for are a SUPPORT object of both, and the benchmarks keep linking them as they did. The files are tut2catch.py's output, with the names the idiom pass that follows chose. It marked two sites TM-CHECK, both in code this Mac does not compile, resolved by hand: alkeymap's off-Mac check that no binding is Control-Alt with a key that types (an ensure() whose message is built), and alvimkeymap's off-Mac "nothing changed" (an ensure_equals() over strings). tut_parity.py, against the TUT executables' recorded results (1,541 tests, all ok), holds plain for 115 files and under --recounted for the two hand-resolved ones, with four assertion sites reviewed as never running in a passing case: three reached only to fail, inside an if that finds a mismatch (alcodeeditor, allinetable, alsyntaxhighlighter), and one of altextview's that runs only where a wide character's drawn width does not land near half a column, as it does in this host's fonts. All 119 entries pass. Co-Authored-By: Claude Opus 5.5 --- indra/llui/CMakeLists.txt | 323 +- indra/llui/tests/alanchoredranges_test.cpp | 92 +- indra/llui/tests/alangledial_test.cpp | 54 +- indra/llui/tests/albracketindex_test.cpp | 132 +- indra/llui/tests/alcanvasview_test.cpp | 880 ++- indra/llui/tests/alchangepeek_test.cpp | 583 +- indra/llui/tests/alchoicelist_test.cpp | 173 +- indra/llui/tests/alcodecards_test.cpp | 186 +- indra/llui/tests/alcodeeditor_test.cpp | 3697 ++++++--- indra/llui/tests/alcolorfield_test.cpp | 203 +- indra/llui/tests/alcolorpicker_test.cpp | 133 +- indra/llui/tests/alcolorsheet_test.cpp | 407 +- indra/llui/tests/alcolortablepanel_test.cpp | 78 +- indra/llui/tests/alcompletionmodel_test.cpp | 195 +- indra/llui/tests/alcornerfield_test.cpp | 118 +- indra/llui/tests/aldifflexer_test.cpp | 293 +- indra/llui/tests/aldiffmerge_test.cpp | 371 +- indra/llui/tests/aldiffmodel_test.cpp | 1795 +++-- indra/llui/tests/aldiffview_gl_test.cpp | 115 +- indra/llui/tests/aldiffview_test.cpp | 1883 +++-- indra/llui/tests/aldockpanel_test.cpp | 240 +- indra/llui/tests/aldraggesture_test.cpp | 136 +- indra/llui/tests/alemptystate_test.cpp | 157 +- indra/llui/tests/alfenwicktree_test.cpp | 40 +- indra/llui/tests/alfindbar_test.cpp | 107 +- indra/llui/tests/alfixlistmodel_test.cpp | 91 +- indra/llui/tests/alflagsfield_test.cpp | 128 +- indra/llui/tests/alfolderfilter_test.cpp | 80 +- indra/llui/tests/alfoldmodel_test.cpp | 243 +- indra/llui/tests/alfollowscontrol_test.cpp | 195 +- indra/llui/tests/alfontfield_test.cpp | 250 +- indra/llui/tests/alfuzzymatch_test.cpp | 81 +- indra/llui/tests/alhistorylist_test.cpp | 167 +- indra/llui/tests/alimagefield_test.cpp | 89 +- indra/llui/tests/aljumpbar_test.cpp | 255 +- indra/llui/tests/alkeycapture_test.cpp | 96 +- indra/llui/tests/alkeymap_test.cpp | 172 +- indra/llui/tests/allinetable_test.cpp | 102 +- indra/llui/tests/almaxima_test.cpp | 25 +- indra/llui/tests/almenuslot_test.cpp | 66 +- indra/llui/tests/alnotecarditems_test.cpp | 93 +- indra/llui/tests/aloffsetpad_test.cpp | 84 +- indra/llui/tests/aloutputview_test.cpp | 354 +- indra/llui/tests/alpanefolds_test.cpp | 243 +- indra/llui/tests/alpanelist_test.cpp | 403 +- indra/llui/tests/alplace_test.cpp | 100 +- indra/llui/tests/alpopover_test.cpp | 359 +- indra/llui/tests/alprefixsums_test.cpp | 40 +- indra/llui/tests/alpropertygrid_test.cpp | 567 +- indra/llui/tests/alquickopen_test.cpp | 342 +- indra/llui/tests/alscopebar_test.cpp | 140 +- indra/llui/tests/alsettingrow_test.cpp | 477 +- indra/llui/tests/alsnippetsession_test.cpp | 219 +- indra/llui/tests/alspecimenlist_test.cpp | 316 +- indra/llui/tests/alstringmatch_test.cpp | 79 +- indra/llui/tests/alstudiofloater_test.cpp | 460 +- indra/llui/tests/alsurface_gl_test.cpp | 58 +- indra/llui/tests/alsurface_test.cpp | 89 +- indra/llui/tests/alsyntaxgrammars_test.cpp | 639 +- indra/llui/tests/alsyntaxhighlighter_test.cpp | 368 +- indra/llui/tests/altabstrip_test.cpp | 582 +- indra/llui/tests/altextcarets_test.cpp | 164 +- indra/llui/tests/altextdiff_test.cpp | 1056 ++- indra/llui/tests/altextdocument_test.cpp | 688 +- indra/llui/tests/altextediting_test.cpp | 277 +- indra/llui/tests/altextfind_test.cpp | 164 +- indra/llui/tests/altextgotoline_test.cpp | 212 +- indra/llui/tests/altextindent_test.cpp | 429 +- indra/llui/tests/altextlayout_test.cpp | 1033 ++- indra/llui/tests/altextsearch_test.cpp | 442 +- indra/llui/tests/altextspelling_test.cpp | 118 +- indra/llui/tests/altextundo_test.cpp | 719 +- indra/llui/tests/altextview_gl_test.cpp | 327 +- indra/llui/tests/altextview_test.cpp | 3472 ++++++--- indra/llui/tests/alundostack_test.cpp | 323 +- indra/llui/tests/alunifieddiff_test.cpp | 102 +- indra/llui/tests/alviewtype_test.cpp | 256 +- indra/llui/tests/alvimkeymap_test.cpp | 6780 +++++++++++------ indra/llui/tests/alvimmappings_test.cpp | 316 +- indra/llui/tests/alvimpattern_test.cpp | 825 +- indra/llui/tests/alvimregisters_test.cpp | 213 +- indra/llui/tests/alxuicatalog_test.cpp | 229 +- indra/llui/tests/alxuidiagnostics_test.cpp | 64 +- indra/llui/tests/alxuidocuments_test.cpp | 554 +- indra/llui/tests/alxuiedit_test.cpp | 1118 +-- indra/llui/tests/alxuifields_test.cpp | 90 +- indra/llui/tests/alxuifill_test.cpp | 70 +- indra/llui/tests/alxuifindings_test.cpp | 167 +- indra/llui/tests/alxuilint_test.cpp | 663 +- indra/llui/tests/alxuinotes_test.cpp | 61 +- indra/llui/tests/alxuioverlay_test.cpp | 143 +- indra/llui/tests/alxuischema_test.cpp | 445 +- indra/llui/tests/alxuiselection_test.cpp | 204 +- indra/llui/tests/alxuiservice_test.cpp | 166 +- indra/llui/tests/alxuishellbuild_test.cpp | 45 +- indra/llui/tests/alxuisourcemap_test.cpp | 84 +- indra/llui/tests/alxuitranslate_test.cpp | 440 +- indra/llui/tests/alxuitreemodel_test.cpp | 312 +- indra/llui/tests/llaccordionctrltab_test.cpp | 134 +- .../tests/llchatautocompletemodel_test.cpp | 52 +- indra/llui/tests/llcombobox_test.cpp | 76 +- indra/llui/tests/llemojidictionary_test.cpp | 124 +- indra/llui/tests/llfloater_test.cpp | 81 +- indra/llui/tests/llfocusmgr_test.cpp | 113 +- indra/llui/tests/lllayoutstack_test.cpp | 213 +- indra/llui/tests/llradiogroup_test.cpp | 73 +- indra/llui/tests/llresizebar_test.cpp | 87 +- indra/llui/tests/llspinctrl_test.cpp | 127 +- indra/llui/tests/llstatbar_test.cpp | 152 +- indra/llui/tests/lltabcontainer_test.cpp | 495 +- indra/llui/tests/lltextparser_test.cpp | 91 +- indra/llui/tests/lluicolortable_test.cpp | 259 +- indra/llui/tests/lluistring_test.cpp | 136 +- indra/llui/tests/llurlentry_test.cpp | 75 +- indra/llui/tests/llurlmatch_test.cpp | 128 +- indra/llui/tests/llurlregistry_test.cpp | 56 +- indra/llui/tests/llview_test.cpp | 250 +- indra/llui/tests/llxuiparser_test.cpp | 148 +- 118 files changed, 29081 insertions(+), 17003 deletions(-) diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index a5348deb66..7baf93108d 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -562,206 +562,157 @@ if(AL_BUILD_TESTS) llcommon ${spellcheck_lib} ) - # Four that test templates whole in their headers, and need nothing - # compiled of llui's. - al_add_test(alfenwicktree PROJECT llui LIBRARIES ${test_libs}) - al_add_test(allinetable PROJECT llui LIBRARIES ${test_libs}) - al_add_test(almaxima PROJECT llui LIBRARIES ${test_libs}) - al_add_test(alprefixsums PROJECT llui LIBRARIES ${test_libs}) - # What llui asks the viewer for, defined once: compiled here and linked - # into every test and benchmark below, which link the library. A test - # file holds no copy of its own, since two in one binary would not link. + # into every test and benchmark below. A test file holds no copy of its + # own, since two in one binary would not link. add_library(llui_test_fakes OBJECT tests/llui_test_fakes.cpp) target_link_libraries(llui_test_fakes PRIVATE al::flags llcommon) set_target_properties(llui_test_fakes PROPERTIES FOLDER "Tests/llui") - set(test_libs llui llui_test_fakes ${test_libs}) - foreach( - test - llstatbar - lltextparser - llview - lluistring - llxuiparser - llfocusmgr - alxuioverlay - alxuidocuments - alxuifindings - alxuitranslate - ) - al_add_test(${test} PROJECT llui LIBRARIES ${test_libs}) - endforeach() - # Tests of one of llui's files each, linked against the library that - # holds it rather than compiling a copy of their own. - foreach( - test - llchatautocompletemodel - alkeymap - alanchoredranges - altextdocument - altextcarets - altextundo - alnotecarditems - alsyntaxhighlighter - llurlentry - llemojidictionary - ) - al_add_test(${test} PROJECT llui LIBRARIES ${test_libs}) - endforeach() - # What comparing two texts is made of, below the view. - al_add_test(altextdiff PROJECT llui LIBRARIES ${test_libs}) - # Two texts' differences written as a unified diff. - al_add_test(alunifieddiff PROJECT llui LIBRARIES ${test_libs}) - # A merge settled in a comparison, and lines of a text put in place of - # others as one edit of it. - al_add_test( - aldiffmerge - PROJECT llui - LIBRARIES ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - al_add_test( - aldiffmodel + # llui's tests with no GL: templates tested whole in their headers; text, + # strings, XUI and parsing; a file of llui's each, from the key map to the + # URL entries; what comparing two texts is made of, and the merges and + # grammars over it; and every widget above LLView, built over one headless + # UI on the source tree's skins and fonts. + al_add_test_binary( + llui_tests PROJECT llui - LIBRARIES ${test_libs} + SOURCES + tests/alanchoredranges_test.cpp + tests/alangledial_test.cpp + tests/albracketindex_test.cpp + tests/alcanvasview_test.cpp + tests/alchangepeek_test.cpp + tests/alchoicelist_test.cpp + tests/alcodecards_test.cpp + tests/alcodeeditor_test.cpp + tests/alcolorfield_test.cpp + tests/alcolorpicker_test.cpp + tests/alcolorsheet_test.cpp + tests/alcolortablepanel_test.cpp + tests/alcompletionmodel_test.cpp + tests/alcornerfield_test.cpp + tests/aldifflexer_test.cpp + tests/aldiffmerge_test.cpp + tests/aldiffmodel_test.cpp + tests/aldiffview_test.cpp + tests/aldockpanel_test.cpp + tests/aldraggesture_test.cpp + tests/alemptystate_test.cpp + tests/alfenwicktree_test.cpp + tests/alfindbar_test.cpp + tests/alfixlistmodel_test.cpp + tests/alflagsfield_test.cpp + tests/alfolderfilter_test.cpp + tests/alfoldmodel_test.cpp + tests/alfollowscontrol_test.cpp + tests/alfontfield_test.cpp + tests/alfuzzymatch_test.cpp + tests/alhistorylist_test.cpp + tests/alimagefield_test.cpp + tests/aljumpbar_test.cpp + tests/alkeycapture_test.cpp + tests/alkeymap_test.cpp + tests/allinetable_test.cpp + tests/almaxima_test.cpp + tests/almenuslot_test.cpp + tests/alnotecarditems_test.cpp + tests/aloffsetpad_test.cpp + tests/aloutputview_test.cpp + tests/alpanefolds_test.cpp + tests/alpanelist_test.cpp + tests/alplace_test.cpp + tests/alpopover_test.cpp + tests/alprefixsums_test.cpp + tests/alpropertygrid_test.cpp + tests/alquickopen_test.cpp + tests/alscopebar_test.cpp + tests/alsettingrow_test.cpp + tests/alsnippetsession_test.cpp + tests/alspecimenlist_test.cpp + tests/alstringmatch_test.cpp + tests/alstudiofloater_test.cpp + tests/alsurface_test.cpp + tests/alsyntaxgrammars_test.cpp + tests/alsyntaxhighlighter_test.cpp + tests/altabstrip_test.cpp + tests/altextcarets_test.cpp + tests/altextdiff_test.cpp + tests/altextdocument_test.cpp + tests/altextediting_test.cpp + tests/altextfind_test.cpp + tests/altextgotoline_test.cpp + tests/altextindent_test.cpp + tests/altextlayout_test.cpp + tests/altextsearch_test.cpp + tests/altextspelling_test.cpp + tests/altextundo_test.cpp + tests/altextview_test.cpp + tests/alundostack_test.cpp + tests/alunifieddiff_test.cpp + tests/alviewtype_test.cpp + tests/alvimkeymap_test.cpp + tests/alvimmappings_test.cpp + tests/alvimpattern_test.cpp + tests/alvimregisters_test.cpp + tests/alxuicatalog_test.cpp + tests/alxuidiagnostics_test.cpp + tests/alxuidocuments_test.cpp + tests/alxuiedit_test.cpp + tests/alxuifields_test.cpp + tests/alxuifill_test.cpp + tests/alxuifindings_test.cpp + tests/alxuilint_test.cpp + tests/alxuinotes_test.cpp + tests/alxuioverlay_test.cpp + tests/alxuischema_test.cpp + tests/alxuiselection_test.cpp + tests/alxuiservice_test.cpp + tests/alxuishellbuild_test.cpp + tests/alxuisourcemap_test.cpp + tests/alxuitranslate_test.cpp + tests/alxuitreemodel_test.cpp + tests/llaccordionctrltab_test.cpp + tests/llchatautocompletemodel_test.cpp + tests/llcombobox_test.cpp + tests/llemojidictionary_test.cpp + tests/llfloater_test.cpp + tests/llfocusmgr_test.cpp + tests/lllayoutstack_test.cpp + tests/llradiogroup_test.cpp + tests/llresizebar_test.cpp + tests/llspinctrl_test.cpp + tests/llstatbar_test.cpp + tests/lltabcontainer_test.cpp + tests/lltextparser_test.cpp + tests/lluicolortable_test.cpp + tests/lluistring_test.cpp + tests/llurlentry_test.cpp + tests/llurlmatch_test.cpp + tests/llurlregistry_test.cpp + tests/llview_test.cpp + tests/llxuiparser_test.cpp + SUPPORT llui_test_fakes + LIBRARIES llui ${test_libs} DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" ) - # A grammar's regions for a comparison, read again only where a text - # changed: over the grammars the viewer ships. - al_add_test( - aldifflexer - PROJECT llui - LIBRARIES ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # Tests that build widgets above LLView stand up a UI over the source - # tree's skins and fonts; this is where it is. - foreach( - test - llurlmatch - lllayoutstack - llresizebar - llaccordionctrltab - llspinctrl - llcombobox - llradiogroup - lltabcontainer - llfloater - alpropertygrid - alcolorfield - alcolortablepanel - alimagefield - alcolorpicker - alfontfield - alflagsfield - alxuinotes - alfollowscontrol - alcanvasview - aldockpanel - alpanefolds - alangledial - alcornerfield - aldraggesture - aloffsetpad - alemptystate - alxuifill - alviewtype - alxuidiagnostics - alxuishellbuild - alxuicatalog - alxuilint - alxuiedit - alfindbar - alfolderfilter - alhistorylist - alplace - alpopover - alkeycapture - alscopebar - aljumpbar - almenuslot - alpanelist - alspecimenlist - altabstrip - alquickopen - alstudiofloater - altextgotoline - alxuifields - alxuischema - alxuiselection - alxuisourcemap - alxuitreemodel - alsettingrow - alstringmatch - alsurface - alcolorsheet - lluicolortable - alundostack - alsyntaxgrammars - altextediting - altextfind - altextindent - altextlayout - altextsearch - altextspelling - altextview - alcodecards - alcodeeditor - alchangepeek - aldiffview - albracketindex - alcompletionmodel - alfuzzymatch - alfixlistmodel - alfoldmodel - alsnippetsession - alvimkeymap - alvimmappings - alvimpattern - alvimregisters - alxuiservice - aloutputview - alchoicelist - llurlregistry - ) - al_add_test( - ${test} - PROJECT llui - LIBRARIES ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - endforeach() - # What the text view draws, read back from llrender's hidden window. - al_add_test( - altextview_gl - GL + # What the text view, a surface and its tab strip, and a comparison draw, + # read back from llrender's hidden window: one GL context, and the + # headless UI's fonts with GL textures on it. + al_add_test_binary( + llui_gl_tests PROJECT llui - LIBRARIES ${test_libs} + SOURCES tests/aldiffview_gl_test.cpp tests/alsurface_gl_test.cpp tests/altextview_gl_test.cpp + SUPPORT llui_test_fakes + LIBRARIES llui ${test_libs} DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) - - # A surface's ground and frame, and the tab strip's tabs, read back the - # same way. - al_add_test( - alsurface_gl GL - PROJECT llui - LIBRARIES ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" ) - # What a comparison draws over its sides -- the current change's outline, - # a folded row's words -- read back the same way. - al_add_test( - aldiffview_gl - GL - PROJECT llui - LIBRARIES ${test_libs} - DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" - ) + # The benchmarks, still executables of their own, link the fakes with + # the library. + set(test_libs llui llui_test_fakes ${test_libs}) # The code editor over 50,000-line scripts: what each change to the edit # pipeline, the highlighter, the layout, the folds or find is measured by. diff --git a/indra/llui/tests/alanchoredranges_test.cpp b/indra/llui/tests/alanchoredranges_test.cpp index bbf2354716..7ff88a9410 100644 --- a/indra/llui/tests/alanchoredranges_test.cpp +++ b/indra/llui/tests/alanchoredranges_test.cpp @@ -26,12 +26,12 @@ #include "alanchoredranges.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alanchoredranges_data { @@ -53,41 +53,39 @@ namespace tut return out; } }; - typedef test_group alanchoredranges_group; - typedef alanchoredranges_group::object alanchoredranges_object; - alanchoredranges_group alanchoredranges_instance("alanchoredranges"); - template<> template<> - void alanchoredranges_object::test<1>() + TEST_CASE_METHOD(alanchoredranges_data, "alanchoredranges: ranges sort by start and ties keep the order they came in", "[alanchoredranges]") { - set_test_name("put in order by where they begin, those beginning together as given; one more after its equals"); ALAnchoredRanges marks; marks.assign({ { on(3, 0, 2), "c" }, { on(1, 4, 5), "b" }, { on(1, 4, 9), "b2" }, { on(0, 0, 1), "a" } }); - ensure_equals("in order", names(marks), std::string("a,b,b2,c")); + UNSCOPED_INFO("in order"); + CHECK(names(marks) == std::string("a,b,b2,c")); marks.insert({ on(1, 4, 6), "b3" }); - ensure_equals("after its equals", names(marks), std::string("a,b,b2,b3,c")); + UNSCOPED_INFO("after its equals"); + CHECK(names(marks) == std::string("a,b,b2,b3,c")); } - template<> template<> - void alanchoredranges_object::test<2>() + TEST_CASE_METHOD(alanchoredranges_data, "alanchoredranges: an edit drops ranges it cuts and moves only those after it", "[alanchoredranges]") { - set_test_name("an edit: those it cut through go, those after it move along, those before it stay"); ALTextDocument doc("zero\none two three\nfour\nfive"); ALAnchoredRanges marks; marks.assign({ { on(0, 0, 4), "zero" }, { on(1, 0, 3), "one" }, { on(1, 4, 7), "two" }, { on(1, 8, 13), "three" }, { on(2, 0, 4), "four" } }); const ALTextDocument::Edit edit = doc.replace(on(1, 4, 7), "2\nTWO"); const size_t gone = marks.apply(edit); - ensure_equals("the one it cut through", gone, size_t(1)); - ensure_equals("gone", names(marks), std::string("zero,one,three,four")); - ensure("before it: where it was", marks[1].range == on(1, 0, 3)); - ensure("after it on its line: along the line it made", marks[2].range == on(2, 4, 9)); - ensure("below it: a line down", marks[3].range == on(3, 0, 4)); + UNSCOPED_INFO("the one it cut through"); + CHECK(gone == size_t(1)); + UNSCOPED_INFO("gone"); + CHECK(names(marks) == std::string("zero,one,three,four")); + UNSCOPED_INFO("before it: where it was"); + CHECK(marks[1].range == on(1, 0, 3)); + UNSCOPED_INFO("after it on its line: along the line it made"); + CHECK(marks[2].range == on(2, 4, 9)); + UNSCOPED_INFO("below it: a line down"); + CHECK(marks[3].range == on(3, 0, 4)); } - template<> template<> - void alanchoredranges_object::test<3>() + TEST_CASE_METHOD(alanchoredranges_data, "alanchoredranges: a one-line edit skips later lines and onLine finds ranges from above", "[alanchoredranges]") { - set_test_name("an edit within a line: those on later lines not looked at; what is on a line found by it, a range from above included"); ALTextDocument doc("a\nb\nc\nd\ne"); ALAnchoredRanges marks; marks.assign({ { ALTextRange(ALTextPos(0, 0), ALTextPos(2, 1)), "tall" }, { on(1, 0, 1), "b" }, { on(3, 0, 1), "d" }, { on(4, 0, 1), "e" } }); @@ -98,8 +96,10 @@ namespace tut return e.slide(m.range); }, [](Mark&) {}); - ensure("the tall one it landed in, and the one on its line: no more", asked == 2); - ensure_equals("the one it landed in gone", names(marks), std::string("b,d,e")); + UNSCOPED_INFO("the tall one it landed in, and the one on its line: no more"); + CHECK(asked == 2); + UNSCOPED_INFO("the one it landed in gone"); + CHECK(names(marks) == std::string("b,d,e")); marks.assign({ { ALTextRange(ALTextPos(0, 0), ALTextPos(2, 1)), "tall" }, { on(1, 0, 1), "b" }, { on(3, 0, 1), "d" } }); const auto span = marks.onLine(2); std::string on_two; @@ -107,13 +107,12 @@ namespace tut { on_two += it->name; } - ensure_equals("from above, and on the line", on_two, std::string("tallb")); + UNSCOPED_INFO("from above, and on the line"); + CHECK(on_two == std::string("tallb")); } - template<> template<> - void alanchoredranges_object::test<4>() + TEST_CASE_METHOD(alanchoredranges_data, "alanchoredranges: a caller's slide rule places hints around typing and reports drops", "[alanchoredranges]") { - set_test_name("a rule of the caller's: places kept before or after what is typed at them, in order after; what goes told of"); struct Hint { ALTextPos at; @@ -138,31 +137,50 @@ namespace tut return true; }, [&gone](Hint& h) { gone.push_back(h.name); }); - ensure("before stays, after moves", hints[0].name == "inside" && hints[1].name == "before" && hints[1].at == ALTextPos(0, 4) && hints[2].name == "after" && - hints[2].at == ALTextPos(0, 7)); + { + INFO("before stays, after moves"); + CHECK(hints[0].name == "inside"); + CHECK(hints[1].name == "before"); + CHECK(hints[1].at == ALTextPos(0, 4)); + CHECK(hints[2].name == "after"); + CHECK(hints[2].at == ALTextPos(0, 7)); + } hints.apply( doc.replace(on(0, 0, 3), ""), [](Hint& h, const ALTextDocument::Edit& e) { return !(e.range.begin < h.at && h.at < e.range.end); }, [&gone](Hint& h) { gone.push_back(h.name); }); - ensure("what went, told of", gone == std::vector({ "inside" })); + UNSCOPED_INFO("what went, told of"); + CHECK(gone == std::vector({ "inside" })); } - template<> template<> - void alanchoredranges_object::test<5>() + TEST_CASE_METHOD(alanchoredranges_data, "alanchoredranges: a followed index moves with its range or becomes -1 when it goes", "[alanchoredranges]") { - set_test_name("one followed through an edit: to where it is after, or none where it went"); ALTextDocument doc("aa bb cc\ndd ee"); ALAnchoredRanges matches; matches.assign({ on(0, 0, 2), on(0, 3, 5), on(0, 6, 8), on(1, 0, 2), on(1, 3, 5) }); S32 current = 2; matches.apply(doc.replace(on(0, 3, 5), "B"), ¤t); - ensure("the one before it gone, the current a place back", current == 1 && matches.size() == 4 && matches[1] == on(0, 5, 7)); + { + INFO("the one before it gone, the current a place back"); + CHECK(current == 1); + REQUIRE(matches.size() == 4); + CHECK(matches[1] == on(0, 5, 7)); + } current = 3; matches.apply(doc.replace(on(0, 0, 0), "x"), ¤t); - ensure("past the edit's line, not looked at, and where it was", current == 3 && matches[3] == on(1, 3, 5)); + { + INFO("past the edit's line, not looked at, and where it was"); + CHECK(current == 3); + CHECK(matches[3] == on(1, 3, 5)); + } matches.apply(doc.replace(on(1, 3, 5), "E"), ¤t); - ensure("the current one cut through: none", current == -1 && matches.size() == 3); + { + INFO("the current one cut through: none"); + CHECK(current == -1); + REQUIRE(matches.size() == 3); + } current = 2; matches.apply(doc.replace(on(0, 1, 2), ""), ¤t); - ensure("the one before it gone, below the edit's line: a place back", current == 1); + UNSCOPED_INFO("the one before it gone, below the edit's line: a place back"); + CHECK(current == 1); } } diff --git a/indra/llui/tests/alangledial_test.cpp b/indra/llui/tests/alangledial_test.cpp index fef9b389f3..c7216f9249 100644 --- a/indra/llui/tests/alangledial_test.cpp +++ b/indra/llui/tests/alangledial_test.cpp @@ -33,11 +33,11 @@ #include "alheadlessui_fixture.h" #include "aluistatescope.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { struct alangledial_data { @@ -57,59 +57,58 @@ namespace tut } }; - typedef test_group alangledial_test; - typedef alangledial_test::object alangledial_object; - tut::alangledial_test alangledial_testgroup("alangledial"); - // A press turns the dial to the pointer, a drag keeps turning it, and // the release commits once. - template<> template<> - void alangledial_object::test<1>() + TEST_CASE_METHOD(alangledial_data, "alangledial: a press and a drag turn the dial and the release commits once", "[alangledial]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALAngleDial* d = dial(); S32 commits = 0; d->setCommitCallback([&commits](LLUICtrl*, const LLSD&) { ++commits; }); d->handleMouseDown(60, 35, MASK_NONE); - ensure_equals("pressed to the right, it points right", d->getValue().asString(), std::string("1 0")); - ensure("and holds the mouse", d->hasMouseCapture()); + UNSCOPED_INFO("pressed to the right, it points right"); + CHECK(d->getValue().asString() == std::string("1 0")); + UNSCOPED_INFO("and holds the mouse"); + CHECK(d->hasMouseCapture()); d->handleHover(35, 60, MASK_NONE); - ensure("dragged up, it points up", d->getValue().asString().ends_with(" 1")); - ensure_equals("nothing committed yet", commits, 0); + UNSCOPED_INFO("dragged up, it points up"); + CHECK(d->getValue().asString().ends_with(" 1")); + UNSCOPED_INFO("nothing committed yet"); + CHECK(commits == 0); d->handleMouseUp(35, 60, MASK_NONE); - ensure_equals("the release commits once", commits, 1); - ensure("and lets the mouse go", !d->hasMouseCapture()); + UNSCOPED_INFO("the release commits once"); + CHECK(commits == 1); + UNSCOPED_INFO("and lets the mouse go"); + CHECK_FALSE(d->hasMouseCapture()); d->die(); } // The mouse taken away mid-drag ends the drag: the pointer passing over // the dial afterwards, no button held, turns nothing. - template<> template<> - void alangledial_object::test<2>() + TEST_CASE_METHOD(alangledial_data, "alangledial: losing mouse capture mid-drag ends the drag", "[alangledial]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALAngleDial* d = dial(); d->handleMouseDown(60, 35, MASK_NONE); gFocusMgr.setMouseCapture(nullptr); d->handleHover(35, 60, MASK_NONE); - ensure_equals("a hover after the loss turns nothing", d->getValue().asString(), std::string("1 0")); + UNSCOPED_INFO("a hover after the loss turns nothing"); + CHECK(d->getValue().asString() == std::string("1 0")); d->die(); } - template<> template<> - void alangledial_object::test<3>() + TEST_CASE_METHOD(alangledial_data, "alangledial: degrees typed in the box turn the dial and a set value turns the box", "[alangledial]") { - set_test_name("degrees typed in the box turn the dial, committed once, and a value turns the box"); if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALAngleDial* d = dial(); S32 commits = 0; @@ -117,10 +116,13 @@ namespace tut LLSpinCtrl* degrees = d->getChild("degrees"); degrees->setValue(LLSD(0.0)); degrees->onCommit(); - ensure_equals("typed: it points right", d->getValue().asString(), std::string("1 0")); - ensure_equals("committed once", commits, 1); + UNSCOPED_INFO("typed: it points right"); + CHECK(d->getValue().asString() == std::string("1 0")); + UNSCOPED_INFO("committed once"); + CHECK(commits == 1); d->setValue("0 1"); - ensure_equals("a value pointing up is ninety degrees", degrees->getValue().asInteger(), 90); + UNSCOPED_INFO("a value pointing up is ninety degrees"); + CHECK(degrees->getValue().asInteger() == 90); d->die(); } } diff --git a/indra/llui/tests/albracketindex_test.cpp b/indra/llui/tests/albracketindex_test.cpp index 29d73cd707..a9e1b95cbc 100644 --- a/indra/llui/tests/albracketindex_test.cpp +++ b/indra/llui/tests/albracketindex_test.cpp @@ -27,14 +27,14 @@ #include "albracketindex.h" #include "alsyntaxhighlighter.h" -#include "../test/lltut.h" +#include #include #include #include #include -namespace tut +namespace { struct albracketindex_data { @@ -71,7 +71,8 @@ namespace tut description["states"]["line_comment"] = line_comment; auto loaded = std::make_shared(); std::string error; - ensure("grammar loads: " + error, loaded->load(description, error)); + UNSCOPED_INFO("grammar loads: " + error); + REQUIRE(loaded->load(description, error)); return loaded; } @@ -98,42 +99,47 @@ namespace tut return index.enclosing(ALTextPos(line, column), bracket, count, out, lines) ? llformat("%d:%d", out.line, out.column) : std::string("none"); } }; - typedef test_group albracketindex_group; - typedef albracketindex_group::object albracketindex_object; - albracketindex_group albracketindex_instance("albracketindex"); - template<> template<> - void albracketindex_object::test<1>() + TEST_CASE_METHOD(albracketindex_data, "albracketindex: match pairs brackets across lines but none in strings or comments", "[albracketindex]") { - set_test_name("a bracket's partner, its own kind's nesting counted, across lines, both ways; one in a string or a comment is none"); make("f(a[1], \"(\" /* ) */\n { g(x) }\n) // (\n"); - ensure_equals("the round pair across lines", matched(0, 1), std::string("2:0")); - ensure_equals("and back", matched(2, 0), std::string("0:1")); - ensure_equals("a square pair inside it", matched(0, 3), std::string("0:5")); - ensure_equals("a curly pair on its own line", matched(1, 2), std::string("1:9")); - ensure_equals("in a string: none", matched(0, 9), std::string("none")); - ensure_equals("in a comment: none", matched(2, 5), std::string("none")); - ensure_equals("not a bracket: none", matched(0, 0), std::string("none")); + UNSCOPED_INFO("the round pair across lines"); + CHECK(matched(0, 1) == std::string("2:0")); + UNSCOPED_INFO("and back"); + CHECK(matched(2, 0) == std::string("0:1")); + UNSCOPED_INFO("a square pair inside it"); + CHECK(matched(0, 3) == std::string("0:5")); + UNSCOPED_INFO("a curly pair on its own line"); + CHECK(matched(1, 2) == std::string("1:9")); + UNSCOPED_INFO("in a string: none"); + CHECK(matched(0, 9) == std::string("none")); + UNSCOPED_INFO("in a comment: none"); + CHECK(matched(2, 5) == std::string("none")); + UNSCOPED_INFO("not a bracket: none"); + CHECK(matched(0, 0) == std::string("none")); } - template<> template<> - void albracketindex_object::test<2>() + TEST_CASE_METHOD(albracketindex_data, "albracketindex: enclosing finds the bracket of a kind left open around a place", "[albracketindex]") { - set_test_name("the brackets of a kind left open around a place, the count out; for a closer, after it"); make("a(b(c)d[e(f)g]h)\n"); - ensure_equals("the one around, the other kind passed through, its own kind's pairs skipped", around(0, 12, '('), std::string("0:1")); - ensure_equals("inside the inner pair, its opener", around(0, 11, '('), std::string("0:9")); - ensure_equals("inside the square", around(0, 10, '['), std::string("0:7")); - ensure_equals("two out", around(0, 5, '(', 2), std::string("0:1")); - ensure_equals("not so many", around(0, 5, '(', 3), std::string("none")); - ensure_equals("a closer under the place not counted", around(0, 5, ')'), std::string("0:15")); - ensure_equals("the closer after, the pair inside skipped", around(0, 2, ')'), std::string("0:15")); + UNSCOPED_INFO("the one around, the other kind passed through, its own kind's pairs skipped"); + CHECK(around(0, 12, '(') == std::string("0:1")); + UNSCOPED_INFO("inside the inner pair, its opener"); + CHECK(around(0, 11, '(') == std::string("0:9")); + UNSCOPED_INFO("inside the square"); + CHECK(around(0, 10, '[') == std::string("0:7")); + UNSCOPED_INFO("two out"); + CHECK(around(0, 5, '(', 2) == std::string("0:1")); + UNSCOPED_INFO("not so many"); + CHECK(around(0, 5, '(', 3) == std::string("none")); + UNSCOPED_INFO("a closer under the place not counted"); + CHECK(around(0, 5, ')') == std::string("0:15")); + UNSCOPED_INFO("the closer after, the pair inside skipped"); + CHECK(around(0, 2, ')') == std::string("0:15")); } - template<> template<> - void albracketindex_object::test<3>() + TEST_CASE_METHOD(albracketindex_data, "albracketindex: a search stops at its reach and depths count the brackets left open", "[albracketindex]") { - set_test_name("a search goes only as far as told; the depth at each line's start and at a place, a closer past none closing nothing"); std::string text = "(\n"; for (int i = 0; i < 50; ++i) { @@ -141,42 +147,58 @@ namespace tut } text += ")\n)\n"; make(text.c_str()); - ensure_equals("found within reach", matched(0, 0, 60), std::string("51:0")); - ensure_equals("not past the reach", matched(0, 0, 10), std::string("none")); - ensure_equals("nor back past it", matched(51, 0, 10), std::string("none")); - ensure_equals("open inside", index.depthBefore(1), 1); - ensure_equals("closed after", index.depthBefore(52), 0); - ensure_equals("a closer past none closes nothing", index.depthBefore(53), 0); - ensure_equals("at a place of a line", index.depthAt(ALTextPos(0, 1)), 1); - ensure_equals("before the bracket", index.depthAt(ALTextPos(0, 0)), 0); + UNSCOPED_INFO("found within reach"); + CHECK(matched(0, 0, 60) == std::string("51:0")); + UNSCOPED_INFO("not past the reach"); + CHECK(matched(0, 0, 10) == std::string("none")); + UNSCOPED_INFO("nor back past it"); + CHECK(matched(51, 0, 10) == std::string("none")); + UNSCOPED_INFO("open inside"); + CHECK(index.depthBefore(1) == 1); + UNSCOPED_INFO("closed after"); + CHECK(index.depthBefore(52) == 0); + UNSCOPED_INFO("a closer past none closes nothing"); + CHECK(index.depthBefore(53) == 0); + UNSCOPED_INFO("at a place of a line"); + CHECK(index.depthAt(ALTextPos(0, 1)) == 1); + UNSCOPED_INFO("before the bracket"); + CHECK(index.depthAt(ALTextPos(0, 0)) == 0); } - template<> template<> - void albracketindex_object::test<4>() + TEST_CASE_METHOD(albracketindex_data, "albracketindex: the index follows edits to a line's text and to its tokens", "[albracketindex]") { - set_test_name("kept true through edits: a line's text changed, and its tokens changed under it by a comment opened above"); make("(a\nb)\nc\n"); - ensure_equals("paired", matched(0, 0), std::string("1:1")); - ensure_equals("depth", index.depthBefore(1), 1); + UNSCOPED_INFO("paired"); + CHECK(matched(0, 0) == std::string("1:1")); + UNSCOPED_INFO("depth"); + CHECK(index.depthBefore(1) == 1); doc.insert(ALTextPos(0, 0), "/*"); - ensure_equals("the opener now in a comment", matched(0, 2), std::string("none")); - ensure_equals("the closer too, the comment running on", matched(1, 1), std::string("none")); - ensure_equals("and nothing open", index.depthBefore(1), 0); + UNSCOPED_INFO("the opener now in a comment"); + CHECK(matched(0, 2) == std::string("none")); + UNSCOPED_INFO("the closer too, the comment running on"); + CHECK(matched(1, 1) == std::string("none")); + UNSCOPED_INFO("and nothing open"); + CHECK(index.depthBefore(1) == 0); doc.remove(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 2))); - ensure_equals("paired again", matched(0, 0), std::string("1:1")); - ensure_equals("open again", index.depthBefore(1), 1); + UNSCOPED_INFO("paired again"); + CHECK(matched(0, 0) == std::string("1:1")); + UNSCOPED_INFO("open again"); + CHECK(index.depthBefore(1) == 1); doc.replace(ALTextRange(ALTextPos(1, 1), ALTextPos(1, 2)), "]"); - ensure_equals("a bracket of another kind in its place pairs with nothing", matched(0, 0), std::string("none")); + UNSCOPED_INFO("a bracket of another kind in its place pairs with nothing"); + CHECK(matched(0, 0) == std::string("none")); } - template<> template<> - void albracketindex_object::test<5>() + TEST_CASE_METHOD(albracketindex_data, "albracketindex: a bracket inside a path is none and those around it pair past it", "[albracketindex]") { - set_test_name("a bracket in a path, drawn as the string it is, is none: the brackets around it pair as though it were not there"); make("f(\"v1.0)\", g(x));\n{\n}\n", /*paths*/ true); - ensure_equals("the call's opener pairs with its own closer, not the path's", matched(0, 1), std::string("0:15")); - ensure_equals("in a path: none", matched(0, 7), std::string("none")); - ensure_equals("nothing open after the line", index.depthBefore(1), 0); - ensure_equals("nor inside the block below", index.depthBefore(2), 1); + UNSCOPED_INFO("the call's opener pairs with its own closer, not the path's"); + CHECK(matched(0, 1) == std::string("0:15")); + UNSCOPED_INFO("in a path: none"); + CHECK(matched(0, 7) == std::string("none")); + UNSCOPED_INFO("nothing open after the line"); + CHECK(index.depthBefore(1) == 0); + UNSCOPED_INFO("nor inside the block below"); + CHECK(index.depthBefore(2) == 1); } } diff --git a/indra/llui/tests/alcanvasview_test.cpp b/indra/llui/tests/alcanvasview_test.cpp index 0271c4e73b..46c9741062 100644 --- a/indra/llui/tests/alcanvasview_test.cpp +++ b/indra/llui/tests/alcanvasview_test.cpp @@ -35,7 +35,7 @@ #include "alheadlessui_fixture.h" #include "aluistatescope.h" -#include "../test/lltut.h" +#include #include #include @@ -77,7 +77,7 @@ namespace } } -namespace tut +namespace { struct alcanvasview_data { @@ -132,118 +132,124 @@ namespace tut } }; - typedef test_group alcanvasview_test; - typedef alcanvasview_test::object alcanvasview_object; - tut::alcanvasview_test alcanvasview_testgroup("alcanvasview"); - // A surface is never smaller than the room it is shown in: one that // stopped at the edge of what is on it has nowhere to draw a rule along // and nothing to drop onto. - template<> template<> - void alcanvasview_object::test<1>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a surface is never smaller than the room it is shown in", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(ROOM_W, ROOM_H); place(canvas, 200, 100); - ensure_equals("as wide as the room", canvas->getRect().getWidth(), ROOM_W); - ensure_equals("and as tall", canvas->getRect().getHeight(), ROOM_H); + UNSCOPED_INFO("as wide as the room"); + CHECK(canvas->getRect().getWidth() == ROOM_W); + UNSCOPED_INFO("and as tall"); + CHECK(canvas->getRect().getHeight() == ROOM_H); canvas->die(); } // And never smaller than what is on it, which is the whole of why a // canvas scrolls. - template<> template<> - void alcanvasview_object::test<2>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a surface is never smaller than what is on it", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(ROOM_W, ROOM_H); place(canvas, 900, 800); - ensure("wider than the room " + size(canvas), canvas->getRect().getWidth() > ROOM_W); - ensure("and taller " + size(canvas), canvas->getRect().getHeight() > ROOM_H); + UNSCOPED_INFO("wider than the room " + size(canvas)); + CHECK(canvas->getRect().getWidth() > ROOM_W); + UNSCOPED_INFO("and taller " + size(canvas)); + CHECK(canvas->getRect().getHeight() > ROOM_H); canvas->die(); } // A surface has two sizes. What is on it keeps its own numbers; what it // covers is those times the zoom, and that is what has to be scrolled. - template<> template<> - void alcanvasview_object::test<3>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a zoomed surface covers its own size times the zoom", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(ROOM_W, ROOM_H); place(canvas, 400, 300); canvas->setZoom(2.f); - ensure_equals("the surface is still measured in the file's numbers", - canvas->surfaceWidth(), 408); - ensure_equals("and it covers twice that", canvas->getRect().getWidth(), 816); - ensure("which is more than the room " + size(canvas), - canvas->getRect().getWidth() > ROOM_W); + UNSCOPED_INFO("the surface is still measured in the file's numbers"); + CHECK(canvas->surfaceWidth() == 408); + UNSCOPED_INFO("and it covers twice that"); + CHECK(canvas->getRect().getWidth() == 816); + UNSCOPED_INFO("which is more than the room " + size(canvas)); + CHECK(canvas->getRect().getWidth() > ROOM_W); // Drawn smaller, the surface grows in its own coordinates instead, // so the region is still filled and there is still nothing to scroll. canvas->setZoom(0.5f); - ensure_equals("half size covers the room exactly", canvas->getRect().getWidth(), ROOM_W); - ensure_equals("and no more of it", canvas->getRect().getHeight(), ROOM_H); - ensure("so the surface itself is twice the room", canvas->surfaceWidth() >= ROOM_W * 2); + UNSCOPED_INFO("half size covers the room exactly"); + CHECK(canvas->getRect().getWidth() == ROOM_W); + UNSCOPED_INFO("and no more of it"); + CHECK(canvas->getRect().getHeight() == ROOM_H); + UNSCOPED_INFO("so the surface itself is twice the room"); + CHECK(canvas->surfaceWidth() >= ROOM_W * 2); canvas->die(); } // The pointer is taken out of the zoom at one door, so everything that // finds or moves anything works in the file's own numbers. - template<> template<> - void alcanvasview_object::test<4>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: toContent takes the zoom out of a pointer position", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setZoom(2.f); S32 x = 100; S32 y = 50; canvas->toContent(x, y); - ensure_equals("across", x, 50); - ensure_equals("down", y, 25); + UNSCOPED_INFO("across"); + CHECK(x == 50); + UNSCOPED_INFO("down"); + CHECK(y == 25); canvas->die(); } // The surface changes size; what is on it does not. Carrying it by its // follows flags would stretch a previewed window to the size of the // thing previewing it. - template<> template<> - void alcanvasview_object::test<5>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: what is on a surface keeps its size when the surface grows", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(300, 200); LLPanel* root = place(canvas, 200, 100); - ensure_equals("it was built at its own size", size(root), std::string("200 x 100")); + UNSCOPED_INFO("it was built at its own size"); + CHECK(size(root) == std::string("200 x 100")); canvas->setLeastSurface(ROOM_W, ROOM_H); - ensure("the surface grew " + size(canvas), canvas->getRect().getWidth() >= ROOM_W); - ensure_equals("what is on it did not", size(root), std::string("200 x 100")); + UNSCOPED_INFO("the surface grew " + size(canvas)); + CHECK(canvas->getRect().getWidth() >= ROOM_W); + UNSCOPED_INFO("what is on it did not"); + CHECK(size(root) == std::string("200 x 100")); canvas->die(); } // What is on it keeps its distance from the surface's top left corner // when the surface changes size, because a view otherwise keeps its // distance from the bottom. - template<> template<> - void alcanvasview_object::test<6>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: what is on a surface keeps its distance from the top left corner", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(300, 200); LLPanel* root = place(canvas, 200, 100, 4); - ensure_equals("placed at the margin", root->getRect().mLeft, 4); - ensure_equals("under the top", canvas->surfaceHeight() - root->getRect().mTop, 4); + UNSCOPED_INFO("placed at the margin"); + CHECK(root->getRect().mLeft == 4); + UNSCOPED_INFO("under the top"); + CHECK(canvas->surfaceHeight() - root->getRect().mTop == 4); canvas->setLeastSurface(ROOM_W, ROOM_H); - ensure_equals("still at the margin", root->getRect().mLeft, 4); - ensure_equals("still under the top", canvas->surfaceHeight() - root->getRect().mTop, 4); + UNSCOPED_INFO("still at the margin"); + CHECK(root->getRect().mLeft == 4); + UNSCOPED_INFO("still under the top"); + CHECK(canvas->surfaceHeight() - root->getRect().mTop == 4); canvas->die(); } // A child placed past its parent's edge is drawn past it, and a surface // cut to the rect would leave that clipped with nowhere to scroll to. - template<> template<> - void alcanvasview_object::test<7>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: the surface grows to reach a child placed past its parent's edge", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(300, 200); @@ -256,16 +262,16 @@ namespace tut root->addChild(LLUICtrlFactory::create(p)); canvas->rememberRoot(); - ensure("the surface reaches what is drawn " + size(canvas), - canvas->surfaceWidth() > was); - ensure("as far as it goes", canvas->surfaceWidth() >= 4 + 500); + UNSCOPED_INFO("the surface reaches what is drawn " + size(canvas)); + CHECK(canvas->surfaceWidth() > was); + UNSCOPED_INFO("as far as it goes"); + CHECK(canvas->surfaceWidth() >= 4 + 500); canvas->die(); } // A preview moved towards an edge takes the surface with it: where it // sits is remembered nowhere else, so the surface has to grow to keep it. - template<> template<> - void alcanvasview_object::test<8>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a preview moved towards an edge takes the surface with it", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(300, 200); @@ -274,124 +280,18 @@ namespace tut root->translate(400, -300); canvas->rememberRoot(); - ensure("the surface followed it across " + size(canvas), canvas->surfaceWidth() >= 604); - ensure("and down " + size(canvas), canvas->surfaceHeight() >= 404); - ensure_equals("and it stayed where it was put", root->getRect().mLeft, 404); - canvas->die(); - } - - // Moved twice, which is what anybody moving anything does. The second - // move is measured against the surface the first one grew, so a surface - // that forgets what it needs before reading where the root is puts the - // root back higher than it was and grows under it again -- a preview - // that jumps on every drag and walks off the end of what can be - // scrolled to. - template<> template<> - void alcanvasview_object::test<14>() - { - ALCanvasView* canvas = surface(); - canvas->setLeastSurface(300, 200); - LLPanel* root = place(canvas, 200, 100); - - for (S32 again = 0; again < 3; ++again) - { - root->translate(60, -50); - // Where it was put, from the corner the surface grows out of. - // Its own y is not that: a surface that grows does so downwards, - // so everything on it counts from a new bottom. - const S32 want_left = root->getRect().mLeft; - const S32 want_down = canvas->surfaceHeight() - root->getRect().mTop; - canvas->rememberRoot(); - - const std::string which = "move " + std::to_string(again) + ": "; - ensure_equals(which + "it stayed where it was put, across", root->getRect().mLeft, want_left); - ensure_equals(which + "and down from the top", - canvas->surfaceHeight() - root->getRect().mTop, want_down); - ensure(which + "and the surface reaches under it " + where(root->getRect()), - root->getRect().mBottom >= 0 && root->getRect().mRight <= canvas->surfaceWidth()); - } - canvas->die(); - } - - // What is on a surface can move itself. A previewed window dragged by - // its own title bar does: nothing tells the surface, so the surface asks - // -- and without that the next change of size puts the window back where - // the surface last put it, which is a window that cannot be dragged by - // the handle it is meant to be dragged by. - template<> template<> - void alcanvasview_object::test<16>() - { - ALCanvasView* canvas = surface(); - canvas->setLeastSurface(300, 200); - LLPanel* root = place(canvas, 200, 100); - - // Moved by something that is not the surface. - root->translate(70, -40); - const LLRect moved = root->getRect(); - canvas->refresh(); - ensure_equals("it is still where it was moved to, across", - root->getRect().mLeft, moved.mLeft); - - // And it stays there through the change of size that used to undo it. - const S32 from_top = canvas->surfaceHeight() - root->getRect().mTop; - canvas->setLeastSurface(600, 500); - ensure_equals("across a change of size, across", root->getRect().mLeft, moved.mLeft); - ensure_equals("and down from the top", - canvas->surfaceHeight() - root->getRect().mTop, from_top); - - // Asking again when nothing has moved changes nothing. - const LLRect settled = root->getRect(); - canvas->refresh(); - ensure_equals("asking twice is asking once", where(root->getRect()), where(settled)); - canvas->die(); - } - - // A surface reaches to a margin past what is on it, so whatever was last - // dragged towards a far edge is sitting right against that edge -- well - // inside the distance a drag snaps from. Line the window up with the edge - // and the surface grows to keep its margin, which puts the edge back - // against the window, which lines it up again. A drag is offered the - // pointer every frame whether it moved or not, so this runs on its own: a - // window that shakes where it is held, over a surface that grows without - // end under it. - template<> template<> - void alcanvasview_object::test<17>() - { - // How far a drag snaps from: LLDragHandle::sSnapMargin. - constexpr S32 SNAP_FROM = 5; - - ALCanvasView* canvas = surface(); - canvas->setLeastSurface(300, 200); - LLPanel* root = place(canvas, 200, 100); - - // Far enough down that the surface has had to grow to hold it, which - // is what brings its bottom edge up against the root's. - root->translate(40, -160); - canvas->rememberRoot(); - ensure("the surface reaches just under it " + where(root->getRect()), - root->getRect().mBottom > 0 && root->getRect().mBottom <= SNAP_FROM); - - const LLRect held = root->getRect(); - const S32 was = canvas->surfaceHeight(); - for (S32 frame = 0; frame < 8; ++frame) - { - LLRect snapped; - LLView* to = root->findSnapRect(snapped, LLCoordGL(0, -1), - LLView::SNAP_PARENT_AND_SIBLINGS, SNAP_FROM); - ensure("frame " + std::to_string(frame) - + ": nothing on a surface has an edge of it to line up with", to == nullptr); - root->setShape(snapped, true); - canvas->refresh(); - } - ensure_equals("it is still where it was held", where(root->getRect()), where(held)); - ensure_equals("and the surface did not grow under it", canvas->surfaceHeight(), was); + UNSCOPED_INFO("the surface followed it across " + size(canvas)); + CHECK(canvas->surfaceWidth() >= 604); + UNSCOPED_INFO("and down " + size(canvas)); + CHECK(canvas->surfaceHeight() >= 404); + UNSCOPED_INFO("and it stayed where it was put"); + CHECK(root->getRect().mLeft == 404); canvas->die(); } // There is no growing off the top left of a canvas, so what is dragged // that way stops at the corner rather than disappearing over it. - template<> template<> - void alcanvasview_object::test<9>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: what is moved off the top left stops at the corner", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setLeastSurface(ROOM_W, ROOM_H); @@ -400,28 +300,28 @@ namespace tut root->translate(-80, 200); canvas->rememberRoot(); - ensure("it did not go off the left " + where(root->getRect()), root->getRect().mLeft >= 0); - ensure("nor off the top " + where(root->getRect()), - root->getRect().mTop <= canvas->surfaceHeight()); + UNSCOPED_INFO("it did not go off the left " + where(root->getRect())); + CHECK(root->getRect().mLeft >= 0); + UNSCOPED_INFO("nor off the top " + where(root->getRect())); + CHECK(root->getRect().mTop <= canvas->surfaceHeight()); canvas->die(); } // A surface that is not sizable is whatever it was given: a canvas in a // window of its own is sized by the window. - template<> template<> - void alcanvasview_object::test<10>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a surface that is not sizable keeps the rect it was given", "[alcanvasview]") { ALCanvasView* canvas = surface(false); canvas->setLeastSurface(ROOM_W, ROOM_H); place(canvas, 900, 800); - ensure_equals("it kept the rect it was made with", canvas->getRect().getWidth(), ROOM_W); + UNSCOPED_INFO("it kept the rect it was made with"); + CHECK(canvas->getRect().getWidth() == ROOM_W); canvas->die(); } // Two variants side by side are one row as wide as both of them, and // each gets half the room to fill. - template<> template<> - void alcanvasview_object::test<11>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: two variants in a row sit side by side and each fill half the room", "[alcanvasview]") { LLPanel::Params rp(LLUICtrlFactory::getDefaultParams()); rp.name = "row"; @@ -440,25 +340,27 @@ namespace tut row->layout(); const S32 half = (ROOM_W - ALCanvasRow::GUTTER) / 2; - ensure_equals("each fills its share of the room", first->getRect().getWidth(), half); - ensure_equals("both of them", second->getRect().getWidth(), half); - ensure_equals("and the row is the two of them and the gutter", - row->getRect().getWidth(), half * 2 + ALCanvasRow::GUTTER); - ensure_equals("the second is to the right of the first", - second->getRect().mLeft, half + ALCanvasRow::GUTTER); - ensure_equals("and their tops line up", first->getRect().mTop, second->getRect().mTop); + UNSCOPED_INFO("each fills its share of the room"); + CHECK(first->getRect().getWidth() == half); + UNSCOPED_INFO("both of them"); + CHECK(second->getRect().getWidth() == half); + UNSCOPED_INFO("and the row is the two of them and the gutter"); + CHECK(row->getRect().getWidth() == half * 2 + ALCanvasRow::GUTTER); + UNSCOPED_INFO("the second is to the right of the first"); + CHECK(second->getRect().mLeft == half + ALCanvasRow::GUTTER); + UNSCOPED_INFO("and their tops line up"); + CHECK(first->getRect().mTop == second->getRect().mTop); row->die(); } // And the whole of it: a container, a row, a surface, and something on // it too big for the region. This is the question the canvas exists to // answer -- what does not fit is scrolled to rather than cut off. - template<> template<> - void alcanvasview_object::test<12>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a preview too big for its region in a row scrolls rather than clips", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -486,15 +388,22 @@ namespace tut place(canvas, 200, 100); row->shareRoom(); row->layout(); - ensure("what fits needs no scrollbar", !down->getVisible() && !across->getVisible()); - ensure_equals("the row covers the region across", row->getRect().getWidth(), ROOM_W); - ensure_equals("and down", row->getRect().getHeight(), ROOM_H); + { + INFO("what fits needs no scrollbar"); + CHECK_FALSE(down->getVisible()); + CHECK_FALSE(across->getVisible()); + } + UNSCOPED_INFO("the row covers the region across"); + CHECK(row->getRect().getWidth() == ROOM_W); + UNSCOPED_INFO("and down"); + CHECK(row->getRect().getHeight() == ROOM_H); // Something taller than the region. canvas->clear(); place(canvas, 200, ROOM_H * 2); row->layout(); - ensure("a preview taller than the region scrolls", down->getVisible()); + UNSCOPED_INFO("a preview taller than the region scrolls"); + CHECK(down->getVisible()); // And the same by zooming rather than by building something bigger: // a preview drawn past the edge of the region is clipped, and being @@ -502,24 +411,97 @@ namespace tut canvas->clear(); place(canvas, 200, 100); row->layout(); - ensure("small again, and quiet", !down->getVisible()); + UNSCOPED_INFO("small again, and quiet"); + CHECK_FALSE(down->getVisible()); canvas->setZoom(4.f); row->layout(); - ensure("zoomed past the region, it scrolls", down->getVisible() || across->getVisible()); + UNSCOPED_INFO("zoomed past the region, it scrolls"); + CHECK((down->getVisible() || across->getVisible())); area->die(); } + // A region with nothing built on it yet is still the canvas's: the row + // covers it, so a wheel or a drop there reaches a canvas rather than + // falling through to whatever is behind. + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a row with nothing built on it yet still covers the region", "[alcanvasview]") + { + if (!ui.ok()) + { + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + } + + LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); + cp.name = "area"; + cp.rect = LLRect(0, ROOM_H, ROOM_W, 0); + LLScrollContainer* area = LLUICtrlFactory::create(cp); + + LLPanel::Params rp(LLUICtrlFactory::getDefaultParams()); + rp.name = "row"; + rp.rect = LLRect(0, 1, 1, 0); + rp.follows.flags = FOLLOWS_LEFT | FOLLOWS_TOP; + ALCanvasRow* row = new ALCanvasRow(rp); + row->initFromParams(rp); + area->addChild(row); + + ALCanvasView* canvas = surface(); + row->addCanvas(canvas); + row->show(canvas, false); + row->layout(); + + const LLRect region = area->getContentWindowRect(); + UNSCOPED_INFO("the row covers the region across"); + CHECK(row->getRect().getWidth() == region.getWidth()); + UNSCOPED_INFO("and down"); + CHECK(row->getRect().getHeight() == region.getHeight()); + area->die(); + } + + // Moved twice, which is what anybody moving anything does. The second + // move is measured against the surface the first one grew, so a surface + // that forgets what it needs before reading where the root is puts the + // root back higher than it was and grows under it again -- a preview + // that jumps on every drag and walks off the end of what can be + // scrolled to. + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a root moved again and again stays where each move put it", "[alcanvasview]") + { + ALCanvasView* canvas = surface(); + canvas->setLeastSurface(300, 200); + LLPanel* root = place(canvas, 200, 100); + + for (S32 again = 0; again < 3; ++again) + { + root->translate(60, -50); + // Where it was put, from the corner the surface grows out of. + // Its own y is not that: a surface that grows does so downwards, + // so everything on it counts from a new bottom. + const S32 want_left = root->getRect().mLeft; + const S32 want_down = canvas->surfaceHeight() - root->getRect().mTop; + canvas->rememberRoot(); + + const std::string which = "move " + std::to_string(again) + ": "; + UNSCOPED_INFO(which + "it stayed where it was put, across"); + REQUIRE(root->getRect().mLeft == want_left); + UNSCOPED_INFO(which + "and down from the top"); + REQUIRE(canvas->surfaceHeight() - root->getRect().mTop == want_down); + { + INFO(which + "and the surface reaches under it " + where(root->getRect())); + REQUIRE(root->getRect().mBottom >= 0); + REQUIRE(root->getRect().mRight <= canvas->surfaceWidth()); + } + } + canvas->die(); + } + // Asking a row to lay itself out twice gives the same answer twice. // A row that says something different every time it is asked moves what // is under the pointer on every frame, and then nothing on it can be // dragged: the hand goes one way and the thing being dragged goes // another, because the coordinates it is being dragged in have moved. - template<> template<> - void alcanvasview_object::test<15>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a row laid out twice gives the same answer twice", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -554,17 +536,98 @@ namespace tut } else if (pass > 1) { - ensure_equals("pass " + std::to_string(pass) + ": the row is where it was, across", - row->getRect().getWidth(), settled.getWidth()); - ensure_equals("pass " + std::to_string(pass) + ": and down", - row->getRect().getHeight(), settled.getHeight()); - ensure_equals("pass " + std::to_string(pass) + ": and so is what is on it", - canvas->getRect().mLeft, 0); + UNSCOPED_INFO("pass " + std::to_string(pass) + ": the row is where it was, across"); + REQUIRE(row->getRect().getWidth() == settled.getWidth()); + UNSCOPED_INFO("pass " + std::to_string(pass) + ": and down"); + REQUIRE(row->getRect().getHeight() == settled.getHeight()); + UNSCOPED_INFO("pass " + std::to_string(pass) + ": and so is what is on it"); + REQUIRE(canvas->getRect().mLeft == 0); } } area->die(); } + // What is on a surface can move itself. A previewed window dragged by + // its own title bar does: nothing tells the surface, so the surface asks + // -- and without that the next change of size puts the window back where + // the surface last put it, which is a window that cannot be dragged by + // the handle it is meant to be dragged by. + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a root moved by something else stays where it was moved", "[alcanvasview]") + { + ALCanvasView* canvas = surface(); + canvas->setLeastSurface(300, 200); + LLPanel* root = place(canvas, 200, 100); + + // Moved by something that is not the surface. + root->translate(70, -40); + const LLRect moved = root->getRect(); + canvas->refresh(); + UNSCOPED_INFO("it is still where it was moved to, across"); + CHECK(root->getRect().mLeft == moved.mLeft); + + // And it stays there through the change of size that used to undo it. + const S32 from_top = canvas->surfaceHeight() - root->getRect().mTop; + canvas->setLeastSurface(600, 500); + UNSCOPED_INFO("across a change of size, across"); + CHECK(root->getRect().mLeft == moved.mLeft); + UNSCOPED_INFO("and down from the top"); + CHECK(canvas->surfaceHeight() - root->getRect().mTop == from_top); + + // Asking again when nothing has moved changes nothing. + const LLRect settled = root->getRect(); + canvas->refresh(); + UNSCOPED_INFO("asking twice is asking once"); + CHECK(where(root->getRect()) == where(settled)); + canvas->die(); + } + + // A surface reaches to a margin past what is on it, so whatever was last + // dragged towards a far edge is sitting right against that edge -- well + // inside the distance a drag snaps from. Line the window up with the edge + // and the surface grows to keep its margin, which puts the edge back + // against the window, which lines it up again. A drag is offered the + // pointer every frame whether it moved or not, so this runs on its own: a + // window that shakes where it is held, over a surface that grows without + // end under it. + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a root held near the surface's far edge does not snap or drift", "[alcanvasview]") + { + // How far a drag snaps from: LLDragHandle::sSnapMargin. + constexpr S32 SNAP_FROM = 5; + + ALCanvasView* canvas = surface(); + canvas->setLeastSurface(300, 200); + LLPanel* root = place(canvas, 200, 100); + + // Far enough down that the surface has had to grow to hold it, which + // is what brings its bottom edge up against the root's. + root->translate(40, -160); + canvas->rememberRoot(); + { + INFO("the surface reaches just under it " + where(root->getRect())); + REQUIRE(root->getRect().mBottom > 0); + CHECK(root->getRect().mBottom <= SNAP_FROM); + } + + const LLRect held = root->getRect(); + const S32 was = canvas->surfaceHeight(); + for (S32 frame = 0; frame < 8; ++frame) + { + LLRect snapped; + LLView* to = root->findSnapRect(snapped, LLCoordGL(0, -1), + LLView::SNAP_PARENT_AND_SIBLINGS, SNAP_FROM); + UNSCOPED_INFO("frame " + std::to_string(frame) + + ": nothing on a surface has an edge of it to line up with"); + REQUIRE(to == nullptr); + root->setShape(snapped, true); + canvas->refresh(); + } + UNSCOPED_INFO("it is still where it was held"); + CHECK(where(root->getRect()) == where(held)); + UNSCOPED_INFO("and the surface did not grow under it"); + CHECK(canvas->surfaceHeight() == was); + canvas->die(); + } + // The tables under a canvas fold away and come back, which is the region // growing and shrinking under a surface that is sized to what is on it. // A surface pins its own bottom when it resizes itself, so a surface that @@ -572,12 +635,11 @@ namespace tut // again. What hangs over the row is drawn nowhere and clicked on by // nobody: a preview whose window is up there cannot be picked up by its // title bar, however many times it is dragged at. - template<> template<> - void alcanvasview_object::test<18>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: tables folding under a row keep the surface and window inside it", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -612,12 +674,12 @@ namespace tut canvas->refresh(); row->settle(); - ensure(which + "the surface is inside the row, " + where(canvas->getRect()) - + " in " + where(row->getLocalRect()), - row->getLocalRect().contains(canvas->getRect())); - ensure(which + "and the window is on the surface, " + where(root->getRect()) - + " on " + where(canvas->getLocalRect()), - canvas->getLocalRect().contains(root->getRect())); + UNSCOPED_INFO(which + "the surface is inside the row, " + where(canvas->getRect()) + + " in " + where(row->getLocalRect())); + REQUIRE(row->getLocalRect().contains(canvas->getRect())); + UNSCOPED_INFO(which + "and the window is on the surface, " + where(root->getRect()) + + " on " + where(canvas->getLocalRect())); + REQUIRE(canvas->getLocalRect().contains(root->getRect())); } area->die(); } @@ -628,12 +690,11 @@ namespace tut // that was bigger than it, so what was scrolled to becomes what fits -- // and wherever that leaves the window, it has to be somewhere the // pointer can reach it. - template<> template<> - void alcanvasview_object::test<19>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a window's title bar stays reachable with the tables folded away", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -664,8 +725,8 @@ namespace tut bar.mBottom = bar.mTop - 16; return bar; }; - ensure("with the tables there, the title bar can be reached", - area->calcScreenRect().overlaps(title())); + UNSCOPED_INFO("with the tables there, the title bar can be reached"); + CHECK(area->calcScreenRect().overlaps(title())); // The tables folded away: the region grows and the preview fits. area->reshape(ROOM_W, ROOM_H * 2); @@ -673,8 +734,8 @@ namespace tut LLRect window; area->localRectToScreen(area->getContentWindowRect(), &window); - ensure("with them folded away it still can, " + where(title()) + " in " + where(window), - window.overlaps(title())); + UNSCOPED_INFO("with them folded away it still can, " + where(title()) + " in " + where(window)); + CHECK(window.overlaps(title())); area->die(); } @@ -684,12 +745,11 @@ namespace tut // does. It has to land the same whether the surface is bigger than the // region it is shown in or smaller, because folding the tables away // underneath changes which of those it is and nothing else. - template<> template<> - void alcanvasview_object::test<20>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a floater dragged by its title bar moves the same at any region size", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -749,46 +809,53 @@ namespace tut return canvas->childFromPoint(on_the_bar_x, on_the_bar_y, false) == floater && floater->pointInView(on_the_bar_x - r.mLeft, on_the_bar_y - r.mBottom); }; - ensure("a press on the title bar reaches the window", title_reached()); + UNSCOPED_INFO("a press on the title bar reaches the window"); + CHECK(title_reached()); // The tables there: the surface is taller than the region, and what // does not fit is scrolled to. area->reshape(ROOM_W, 200); row->settle(); - ensure("with the tables there it still reaches it", title_reached()); + UNSCOPED_INFO("with the tables there it still reaches it"); + CHECK(title_reached()); S32 was = down_from_top(); drag_down(20); - ensure_equals("with the tables there it moves", down_from_top(), was + 20); + UNSCOPED_INFO("with the tables there it moves"); + CHECK(down_from_top() == was + 20); // Folded away: the region is taller than the surface. area->reshape(ROOM_W, 800); row->settle(); - ensure("with them folded away it still reaches it", title_reached()); + UNSCOPED_INFO("with them folded away it still reaches it"); + CHECK(title_reached()); was = down_from_top(); drag_down(20); - ensure_equals("with them folded away it moves too", down_from_top(), was + 20); + UNSCOPED_INFO("with them folded away it moves too"); + CHECK(down_from_top() == was + 20); area->die(); } // An edit rebuilds what is on the surface, and a rebuild starts by // clearing it. Where the last root was left has to outlive that, or // writing one field sends the window it is about back to the corner. - template<> template<> - void alcanvasview_object::test<21>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: where the root was left outlives the surface being cleared", "[alcanvasview]") { ALCanvasView* canvas = surface(); S32 left = -1; S32 down = -1; - ensure("a surface with nothing on it has no place to keep", - !canvas->keptPlace(left, down)); + UNSCOPED_INFO("a surface with nothing on it has no place to keep"); + CHECK_FALSE(canvas->keptPlace(left, down)); canvas->setLeastSurface(300, 200); LLPanel* root = place(canvas, 200, 100); root->translate(40, -60); canvas->rememberRoot(); - ensure("once something has been put on it, it has", canvas->keptPlace(left, down)); - ensure_equals("across", left, root->getRect().mLeft); - ensure_equals("and down from the top", down, + UNSCOPED_INFO("once something has been put on it, it has"); + CHECK(canvas->keptPlace(left, down)); + UNSCOPED_INFO("across"); + CHECK(left == root->getRect().mLeft); + UNSCOPED_INFO("and down from the top"); + CHECK(down == canvas->surfaceHeight() - root->getRect().mTop); // The rebuild: everything on it goes, and what is built next asks. @@ -797,58 +864,25 @@ namespace tut canvas->clear(); left = -1; down = -1; - ensure("and still has it once the surface is cleared", canvas->keptPlace(left, down)); - ensure_equals("the same place, across", left, was_left); - ensure_equals("and down", down, was_down); + UNSCOPED_INFO("and still has it once the surface is cleared"); + CHECK(canvas->keptPlace(left, down)); + UNSCOPED_INFO("the same place, across"); + CHECK(left == was_left); + UNSCOPED_INFO("and down"); + CHECK(down == was_down); canvas->die(); } - // A region with nothing built on it yet is still the canvas's: the row - // covers it, so a wheel or a drop there reaches a canvas rather than - // falling through to whatever is behind. - template<> template<> - void alcanvasview_object::test<13>() - { - if (!ui.ok()) - { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); - } - - LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); - cp.name = "area"; - cp.rect = LLRect(0, ROOM_H, ROOM_W, 0); - LLScrollContainer* area = LLUICtrlFactory::create(cp); - - LLPanel::Params rp(LLUICtrlFactory::getDefaultParams()); - rp.name = "row"; - rp.rect = LLRect(0, 1, 1, 0); - rp.follows.flags = FOLLOWS_LEFT | FOLLOWS_TOP; - ALCanvasRow* row = new ALCanvasRow(rp); - row->initFromParams(rp); - area->addChild(row); - - ALCanvasView* canvas = surface(); - row->addCanvas(canvas); - row->show(canvas, false); - row->layout(); - - const LLRect region = area->getContentWindowRect(); - ensure_equals("the row covers the region across", row->getRect().getWidth(), region.getWidth()); - ensure_equals("and down", row->getRect().getHeight(), region.getHeight()); - area->die(); - } - // The keyboard given to a surface stays on the surface. A panel handed // the keyboard passes it to the first thing in it that will take it, // which here is a widget in the picture -- and a line editor in the // picture keeps the arrows, so the element they were meant to move // stayed put while the caret in a previewed box went left and right. - template<> template<> - void alcanvasview_object::test<22>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: the keyboard given to a surface stays on the surface", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCanvasView* canvas = surface(); @@ -862,55 +896,64 @@ namespace tut root->addChild(box); canvas->setFocus(true); - ensure("the surface has the keyboard", canvas->hasFocus()); - ensure("and the box in the picture does not", !box->hasFocus()); + UNSCOPED_INFO("the surface has the keyboard"); + CHECK(canvas->hasFocus()); + UNSCOPED_INFO("and the box in the picture does not"); + CHECK_FALSE(box->hasFocus()); // A box in the picture can still be given the keyboard on purpose: // a plain click on a previewed widget is the preview working. box->setFocus(true); - ensure("asked for directly, the box takes it", box->hasFocus()); + UNSCOPED_INFO("asked for directly, the box takes it"); + CHECK(box->hasFocus()); box->setFocus(false); canvas->die(); } // The wheel climbs a ladder that doubles and halves rather than a fixed // amount, and stops at the ends of the zoom's range. - template<> template<> - void alcanvasview_object::test<23>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: steppedZoom climbs a ladder of rungs and stops at its ends", "[alcanvasview]") { ALCanvasView* canvas = surface(); - ensure_equals("life size to start", canvas->zoom(), 1.f); + UNSCOPED_INFO("life size to start"); + CHECK(canvas->zoom() == 1.f); const F32 up[] = { 1.5f, 2.f, 3.f, 4.f, 6.f, 8.f }; for (const F32 step : up) { canvas->setZoom(canvas->steppedZoom(1)); - ensure_equals("up a rung", canvas->zoom(), step); + UNSCOPED_INFO("up a rung"); + REQUIRE(canvas->zoom() == step); } - ensure_equals("and no further", canvas->steppedZoom(1), ALCanvasView::MAX_ZOOM); + UNSCOPED_INFO("and no further"); + CHECK(canvas->steppedZoom(1) == ALCanvasView::MAX_ZOOM); canvas->setZoom(1.f); const F32 down[] = { 0.75f, 0.5f, 0.25f }; for (const F32 step : down) { canvas->setZoom(canvas->steppedZoom(-1)); - ensure_equals("down a rung", canvas->zoom(), step); + UNSCOPED_INFO("down a rung"); + REQUIRE(canvas->zoom() == step); } - ensure_equals("to the floor", canvas->steppedZoom(-1), ALCanvasView::MIN_ZOOM); + UNSCOPED_INFO("to the floor"); + CHECK(canvas->steppedZoom(-1) == ALCanvasView::MIN_ZOOM); // A zoom between rungs goes to the next rung, not past it. canvas->setZoom(2.4f); - ensure_equals("between rungs, up", canvas->steppedZoom(1), 3.f); - ensure_equals("between rungs, down", canvas->steppedZoom(-1), 2.f); + UNSCOPED_INFO("between rungs, up"); + CHECK(canvas->steppedZoom(1) == 3.f); + UNSCOPED_INFO("between rungs, down"); + CHECK(canvas->steppedZoom(-1) == 2.f); canvas->setZoom(100.f); - ensure_equals("asked for too much, it is the ceiling", canvas->zoom(), ALCanvasView::MAX_ZOOM); + UNSCOPED_INFO("asked for too much, it is the ceiling"); + CHECK(canvas->zoom() == ALCanvasView::MAX_ZOOM); canvas->die(); } // The zoom that fits is the largest rung at which what is on the // surface still fits the room the surface is shown in. - template<> template<> - void alcanvasview_object::test<24>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: fittingZoom is the largest rung at which the content fits the room", "[alcanvasview]") { LLPanel::Params rp(LLUICtrlFactory::getDefaultParams()); rp.name = "room"; @@ -923,25 +966,27 @@ namespace tut // 208 by 108 with its margin: three times over fits 700 by 400, // four times over does not. - ensure_equals("three times over", canvas->fittingZoom(), 3.f); + UNSCOPED_INFO("three times over"); + CHECK(canvas->fittingZoom() == 3.f); place(canvas, 1000, 100); - ensure_equals("too wide even at life size, so half", canvas->fittingZoom(), 0.5f); + UNSCOPED_INFO("too wide even at life size, so half"); + CHECK(canvas->fittingZoom() == 0.5f); // A surface is never narrower than 120 of its own units, so a small // thing fits at four times over and not the eight its own size // would allow. place(canvas, 20, 20); - ensure_equals("a small thing stops where the surface's floor does", canvas->fittingZoom(), 4.f); + UNSCOPED_INFO("a small thing stops where the surface's floor does"); + CHECK(canvas->fittingZoom() == 4.f); room->die(); } // A surface that is the one thing in its container fills the container, // so what is on it sits in the middle of the room until it outgrows it. - template<> template<> - void alcanvasview_object::test<25>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a surface alone in its container fills it until it outgrows it", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -955,21 +1000,48 @@ namespace tut canvas->refresh(); const LLRect room = area->getContentWindowRect(); - ensure_equals("as wide as the room", canvas->getRect().getWidth(), room.getWidth()); - ensure_equals("and as tall", canvas->getRect().getHeight(), room.getHeight()); + UNSCOPED_INFO("as wide as the room"); + CHECK(canvas->getRect().getWidth() == room.getWidth()); + UNSCOPED_INFO("and as tall"); + CHECK(canvas->getRect().getHeight() == room.getHeight()); canvas->setZoom(4.f); canvas->refresh(); - ensure("zoomed past the room, the surface is bigger than it " + size(canvas), - canvas->getRect().getWidth() > room.getWidth()); + UNSCOPED_INFO("zoomed past the room, the surface is bigger than it " + size(canvas)); + CHECK(canvas->getRect().getWidth() > room.getWidth()); area->die(); } + // What the canvas says about itself: empty until something is on it, + // and the zoom as a percentage once there is. + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: empty and legend describe the canvas and backdrops go by name", "[alcanvasview]") + { + ALCanvasView* canvas = surface(); + + UNSCOPED_INFO("nothing on it yet"); + REQUIRE(canvas->empty()); + UNSCOPED_INFO("life size reads as a hundred"); + CHECK(canvas->legend() == std::string("100%")); + place(canvas, 200, 100); + UNSCOPED_INFO("something on it now"); + REQUIRE_FALSE(canvas->empty()); + canvas->setZoom(2.f); + UNSCOPED_INFO("twice life size"); + CHECK(canvas->legend() == std::string("200%")); + canvas->die(); + + UNSCOPED_INFO("a backdrop by name"); + CHECK(ALCanvasView::backdropNamed("checker") == ALCanvasView::Backdrop::Checker); + UNSCOPED_INFO("and back"); + CHECK(std::string(ALCanvasView::backdropName(ALCanvasView::Backdrop::Light)) == std::string("light")); + UNSCOPED_INFO("an unknown name is none"); + CHECK(ALCanvasView::backdropNamed("velvet") == ALCanvasView::Backdrop::None); + } + // A surface told to fill a room never draws past it, whatever the zoom // makes of the division: a pixel over brings out a scrollbar, which // shrinks the room, which rounds the other way, and the canvas shakes. - template<> template<> - void alcanvasview_object::test<27>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a surface told to fill a room never draws past it at any zoom", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->fitContent(120, 40); @@ -981,45 +1053,26 @@ namespace tut for (S32 room = 640; room < 652; ++room) { canvas->setLeastSurface(room, room - 96); - ensure("no wider than the room at " + std::to_string(zoom) + " with " + std::to_string(room), - canvas->getRect().getWidth() <= room); - ensure("nor taller", canvas->getRect().getHeight() <= room - 96); - ensure("and not far short of it either", canvas->getRect().getWidth() > room - zoom - 1); + UNSCOPED_INFO("no wider than the room at " + std::to_string(zoom) + " with " + std::to_string(room)); + REQUIRE(canvas->getRect().getWidth() <= room); + UNSCOPED_INFO("nor taller"); + REQUIRE(canvas->getRect().getHeight() <= room - 96); + UNSCOPED_INFO("and not far short of it either"); + REQUIRE(canvas->getRect().getWidth() > room - zoom - 1); } } canvas->die(); } - // What the canvas says about itself: empty until something is on it, - // and the zoom as a percentage once there is. - template<> template<> - void alcanvasview_object::test<26>() - { - ALCanvasView* canvas = surface(); - - ensure("nothing on it yet", canvas->empty()); - ensure_equals("life size reads as a hundred", canvas->legend(), std::string("100%")); - place(canvas, 200, 100); - ensure("something on it now", !canvas->empty()); - canvas->setZoom(2.f); - ensure_equals("twice life size", canvas->legend(), std::string("200%")); - canvas->die(); - - ensure("a backdrop by name", ALCanvasView::backdropNamed("checker") == ALCanvasView::Backdrop::Checker); - ensure_equals("and back", std::string(ALCanvasView::backdropName(ALCanvasView::Backdrop::Light)), std::string("light")); - ensure("an unknown name is none", ALCanvasView::backdropNamed("velvet") == ALCanvasView::Backdrop::None); - } - // Zooming about a point keeps that point of the surface where it was in // the window: the container is scrolled by however much the zoom moved // it. Where the pointer is in the window, read the way the canvas reads // it. - template<> template<> - void alcanvasview_object::test<28>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: zoomAbout keeps the point it zooms about where it was in the window", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -1046,8 +1099,10 @@ namespace tut inWindow(300, 500, before_x, before_y); canvas->zoomAbout(2.f, 300, 500); - ensure_equals("zoomed", canvas->zoom(), 2.f); - ensure_equals("scrolled across to keep the point", area->getDocPosHorizontal(), 300); + UNSCOPED_INFO("zoomed"); + CHECK(canvas->zoom() == 2.f); + UNSCOPED_INFO("scrolled across to keep the point"); + CHECK(area->getDocPosHorizontal() == 300); // The same point of the surface is drawn at twice the distance from // the corner, and the container has been scrolled so that it is @@ -1055,19 +1110,20 @@ namespace tut S32 after_x = 0; S32 after_y = 0; inWindow(600, 1000, after_x, after_y); - ensure_equals("same place across", after_x, before_x); - ensure_equals("same place down", after_y, before_y); + UNSCOPED_INFO("same place across"); + CHECK(after_x == before_x); + UNSCOPED_INFO("same place down"); + CHECK(after_y == before_y); area->die(); } // A pan is the container scrolled by how far the pointer went, and no // further than there is to scroll. - template<> template<> - void alcanvasview_object::test<29>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a pan scrolls the container by the drag and no further than it can", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLScrollContainer::Params cp(LLUICtrlFactory::getDefaultParams()); @@ -1081,83 +1137,123 @@ namespace tut canvas->refresh(); area->getContentWindowRect(); - ensure("not panning yet", !canvas->panning()); + UNSCOPED_INFO("not panning yet"); + CHECK_FALSE(canvas->panning()); canvas->beginPan(100, 100); - ensure("panning", canvas->panning()); + UNSCOPED_INFO("panning"); + CHECK(canvas->panning()); // Dragged left and down: what is seen moves that way, so the // container scrolls right and up. canvas->panTo(50, 80); - ensure_equals("scrolled right by the drag", area->getDocPosHorizontal(), 50); - ensure_equals("and not above the top", area->getDocPosVertical(), 0); + UNSCOPED_INFO("scrolled right by the drag"); + CHECK(area->getDocPosHorizontal() == 50); + UNSCOPED_INFO("and not above the top"); + CHECK(area->getDocPosVertical() == 0); canvas->panTo(150, 130); - ensure_equals("not past the left edge", area->getDocPosHorizontal(), 0); - ensure_equals("scrolled down by the drag", area->getDocPosVertical(), 30); + UNSCOPED_INFO("not past the left edge"); + CHECK(area->getDocPosHorizontal() == 0); + UNSCOPED_INFO("scrolled down by the drag"); + CHECK(area->getDocPosVertical() == 30); canvas->endPan(); - ensure("let go", !canvas->panning()); + UNSCOPED_INFO("let go"); + CHECK_FALSE(canvas->panning()); canvas->panBy(-20, 10); - ensure_equals("panned by amount across", area->getDocPosHorizontal(), 20); - ensure_equals("and down", area->getDocPosVertical(), 40); + UNSCOPED_INFO("panned by amount across"); + CHECK(area->getDocPosHorizontal() == 20); + UNSCOPED_INFO("and down"); + CHECK(area->getDocPosVertical() == 40); - ensure("a plain surface does not drag by the left button", !canvas->dragPans()); + UNSCOPED_INFO("a plain surface does not drag by the left button"); + CHECK_FALSE(canvas->dragPans()); canvas->setDragPans(true); - ensure("until it says so", canvas->panGesture()); + UNSCOPED_INFO("until it says so"); + CHECK(canvas->panGesture()); area->die(); } // A viewport keeps the size it is given, whatever is on it, and what is // on it may be left of and below nought: the pointer reaches it through // the origin and the zoom, and the way back out lands where it went in. - template<> template<> - void alcanvasview_object::test<30>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a viewport keeps its size and maps the pointer through its origin", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setViewport(true); canvas->fitContent(3000, 2000); - ensure_equals("its place's width, not its content's", canvas->getRect().getWidth(), ROOM_W); - ensure_equals("and height", canvas->getRect().getHeight(), ROOM_H); + UNSCOPED_INFO("its place's width, not its content's"); + CHECK(canvas->getRect().getWidth() == ROOM_W); + UNSCOPED_INFO("and height"); + CHECK(canvas->getRect().getHeight() == ROOM_H); canvas->setContentOrigin(300.f, 200.f); Target* node = target(canvas, LLRect(-100, -40, -60, -60)); S32 x = -80, y = -50; canvas->toDrawn(x, y); - ensure_equals("drawn across from the origin", x, 220); - ensure_equals("and down", y, 150); + UNSCOPED_INFO("drawn across from the origin"); + CHECK(x == 220); + UNSCOPED_INFO("and down"); + CHECK(y == 150); canvas->toContent(x, y); - ensure("and back where it was", x == -80 && y == -50); - ensure("clicked where it is drawn", canvas->handleMouseDown(220, 150, MASK_NONE)); - ensure("reached, in its own coordinates", node->clicks.size() == 1 && node->clicks[0] == std::make_pair(20, 10)); + { + INFO("and back where it was"); + CHECK(x == -80); + CHECK(y == -50); + } + UNSCOPED_INFO("clicked where it is drawn"); + CHECK(canvas->handleMouseDown(220, 150, MASK_NONE)); + { + INFO("reached, in its own coordinates"); + REQUIRE(node->clicks.size() == 1); + CHECK(node->clicks[0] == std::make_pair(20, 10)); + } canvas->setZoom(2.f); x = -80; y = -50; canvas->toDrawn(x, y); canvas->handleMouseDown(x, y, MASK_NONE); - ensure("reached at twice the size too", node->clicks.size() == 2 && node->clicks[1] == std::make_pair(20, 10)); + { + INFO("reached at twice the size too"); + REQUIRE(node->clicks.size() == 2); + CHECK(node->clicks[1] == std::make_pair(20, 10)); + } const LLRect seen = canvas->contentInView(); - ensure("what is seen, in the surface's own terms, holds it", seen.mLeft <= -100 && seen.mRight >= -60 && seen.mBottom <= -60 && seen.mTop >= -40); + { + INFO("what is seen, in the surface's own terms, holds it"); + CHECK(seen.mLeft <= -100); + CHECK(seen.mRight >= -60); + CHECK(seen.mBottom <= -60); + CHECK(seen.mTop >= -40); + } canvas->die(); } // A viewport pans by its origin, with no container to scroll; zooms // about the pointer or the middle keeping that point where it was; and // keeps its top left where it was as its place changes height. - template<> template<> - void alcanvasview_object::test<31>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a viewport pans and zooms by its origin with no container to scroll", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCanvasView* canvas = surface(false); gFloaterView->addChild(canvas); canvas->setViewport(true); canvas->beginPan(100, 100); canvas->panTo(160, 70); - ensure("the origin follows the hand", canvas->contentOriginX() == 60.f && canvas->contentOriginY() == -30.f); + { + INFO("the origin follows the hand"); + REQUIRE(canvas->contentOriginX() == 60.f); + CHECK(canvas->contentOriginY() == -30.f); + } canvas->endPan(); canvas->panBy(-10, 5); - ensure("and moves by an amount", canvas->contentOriginX() == 50.f && canvas->contentOriginY() == -25.f); + { + INFO("and moves by an amount"); + REQUIRE(canvas->contentOriginX() == 50.f); + CHECK(canvas->contentOriginY() == -25.f); + } const auto contentAt = [canvas](S32 x, S32 y) { canvas->toContent(x, y); @@ -1165,22 +1261,25 @@ namespace tut }; const std::pair under = contentAt(300, 200); canvas->zoomAbout(2.f, 300, 200); - ensure_equals("zoomed", canvas->zoom(), 2.f); - ensure("what was under the pointer is under it still", contentAt(300, 200) == under); + UNSCOPED_INFO("zoomed"); + CHECK(canvas->zoom() == 2.f); + UNSCOPED_INFO("what was under the pointer is under it still"); + CHECK(contentAt(300, 200) == under); const std::pair middle = contentAt(ROOM_W / 2, ROOM_H / 2); canvas->setZoom(4.f); - ensure("a zoom asked for keeps the middle", contentAt(ROOM_W / 2, ROOM_H / 2) == middle); + UNSCOPED_INFO("a zoom asked for keeps the middle"); + CHECK(contentAt(ROOM_W / 2, ROOM_H / 2) == middle); const std::pair corner = contentAt(0, ROOM_H); canvas->reshape(ROOM_W, ROOM_H + 100); - ensure("the top left stays put as the place grows", contentAt(0, ROOM_H + 100) == corner); + UNSCOPED_INFO("the top left stays put as the place grows"); + CHECK(contentAt(0, ROOM_H + 100) == corner); canvas->die(); } // A drop reaches what is on a surface where it is drawn, zoomed or // panned, bounded or not. - template<> template<> - void alcanvasview_object::test<32>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a drop reaches what is on a surface where it is drawn", "[alcanvasview]") { std::string tip; EAcceptance accept = ACCEPT_NO; @@ -1188,8 +1287,13 @@ namespace tut ALCanvasView* canvas = surface(); Target* node = target(canvas, LLRect(40, 80, 100, 20)); canvas->setZoom(2.f); - ensure("taken", canvas->handleDragAndDrop(100, 60, MASK_NONE, true, DAD_NOTECARD, nullptr, &accept, tip)); - ensure("on a zoomed surface, in its own coordinates", node->drops.size() == 1 && node->drops[0] == std::make_pair(10, 10)); + UNSCOPED_INFO("taken"); + REQUIRE(canvas->handleDragAndDrop(100, 60, MASK_NONE, true, DAD_NOTECARD, nullptr, &accept, tip)); + { + INFO("on a zoomed surface, in its own coordinates"); + REQUIRE(node->drops.size() == 1); + CHECK(node->drops[0] == std::make_pair(10, 10)); + } canvas->die(); } { @@ -1199,8 +1303,13 @@ namespace tut Target* node = target(canvas, LLRect(-50, 0, 0, -50)); accept = ACCEPT_NO; canvas->handleDragAndDrop(160, 70, MASK_NONE, false, DAD_NOTECARD, nullptr, &accept, tip); - ensure("in a viewport, left of and below nought", node->drops.size() == 1 && node->drops[0] == std::make_pair(10, 20)); - ensure("and what it says of it answered", accept == ACCEPT_YES_SINGLE); + { + INFO("in a viewport, left of and below nought"); + REQUIRE(node->drops.size() == 1); + CHECK(node->drops[0] == std::make_pair(10, 20)); + } + UNSCOPED_INFO("and what it says of it answered"); + CHECK(accept == ACCEPT_YES_SINGLE); canvas->die(); } } @@ -1208,12 +1317,11 @@ namespace tut // What is on a zoomed or panned canvas is on the screen where it is // drawn, which is what a popover beside it is placed by -- not where // its own screen rect, at a hundred per cent from the corner, says. - template<> template<> - void alcanvasview_object::test<33>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: screenRectOf places a child where a zoomed and panned canvas draws it", "[alcanvasview]") { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCanvasView* canvas = surface(false); gFloaterView->addChild(canvas); @@ -1226,33 +1334,53 @@ namespace tut const F32 ox = canvas->contentOriginX(); const F32 oy = canvas->contentOriginY(); const LLRect screen = canvas->screenRectOf(node); - ensure_equals("its left, where it is drawn", screen.mLeft, sx + ll_round(ox - 40.f)); - ensure_equals("its right", screen.mRight, sx + ll_round(ox + 40.f)); - ensure_equals("its top", screen.mTop, sy + ll_round(oy + 60.f)); - ensure_equals("its bottom", screen.mBottom, sy + ll_round(oy + 20.f)); - ensure("not where its own rect says", node->calcScreenRect() != screen); + UNSCOPED_INFO("its left, where it is drawn"); + CHECK(screen.mLeft == sx + ll_round(ox - 40.f)); + UNSCOPED_INFO("its right"); + CHECK(screen.mRight == sx + ll_round(ox + 40.f)); + UNSCOPED_INFO("its top"); + CHECK(screen.mTop == sy + ll_round(oy + 60.f)); + UNSCOPED_INFO("its bottom"); + CHECK(screen.mBottom == sy + ll_round(oy + 20.f)); + UNSCOPED_INFO("not where its own rect says"); + CHECK(node->calcScreenRect() != screen); S32 x = -20, y = 10; canvas->toScreen(x, y); - ensure("a point out the same way", x == screen.mLeft && y == screen.mBottom); + { + INFO("a point out the same way"); + CHECK(x == screen.mLeft); + CHECK(y == screen.mBottom); + } canvas->die(); } // A bounded surface's origin is its corner: moved by nothing, and // back at the corner when a viewport stops being one. - template<> template<> - void alcanvasview_object::test<34>() + TEST_CASE_METHOD(alcanvasview_data, "alcanvasview: a bounded surface's origin is its corner", "[alcanvasview]") { ALCanvasView* canvas = surface(); canvas->setContentOrigin(40.f, 40.f); - ensure("not a viewport's to move", canvas->contentOriginX() == 0.f && canvas->contentOriginY() == 0.f); + { + INFO("not a viewport's to move"); + REQUIRE(canvas->contentOriginX() == 0.f); + CHECK(canvas->contentOriginY() == 0.f); + } canvas->setViewport(true); canvas->setContentOrigin(40.f, 40.f); canvas->setViewport(false); - ensure("a corner again", canvas->contentOriginX() == 0.f && canvas->contentOriginY() == 0.f); + { + INFO("a corner again"); + REQUIRE(canvas->contentOriginX() == 0.f); + CHECK(canvas->contentOriginY() == 0.f); + } S32 x = 30, y = 30; canvas->setZoom(2.f); canvas->toContent(x, y); - ensure("and the zoom alone taken out", x == 15 && y == 15); + { + INFO("and the zoom alone taken out"); + CHECK(x == 15); + CHECK(y == 15); + } canvas->die(); } } diff --git a/indra/llui/tests/alchangepeek_test.cpp b/indra/llui/tests/alchangepeek_test.cpp index 9e70599d3e..33d0117827 100644 --- a/indra/llui/tests/alchangepeek_test.cpp +++ b/indra/llui/tests/alchangepeek_test.cpp @@ -35,12 +35,12 @@ #include "alheadlessui_fixture.h" -#include "../test/lltut.h" +#include #include #include -namespace tut +namespace { struct alchangepeek_data { @@ -64,7 +64,7 @@ namespace tut { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCodeEditor::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "source"; @@ -80,109 +80,199 @@ namespace tut static void press(ALChangePeek& peek, const char* name) { ALViewType::as(peek.getChild(name))->press(); } }; - typedef test_group alchangepeek_group; - typedef alchangepeek_group::object alchangepeek_object; - tut::alchangepeek_group alchangepeek_test("alchangepeek"); - - template<> template<> - void alchangepeek_object::test<1>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: changesOf finds each kind of change and changeAt the one a line is in", "[alchangepeek]") { - set_test_name("a text's changes from a saved one: lines changed, put in, taken out at the end; and the change a line is in"); const std::vector saved = { "a", "b", "c", "d", "e" }; const std::vector now = { "a", "B", "c", "x", "d" }; const std::vector found = ALChangePeek::changesOf(saved, now); - ensure_equals("three", found.size(), 3U); - ensure("changed", found[0] == Change{ 1, 1, 1, 1 }); - ensure("put in", found[1] == Change{ 3, 1, 3, 0 }); - ensure("taken out at the end", found[2] == Change{ 5, 0, 4, 1 }); - ensure_equals("by its line", ALChangePeek::changeAt(found, 1), 0); - ensure_equals("a line put in", ALChangePeek::changeAt(found, 3), 1); - ensure_equals("the line before lines taken out at the end", ALChangePeek::changeAt(found, 4), 2); - ensure_equals("a line the same", ALChangePeek::changeAt(found, 2), -1); - ensure("none where nothing changed", ALChangePeek::changesOf(saved, saved).empty()); + UNSCOPED_INFO("three"); + REQUIRE(found.size() == 3U); + UNSCOPED_INFO("changed"); + CHECK(found[0] == Change{ 1, 1, 1, 1 }); + UNSCOPED_INFO("put in"); + CHECK(found[1] == Change{ 3, 1, 3, 0 }); + UNSCOPED_INFO("taken out at the end"); + CHECK(found[2] == Change{ 5, 0, 4, 1 }); + UNSCOPED_INFO("by its line"); + CHECK(ALChangePeek::changeAt(found, 1) == 0); + UNSCOPED_INFO("a line put in"); + CHECK(ALChangePeek::changeAt(found, 3) == 1); + UNSCOPED_INFO("the line before lines taken out at the end"); + CHECK(ALChangePeek::changeAt(found, 4) == 2); + UNSCOPED_INFO("a line the same"); + CHECK(ALChangePeek::changeAt(found, 2) == -1); + UNSCOPED_INFO("none where nothing changed"); + REQUIRE(ALChangePeek::changesOf(saved, saved).empty()); } - template<> template<> - void alchangepeek_object::test<2>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: a peek shows the saved lines in a gap under the change and steps on", "[alchangepeek]") { - set_test_name("peeked at: its lines as saved in a gap under its lines now, said which of how many; stepped through, the gap going with it"); ALCodeEditor& e = make("a\nb\nc\nd\ne", "a\nB\nc\nx\nd"); - ensure("a change", e.peekChange(1)); + UNSCOPED_INFO("a change"); + CHECK(e.peekChange(1)); ALChangePeek& peek = *e.changePeek(); - ensure("open, the first of three", peek.isOpen() && peek.changeShown() == 0 && peek.changeCount() == 3); - ensure("said", peek.said().find("1 of 3") != std::string::npos && peek.said().find("Changed") != std::string::npos); - ensure_equals("the line as saved", peek.savedText()->wholeText(), std::string("b")); - ensure_equals("numbered as it was", peek.savedText()->lineAnnotation(0).number, 2); - ensure_equals("under its line", peek.gapLine(), 2); - ensure("a gap opened there", e.lineAnnotation(2).gap > 0 && e.layout().gapRows(2) > 0); - ensure("drawn in it", peek.getVisible() && peek.getRect().getHeight() > 0); + { + INFO("open, the first of three"); + CHECK(peek.isOpen()); + CHECK(peek.changeShown() == 0); + CHECK(peek.changeCount() == 3); + } + { + INFO("said"); + REQUIRE(peek.said().find("1 of 3") != std::string::npos); + REQUIRE(peek.said().find("Changed") != std::string::npos); + } + UNSCOPED_INFO("the line as saved"); + CHECK(peek.savedText()->wholeText() == std::string("b")); + UNSCOPED_INFO("numbered as it was"); + CHECK(peek.savedText()->lineAnnotation(0).number == 2); + UNSCOPED_INFO("under its line"); + CHECK(peek.gapLine() == 2); + { + INFO("a gap opened there"); + CHECK(e.lineAnnotation(2).gap > 0); + CHECK(e.layout().gapRows(2) > 0); + } + { + INFO("drawn in it"); + CHECK(peek.getVisible()); + CHECK(peek.getRect().getHeight() > 0); + } const LLRect text = e.textRect(); const S32 under = text.mTop - (e.layout().lineTop(1) + e.layout().lineHeight(1) - e.scrollY()); - ensure_equals("its top at its line's foot", peek.getRect().mTop, under); - ensure("above the next line", peek.getRect().mBottom >= text.mTop - (e.layout().lineTop(2) - e.scrollY())); - ensure("across the text", peek.getRect().mLeft == text.mLeft && peek.getRect().mRight == text.mRight); + UNSCOPED_INFO("its top at its line's foot"); + CHECK(peek.getRect().mTop == under); + UNSCOPED_INFO("above the next line"); + CHECK(peek.getRect().mBottom >= text.mTop - (e.layout().lineTop(2) - e.scrollY())); + { + INFO("across the text"); + CHECK(peek.getRect().mLeft == text.mLeft); + CHECK(peek.getRect().mRight == text.mRight); + } press(peek, "next"); - ensure("the line put in", peek.changeShown() == 1 && peek.gapLine() == 4 && !peek.savedText()->getVisible()); - ensure("said as put in", peek.said().find("Added") != std::string::npos); - ensure("the gap gone from where it was", e.lineAnnotation(2).gap == 0); - ensure_equals("the caret on its line", e.caret().line, 3); + { + INFO("the line put in"); + REQUIRE(peek.changeShown() == 1); + REQUIRE(peek.gapLine() == 4); + CHECK_FALSE(peek.savedText()->getVisible()); + } + UNSCOPED_INFO("said as put in"); + REQUIRE(peek.said().find("Added") != std::string::npos); + UNSCOPED_INFO("the gap gone from where it was"); + CHECK(e.lineAnnotation(2).gap == 0); + UNSCOPED_INFO("the caret on its line"); + CHECK(e.caret().line == 3); press(peek, "next"); - ensure("taken out at the end, under the last line", peek.changeShown() == 2 && peek.gapLine() == 5 && peek.savedText()->wholeText() == "e"); - ensure("none after", !peek.step(true) && peek.changeShown() == 2); + { + INFO("taken out at the end, under the last line"); + REQUIRE(peek.changeShown() == 2); + REQUIRE(peek.gapLine() == 5); + CHECK(peek.savedText()->wholeText() == "e"); + } + { + INFO("none after"); + CHECK_FALSE(peek.step(true)); + CHECK(peek.changeShown() == 2); + } press(peek, "previous"); press(peek, "previous"); - ensure("back to the first", peek.changeShown() == 0 && !peek.step(false)); + { + INFO("back to the first"); + CHECK(peek.changeShown() == 0); + CHECK_FALSE(peek.step(false)); + } press(peek, "close"); - ensure("closed, its gap shut", !peek.isOpen() && !peek.getVisible() && e.lineAnnotation(2).gap == 0); + { + INFO("closed, its gap shut"); + CHECK_FALSE(peek.isOpen()); + CHECK_FALSE(peek.getVisible()); + CHECK(e.lineAnnotation(2).gap == 0); + } } - template<> template<> - void alchangepeek_object::test<3>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: take_back reverts a change as one undo step and other edits close it", "[alchangepeek]") { - set_test_name("a change taken back as one step to undo, the peek on to the next; an edit not its own closes it; nothing saved, nothing to peek"); ALCodeEditor& e = make("a\nb\nc\nd\ne", "a\nB\nc\nx\nd"); e.peekChange(1); ALChangePeek& peek = *e.changePeek(); press(peek, "take_back"); - ensure_equals("made as saved", e.wholeText(), std::string("a\nb\nc\nx\nd")); - ensure("on to the line put in, now the first of two", peek.isOpen() && peek.changeShown() == 0 && peek.changeCount() == 2 && peek.gapLine() == 4); - ensure("no gap left behind", e.lineAnnotation(2).gap == 0); + UNSCOPED_INFO("made as saved"); + CHECK(e.wholeText() == std::string("a\nb\nc\nx\nd")); + { + INFO("on to the line put in, now the first of two"); + CHECK(peek.isOpen()); + CHECK(peek.changeShown() == 0); + CHECK(peek.changeCount() == 2); + CHECK(peek.gapLine() == 4); + } + UNSCOPED_INFO("no gap left behind"); + CHECK(e.lineAnnotation(2).gap == 0); press(peek, "next"); - ensure("the last taken back", peek.takeBack() && e.wholeText() == "a\nb\nc\nx\nd\ne"); - ensure("none after it: closed", !peek.isOpen() && e.lineAnnotation(5).gap == 0 && e.lineAnnotation(6).gap == 0); + { + INFO("the last taken back"); + CHECK(peek.takeBack()); + CHECK(e.wholeText() == "a\nb\nc\nx\nd\ne"); + } + { + INFO("none after it: closed"); + CHECK_FALSE(peek.isOpen()); + CHECK(e.lineAnnotation(5).gap == 0); + CHECK(e.lineAnnotation(6).gap == 0); + } e.undo(); - ensure_equals("one step back", e.wholeText(), std::string("a\nb\nc\nx\nd")); - ensure("peeked again", e.peekChange(3) && peek.isOpen()); + UNSCOPED_INFO("one step back"); + CHECK(e.wholeText() == std::string("a\nb\nc\nx\nd")); + { + INFO("peeked again"); + CHECK(e.peekChange(3)); + CHECK(peek.isOpen()); + } e.goTo(ALTextPos(0, 0)); e.insertText("// "); - ensure("an edit not its own closes it", !peek.isOpen() && e.lineAnnotation(4).gap == 0); + { + INFO("an edit not its own closes it"); + CHECK_FALSE(peek.isOpen()); + CHECK(e.lineAnnotation(4).gap == 0); + } e.undoJournal().markNeverSaved(); - ensure("nothing saved to tell a change by", !e.peekChange(3) && !peek.isOpen()); + { + INFO("nothing saved to tell a change by"); + CHECK_FALSE(e.peekChange(3)); + CHECK_FALSE(peek.isOpen()); + } } - template<> template<> - void alchangepeek_object::test<4>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: a press on a change bar opens the peek and Escape closes it", "[alchangepeek]") { - set_test_name("a press on a changed line's bar peeks at its change; Escape in the peek closes it"); ALCodeEditor& e = make("one\ntwo\nthree", "one\n2\nthree"); const LLRect text = e.textRect(); const S32 y = text.mTop - (e.layout().lineTop(1) - e.scrollY()) - e.layout().lineHeight(1) / 2; - ensure("barred", e.lineChanged(1)); - ensure("pressed on the bar", e.handleMouseDown(e.leftEdge() + 1, y, MASK_NONE)); - ensure("open at its change", e.changePeek() && e.changePeek()->isOpen() && e.changePeek()->changeShown() == 0); - ensure("Escape closes it", e.changePeek()->handleKeyHere(KEY_ESCAPE, MASK_NONE) && !e.changePeek()->isOpen()); + UNSCOPED_INFO("barred"); + CHECK(e.lineChanged(1)); + UNSCOPED_INFO("pressed on the bar"); + CHECK(e.handleMouseDown(e.leftEdge() + 1, y, MASK_NONE)); + { + INFO("open at its change"); + REQUIRE(e.changePeek()); + REQUIRE(e.changePeek()->isOpen()); + CHECK(e.changePeek()->changeShown() == 0); + } + { + INFO("Escape closes it"); + REQUIRE(e.changePeek()->handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK_FALSE(e.changePeek()->isOpen()); + } const S32 same = text.mTop - (e.layout().lineTop(0) - e.scrollY()) - e.layout().lineHeight(0) / 2; e.handleMouseDown(e.leftEdge() + 1, same, MASK_NONE); - ensure("a line not barred: no peek", !e.changePeek()->isOpen()); + UNSCOPED_INFO("a line not barred: no peek"); + CHECK_FALSE(e.changePeek()->isOpen()); } - template<> template<> - void alchangepeek_object::test<5>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: changed words are marked in the peek and the editor until it closes", "[alchangepeek]") { - set_test_name("the words that changed marked: on the line as saved in the peek, and on the line now in the editor; let go of as it closes"); ALCodeEditor& e = make("integer count = 1;\nstring s;", "integer total = 2;\nstring s;"); e.peekChange(0); ALChangePeek& peek = *e.changePeek(); @@ -190,27 +280,35 @@ namespace tut return range.begin.line == line && range.begin.column <= begin && range.end.column >= end; }; const std::vector& was = peek.savedText()->decorations(); - ensure("the word as saved", std::any_of(was.begin(), was.end(), [&](const ALCodeEditor::Decoration& d) { return has(d.range, 0, 8, 13); })); - ensure("and its number", std::any_of(was.begin(), was.end(), [&](const ALCodeEditor::Decoration& d) { return has(d.range, 0, 16, 17); })); - ensure("nothing else of the line", std::none_of(was.begin(), was.end(), [&](const ALCodeEditor::Decoration& d) { return has(d.range, 0, 0, 7); })); + UNSCOPED_INFO("the word as saved"); + CHECK(std::any_of(was.begin(), was.end(), [&](const ALCodeEditor::Decoration& d) { return has(d.range, 0, 8, 13); })); + UNSCOPED_INFO("and its number"); + CHECK(std::any_of(was.begin(), was.end(), [&](const ALCodeEditor::Decoration& d) { return has(d.range, 0, 16, 17); })); + UNSCOPED_INFO("nothing else of the line"); + CHECK(std::none_of(was.begin(), was.end(), [&](const ALCodeEditor::Decoration& d) { return has(d.range, 0, 0, 7); })); const std::vector& now = e.highlights(ALCodeEditor::Highlight::Change); - ensure("the word now, in the editor", std::any_of(now.begin(), now.end(), [&](const ALTextRange& r) { return has(r, 0, 8, 13); })); - ensure("only on the line changed", std::none_of(now.begin(), now.end(), [](const ALTextRange& r) { return r.begin.line != 0; })); + UNSCOPED_INFO("the word now, in the editor"); + CHECK(std::any_of(now.begin(), now.end(), [&](const ALTextRange& r) { return has(r, 0, 8, 13); })); + UNSCOPED_INFO("only on the line changed"); + CHECK(std::none_of(now.begin(), now.end(), [](const ALTextRange& r) { return r.begin.line != 0; })); peek.close(); - ensure("let go of", e.highlights(ALCodeEditor::Highlight::Change).empty()); + UNSCOPED_INFO("let go of"); + REQUIRE(e.highlights(ALCodeEditor::Highlight::Change).empty()); // A line put in alone has nothing to mark against. editor->die(); editor = nullptr; ALCodeEditor& f = make("a\nb", "a\nput in\nb"); f.peekChange(1); - ensure("nothing marked", f.highlights(ALCodeEditor::Highlight::Change).empty() && f.changePeek()->savedText()->decorations().empty()); + { + INFO("nothing marked"); + REQUIRE(f.highlights(ALCodeEditor::Highlight::Change).empty()); + REQUIRE(f.changePeek()->savedText()->decorations().empty()); + } } - template<> template<> - void alchangepeek_object::test<6>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: vim's change motions step through changes and an open peek follows", "[alchangepeek]") { - set_test_name("vim's ]c and [c in the source step through its changes since saved, a count as far as there are; a peek open goes along"); ALCodeEditor& e = make("a\nb\nc\nd\ne\nf\ng", "a\nB\nc\nd\nE\nE2\nf\ng\nh"); e.setModalKeymap(std::make_unique()); e.setFocus(true); @@ -223,52 +321,97 @@ namespace tut }; const std::string text = e.wholeText(); typed("]c"); - ensure_equals("the first change", e.caret().line, 1); + UNSCOPED_INFO("the first change"); + CHECK(e.caret().line == 1); typed("]c"); - ensure_equals("the next", e.caret().line, 4); + UNSCOPED_INFO("the next"); + CHECK(e.caret().line == 4); typed("j[c"); - ensure_equals("back, from within it, to its start", e.caret().line, 4); + UNSCOPED_INFO("back, from within it, to its start"); + CHECK(e.caret().line == 4); typed("[c"); - ensure_equals("the one before", e.caret().line, 1); + UNSCOPED_INFO("the one before"); + CHECK(e.caret().line == 1); typed("5]c"); - ensure_equals("a count, as far as there are", e.caret().line, 8); - ensure("nothing typed", e.wholeText() == text); + UNSCOPED_INFO("a count, as far as there are"); + CHECK(e.caret().line == 8); + UNSCOPED_INFO("nothing typed"); + CHECK(e.wholeText() == text); - ensure("peeked at", e.peekChange(1) && e.changePeek()->changeShown() == 0); + { + INFO("peeked at"); + REQUIRE(e.peekChange(1)); + CHECK(e.changePeek()->changeShown() == 0); + } e.goTo(ALTextPos(1, 0)); typed("]c"); - ensure("the peek gone along", e.caret().line == 4 && e.changePeek()->isOpen() && e.changePeek()->changeShown() == 1); - ensure("as the editor steps without vim", e.stepChange(false) && e.caret().line == 1 && e.changePeek()->changeShown() == 0); - ensure("none before the first", !e.stepChange(false) && e.caret().line == 1); + { + INFO("the peek gone along"); + REQUIRE(e.caret().line == 4); + REQUIRE(e.changePeek()->isOpen()); + CHECK(e.changePeek()->changeShown() == 1); + } + { + INFO("as the editor steps without vim"); + REQUIRE(e.stepChange(false)); + REQUIRE(e.caret().line == 1); + CHECK(e.changePeek()->changeShown() == 0); + } + { + INFO("none before the first"); + CHECK_FALSE(e.stepChange(false)); + CHECK(e.caret().line == 1); + } } - template<> template<> - void alchangepeek_object::test<7>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: bars ignore CR LF and lone CR line ends and a save closes the peek", "[alchangepeek]") { - set_test_name("the bars against a saved text ended by CR LF or a lone CR: the lines that differ only; a save closes a peek; its lines tinted as a comparison's"); ALCodeEditor& e = make("a\nb\nc\nd", "a\nB\nc\nd"); e.barChangesSince("a\r\nb\r\nc\r\nd"); - ensure("CR LF: the second line alone", !e.lineChanged(0) && e.lineChanged(1) && !e.lineChanged(2) && !e.lineChanged(3)); + { + INFO("CR LF: the second line alone"); + CHECK_FALSE(e.lineChanged(0)); + CHECK(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(2)); + CHECK_FALSE(e.lineChanged(3)); + } e.barChangesSince("a\rb\rc\rd"); - ensure("a lone CR: the same", !e.lineChanged(0) && e.lineChanged(1) && !e.lineChanged(2) && !e.lineChanged(3)); + { + INFO("a lone CR: the same"); + CHECK_FALSE(e.lineChanged(0)); + CHECK(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(2)); + CHECK_FALSE(e.lineChanged(3)); + } - ensure("peeked", e.peekChange(1) && e.changePeek()->isOpen()); - ensure("tinted as a comparison tints a line taken out", - e.changePeek()->savedText()->lineAnnotation(0).tint == ALDiffColors::get(ALDiffColors::Name::Removed).get()); + { + INFO("peeked"); + REQUIRE(e.peekChange(1)); + CHECK(e.changePeek()->isOpen()); + } + UNSCOPED_INFO("tinted as a comparison tints a line taken out"); + CHECK(e.changePeek()->savedText()->lineAnnotation(0).tint == ALDiffColors::get(ALDiffColors::Name::Removed).get()); e.resetDirty(); - ensure("saved: closed, the gap shut", !e.changePeek()->isOpen() && e.lineAnnotation(2).gap == 0); + { + INFO("saved: closed, the gap shut"); + CHECK_FALSE(e.changePeek()->isOpen()); + CHECK(e.lineAnnotation(2).gap == 0); + } e.selectAll(); e.insertText("a\nB!\nc\nd"); - ensure("peeked again", e.peekChange(1) && e.changePeek()->isOpen()); + { + INFO("peeked again"); + REQUIRE(e.peekChange(1)); + CHECK(e.changePeek()->isOpen()); + } ALTextUndo::SavePoint point = e.undoJournal().savePoint(); e.markSavedAt(point); - ensure("saved at a point: closed", !e.changePeek()->isOpen()); + UNSCOPED_INFO("saved at a point: closed"); + CHECK_FALSE(e.changePeek()->isOpen()); } - template<> template<> - void alchangepeek_object::test<8>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: a peek scrolled partly out of the text is cut to the part in sight", "[alchangepeek]") { - set_test_name("scrolled partly out of the text: its rectangle the part in sight, what is inside it placed as the whole has them, the part cut off not its own"); std::string saved; std::string now; for (S32 n = 0; n < 80; ++n) @@ -277,60 +420,105 @@ namespace tut now += (n ? "\n" : "") + std::string(n >= 40 && n <= 47 ? "changed " : "line ") + std::to_string(n); } ALCodeEditor& e = make(saved, now); - ensure("a change", e.peekChange(40)); + UNSCOPED_INFO("a change"); + CHECK(e.peekChange(40)); ALChangePeek& peek = *e.changePeek(); const LLRect text = e.textRect(); // The change in sight, whole. e.setScrollY(e.layout().lineTop(38)); peek.place(); const S32 tall = peek.getRect().getHeight(); - ensure("whole while in sight", tall > 40 && peek.cutBelow() == 0); + { + INFO("whole while in sight"); + CHECK(tall > 40); + CHECK(peek.cutBelow() == 0); + } // Its top above the text's. e.setScrollY(e.layout().gapTop(peek.gapLine()) + 20); peek.place(); - ensure("cut at the text's top", peek.getRect().mTop == text.mTop && peek.getRect().getHeight() == tall - 20 && peek.cutBelow() == 0); + { + INFO("cut at the text's top"); + CHECK(peek.getRect().mTop == text.mTop); + CHECK(peek.getRect().getHeight() == tall - 20); + CHECK(peek.cutBelow() == 0); + } // Its top a little above the text's foot: the rest below, cut off. e.setScrollY(e.layout().gapTop(peek.gapLine()) - (text.getHeight() - 30)); peek.place(); - ensure("cut at the text's foot", peek.getRect().mBottom == text.mBottom && peek.getRect().getHeight() == 30 && peek.cutBelow() == tall - 30); - ensure("a point below the text not its own", !peek.getRect().pointInRect(text.mLeft + 10, text.mBottom - 1)); + { + INFO("cut at the text's foot"); + CHECK(peek.getRect().mBottom == text.mBottom); + CHECK(peek.getRect().getHeight() == 30); + CHECK(peek.cutBelow() == tall - 30); + } + UNSCOPED_INFO("a point below the text not its own"); + CHECK_FALSE(peek.getRect().pointInRect(text.mLeft + 10, text.mBottom - 1)); const LLRect bar = peek.getChild("close")->getRect(); - ensure("its bar at the top of what is in sight", bar.mTop == 30 - 2); + UNSCOPED_INFO("its bar at the top of what is in sight"); + CHECK(bar.mTop == 30 - 2); } - template<> template<> - void alchangepeek_object::test<9>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: changesSinceSaved is kept until an edit or an undo or a save", "[alchangepeek]") { - set_test_name("the changes since saved worked out once while nothing moves: stepped through, and a peek shown at each, asking nothing again; an edit, an undo, a save each work them out again"); ALCodeEditor& e = make("a\nb\nc\nd\ne", "a\nB\nc\nx\nd"); const auto first = e.changesSinceSaved(); - ensure("known: three", first && first->changes.size() == 3 && first->version == e.document().version()); - ensure("asked again, the same", e.changesSinceSaved() == first); + { + INFO("known: three"); + REQUIRE(first); + REQUIRE(first->changes.size() == 3); + CHECK(first->version == e.document().version()); + } + UNSCOPED_INFO("asked again, the same"); + CHECK(e.changesSinceSaved() == first); e.goTo(ALTextPos(0, 0)); - ensure("stepped to the first", e.stepChange(true) && e.caret().line == 1); - ensure("a peek shown there", e.peekChange(1)); - ensure("and on to the next", e.stepChange(true) && e.caret().line == 3 && e.changePeek()->changeShown() == 1); - ensure("nothing worked out again", e.changesSinceSaved() == first); + { + INFO("stepped to the first"); + CHECK(e.stepChange(true)); + CHECK(e.caret().line == 1); + } + UNSCOPED_INFO("a peek shown there"); + CHECK(e.peekChange(1)); + { + INFO("and on to the next"); + REQUIRE(e.stepChange(true)); + REQUIRE(e.caret().line == 3); + CHECK(e.changePeek()->changeShown() == 1); + } + UNSCOPED_INFO("nothing worked out again"); + CHECK(e.changesSinceSaved() == first); e.goTo(ALTextPos(0, 0)); e.insertText("y"); const auto edited = e.changesSinceSaved(); - ensure("an edit: again, of the text as it is", edited && edited != first && edited->version == e.document().version() && - edited->now.front() == "ya"); - ensure("undone: again, three", e.perform(ALEditorCommand::Undo) && e.changesSinceSaved() != edited && e.changesSinceSaved()->changes.size() == 3); + { + INFO("an edit: again, of the text as it is"); + REQUIRE(edited); + REQUIRE(edited != first); + REQUIRE(edited->version == e.document().version()); + CHECK(edited->now.front() == "ya"); + } + { + INFO("undone: again, three"); + REQUIRE(e.perform(ALEditorCommand::Undo)); + REQUIRE(e.changesSinceSaved() != edited); + REQUIRE(e.changesSinceSaved()->changes.size() == 3); + } e.resetDirty(); - ensure("saved: none", e.changesSinceSaved() && e.changesSinceSaved()->changes.empty()); + { + INFO("saved: none"); + REQUIRE(e.changesSinceSaved()); + REQUIRE(e.changesSinceSaved()->changes.empty()); + } // Changed by its document, which no journal is told of: again. e.document().replace(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "z"); - ensure("the text as it is", e.changesSinceSaved()->now.front() == "za"); + UNSCOPED_INFO("the text as it is"); + CHECK(e.changesSinceSaved()->now.front() == "za"); } - template<> template<> - void alchangepeek_object::test<10>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: an edit above a peek that adds or takes lines closes it leaving no gap", "[alchangepeek]") { - set_test_name("an edit above an open peek that adds or takes lines closes it with no gap left anywhere, its own or the text's end's"); ALCodeEditor& e = make("a\nb\nc\nd\ne", "a\nB\nc\nx\nd"); const auto clear = [&e]() { for (S32 l = 0; l <= e.document().lineCount(); ++l) @@ -342,85 +530,160 @@ namespace tut } return true; }; - ensure("no gap before", clear()); + UNSCOPED_INFO("no gap before"); + CHECK(clear()); // A line put in above it. - ensure("peeked", e.peekChange(1) && e.changePeek()->gapLine() == 2 && e.lineAnnotation(2).gap > 0); + { + INFO("peeked"); + REQUIRE(e.peekChange(1)); + CHECK(e.changePeek()->gapLine() == 2); + CHECK(e.lineAnnotation(2).gap > 0); + } e.goTo(ALTextPos(0, 1)); e.insertText("\n"); - ensure("closed", !e.changePeek()->isOpen()); - ensure("a line put in above: no gap left", clear()); + UNSCOPED_INFO("closed"); + CHECK_FALSE(e.changePeek()->isOpen()); + UNSCOPED_INFO("a line put in above: no gap left"); + CHECK(clear()); // A line taken out above it. - ensure("peeked again", e.peekChange(4) && e.changePeek()->gapLine() == 5 && e.lineAnnotation(5).gap > 0); + { + INFO("peeked again"); + REQUIRE(e.peekChange(4)); + CHECK(e.changePeek()->gapLine() == 5); + CHECK(e.lineAnnotation(5).gap > 0); + } e.goTo(ALTextPos(1, 0)); - ensure("joined", e.handleKeyHere(KEY_BACKSPACE, MASK_NONE) && e.document().lineCount() == 5); - ensure("closed again", !e.changePeek()->isOpen()); - ensure("a line taken out above: no gap left", clear()); + { + INFO("joined"); + CHECK(e.handleKeyHere(KEY_BACKSPACE, MASK_NONE)); + CHECK(e.document().lineCount() == 5); + } + UNSCOPED_INFO("closed again"); + CHECK_FALSE(e.changePeek()->isOpen()); + UNSCOPED_INFO("a line taken out above: no gap left"); + CHECK(clear()); // Under the last line, the text's end's gap, with a line put in. const S32 last = e.document().lineCount() - 1; - ensure("peeked at the end", e.peekChange(last) && e.changePeek()->gapLine() == e.document().lineCount() && - e.lineAnnotation(e.document().lineCount()).gap > 0); + { + INFO("peeked at the end"); + REQUIRE(e.peekChange(last)); + CHECK(e.changePeek()->gapLine() == e.document().lineCount()); + CHECK(e.lineAnnotation(e.document().lineCount()).gap > 0); + } e.goTo(ALTextPos(0, 0)); e.insertText("\n"); - ensure("closed at the end", !e.changePeek()->isOpen()); - ensure("the end's gap shut", clear()); + UNSCOPED_INFO("closed at the end"); + CHECK_FALSE(e.changePeek()->isOpen()); + UNSCOPED_INFO("the end's gap shut"); + CHECK(clear()); } - template<> template<> - void alchangepeek_object::test<11>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: F7 steps the peek and Escape closes it once the selection is gone", "[alchangepeek]") { - set_test_name("from the editor: F7 and Shift-F7 step the peek; Escape closes it once nothing else has it; and under vim"); ALCodeEditor& e = make("a\nb\nc\nd\ne", "a\nB\nc\nx\nd"); e.setFocus(true); - ensure("peeked", e.peekChange(1)); + UNSCOPED_INFO("peeked"); + CHECK(e.peekChange(1)); ALChangePeek& peek = *e.changePeek(); - ensure("F7: the next", e.handleKeyHere(KEY_F7, MASK_NONE) && peek.changeShown() == 1); - ensure("Shift-F7: the one before", e.handleKeyHere(KEY_F7, MASK_SHIFT) && peek.changeShown() == 0); + { + INFO("F7: the next"); + CHECK(e.handleKeyHere(KEY_F7, MASK_NONE)); + CHECK(peek.changeShown() == 1); + } + { + INFO("Shift-F7: the one before"); + CHECK(e.handleKeyHere(KEY_F7, MASK_SHIFT)); + CHECK(peek.changeShown() == 0); + } e.selectAll(); - ensure("Escape lets the selection go first", e.handleKeyHere(KEY_ESCAPE, MASK_NONE) && peek.isOpen() && !e.hasSelection()); - ensure("then closes it", e.handleKeyHere(KEY_ESCAPE, MASK_NONE) && !peek.isOpen() && e.lineAnnotation(2).gap == 0); - ensure("closed, F7 is the text's again", !peek.isOpen()); + { + INFO("Escape lets the selection go first"); + CHECK(e.handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK(peek.isOpen()); + CHECK_FALSE(e.hasSelection()); + } + { + INFO("then closes it"); + CHECK(e.handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK_FALSE(peek.isOpen()); + CHECK(e.lineAnnotation(2).gap == 0); + } + UNSCOPED_INFO("closed, F7 is the text's again"); + CHECK_FALSE(peek.isOpen()); e.setModalKeymap(std::make_unique()); - ensure("peeked under vim", e.peekChange(1) && peek.isOpen()); - ensure("Escape in its normal mode closes it", e.handleKeyHere(KEY_ESCAPE, MASK_NONE) && !peek.isOpen() && e.lineAnnotation(2).gap == 0); + { + INFO("peeked under vim"); + CHECK(e.peekChange(1)); + CHECK(peek.isOpen()); + } + { + INFO("Escape in its normal mode closes it"); + CHECK(e.handleKeyHere(KEY_ESCAPE, MASK_NONE)); + CHECK_FALSE(peek.isOpen()); + CHECK(e.lineAnnotation(2).gap == 0); + } } - template<> template<> - void alchangepeek_object::test<12>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: without a gutter a press at the left edge of a changed line is no peek", "[alchangepeek]") { - set_test_name("without a gutter, a press at the text's left edge on a changed line is the text's, not a peek"); ALCodeEditor& e = make("one\ntwo\nthree", "one\n2\nthree"); e.setShowLineNumbers(false); e.setShowFoldMarkers(false); - ensure_equals("no gutter", e.gutterWidth(), 0); + UNSCOPED_INFO("no gutter"); + CHECK(e.gutterWidth() == 0); const LLRect text = e.textRect(); const S32 y = text.mTop - (e.layout().lineTop(1) - e.scrollY()) - e.layout().lineHeight(1) / 2; - ensure("barred", e.lineChanged(1)); - ensure_equals("no bar to press", e.changeBarAt(e.leftEdge() + 1, y), -1); + UNSCOPED_INFO("barred"); + CHECK(e.lineChanged(1)); + UNSCOPED_INFO("no bar to press"); + CHECK(e.changeBarAt(e.leftEdge() + 1, y) == -1); e.handleMouseDown(e.leftEdge() + 1, y, MASK_NONE); e.handleMouseUp(e.leftEdge() + 1, y, MASK_NONE); - ensure("no peek", !e.changePeek() || !e.changePeek()->isOpen()); + UNSCOPED_INFO("no peek"); + REQUIRE((!e.changePeek() || !e.changePeek()->isOpen())); } - template<> template<> - void alchangepeek_object::test<13>() + TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: a line put back as saved loses its bar once changes are worked out", "[alchangepeek]") { - set_test_name("a line typed on and put back as it was saved loses its bar once the changes are worked out, so the bars, the peek and ]c agree"); ALCodeEditor& e = make("a\nb\nc", "a\nbx\nC"); // The x taken out again: the line is the saved one, though the // history is two steps off the save. e.goTo(ALTextPos(1, 2)); - ensure("put back", e.handleKeyHere(KEY_BACKSPACE, MASK_NONE) && e.document().text() == "a\nb\nC"); - ensure("still unsaved, every line barred", e.isDirty() && e.lineChanged(0) && e.lineChanged(1) && e.lineChanged(2)); - ensure("no change on it to peek at", !e.peekChange(1)); - ensure("its bar gone, and the first line's", !e.lineChanged(1) && !e.lineChanged(0)); - ensure("the changed line's kept", e.lineChanged(2) && e.peekChange(2)); + { + INFO("put back"); + CHECK(e.handleKeyHere(KEY_BACKSPACE, MASK_NONE)); + CHECK(e.document().text() == "a\nb\nC"); + } + { + INFO("still unsaved, every line barred"); + CHECK(e.isDirty()); + CHECK(e.lineChanged(0)); + CHECK(e.lineChanged(1)); + CHECK(e.lineChanged(2)); + } + UNSCOPED_INFO("no change on it to peek at"); + CHECK_FALSE(e.peekChange(1)); + { + INFO("its bar gone, and the first line's"); + CHECK_FALSE(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(0)); + } + { + INFO("the changed line's kept"); + CHECK(e.lineChanged(2)); + CHECK(e.peekChange(2)); + } e.changePeek()->close(); e.goTo(ALTextPos(0, 0)); - ensure("]c goes to the barred line", e.stepChange(true) && e.caret().line == 2); + { + INFO("]c goes to the barred line"); + CHECK(e.stepChange(true)); + CHECK(e.caret().line == 2); + } } } diff --git a/indra/llui/tests/alchoicelist_test.cpp b/indra/llui/tests/alchoicelist_test.cpp index ef0d6d06be..daff57b408 100644 --- a/indra/llui/tests/alchoicelist_test.cpp +++ b/indra/llui/tests/alchoicelist_test.cpp @@ -31,11 +31,11 @@ #include "alheadlessui_fixture.h" -#include "../test/lltut.h" +#include #include -namespace tut +namespace { struct alchoicelist_data { @@ -56,7 +56,7 @@ namespace tut { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALChoiceList::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "choices"; @@ -82,54 +82,68 @@ namespace tut y = text.mTop - list->layout().lineTop(line) - list->layout().rowHeightOf(line, 0) / 2 + list->scrollY(); } }; - typedef test_group alchoicelist_group; - typedef alchoicelist_group::object alchoicelist_object; - tut::alchoicelist_group alchoicelist_instance("alchoicelist"); - template<> template<> - void alchoicelist_object::test<1>() + TEST_CASE_METHOD(alchoicelist_data, "alchoicelist: choices are lines with notes in a column and one is chosen and moved", "[alchoicelist]") { - set_test_name("the choices are lines with their notes in a column after the widest, one chosen and moved along, round the ends or not"); ALChoiceList& v = make(); v.setChoices({ choice("llSay", "(integer, string)"), choice("llShout", ""), choice("llOwnerSay", "(string)") }, 1); - ensure_equals("three lines", v.document().lineCount(), 3); - ensure_equals("a note after a tab; none is a space in the note's face", v.document().line(1), std::string("llShout\t ")); - ensure_equals("chosen as asked", v.chosen(), 1); - ensure("the caret is the chosen line", v.caret() == ALTextPos(1, 0)); + UNSCOPED_INFO("three lines"); + CHECK(v.document().lineCount() == 3); + UNSCOPED_INFO("a note after a tab; none is a space in the note's face"); + CHECK(v.document().line(1) == std::string("llShout\t ")); + UNSCOPED_INFO("chosen as asked"); + CHECK(v.chosen() == 1); + UNSCOPED_INFO("the caret is the chosen line"); + CHECK(v.caret() == ALTextPos(1, 0)); // The notes start where the tab stop is, past the widest text. const F32 note0 = v.layout().xOf(0, 6); const F32 note2 = v.layout().xOf(2, 11); - ensure("the notes line up", std::abs(note0 - note2) < 1.f); - ensure("past the widest text", note2 > v.layout().xOf(2, 10)); - ensure_equals("two styles a line: the note's face on every line, and the ink where one is given", v.styles().size(), size_t(3)); + UNSCOPED_INFO("the notes line up"); + CHECK(std::abs(note0 - note2) < 1.f); + UNSCOPED_INFO("past the widest text"); + CHECK(note2 > v.layout().xOf(2, 10)); + UNSCOPED_INFO("two styles a line: the note's face on every line, and the ink where one is given"); + REQUIRE(v.styles().size() == size_t(3)); v.moveChoice(1, true); - ensure_equals("down one", v.chosen(), 2); + UNSCOPED_INFO("down one"); + CHECK(v.chosen() == 2); v.moveChoice(1, true); - ensure_equals("round the end", v.chosen(), 0); + UNSCOPED_INFO("round the end"); + CHECK(v.chosen() == 0); v.moveChoice(-1, true); - ensure_equals("round the start", v.chosen(), 2); + UNSCOPED_INFO("round the start"); + CHECK(v.chosen() == 2); v.moveChoice(5, false); - ensure_equals("stopped at the end", v.chosen(), 2); + UNSCOPED_INFO("stopped at the end"); + CHECK(v.chosen() == 2); v.moveChoice(-5, false); - ensure_equals("stopped at the start", v.chosen(), 0); + UNSCOPED_INFO("stopped at the start"); + CHECK(v.chosen() == 0); v.choose(7); - ensure_equals("clamped", v.chosen(), 2); + UNSCOPED_INFO("clamped"); + CHECK(v.chosen() == 2); v.setChoices({ choice("Type a line number.", "") }, -1); - ensure_equals("a list only saying something: none chosen", v.chosen(), -1); + UNSCOPED_INFO("a list only saying something: none chosen"); + CHECK(v.chosen() == -1); v.moveChoice(1, false); - ensure_equals("an arrow chooses from the top", v.chosen(), 0); + UNSCOPED_INFO("an arrow chooses from the top"); + CHECK(v.chosen() == 0); v.setChoices({ choice("llSay", "(integer, string)"), choice("llShout", ""), choice("llOwnerSay", "(string)") }, 2); - ensure("as tall as its rows and the padding", v.heightFor(2) > 2 * v.layout().rowHeight() && v.heightFor(2) < v.heightFor(3)); + { + INFO("as tall as its rows and the padding"); + CHECK(v.heightFor(2) > 2 * v.layout().rowHeight()); + CHECK(v.heightFor(2) < v.heightFor(3)); + } v.setChoices({}); - ensure_equals("none: nothing chosen", v.chosen(), -1); + UNSCOPED_INFO("none: nothing chosen"); + CHECK(v.chosen() == -1); v.moveChoice(1, true); - ensure_equals("and nothing to move", v.chosen(), -1); + UNSCOPED_INFO("and nothing to move"); + CHECK(v.chosen() == -1); } - template<> template<> - void alchoicelist_object::test<2>() + TEST_CASE_METHOD(alchoicelist_data, "alchoicelist: a press chooses and a double click picks and the chosen line is shown", "[alchoicelist]") { - set_test_name("a press chooses the line under the mouse without taking the keyboard, a double click picks it, and the chosen line is kept in sight"); ALChoiceList& v = make(300, 40); std::vector many; for (int i = 0; i < 20; ++i) @@ -141,59 +155,80 @@ namespace tut v.onPicked([&picked](S32 index) { picked = index; }); S32 x, y; pointOf(1, x, y); - ensure("the press is taken", v.handleMouseDown(x, y, MASK_NONE)); + UNSCOPED_INFO("the press is taken"); + CHECK(v.handleMouseDown(x, y, MASK_NONE)); v.handleMouseUp(x, y, MASK_NONE); - ensure_equals("the line under it chosen", v.chosen(), 1); - ensure("no keyboard", !v.hasFocus()); - ensure_equals("nothing picked yet", picked, -1); - ensure("the double click is taken", v.handleDoubleClick(x, y, MASK_NONE)); - ensure_equals("picked", picked, 1); + UNSCOPED_INFO("the line under it chosen"); + CHECK(v.chosen() == 1); + UNSCOPED_INFO("no keyboard"); + CHECK_FALSE(v.hasFocus()); + UNSCOPED_INFO("nothing picked yet"); + CHECK(picked == -1); + UNSCOPED_INFO("the double click is taken"); + CHECK(v.handleDoubleClick(x, y, MASK_NONE)); + UNSCOPED_INFO("picked"); + CHECK(picked == 1); v.choose(15); - ensure("scrolled to show it", v.scrollY() > 0 && v.firstVisibleLine() <= 15 && 15 <= v.lastVisibleLine()); + { + INFO("scrolled to show it"); + CHECK(v.scrollY() > 0); + CHECK(v.firstVisibleLine() <= 15); + CHECK(15 <= v.lastVisibleLine()); + } v.choose(0); - ensure_equals("and back", v.scrollY(), 0); + UNSCOPED_INFO("and back"); + CHECK(v.scrollY() == 0); } - template<> template<> - void alchoicelist_object::test<3>() + TEST_CASE_METHOD(alchoicelist_data, "alchoicelist: a mark on any choice gives every line a column for it", "[alchoicelist]") { - set_test_name("a mark before the text -- an icon or a badge -- gives every line a column for it, and none takes it away"); ALChoiceList& v = make(); ALChoiceList::Choice marked = choice("llSay", "(integer, string)"); marked.badge = "f"; v.setChoices({ marked, choice("plain", "") }); const F32 with = v.layout().xOf(0, 0); - ensure("the text starts past the marks' column", with >= static_cast(v.layout().rowHeight())); - ensure("on every line alike", std::abs(v.layout().xOf(1, 0) - with) < 1.f); + UNSCOPED_INFO("the text starts past the marks' column"); + CHECK(with >= static_cast(v.layout().rowHeight())); + UNSCOPED_INFO("on every line alike"); + CHECK(std::abs(v.layout().xOf(1, 0) - with) < 1.f); v.setChoices({ choice("llSay", ""), choice("plain", "") }); - ensure("no marks, no column", v.layout().xOf(0, 0) < 1.f); + UNSCOPED_INFO("no marks, no column"); + CHECK(v.layout().xOf(0, 0) < 1.f); } - template<> template<> - void alchoicelist_object::test<4>() + TEST_CASE_METHOD(alchoicelist_data, "alchoicelist: a choice is one line and its note one column whatever they hold", "[alchoicelist]") { - set_test_name("a choice is one line and its note one column, whatever their words hold"); ALChoiceList& v = make(); v.setChoices({ choice("snippet", "for each\nitem in a list"), choice("odd\tone", "(string)"), choice("last", "") }, 0); - ensure_equals("a line a choice", v.document().lineCount(), 3); - ensure_equals("the note's break shown as a space", v.document().line(0), std::string("snippet\tfor each item in a list")); - ensure_equals("the text's tab too, so its note starts after the whole of it", v.document().line(1), std::string("odd one\t(string)")); - ensure_equals("the choice below is on its own line", v.document().line(2), std::string("last\t ")); - ensure_equals("the words handed back as given", v.choices()[0].note, std::string("for each\nitem in a list")); + UNSCOPED_INFO("a line a choice"); + CHECK(v.document().lineCount() == 3); + UNSCOPED_INFO("the note's break shown as a space"); + CHECK(v.document().line(0) == std::string("snippet\tfor each item in a list")); + UNSCOPED_INFO("the text's tab too, so its note starts after the whole of it"); + CHECK(v.document().line(1) == std::string("odd one\t(string)")); + UNSCOPED_INFO("the choice below is on its own line"); + CHECK(v.document().line(2) == std::string("last\t ")); + UNSCOPED_INFO("the words handed back as given"); + CHECK(v.choices()[0].note == std::string("for each\nitem in a list")); S32 x, y; pointOf(2, x, y); v.handleMouseDown(x, y, MASK_NONE); - ensure_equals("the line pressed is the choice picked", v.chosen(), 2); + UNSCOPED_INFO("the line pressed is the choice picked"); + CHECK(v.chosen() == 2); } - template<> template<> - void alchoicelist_object::test<5>() + TEST_CASE_METHOD(alchoicelist_data, "alchoicelist: no sideways scroll and as a menu hover chooses and a click picks once", "[alchoicelist]") { - set_test_name("never scrolled sideways, and as wide as its choices and their notes; as a menu, chosen as the mouse passes and " - "picked by a click on one, once"); ALChoiceList& v = make(80, 40); v.setChoices({ choice("Insert ';'", ""), choice("Change 'llSya' to 'llSay'", "a longer note") }, 0); - ensure("wider than it is, and still no bar sideways", v.layout().contentWidth() > 80.f && !v.hasHorizontalScrollbar()); - ensure("its width fits the widest choice and note", v.widthFor() >= static_cast(v.layout().contentWidth()) + 2 && - v.widthFor() <= static_cast(v.layout().contentWidth()) + 40); + { + INFO("wider than it is, and still no bar sideways"); + CHECK(v.layout().contentWidth() > 80.f); + CHECK_FALSE(v.hasHorizontalScrollbar()); + } + { + INFO("its width fits the widest choice and note"); + CHECK(v.widthFor() >= static_cast(v.layout().contentWidth()) + 2); + CHECK(v.widthFor() <= static_cast(v.layout().contentWidth()) + 40); + } v.setMenuLike(true); S32 picked = -1, picks = 0; @@ -204,16 +239,26 @@ namespace tut S32 x, y; pointOf(1, x, y); v.handleHover(x, y, MASK_NONE); - ensure("the mouse over one chooses it", v.chosen() == 1 && picked == -1); + { + INFO("the mouse over one chooses it"); + CHECK(v.chosen() == 1); + CHECK(picked == -1); + } S32 x0, y0; pointOf(0, x0, y0); v.handleMouseDown(x0, y0, MASK_NONE); v.handleMouseUp(x, y, MASK_NONE); - ensure("pressed on one and let go on another: nothing picked", picked == -1); + UNSCOPED_INFO("pressed on one and let go on another: nothing picked"); + CHECK(picked == -1); v.handleMouseDown(x, y, MASK_NONE); v.handleMouseUp(x, y, MASK_NONE); - ensure("a click picks", picked == 1 && picks == 1); + { + INFO("a click picks"); + CHECK(picked == 1); + CHECK(picks == 1); + } v.handleDoubleClick(x, y, MASK_NONE); - ensure("a double click's second click picks nothing more", picks == 1); + UNSCOPED_INFO("a double click's second click picks nothing more"); + CHECK(picks == 1); } } diff --git a/indra/llui/tests/alcodecards_test.cpp b/indra/llui/tests/alcodecards_test.cpp index 744bc42706..d0803d5535 100644 --- a/indra/llui/tests/alcodecards_test.cpp +++ b/indra/llui/tests/alcodecards_test.cpp @@ -26,9 +26,9 @@ #include "alcodecards.h" -#include "../test/lltut.h" +#include -namespace tut +namespace { struct alcodecards_data { @@ -41,115 +41,169 @@ namespace tut } }; - typedef test_group alcodecards_group; - typedef alcodecards_group::object alcodecards_object; - alcodecards_group alcodecards_instance("alcodecards"); - - template<> template<> - void alcodecards_object::test<1>() + TEST_CASE_METHOD(alcodecards_data, "alcodecards: compose lays out problems then fixes then a blank line and the head", "[alcodecards]") { - set_test_name("a card: its problems a line or more each, a fix under them, a blank line, the head, and what follows it"); const std::vector problems{ { "unknown global 'x'", LLColor4::red }, { "two\nlines", LLColor4::yellow } }; const std::vector fixes{ fix("Declare 'x'", "declare") }; const std::vector links{ { "declared at line 3", "go", LLSD("line 3") }, { "never said", "", LLSD() } }; const std::string says = "local x: number\nA number.\n" + ALCodeCards::deprecatedNote() + "\ndeclared at line 3"; const ALCodeCards::Composition card = ALCodeCards::compose(problems, fixes, says, links); - ensure_equals("the text", card.text, + UNSCOPED_INFO("the text"); + CHECK(card.text == "unknown global 'x'\ntwo\nlines\nFix: Declare 'x'\n\nlocal x: number\nA number.\n" + ALCodeCards::deprecatedNote() + "\ndeclared at line 3"); - ensure("each problem's lines, the second two", card.problemLines == std::vector>{ { 0, 0 }, { 1, 1 }, { 2, 1 } }); - ensure("the fix on the line after them", card.fixLines.size() == 1 && card.fixLines[0].first == 3 && card.fixLines[0].second.asString() == "declare"); - ensure_equals("the head after the blank line", card.headLine, 5); - ensure("the deprecation noted", card.deprecatedLines == std::vector{ 7 }); - ensure("the link on its line, the one that says nothing there none", - card.linkLines == std::vector>{ { 8, 0 } }); + UNSCOPED_INFO("each problem's lines, the second two"); + CHECK(card.problemLines == std::vector>{ { 0, 0 }, { 1, 1 }, { 2, 1 } }); + { + INFO("the fix on the line after them"); + REQUIRE(card.fixLines.size() == 1); + CHECK(card.fixLines[0].first == 3); + CHECK(card.fixLines[0].second.asString() == "declare"); + } + UNSCOPED_INFO("the head after the blank line"); + CHECK(card.headLine == 5); + UNSCOPED_INFO("the deprecation noted"); + CHECK(card.deprecatedLines == std::vector{ 7 }); + UNSCOPED_INFO("the link on its line, the one that says nothing there none"); + CHECK(card.linkLines == std::vector>{ { 8, 0 } }); } - template<> template<> - void alcodecards_object::test<2>() + TEST_CASE_METHOD(alcodecards_data, "alcodecards: a card of the word alone or of problems alone", "[alcodecards]") { - set_test_name("a card of what the word is alone, and of problems alone"); const ALCodeCards::Composition word = ALCodeCards::compose({}, {}, "ll.Say(channel: number, text: string)", {}); - ensure("the head is the first line", word.headLine == 0 && word.problemLines.empty() && word.text == "ll.Say(channel: number, text: string)"); + { + INFO("the head is the first line"); + CHECK(word.headLine == 0); + REQUIRE(word.problemLines.empty()); + CHECK(word.text == "ll.Say(channel: number, text: string)"); + } const ALCodeCards::Composition wrong = ALCodeCards::compose({ { "expected ')'", LLColor4::red } }, {}, std::string(), {}); - ensure("no head, no blank line", wrong.headLine == -1 && wrong.text == "expected ')'"); + { + INFO("no head, no blank line"); + CHECK(wrong.headLine == -1); + CHECK(wrong.text == "expected ')'"); + } } - template<> template<> - void alcodecards_object::test<3>() + TEST_CASE_METHOD(alcodecards_data, "alcodecards: card widths are limited and a card goes under or above its word", "[alcodecards]") { - set_test_name("a card goes under what it is about, above it where under runs off the text, and within the text's width"); const LLRect text(0, 400, 600, 0); - ensure_equals("no wider than the limit", ALCodeCards::widthLimit(2000), ALCodeCards::MAX_WIDTH); - ensure_equals("nor the text less its room", ALCodeCards::widthLimit(300), 300 - 2 * ALCodeCards::PAD); - ensure_equals("as wide as its widest line and its room", ALCodeCards::width(100, 400), 100 + 2 * ALCodeCards::PAD + 2); - ensure_equals("never narrower than a word", ALCodeCards::width(1, 400), 40); - ensure_equals("nor wider than allowed", ALCodeCards::width(900, 400), 400); + UNSCOPED_INFO("no wider than the limit"); + CHECK(ALCodeCards::widthLimit(2000) == ALCodeCards::MAX_WIDTH); + UNSCOPED_INFO("nor the text less its room"); + CHECK(ALCodeCards::widthLimit(300) == 300 - 2 * ALCodeCards::PAD); + UNSCOPED_INFO("as wide as its widest line and its room"); + CHECK(ALCodeCards::width(100, 400) == 100 + 2 * ALCodeCards::PAD + 2); + UNSCOPED_INFO("never narrower than a word"); + CHECK(ALCodeCards::width(1, 400) == 40); + UNSCOPED_INFO("nor wider than allowed"); + CHECK(ALCodeCards::width(900, 400) == 400); const LLRect under = ALCodeCards::place(LLRect(50, 300, 120, 285), text, 200, 100); - ensure("under the row, at its start", under == LLRect(50, 283, 250, 183)); + UNSCOPED_INFO("under the row, at its start"); + CHECK(under == LLRect(50, 283, 250, 183)); const LLRect above = ALCodeCards::place(LLRect(50, 60, 120, 45), text, 200, 100); - ensure("above it where under would run off", above == LLRect(50, 162, 250, 62)); + UNSCOPED_INFO("above it where under would run off"); + CHECK(above == LLRect(50, 162, 250, 62)); const LLRect right = ALCodeCards::place(LLRect(550, 300, 580, 285), text, 200, 100); - ensure("kept within the text's width", right.mLeft == 400 && right.mRight == 600); + { + INFO("kept within the text's width"); + CHECK(right.mLeft == 400); + CHECK(right.mRight == 600); + } } - template<> template<> - void alcodecards_object::test<4>() + TEST_CASE_METHOD(alcodecards_data, "alcodecards: an answer is taken only for the word and text version asked about", "[alcodecards]") { - set_test_name("the analyzer's answer is kept for the word it was asked about as the text stood, and one come late is not taken"); ALCodeCards cards; const ALTextRange word(ALTextPos(2, 4), ALTextPos(2, 9)); - ensure("not asked", !cards.askedAbout(word, 7)); + UNSCOPED_INFO("not asked"); + CHECK_FALSE(cards.askedAbout(word, 7)); cards.asking(word, 7); - ensure("asked", cards.askedAbout(word, 7)); - ensure("not for another version of the text", !cards.askedAbout(word, 8)); - ensure("an answer for another word is not taken", !cards.heard(ALTextPos(3, 0), 7, "number", {})); - ensure("nor one for the text as it was", !cards.heard(ALTextPos(2, 4), 6, "number", {})); - ensure("an empty one taken, as nothing known of the word", cards.heard(ALTextPos(2, 4), 7, std::string(), {}) && cards.answer().empty()); - ensure("the word's, taken", cards.heard(ALTextPos(2, 4), 7, "local n: number", { { "declared here", "", LLSD(1) } })); - ensure("kept", cards.answer() == "local n: number" && cards.links().size() == 1); + UNSCOPED_INFO("asked"); + CHECK(cards.askedAbout(word, 7)); + UNSCOPED_INFO("not for another version of the text"); + CHECK_FALSE(cards.askedAbout(word, 8)); + UNSCOPED_INFO("an answer for another word is not taken"); + CHECK_FALSE(cards.heard(ALTextPos(3, 0), 7, "number", {})); + UNSCOPED_INFO("nor one for the text as it was"); + CHECK_FALSE(cards.heard(ALTextPos(2, 4), 6, "number", {})); + { + INFO("an empty one taken, as nothing known of the word"); + CHECK(cards.heard(ALTextPos(2, 4), 7, std::string(), {})); + REQUIRE(cards.answer().empty()); + } + UNSCOPED_INFO("the word's, taken"); + CHECK(cards.heard(ALTextPos(2, 4), 7, "local n: number", { { "declared here", "", LLSD(1) } })); + { + INFO("kept"); + CHECK(cards.answer() == "local n: number"); + REQUIRE(cards.links().size() == 1); + } cards.asking(ALTextRange(ALTextPos(4, 0), ALTextPos(4, 2)), 7); - ensure("let go of for the next word", cards.answer().empty() && cards.links().empty()); + { + INFO("let go of for the next word"); + REQUIRE(cards.answer().empty()); + REQUIRE(cards.links().empty()); + } } - template<> template<> - void alcodecards_object::test<5>() + TEST_CASE_METHOD(alcodecards_data, "alcodecards: a signature is kept for its call and placed and scrolled to fit", "[alcodecards]") { - set_test_name("a signature stays for the call on the caret's line, its box above the row within the view, and its label scrolled to the parameter"); ALCodeCards cards; ALCodeCards::Signature sig; sig.label = "f(a, b)"; cards.showSignature(ALTextPos(3, 10), sig); - ensure("about the call", cards.signatureFor(ALTextPos(3, 12))); - ensure("not before where it began", !cards.signatureFor(ALTextPos(3, 9))); - ensure("nor on another line", !cards.signatureFor(ALTextPos(4, 12))); + UNSCOPED_INFO("about the call"); + CHECK(cards.signatureFor(ALTextPos(3, 12))); + UNSCOPED_INFO("not before where it began"); + CHECK_FALSE(cards.signatureFor(ALTextPos(3, 9))); + UNSCOPED_INFO("nor on another line"); + CHECK_FALSE(cards.signatureFor(ALTextPos(4, 12))); cards.hideSignature(); - ensure("gone", !cards.signature() && !cards.signatureFor(ALTextPos(3, 12))); + { + INFO("gone"); + REQUIRE_FALSE(cards.signature()); + CHECK_FALSE(cards.signatureFor(ALTextPos(3, 12))); + } const LLRect view(0, 300, 400, 0); - ensure("above the row", ALCodeCards::signatureBox(200, 40, LLRect(50, 200, 50, 184), view) == LLRect(50, 240, 250, 200)); - ensure("under it where above runs off the top", - ALCodeCards::signatureBox(200, 40, LLRect(50, 280, 50, 264), view) == LLRect(50, 264, 250, 224)); + UNSCOPED_INFO("above the row"); + CHECK(ALCodeCards::signatureBox(200, 40, LLRect(50, 200, 50, 184), view) == LLRect(50, 240, 250, 200)); + UNSCOPED_INFO("under it where above runs off the top"); + CHECK(ALCodeCards::signatureBox(200, 40, LLRect(50, 280, 50, 264), view) == LLRect(50, 264, 250, 224)); const LLRect wide = ALCodeCards::signatureBox(900, 40, LLRect(300, 200, 300, 184), view); - ensure("no wider than the view, and inside it", wide.mLeft == 0 && wide.getWidth() == 400); + { + INFO("no wider than the view, and inside it"); + CHECK(wide.mLeft == 0); + CHECK(wide.getWidth() == 400); + } - ensure_equals("a label that fits is not scrolled", ALCodeCards::labelShift(100.f, 80.f, 150.f), 0.f); - ensure_equals("one that does not, through the parameter", ALCodeCards::labelShift(300.f, 250.f, 150.f), 100.f); - ensure_equals("never past its end", ALCodeCards::labelShift(300.f, 900.f, 150.f), 150.f); - ensure_equals("nor before its start", ALCodeCards::labelShift(300.f, 20.f, 150.f), 0.f); + UNSCOPED_INFO("a label that fits is not scrolled"); + CHECK(ALCodeCards::labelShift(100.f, 80.f, 150.f) == 0.f); + UNSCOPED_INFO("one that does not, through the parameter"); + CHECK(ALCodeCards::labelShift(300.f, 250.f, 150.f) == 100.f); + UNSCOPED_INFO("never past its end"); + CHECK(ALCodeCards::labelShift(300.f, 900.f, 150.f) == 150.f); + UNSCOPED_INFO("nor before its start"); + CHECK(ALCodeCards::labelShift(300.f, 20.f, 150.f) == 0.f); } - template<> template<> - void alcodecards_object::test<6>() + TEST_CASE_METHOD(alcodecards_data, "alcodecards: with no problems a card's first fix is its first line", "[alcodecards]") { - set_test_name("a card of fixes and what the word is, with no problems above them: the first fix the first line, the head under the blank line"); const std::string says = "integer x\n" + ALCodeCards::deprecatedNote(); const ALCodeCards::Composition card = ALCodeCards::compose({}, { fix("Declare 'x'", "declare") }, says, {}); - ensure_equals("no blank line above the fix", card.text, "Fix: Declare 'x'\n\ninteger x\n" + ALCodeCards::deprecatedNote()); - ensure("the fix on the first line", card.fixLines.size() == 1 && card.fixLines[0].first == 0); - ensure_equals("the head after the blank line", card.headLine, 2); - ensure("the deprecation on the line it is on", card.deprecatedLines == std::vector{ 3 }); + UNSCOPED_INFO("no blank line above the fix"); + CHECK(card.text == "Fix: Declare 'x'\n\ninteger x\n" + ALCodeCards::deprecatedNote()); + { + INFO("the fix on the first line"); + REQUIRE(card.fixLines.size() == 1); + CHECK(card.fixLines[0].first == 0); + } + UNSCOPED_INFO("the head after the blank line"); + CHECK(card.headLine == 2); + UNSCOPED_INFO("the deprecation on the line it is on"); + CHECK(card.deprecatedLines == std::vector{ 3 }); } } diff --git a/indra/llui/tests/alcodeeditor_test.cpp b/indra/llui/tests/alcodeeditor_test.cpp index 3f80cb842b..f46273063c 100644 --- a/indra/llui/tests/alcodeeditor_test.cpp +++ b/indra/llui/tests/alcodeeditor_test.cpp @@ -42,13 +42,13 @@ #include "aluistatescope.h" #include "altextviewprobe.h" -#include "../test/lltut.h" +#include #include #include #include -namespace tut +namespace { struct alcodeeditor_data { @@ -71,7 +71,7 @@ namespace tut { if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } ALCodeEditor::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "editor"; @@ -85,7 +85,7 @@ namespace tut return *editor; } - void key(KEY k, MASK m = MASK_NONE) { ensure("key taken", editor->handleKeyHere(k, m)); } + void key(KEY k, MASK m = MASK_NONE) { UNSCOPED_INFO("key taken"); REQUIRE(editor->handleKeyHere(k, m)); } void type(const char* text) { @@ -97,127 +97,187 @@ namespace tut } else { - ensure("char taken", editor->handleUnicodeCharHere(static_cast(static_cast(*c)))); + UNSCOPED_INFO("char taken"); + REQUIRE(editor->handleUnicodeCharHere(static_cast(static_cast(*c)))); } } } }; - // More than TUT's fifty a group holds by default, which runs the first - // fifty and says nothing of the rest: keep this above the highest test. - typedef test_group alcodeeditor_group; - typedef alcodeeditor_group::object alcodeeditor_object; - alcodeeditor_group alcodeeditor_instance("alcodeeditor"); - - template<> template<> - void alcodeeditor_object::test<1>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the gutter takes room from the text and grows with the line count", "[alcodeeditor]") { - set_test_name("the gutter takes room from the text, and grows with the line count"); ALCodeEditor& e = make("a"); const S32 gutter = e.gutterWidth(); - ensure("a gutter", gutter > 0); - ensure_equals("the text starts past it", e.textRect().mLeft, 4 + gutter); + UNSCOPED_INFO("a gutter"); + CHECK(gutter > 0); + UNSCOPED_INFO("the text starts past it"); + CHECK(e.textRect().mLeft == 4 + gutter); std::string many; for (S32 i = 0; i < 1200; ++i) { many += "x\n"; } e.setText(many); - ensure("wider for four digits", e.gutterWidth() > gutter); + UNSCOPED_INFO("wider for four digits"); + CHECK(e.gutterWidth() > gutter); e.setShowLineNumbers(false); - ensure("no numbers leaves the fold column", e.gutterWidth() > 0 && e.gutterWidth() < gutter); + { + INFO("no numbers leaves the fold column"); + CHECK(e.gutterWidth() > 0); + CHECK(e.gutterWidth() < gutter); + } e.setShowFoldMarkers(false); - ensure_equals("no gutter", e.gutterWidth(), 0); - ensure_equals("the text starts at the pad", e.textRect().mLeft, 4); + UNSCOPED_INFO("no gutter"); + CHECK(e.gutterWidth() == 0); + UNSCOPED_INFO("the text starts at the pad"); + CHECK(e.textRect().mLeft == 4); } - template<> template<> - void alcodeeditor_object::test<2>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: brackets match across lines and not inside strings or comments", "[alcodeeditor]") { - set_test_name("brackets match across lines and not inside strings or comments"); ALCodeEditor& e = make("f(a, \"(\", [1,\n 2]) // )\n{ }"); e.highlighter().tokens(2); ALTextPos open, close; e.setCaret(ALTextPos(0, 2)); // just after '(' - ensure("the call's parens", e.matchingBrackets(open, close)); - ensure("opens at 1", open == ALTextPos(0, 1)); - ensure("closes on the next line, past the string and before the comment", close == ALTextPos(1, 3)); + UNSCOPED_INFO("the call's parens"); + REQUIRE(e.matchingBrackets(open, close)); + UNSCOPED_INFO("opens at 1"); + CHECK(open == ALTextPos(0, 1)); + UNSCOPED_INFO("closes on the next line, past the string and before the comment"); + CHECK(close == ALTextPos(1, 3)); e.setCaret(ALTextPos(1, 4)); // just after ')' - ensure("from the closing side", e.matchingBrackets(open, close) && open == ALTextPos(0, 1)); + { + INFO("from the closing side"); + CHECK(e.matchingBrackets(open, close)); + CHECK(open == ALTextPos(0, 1)); + } e.setCaret(ALTextPos(0, 11)); // at '[' - ensure("the list", e.matchingBrackets(open, close) && close == ALTextPos(1, 2)); + { + INFO("the list"); + CHECK(e.matchingBrackets(open, close)); + CHECK(close == ALTextPos(1, 2)); + } e.setCaret(ALTextPos(0, 7)); // inside the string, at its '(' - ensure("nothing inside a string", !e.matchingBrackets(open, close)); + UNSCOPED_INFO("nothing inside a string"); + CHECK_FALSE(e.matchingBrackets(open, close)); e.setCaret(ALTextPos(2, 0)); - ensure("braces", e.matchingBrackets(open, close) && close == ALTextPos(2, 2)); + { + INFO("braces"); + CHECK(e.matchingBrackets(open, close)); + CHECK(close == ALTextPos(2, 2)); + } e.setCaret(ALTextPos(0, 3)); - ensure("no bracket here", !e.matchingBrackets(open, close)); + UNSCOPED_INFO("no bracket here"); + CHECK_FALSE(e.matchingBrackets(open, close)); } - template<> template<> - void alcodeeditor_object::test<3>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: marks follow their lines through edits and decorations slide or go", "[alcodeeditor]") { - set_test_name("marks follow their lines through edits, and decorations slide or go"); ALCodeEditor& e = make("one\ntwo\nthree"); e.setMark(1, ALCodeEditor::Mark::Error); std::vector decorations(1); decorations[0].range = ALTextRange(ALTextPos(2, 0), ALTextPos(2, 5)); e.setDecorations(decorations); e.document().insert(ALTextPos(0, 3), "\nnew"); - ensure("the mark moved down", e.markAt(2) == ALCodeEditor::Mark::Error && e.markAt(1) == ALCodeEditor::Mark::None); - ensure_equals("the decoration slid", e.decorations().front().range.begin.line, 3); + { + INFO("the mark moved down"); + CHECK(e.markAt(2) == ALCodeEditor::Mark::Error); + CHECK(e.markAt(1) == ALCodeEditor::Mark::None); + } + UNSCOPED_INFO("the decoration slid"); + CHECK(e.decorations().front().range.begin.line == 3); e.document().replace(ALTextRange(ALTextPos(3, 0), ALTextPos(3, 2)), "TH"); - ensure("cut through, it went", e.decorations().empty()); + UNSCOPED_INFO("cut through, it went"); + REQUIRE(e.decorations().empty()); e.clearMarks(); - ensure("cleared", e.markAt(2) == ALCodeEditor::Mark::None); + UNSCOPED_INFO("cleared"); + CHECK(e.markAt(2) == ALCodeEditor::Mark::None); e.setMark(99, ALCodeEditor::Mark::Note); - ensure("a mark past the end is nowhere", e.markAt(99) == ALCodeEditor::Mark::None); + UNSCOPED_INFO("a mark past the end is nowhere"); + CHECK(e.markAt(99) == ALCodeEditor::Mark::None); } - template<> template<> - void alcodeeditor_object::test<4>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Control-/ toggles the line comment and goToLine moves the caret", "[alcodeeditor]") { - set_test_name("comments toggle with the grammar's token, and go to line moves the caret"); ALCodeEditor& e = make(" a\n\n b"); e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(2, 3))); - ensure("toggled", e.handleKeyHere('/', MASK_CONTROL)); - ensure_equals("commented at the indentation, blank lines alone", e.text(), std::string(" // a\n\n // b")); + UNSCOPED_INFO("toggled"); + CHECK(e.handleKeyHere('/', MASK_CONTROL)); + UNSCOPED_INFO("commented at the indentation, blank lines alone"); + CHECK(e.text() == std::string(" // a\n\n // b")); e.handleKeyHere('/', MASK_CONTROL); - ensure_equals("and back", e.text(), std::string(" a\n\n b")); + UNSCOPED_INFO("and back"); + CHECK(e.text() == std::string(" a\n\n b")); e.setCaret(ALTextPos(2, 3)); e.handleKeyHere('/', MASK_CONTROL); - ensure_equals("one line", e.text(), std::string(" a\n\n // b")); - ensure("the caret kept its place in the text", e.caret() == ALTextPos(2, 6)); + UNSCOPED_INFO("one line"); + CHECK(e.text() == std::string(" a\n\n // b")); + UNSCOPED_INFO("the caret kept its place in the text"); + CHECK(e.caret() == ALTextPos(2, 6)); e.goToLine(1); - ensure("gone to the line", e.caret() == ALTextPos(1, 0)); + UNSCOPED_INFO("gone to the line"); + CHECK(e.caret() == ALTextPos(1, 0)); ALCodeEditor& x = make("", "xml"); - ensure("no line comment in xml", !x.handleKeyHere('/', MASK_CONTROL)); + UNSCOPED_INFO("no line comment in xml"); + CHECK_FALSE(x.handleKeyHere('/', MASK_CONTROL)); } - template<> template<> - void alcodeeditor_object::test<5>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: blocks fold from the header; the caret opens them and edits slide them", "[alcodeeditor]") { - set_test_name("blocks fold from their header, the caret cannot stay inside, and an edit above keeps them"); ALCodeEditor& e = make("default\n{\n state_entry()\n {\n llSay(0, \"a\");\n }\n}\nx"); const std::vector& regions = e.foldRegions(); - ensure_equals("two blocks", regions.size(), size_t(2)); - ensure("the state, from its header through its closing brace", regions[0].start == 0 && regions[0].end == 6); - ensure("the event, the same", regions[1].start == 2 && regions[1].end == 5); - - ensure("folds", e.foldAt(2)); - ensure("folded", e.isFolded(2)); - ensure("its lines are hidden", e.layout().hidden(3) && e.layout().hidden(5) && !e.layout().hidden(6)); - ensure_equals("five rows in sight", e.layout().totalHeight(), 5 * e.layout().rowHeight()); - ensure("not twice", !e.foldAt(2)); + UNSCOPED_INFO("two blocks"); + REQUIRE(regions.size() == size_t(2)); + { + INFO("the state, from its header through its closing brace"); + CHECK(regions[0].start == 0); + CHECK(regions[0].end == 6); + } + { + INFO("the event, the same"); + CHECK(regions[1].start == 2); + CHECK(regions[1].end == 5); + } + + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(2)); + UNSCOPED_INFO("folded"); + CHECK(e.isFolded(2)); + { + INFO("its lines are hidden"); + CHECK(e.layout().hidden(3)); + CHECK(e.layout().hidden(5)); + CHECK_FALSE(e.layout().hidden(6)); + } + UNSCOPED_INFO("five rows in sight"); + CHECK(e.layout().totalHeight() == 5 * e.layout().rowHeight()); + UNSCOPED_INFO("not twice"); + CHECK_FALSE(e.foldAt(2)); e.setCaret(ALTextPos(4, 0)); - ensure("the caret in it opens it", !e.isFolded(2) && !e.layout().hidden(4)); + { + INFO("the caret in it opens it"); + CHECK_FALSE(e.isFolded(2)); + CHECK_FALSE(e.layout().hidden(4)); + } e.foldAll(); - ensure("everything folded", e.isFolded(0) && e.isFolded(2)); - ensure("only the header and the last line in sight", !e.layout().hidden(0) && e.layout().hidden(1) && e.layout().hidden(6) && !e.layout().hidden(7)); - ensure("the caret left the block", e.caret().line == 0); + { + INFO("everything folded"); + CHECK(e.isFolded(0)); + CHECK(e.isFolded(2)); + } + { + INFO("only the header and the last line in sight"); + CHECK_FALSE(e.layout().hidden(0)); + CHECK(e.layout().hidden(1)); + CHECK(e.layout().hidden(6)); + CHECK_FALSE(e.layout().hidden(7)); + } + UNSCOPED_INFO("the caret left the block"); + CHECK(e.caret().line == 0); e.unfoldAll(); - ensure("everything open", !e.layout().anyHidden()); + UNSCOPED_INFO("everything open"); + CHECK_FALSE(e.layout().anyHidden()); // Command-Option with a bracket on the Mac, Control-Shift elsewhere. #if LL_DARWIN @@ -227,46 +287,76 @@ namespace tut #endif e.setCaret(ALTextPos(4, 0)); key('[', FOLD); - ensure("the key folds the block around the caret", e.isFolded(2) && !e.isFolded(0)); - ensure("and moves the caret to its header", e.caret().line == 2); + { + INFO("the key folds the block around the caret"); + CHECK(e.isFolded(2)); + CHECK_FALSE(e.isFolded(0)); + } + UNSCOPED_INFO("and moves the caret to its header"); + CHECK(e.caret().line == 2); key(']', FOLD); - ensure("the key opens it", !e.isFolded(2)); + UNSCOPED_INFO("the key opens it"); + CHECK_FALSE(e.isFolded(2)); - ensure("folds again", e.foldAt(2)); + UNSCOPED_INFO("folds again"); + CHECK(e.foldAt(2)); e.setCaret(ALTextPos(0, 0)); type("\n"); - ensure("an edit above slides the fold", !e.isFolded(2) && e.isFolded(3) && e.layout().hidden(4) && e.layout().hidden(6)); + { + INFO("an edit above slides the fold"); + CHECK_FALSE(e.isFolded(2)); + CHECK(e.isFolded(3)); + CHECK(e.layout().hidden(4)); + CHECK(e.layout().hidden(6)); + } e.setCaret(ALTextPos(3, 4)); type("_"); - ensure("typing on the header keeps the fold", e.isFolded(3) && e.layout().hidden(4)); + { + INFO("typing on the header keeps the fold"); + CHECK(e.isFolded(3)); + CHECK(e.layout().hidden(4)); + } } - template<> template<> - void alcodeeditor_object::test<6>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: completion offers document and grammar words and puts the choice in", "[alcodeeditor]") { - set_test_name("completion offers the document's words and the grammar's, and puts the choice in"); ALCodeEditor& e = make("integer count;\nllSay(0, co"); e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("open", e.completionOpen()); - ensure("the word from the document", !e.completions().empty() && e.completions()[0].text == "count"); + UNSCOPED_INFO("open"); + CHECK(e.completionOpen()); + { + INFO("the word from the document"); + REQUIRE_FALSE(e.completions().empty()); + CHECK(e.completions()[0].text == "count"); + } type("u"); - ensure("still open, narrowed", e.completionOpen() && e.completions()[0].text == "count"); + { + INFO("still open, narrowed"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "count"); + } key(KEY_RETURN); - ensure("closed by the choice", !e.completionOpen()); - ensure_equals("the choice is in", e.text(), std::string("integer count;\nllSay(0, count")); + UNSCOPED_INFO("closed by the choice"); + CHECK_FALSE(e.completionOpen()); + UNSCOPED_INFO("the choice is in"); + CHECK(e.text() == std::string("integer count;\nllSay(0, count")); type(", de"); - ensure("opens on its own two letters in", e.completionOpen()); + UNSCOPED_INFO("opens on its own two letters in"); + CHECK(e.completionOpen()); bool has_default = false; for (const ALCodeEditor::Completion& c : e.completions()) { has_default = has_default || (c.text == "default" && c.kind == ALSyntaxKind::Control); } - ensure("the grammar's word, with its kind", has_default); + UNSCOPED_INFO("the grammar's word, with its kind"); + CHECK(has_default); key(KEY_ESCAPE); - ensure("escape closes it", !e.completionOpen()); + UNSCOPED_INFO("escape closes it"); + CHECK_FALSE(e.completionOpen()); type(")"); - ensure("a bracket does not open it", !e.completionOpen()); + UNSCOPED_INFO("a bracket does not open it"); + CHECK_FALSE(e.completionOpen()); e.setCompletionProvider([](const ALTextPos&, std::string_view prefix, std::vector& out) { ALCodeEditor::Completion c; @@ -275,15 +365,19 @@ namespace tut out.push_back(c); }); type(" cu"); - ensure("the provider's word", e.completionOpen() && e.completions()[0].text == "custom" && e.completions()[0].detail == "made up"); + { + INFO("the provider's word"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "custom"); + CHECK(e.completions()[0].detail == "made up"); + } key(KEY_TAB); - ensure_equals("tab takes it", e.text(), std::string("integer count;\nllSay(0, count, de) custom")); + UNSCOPED_INFO("tab takes it"); + CHECK(e.text() == std::string("integer count;\nllSay(0, count, de) custom")); } - template<> template<> - void alcodeeditor_object::test<7>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: late completions join for the word asked; a signature follows the call", "[alcodeeditor]") { - set_test_name("a late answer joins the list if it is still about the same word, and a signature follows the call"); ALCodeEditor& e = make("x = ab", "lsl"); e.setCaret(e.document().end()); ALTextPos asked(-1, -1); @@ -293,8 +387,13 @@ namespace tut asked_prefix = std::string(prefix); }); key(' ', MASK_CONTROL); - ensure("asked where the word begins", asked == ALTextPos(0, 4) && asked_prefix == "ab"); - ensure("nothing to show yet", !e.completionOpen()); + { + INFO("asked where the word begins"); + CHECK(asked == ALTextPos(0, 4)); + CHECK(asked_prefix == "ab"); + } + UNSCOPED_INFO("nothing to show yet"); + CHECK_FALSE(e.completionOpen()); std::vector late; ALCodeEditor::Completion one; one.text = "abacus"; @@ -303,75 +402,132 @@ namespace tut one.text = "zzz"; late.push_back(one); e.supplyCompletions(ALTextPos(0, 4), late); - ensure("the answer opens the list", e.completionOpen()); - ensure_equals("with the one that fits", e.completions().size(), size_t(1)); - ensure_equals("which is", e.completions()[0].text, std::string("abacus")); + UNSCOPED_INFO("the answer opens the list"); + CHECK(e.completionOpen()); + UNSCOPED_INFO("with the one that fits"); + REQUIRE(e.completions().size() == size_t(1)); + UNSCOPED_INFO("which is"); + CHECK(e.completions()[0].text == std::string("abacus")); type("a"); - ensure("narrowing keeps it", e.completionOpen() && e.completions()[0].text == "abacus"); - ensure("no new request while narrowing", asked == ALTextPos(0, 4)); + { + INFO("narrowing keeps it"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "abacus"); + } + UNSCOPED_INFO("no new request while narrowing"); + CHECK(asked == ALTextPos(0, 4)); e.supplyCompletions(ALTextPos(0, 9), late); - ensure("an answer about another word is ignored", e.completions().size() == 1); + UNSCOPED_INFO("an answer about another word is ignored"); + REQUIRE(e.completions().size() == 1); key(KEY_ESCAPE); std::vector signature_asks; e.setSignatureRequest([&](const ALTextPos& caret) { signature_asks.push_back(caret); }); type("("); - ensure("an opening bracket asks", signature_asks.size() == 1 && signature_asks.back() == e.caret()); + { + INFO("an opening bracket asks"); + REQUIRE(signature_asks.size() == 1); + CHECK(signature_asks.back() == e.caret()); + } ALCodeEditor::Signature sig; sig.label = "float half(integer n)"; sig.parameters = { { 11, 20 } }; sig.active = 0; e.showSignature(e.caret(), sig); - ensure("shown", e.signatureShown() && e.signature()->label == sig.label); + { + INFO("shown"); + REQUIRE(e.signatureShown()); + CHECK(e.signature()->label == sig.label); + } type("4"); - ensure("typing inside asks nothing: the argument is followed here", signature_asks.size() == 1); + UNSCOPED_INFO("typing inside asks nothing: the argument is followed here"); + REQUIRE(signature_asks.size() == 1); e.hideSignature(); - ensure("hidden", !e.signatureShown()); + UNSCOPED_INFO("hidden"); + CHECK_FALSE(e.signatureShown()); e.showSignature(e.caret(), sig); key(KEY_ESCAPE); - ensure("escape hides it", !e.signatureShown()); + UNSCOPED_INFO("escape hides it"); + CHECK_FALSE(e.signatureShown()); e.showSignature(e.caret(), sig); e.setCaret(ALTextPos(0, 0)); - ensure("the caret leaving the call ends it", !e.signatureShown()); + UNSCOPED_INFO("the caret leaving the call ends it"); + CHECK_FALSE(e.signatureShown()); } - template<> template<> - void alcodeeditor_object::test<8>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the caret's name is asked about and lit and a rename is one undo step", "[alcodeeditor]") { - set_test_name("the name at the caret is asked about; highlights light its places and slide; a rename is one step"); ALCodeEditor& e = make("integer count = 1;\ncount = count + 1;\n", "lsl"); - ensure("nothing to ask without anyone to answer", !e.canPerform(ALEditorCommand::GoToDefinition)); + UNSCOPED_INFO("nothing to ask without anyone to answer"); + CHECK_FALSE(e.canPerform(ALEditorCommand::GoToDefinition)); std::vector> asked; e.setSymbolRequest([&](ALEditorCommand command, const ALTextRange& word) { asked.emplace_back(command, word); }); e.setCaret(ALTextPos(1, 0)); - ensure("on a name, it can be asked", e.canPerform(ALEditorCommand::GoToDefinition) && e.canPerform(ALEditorCommand::Rename)); + { + INFO("on a name, it can be asked"); + CHECK(e.canPerform(ALEditorCommand::GoToDefinition)); + CHECK(e.canPerform(ALEditorCommand::Rename)); + } key(KEY_F12); - ensure_equals("F12 asks", asked.size(), size_t(1)); - ensure("for the definition of the word", asked[0].first == ALEditorCommand::GoToDefinition && asked[0].second == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 5))); + UNSCOPED_INFO("F12 asks"); + REQUIRE(asked.size() == size_t(1)); + { + INFO("for the definition of the word"); + CHECK(asked[0].first == ALEditorCommand::GoToDefinition); + CHECK(asked[0].second == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 5))); + } e.setCaret(ALTextPos(1, 13)); // at the end of the second `count` key(KEY_F12, MASK_SHIFT); - ensure("shift-F12 asks for the references, of the word the caret ends", asked.size() == 2 && asked[1].first == ALEditorCommand::FindReferences && asked[1].second == ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13))); + { + INFO("shift-F12 asks for the references, of the word the caret ends"); + REQUIRE(asked.size() == 2); + CHECK(asked[1].first == ALEditorCommand::FindReferences); + CHECK(asked[1].second == ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13))); + } e.setCaret(ALTextPos(1, 6)); // on the `=` - ensure("not on a name", !e.canPerform(ALEditorCommand::FindReferences)); - ensure("and no request is made", !e.handleKeyHere(KEY_F2, MASK_NONE) && asked.size() == 2); + UNSCOPED_INFO("not on a name"); + CHECK_FALSE(e.canPerform(ALEditorCommand::FindReferences)); + { + INFO("and no request is made"); + CHECK_FALSE(e.handleKeyHere(KEY_F2, MASK_NONE)); + REQUIRE(asked.size() == 2); + } e.setHighlights(ALCodeEditor::Highlight::References, { ALTextRange(ALTextPos(0, 8), ALTextPos(0, 13)), ALTextRange(ALTextPos(1, 0), ALTextPos(1, 5)), ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13)) }); constexpr ALCodeEditor::Highlight refs = ALCodeEditor::Highlight::References; - ensure("lit where a name stands", e.highlighted(refs, ALTextPos(1, 2)) && e.highlighted(refs, ALTextPos(1, 5))); - ensure("not elsewhere", !e.highlighted(refs, ALTextPos(1, 6))); - ensure("nor in another layer", !e.highlighted(ALCodeEditor::Highlight::Search, ALTextPos(1, 2))); + { + INFO("lit where a name stands"); + CHECK(e.highlighted(refs, ALTextPos(1, 2))); + CHECK(e.highlighted(refs, ALTextPos(1, 5))); + } + UNSCOPED_INFO("not elsewhere"); + CHECK_FALSE(e.highlighted(refs, ALTextPos(1, 6))); + UNSCOPED_INFO("nor in another layer"); + CHECK_FALSE(e.highlighted(ALCodeEditor::Highlight::Search, ALTextPos(1, 2))); e.setCaret(ALTextPos(0, 0)); type("\n"); - ensure("a line above slides them all", e.highlights().size() == 3 && e.highlights()[0] == ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13)) && e.highlights()[2].begin.line == 2); + { + INFO("a line above slides them all"); + REQUIRE(e.highlights().size() == 3); + CHECK(e.highlights()[0] == ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13))); + CHECK(e.highlights()[2].begin.line == 2); + } e.setCaret(ALTextPos(2, 6)); type("x"); - ensure("text between them pushes along the ones after it", e.highlights().size() == 3 && e.highlights()[2] == ALTextRange(ALTextPos(2, 9), ALTextPos(2, 14)) && e.highlights()[1].begin == ALTextPos(2, 0)); + { + INFO("text between them pushes along the ones after it"); + REQUIRE(e.highlights().size() == 3); + CHECK(e.highlights()[2] == ALTextRange(ALTextPos(2, 9), ALTextPos(2, 14))); + CHECK(e.highlights()[1].begin == ALTextPos(2, 0)); + } e.setCaret(ALTextPos(2, 2)); type("y"); - ensure_equals("text inside one drops it", e.highlights().size(), size_t(2)); + UNSCOPED_INFO("text inside one drops it"); + REQUIRE(e.highlights().size() == size_t(2)); e.undo(); e.undo(); - ensure_equals("back to the text before", e.text(), std::string("\ninteger count = 1;\ncount = count + 1;\n")); + UNSCOPED_INFO("back to the text before"); + CHECK(e.text() == std::string("\ninteger count = 1;\ncount = count + 1;\n")); // A rename, with the caret inside the last place. e.setCaret(ALTextPos(2, 10)); @@ -380,37 +536,49 @@ namespace tut { ALTextRange(ALTextPos(2, 0), ALTextPos(2, 5)), "total" }, { ALTextRange(ALTextPos(2, 8), ALTextPos(2, 13)), "total" }, }; - ensure("replaced", e.replaceAll(edits)); - ensure_equals("everywhere", e.text(), std::string("\ninteger total = 1;\ntotal = total + 1;\n")); - ensure("the caret keeps its place in the word", e.caret() == ALTextPos(2, 10)); + UNSCOPED_INFO("replaced"); + CHECK(e.replaceAll(edits)); + UNSCOPED_INFO("everywhere"); + CHECK(e.text() == std::string("\ninteger total = 1;\ntotal = total + 1;\n")); + UNSCOPED_INFO("the caret keeps its place in the word"); + CHECK(e.caret() == ALTextPos(2, 10)); e.undo(); - ensure_equals("one step back undoes it all", e.text(), std::string("\ninteger count = 1;\ncount = count + 1;\n")); - ensure("read-only, a rename cannot be asked", (e.setReadOnly(true), !e.canPerform(ALEditorCommand::Rename))); - ensure("but a definition can", e.canPerform(ALEditorCommand::GoToDefinition)); + UNSCOPED_INFO("one step back undoes it all"); + CHECK(e.text() == std::string("\ninteger count = 1;\ncount = count + 1;\n")); + UNSCOPED_INFO("read-only, a rename cannot be asked"); + CHECK((e.setReadOnly(true), !e.canPerform(ALEditorCommand::Rename))); + UNSCOPED_INFO("but a definition can"); + CHECK(e.canPerform(ALEditorCommand::GoToDefinition)); } - template<> template<> - void alcodeeditor_object::test<9>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: identifierAt takes letters and digits and underscores but not a dot", "[alcodeeditor]") { - set_test_name("an identifier is letters, digits and underscores: a dot between names does not join them as it does for a word"); ALCodeEditor& e = make("ll.Say(0, count_2) -- 3.5", "slua"); - ensure("Say alone", e.identifierAt(ALTextPos(0, 4)) == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 6))); - ensure("ll alone", e.identifierAt(ALTextPos(0, 1)) == ALTextRange(ALTextPos(0, 0), ALTextPos(0, 2))); - ensure("nothing on the dot", e.identifierAt(ALTextPos(0, 2)).empty()); - ensure("with digits and underscores", e.identifierAt(ALTextPos(0, 12)) == ALTextRange(ALTextPos(0, 10), ALTextPos(0, 17))); - ensure("not a number", e.identifierAt(ALTextPos(0, 23)).empty()); + UNSCOPED_INFO("Say alone"); + CHECK(e.identifierAt(ALTextPos(0, 4)) == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 6))); + UNSCOPED_INFO("ll alone"); + CHECK(e.identifierAt(ALTextPos(0, 1)) == ALTextRange(ALTextPos(0, 0), ALTextPos(0, 2))); + UNSCOPED_INFO("nothing on the dot"); + REQUIRE(e.identifierAt(ALTextPos(0, 2)).empty()); + UNSCOPED_INFO("with digits and underscores"); + CHECK(e.identifierAt(ALTextPos(0, 12)) == ALTextRange(ALTextPos(0, 10), ALTextPos(0, 17))); + UNSCOPED_INFO("not a number"); + REQUIRE(e.identifierAt(ALTextPos(0, 23)).empty()); e.setCaret(ALTextPos(0, 6)); - ensure("the caret at the end of Say", e.identifierAtCaret() == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 6))); + UNSCOPED_INFO("the caret at the end of Say"); + CHECK(e.identifierAtCaret() == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 6))); std::vector asked; e.setSymbolRequest([&](ALEditorCommand, const ALTextRange& word) { asked.push_back(word); }); key(KEY_F12); - ensure("and asked about Say, not ll", asked.size() == 1 && asked[0].begin == ALTextPos(0, 3)); + { + INFO("and asked about Say, not ll"); + REQUIRE(asked.size() == 1); + CHECK(asked[0].begin == ALTextPos(0, 3)); + } } - template<> template<> - void alcodeeditor_object::test<10>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a dot lists the name's members and a function accepted gets brackets", "[alcodeeditor]") { - set_test_name("a dot opens the members of the name before it, and a function accepted comes with its brackets"); ALCodeEditor& e = make("", "slua"); e.highlighter().ownWords().set("function", { "ll.Say", "ll.Abs", "print" }); std::vector> asked; @@ -418,25 +586,44 @@ namespace tut std::vector signatures; e.setSignatureRequest([&](const ALTextPos& caret) { signatures.push_back(caret); }); type("ll"); - ensure("two letters open the list", e.completionOpen()); + UNSCOPED_INFO("two letters open the list"); + CHECK(e.completionOpen()); type("."); - ensure("the dot keeps it open, on the members", e.completionOpen()); - ensure_equals("the members, by their own names", e.completions().size(), size_t(2)); - ensure_equals("the first", e.completions()[0].text, std::string("Abs")); - ensure("asked after the dot with nothing typed yet", !asked.empty() && asked.back().first == ALTextPos(0, 3) && asked.back().second.empty()); + UNSCOPED_INFO("the dot keeps it open, on the members"); + CHECK(e.completionOpen()); + UNSCOPED_INFO("the members, by their own names"); + REQUIRE(e.completions().size() == size_t(2)); + UNSCOPED_INFO("the first"); + CHECK(e.completions()[0].text == std::string("Abs")); + { + INFO("asked after the dot with nothing typed yet"); + REQUIRE_FALSE(asked.empty()); + CHECK(asked.back().first == ALTextPos(0, 3)); + REQUIRE(asked.back().second.empty()); + } type("S"); - ensure("narrowed to Say", e.completionOpen() && e.completions().size() == 1 && e.completions()[0].text == "Say"); + { + INFO("narrowed to Say"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 1); + CHECK(e.completions()[0].text == "Say"); + } key(KEY_TAB); - ensure_equals("accepted with its brackets", e.text(), std::string("ll.Say()")); - ensure("the caret between them", e.caret() == ALTextPos(0, 7)); - ensure("and the signature asked for", signatures.size() == 1 && signatures[0] == ALTextPos(0, 7)); - ensure("the list is closed", !e.completionOpen()); + UNSCOPED_INFO("accepted with its brackets"); + CHECK(e.text() == std::string("ll.Say()")); + UNSCOPED_INFO("the caret between them"); + CHECK(e.caret() == ALTextPos(0, 7)); + { + INFO("and the signature asked for"); + REQUIRE(signatures.size() == 1); + CHECK(signatures[0] == ALTextPos(0, 7)); + } + UNSCOPED_INFO("the list is closed"); + CHECK_FALSE(e.completionOpen()); } - template<> template<> - void alcodeeditor_object::test<11>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a function's parameters go in as placeholders for Tab to move through", "[alcodeeditor]") { - set_test_name("a function accepted puts its parameters in as placeholders, tab moving through them"); ALCodeEditor& e = make("", "lsl"); e.setCompletionProvider([](const ALTextPos&, std::string_view prefix, std::vector& out) { if (std::string_view("llSay").substr(0, prefix.size()) == prefix) @@ -449,46 +636,83 @@ namespace tut } }); std::vector names = ALCodeEditor::parameterNames("(channel: number, msg: string) -> ()"); - ensure("Luau's names", names.size() == 2 && names[0] == "channel" && names[1] == "msg"); + { + INFO("Luau's names"); + REQUIRE(names.size() == 2); + CHECK(names[0] == "channel"); + CHECK(names[1] == "msg"); + } names = ALCodeEditor::parameterNames("float half(integer n)"); - ensure("LSL's names", names.size() == 1 && names[0] == "n"); + { + INFO("LSL's names"); + REQUIRE(names.size() == 1); + CHECK(names[0] == "n"); + } names = ALCodeEditor::parameterNames("() ll.Say(number Channel, string Text)", "Say"); - ensure("after the name, past a return type with brackets of its own", names.size() == 2 && names[0] == "Channel" && names[1] == "Text"); - ensure("none for none", ALCodeEditor::parameterNames("function").empty() && ALCodeEditor::parameterNames("() -> ()").empty()); + { + INFO("after the name, past a return type with brackets of its own"); + REQUIRE(names.size() == 2); + CHECK(names[0] == "Channel"); + CHECK(names[1] == "Text"); + } + { + INFO("none for none"); + REQUIRE(ALCodeEditor::parameterNames("function").empty()); + REQUIRE(ALCodeEditor::parameterNames("() -> ()").empty()); + } type("llS"); - ensure("offered", e.completionOpen()); + UNSCOPED_INFO("offered"); + CHECK(e.completionOpen()); key(KEY_TAB); - ensure_equals("the call with its parameters", e.text(), std::string("llSay(channel, msg)")); - ensure_equals("the first selected", e.selectedText(), std::string("channel")); - ensure_equals("two placeholders", e.placeholders().size(), size_t(2)); + UNSCOPED_INFO("the call with its parameters"); + CHECK(e.text() == std::string("llSay(channel, msg)")); + UNSCOPED_INFO("the first selected"); + CHECK(e.selectedText() == std::string("channel")); + UNSCOPED_INFO("two placeholders"); + REQUIRE(e.placeholders().size() == size_t(2)); type("0"); - ensure_equals("typed over", e.text(), std::string("llSay(0, msg)")); + UNSCOPED_INFO("typed over"); + CHECK(e.text() == std::string("llSay(0, msg)")); key(KEY_TAB); - ensure_equals("tab selects the next", e.selectedText(), std::string("msg")); + UNSCOPED_INFO("tab selects the next"); + CHECK(e.selectedText() == std::string("msg")); type("\"hi\""); key(KEY_TAB); - ensure(llformat("past the last, after the call (not %d:%d), and done (%d left)", e.caret().line, e.caret().column, (int)e.placeholders().size()), - e.caret() == ALTextPos(0, 14) && e.placeholders().empty()); - ensure_equals("the text", e.text(), std::string("llSay(0, \"hi\")")); + { + INFO(llformat("past the last, after the call (not %d:%d), and done (%d left)", e.caret().line, e.caret().column, (int)e.placeholders().size())); + CHECK(e.caret() == ALTextPos(0, 14)); + REQUIRE(e.placeholders().empty()); + } + UNSCOPED_INFO("the text"); + CHECK(e.text() == std::string("llSay(0, \"hi\")")); } - template<> template<> - void alcodeeditor_object::test<12>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a snippet follows the caret's indentation and Tab ends at $0", "[alcodeeditor]") { - set_test_name("a snippet goes in at the caret's indentation, its placeholders tabbed through in number order and $0 last"); ALCodeEditor& e = make(" x;", "lsl"); e.setCaret(ALTextPos(0, 6)); e.insertSnippet("if (${1:condition})\n{\n $0\n}\n${2}cost: $$5"); - ensure_equals("the lines follow the caret's indentation", e.text(), + UNSCOPED_INFO("the lines follow the caret's indentation"); + CHECK(e.text() == std::string(" x;if (condition)\n {\n \n }\n cost: $5")); - ensure_equals("two placeholders", e.placeholders().size(), size_t(2)); - ensure_equals("the first selected", e.selectedText(), std::string("condition")); + UNSCOPED_INFO("two placeholders"); + REQUIRE(e.placeholders().size() == size_t(2)); + UNSCOPED_INFO("the first selected"); + CHECK(e.selectedText() == std::string("condition")); type("a > b"); key(KEY_TAB); - ensure("the empty second, at the last line's start", e.caret() == ALTextPos(4, 4) && !e.hasSelection()); + { + INFO("the empty second, at the last line's start"); + CHECK(e.caret() == ALTextPos(4, 4)); + CHECK_FALSE(e.hasSelection()); + } key(KEY_TAB); - ensure(llformat("then where $0 stood (not %d:%d)", e.caret().line, e.caret().column), e.caret() == ALTextPos(2, 8) && e.placeholders().empty()); + { + INFO(llformat("then where $0 stood (not %d:%d)", e.caret().line, e.caret().column)); + CHECK(e.caret() == ALTextPos(2, 8)); + REQUIRE(e.placeholders().empty()); + } ALCodeEditor& f = make("", "lsl"); f.setCompletionProvider([](const ALTextPos&, std::string_view prefix, std::vector& out) { @@ -503,16 +727,17 @@ namespace tut } }); type("fo"); - ensure("offered", f.completionOpen()); + UNSCOPED_INFO("offered"); + CHECK(f.completionOpen()); key(KEY_TAB); - ensure_equals("the snippet in place of the prefix, a level of it a tab as the editor types", f.text(), std::string("for (i = 0; i < n; ++i)\n{\n\t\n}")); - ensure_equals("its first placeholder selected", f.selectedText(), std::string("i = 0")); + UNSCOPED_INFO("the snippet in place of the prefix, a level of it a tab as the editor types"); + CHECK(f.text() == std::string("for (i = 0; i < n; ++i)\n{\n\t\n}")); + UNSCOPED_INFO("its first placeholder selected"); + CHECK(f.selectedText() == std::string("i = 0")); } - template<> template<> - void alcodeeditor_object::test<13>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: an inlay hint makes room in its line and edits keep or drop it", "[alcodeeditor]") { - set_test_name("an inlay makes room in the line, the caret sits past a word before the text and before a word after it, and an edit keeps or drops it"); ALCodeEditor& e = make("f(a, b)\nlocal x = 1\n", "slua"); ALTextLayout& lay = e.layout(); const F32 was_a = lay.xOf(0, 2); @@ -524,67 +749,93 @@ namespace tut hints.push_back({ ALTextPos(0, 5), "count:", true }); hints.push_back({ ALTextPos(1, 7), ": number", false }); e.setInlayHints(hints); - ensure_equals("three kept", e.inlayHints().size(), size_t(3)); + UNSCOPED_INFO("three kept"); + REQUIRE(e.inlayHints().size() == size_t(3)); const F32 now_a = lay.xOf(0, 2); - ensure("a sits past the word before it", now_a > was_a); - ensure("b further still, past two words", lay.xOf(0, 5) - was_b > now_a - was_a); - ensure("the line is wider", lay.line(0).width > lay.xOf(0, 7) - 1.f && lay.line(0).width > 7.f * lay.columnWidth()); - ensure("x's end sits before the word after it", lay.xOf(1, 7) == was_x); - ensure("and what follows x moved past the word", lay.xOf(1, 8) > was_after_x); + UNSCOPED_INFO("a sits past the word before it"); + CHECK(now_a > was_a); + UNSCOPED_INFO("b further still, past two words"); + CHECK(lay.xOf(0, 5) - was_b > now_a - was_a); + { + INFO("the line is wider"); + CHECK(lay.line(0).width > lay.xOf(0, 7) - 1.f); + CHECK(lay.line(0).width > 7.f * lay.columnWidth()); + } + UNSCOPED_INFO("x's end sits before the word after it"); + CHECK(lay.xOf(1, 7) == was_x); + UNSCOPED_INFO("and what follows x moved past the word"); + CHECK(lay.xOf(1, 8) > was_after_x); // A click in the word lands at the column it belongs to. - ensure_equals("a click in name: is a's column", lay.columnAt(0, 0, (was_a + now_a) * 0.5f, false), 2); + UNSCOPED_INFO("a click in name: is a's column"); + CHECK(lay.columnAt(0, 0, (was_a + now_a) * 0.5f, false) == 2); // A gap glyph is not a text glyph. S32 gaps = 0; for (const ALTextLayout::Glyph& g : lay.line(0).glyphs) gaps += g.inlay >= 0; - ensure_equals("two gaps on the first line", gaps, 2); + UNSCOPED_INFO("two gaps on the first line"); + CHECK(gaps == 2); // Typing at the end of x extends x, and the type after it moves along. e.setCaret(ALTextPos(1, 7)); type("y"); - ensure("the type hint moved past what was typed", e.inlayHints()[2].at == ALTextPos(1, 8)); + UNSCOPED_INFO("the type hint moved past what was typed"); + CHECK(e.inlayHints()[2].at == ALTextPos(1, 8)); // Typing at the start of a stays before a, and the name before it stays too. e.setCaret(ALTextPos(0, 2)); type("z"); - ensure("the name stays where the argument starts", e.inlayHints()[0].at == ALTextPos(0, 2)); - ensure("the next name moved along", e.inlayHints()[1].at == ALTextPos(0, 6)); + UNSCOPED_INFO("the name stays where the argument starts"); + CHECK(e.inlayHints()[0].at == ALTextPos(0, 2)); + UNSCOPED_INFO("the next name moved along"); + CHECK(e.inlayHints()[1].at == ALTextPos(0, 6)); // Deleting through the second line's name takes its hint. e.setSelection(ALTextRange(ALTextPos(1, 6), ALTextPos(1, 9))); key(KEY_DELETE); - ensure_equals("the cut one is gone", e.inlayHints().size(), size_t(2)); + UNSCOPED_INFO("the cut one is gone"); + REQUIRE(e.inlayHints().size() == size_t(2)); // A line put in above moves them down. e.setCaret(ALTextPos(0, 0)); key(KEY_RETURN); - ensure("moved to the second line", e.inlayHints()[0].at == ALTextPos(1, 2) && e.inlayHints()[1].at == ALTextPos(1, 6)); - ensure("and laid out there", lay.xOf(1, 2) > was_a); + { + INFO("moved to the second line"); + CHECK(e.inlayHints()[0].at == ALTextPos(1, 2)); + CHECK(e.inlayHints()[1].at == ALTextPos(1, 6)); + } + UNSCOPED_INFO("and laid out there"); + CHECK(lay.xOf(1, 2) > was_a); // Replaced whole, with none. e.setInlayHints({}); - ensure("none left", e.inlayHints().empty()); - ensure("the line back to its width", lay.xOf(1, 2) == was_a); + UNSCOPED_INFO("none left"); + REQUIRE(e.inlayHints().empty()); + UNSCOPED_INFO("the line back to its width"); + CHECK(lay.xOf(1, 2) == was_a); } - template<> template<> - void alcodeeditor_object::test<14>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: semantic tokens slide past an insertion and go when cut through", "[alcodeeditor]") { - set_test_name("semantic tokens ride the edits: slid past an insertion, dropped when cut through"); ALCodeEditor& e = make("integer n = 1;\nx = n;", "lsl"); std::vector tokens; tokens.push_back({ ALTextRange(ALTextPos(0, 8), ALTextPos(0, 9)), ALSyntaxKind::Variable, false }); tokens.push_back({ ALTextRange(ALTextPos(1, 4), ALTextPos(1, 5)), ALSyntaxKind::Variable, true }); e.setSemanticTokens(tokens); - ensure_equals("two", e.semanticTokens().size(), size_t(2)); + UNSCOPED_INFO("two"); + REQUIRE(e.semanticTokens().size() == size_t(2)); e.setCaret(ALTextPos(1, 0)); type("y"); - ensure("the second slid along", e.semanticTokens()[1].range.begin == ALTextPos(1, 5) && e.semanticTokens()[1].range.end == ALTextPos(1, 6)); - ensure("the first stayed", e.semanticTokens()[0].range.begin == ALTextPos(0, 8)); + { + INFO("the second slid along"); + CHECK(e.semanticTokens()[1].range.begin == ALTextPos(1, 5)); + CHECK(e.semanticTokens()[1].range.end == ALTextPos(1, 6)); + } + UNSCOPED_INFO("the first stayed"); + CHECK(e.semanticTokens()[0].range.begin == ALTextPos(0, 8)); e.setCaret(ALTextPos(0, 9)); key(KEY_BACKSPACE); - ensure_equals("the first, cut through, is gone", e.semanticTokens().size(), size_t(1)); - ensure("the survivor is the struck one", e.semanticTokens()[0].strike); + UNSCOPED_INFO("the first, cut through, is gone"); + REQUIRE(e.semanticTokens().size() == size_t(1)); + UNSCOPED_INFO("the survivor is the struck one"); + CHECK(e.semanticTokens()[0].strike); } - template<> template<> - void alcodeeditor_object::test<15>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the hover card shows a word's docs until the mouse or a key moves", "[alcodeeditor]") { - set_test_name("the hover card shows what is said of a word, in the editor's face then the reading face, with its links; and goes when the mouse or the keys move on"); ALCodeEditor& e = make("llSay(0, x);\nsecond line\n"); e.setHoverProvider([](const ALTextPos&, std::string_view word, std::string& text) { if (word != "llSay") @@ -598,28 +849,57 @@ namespace tut S32 row; const S32 x = text.mLeft + static_cast(e.layout().xOf(0, 2, &row)) + 1; const S32 y = text.mTop - e.layout().rowHeight() / 2; - ensure("nothing shown yet", !e.cardShown()); - ensure("the rest is taken", e.handleToolTip(x, y, MASK_NONE)); - ensure("the card is shown", e.cardShown()); + UNSCOPED_INFO("nothing shown yet"); + CHECK_FALSE(e.cardShown()); + UNSCOPED_INFO("the rest is taken"); + CHECK(e.handleToolTip(x, y, MASK_NONE)); + UNSCOPED_INFO("the card is shown"); + CHECK(e.cardShown()); ALTextView& card = *e.card(); - ensure_equals("with the words", card.document().lineCount(), 3); - ensure("the head in the editor's face", !card.styles().empty() && card.styles()[0].font == e.getFont() && card.styles()[0].range.begin == ALTextPos(0, 0)); - ensure("the deprecation in a colour", card.styles().size() >= 2 && card.styles()[1].color.has_value()); - ensure("the wiki page a link", card.substitutions().size() == 1 && card.substitutions()[0].link && card.substitutions()[0].url == "https://wiki.secondlife.com/wiki/LlSay"); - ensure("the card is a child, over the text, and not the keyboard's", card.getParent() == &e && card.getRect().getWidth() > 40 && !card.hasFocus()); - ensure("under the word", card.getRect().mTop < y); + UNSCOPED_INFO("with the words"); + CHECK(card.document().lineCount() == 3); + { + INFO("the head in the editor's face"); + REQUIRE_FALSE(card.styles().empty()); + CHECK(card.styles()[0].font == e.getFont()); + CHECK(card.styles()[0].range.begin == ALTextPos(0, 0)); + } + { + INFO("the deprecation in a colour"); + REQUIRE(card.styles().size() >= 2); + REQUIRE(card.styles()[1].color.has_value()); + } + { + INFO("the wiki page a link"); + REQUIRE(card.substitutions().size() == 1); + CHECK(card.substitutions()[0].link); + CHECK(card.substitutions()[0].url == "https://wiki.secondlife.com/wiki/LlSay"); + } + { + INFO("the card is a child, over the text, and not the keyboard's"); + CHECK(card.getParent() == &e); + CHECK(card.getRect().getWidth() > 40); + CHECK_FALSE(card.hasFocus()); + } + UNSCOPED_INFO("under the word"); + CHECK(card.getRect().mTop < y); // The mouse on the word keeps it; on the card keeps it; elsewhere lets it go. e.handleHover(x + 3, y, MASK_NONE); - ensure("kept on the word", e.cardShown()); + UNSCOPED_INFO("kept on the word"); + CHECK(e.cardShown()); const LLRect where = card.getRect(); e.handleHover(where.getCenterX(), where.getCenterY(), MASK_NONE); - ensure("kept on the card", e.cardShown()); + UNSCOPED_INFO("kept on the card"); + CHECK(e.cardShown()); e.handleHover(text.mRight - 5, text.mBottom + 5, MASK_NONE); - ensure("gone off both", !e.cardShown()); + UNSCOPED_INFO("gone off both"); + CHECK_FALSE(e.cardShown()); e.handleToolTip(x, y, MASK_NONE); - ensure("shown again", e.cardShown()); + UNSCOPED_INFO("shown again"); + CHECK(e.cardShown()); key(KEY_RIGHT); - ensure("a key hides it", !e.cardShown()); + UNSCOPED_INFO("a key hides it"); + CHECK_FALSE(e.cardShown()); // A problem's message comes the same way, from the gutter, the // mouse moved there. ALCodeEditor::Decoration d; @@ -629,32 +909,41 @@ namespace tut const S32 gutter_y = text.mTop - e.layout().rowHeight() - e.layout().rowHeight() / 2; e.handleHover(e.leftEdge() + 2, gutter_y, MASK_NONE); e.handleToolTip(e.leftEdge() + 2, gutter_y, MASK_NONE); - ensure("the gutter's card", e.cardShown() && e.card()->text() == "something is wrong here"); + { + INFO("the gutter's card"); + REQUIRE(e.cardShown()); + CHECK(e.card()->text() == "something is wrong here"); + } } - template<> template<> - void alcodeeditor_object::test<16>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a string literal is one thing to the mouse and says its size", "[alcodeeditor]") { - set_test_name("a string literal is one thing to the mouse wherever in it, and says its size"); ALCodeEditor& e = make("default { state_entry() { llSay(0, \"Hello, Avatar!\"); } }\n"); // Anywhere in the literal, quotes and all, is the same literal -- // the comma and the space in it as much as the words. const ALTextRange whole = e.stringAt(ALTextPos(0, 41)); - ensure_equals("the quotes are in it", e.document().text(whole), std::string("\"Hello, Avatar!\"")); + UNSCOPED_INFO("the quotes are in it"); + CHECK(e.document().text(whole) == std::string("\"Hello, Avatar!\"")); for (S32 column = whole.begin.column; column < whole.end.column; ++column) { - ensure("the same literal from every byte of it", e.stringAt(ALTextPos(0, column)) == whole); + UNSCOPED_INFO("the same literal from every byte of it"); + REQUIRE(e.stringAt(ALTextPos(0, column)) == whole); } - ensure("and nothing outside it", e.stringAt(ALTextPos(0, whole.end.column)).empty()); - ensure("nor of a name", e.stringAt(ALTextPos(0, 0)).empty()); + UNSCOPED_INFO("and nothing outside it"); + REQUIRE(e.stringAt(ALTextPos(0, whole.end.column)).empty()); + UNSCOPED_INFO("nor of a name"); + REQUIRE(e.stringAt(ALTextPos(0, 0)).empty()); // Its size is of what it holds, not of what it is written as. - ensure_equals("the bytes between the quotes", e.stringSize(whole), std::string("14 bytes")); + UNSCOPED_INFO("the bytes between the quotes"); + CHECK(e.stringSize(whole) == std::string("14 bytes")); // An escape is one byte, and what it is written as is said too. ALCodeEditor& f = make("default { state_entry() { llSay(0, \"a\\tb\"); } }\n"); const ALTextRange escaped = f.stringAt(ALTextPos(0, 36)); - ensure_equals("the whole literal", f.document().text(escaped), std::string("\"a\\tb\"")); - ensure_equals("the tab counted once, and the source said", f.stringSize(escaped), std::string("3 bytes, 6 in source")); + UNSCOPED_INFO("the whole literal"); + CHECK(f.document().text(escaped) == std::string("\"a\\tb\"")); + UNSCOPED_INFO("the tab counted once, and the source said"); + CHECK(f.stringSize(escaped) == std::string("3 bytes, 6 in source")); // And the card comes up on the comma, where no word is. const LLRect text = f.textRect(); @@ -662,15 +951,19 @@ namespace tut const F32 x = f.layout().xOf(0, 36, &row); const S32 at_x = static_cast(static_cast(text.mLeft) + x) + 1; const S32 at_y = text.mTop - f.layout().rowHeight() / 2; - ensure("taken", f.handleToolTip(at_x, at_y, MASK_NONE)); - ensure("a card, and wider than one word wrapped", f.cardShown() && f.card()->getRect().getWidth() > 60); - ensure("saying what it is and how big", f.card()->document().lineCount() == 2); + UNSCOPED_INFO("taken"); + CHECK(f.handleToolTip(at_x, at_y, MASK_NONE)); + { + INFO("a card, and wider than one word wrapped"); + REQUIRE(f.cardShown()); + CHECK(f.card()->getRect().getWidth() > 60); + } + UNSCOPED_INFO("saying what it is and how big"); + CHECK(f.card()->document().lineCount() == 2); } - template<> template<> - void alcodeeditor_object::test<17>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: what floats over the text wears the editor's colours not the skin's", "[alcodeeditor]") { - set_test_name("what the editor floats over its text wears the editor's colours, not the skin's"); ALCodeEditor& e = make("integer count;\nllSay(0, co"); // A script theme of colours the skin has never heard of, so that // anything taken from the colour table cannot match by accident. @@ -680,126 +973,167 @@ namespace tut e.setTextColor(ink); e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("the list is open", e.completionOpen()); + UNSCOPED_INFO("the list is open"); + CHECK(e.completionOpen()); ALChoiceList* list = e.findChild("completions"); - ensure("and is a child of the editor", list != nullptr); - ensure("its ground is the one every surface sits on", list->backgroundColor() == ALSurface::ground(paper, ink)); - ensure("its text is the editor's own", list->textColor() == ink); - ensure("and the row it points at is the one band", list->selectionColor() == ALSurface::chosen(paper, ink)); + UNSCOPED_INFO("and is a child of the editor"); + REQUIRE(list != nullptr); + UNSCOPED_INFO("its ground is the one every surface sits on"); + CHECK(list->backgroundColor() == ALSurface::ground(paper, ink)); + UNSCOPED_INFO("its text is the editor's own"); + CHECK(list->textColor() == ink); + UNSCOPED_INFO("and the row it points at is the one band"); + CHECK(list->selectionColor() == ALSurface::chosen(paper, ink)); // A surface is opaque whatever it was mixed from: the text behind // a card is the one thing the card must not show. - ensure_equals("opaque from translucent parts", ALSurface::ground(LLColor4(0.f, 0.f, 0.f, 0.2f), LLColor4(1.f, 1.f, 1.f, 0.5f)).mV[VALPHA], 1.f); + UNSCOPED_INFO("opaque from translucent parts"); + CHECK(ALSurface::ground(LLColor4(0.f, 0.f, 0.f, 0.2f), LLColor4(1.f, 1.f, 1.f, 0.5f)).mV[VALPHA] == 1.f); } - template<> template<> - void alcodeeditor_object::test<18>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: which blanks a line marks depends on the whitespace mode", "[alcodeeditor]") { - set_test_name("which blanks a line shows as marks, by the mode the editor is in"); // A leading tab, a space after the comma, a no-break space where a // space was meant, and two spaces trailing the line. // 0 1234567 8 9 01 2 34 567 89 const std::string source = "\tllSay(0, \"a" + std::string("\xc2\xa0") + "b\"); "; ALCodeEditor& e = make(source.c_str()); - ensure_equals("the line is as counted", source.size(), size_t(20)); + UNSCOPED_INFO("the line is as counted"); + REQUIRE(source.size() == size_t(20)); // Under the selection is the mode it starts in, and with nothing // selected that is nothing at all. - ensure("nothing selected, nothing marked", e.blanksOn(0).empty()); + UNSCOPED_INFO("nothing selected, nothing marked"); + REQUIRE(e.blanksOn(0).empty()); e.setShowWhitespace(ALCodeEditor::Whitespace::All); std::vector all = e.blanksOn(0); - ensure_equals("every blank on the line", all.size(), size_t(5)); - ensure("the leading tab", all[0].begin == 0 && all[0].end == 1 && all[0].kind == '\t'); - ensure("the space after the comma", all[1].begin == 9 && all[1].kind == ' '); + UNSCOPED_INFO("every blank on the line"); + REQUIRE(all.size() == size_t(5)); + { + INFO("the leading tab"); + CHECK(all[0].begin == 0); + CHECK(all[0].end == 1); + CHECK(all[0].kind == '\t'); + } + { + INFO("the space after the comma"); + CHECK(all[1].begin == 9); + CHECK(all[1].kind == ' '); + } // The no-break space is two bytes and one mark, and is not a space. - ensure("the no-break space, whole", all[2].begin == 12 && all[2].end == 14 && all[2].kind == 'n'); - ensure("the two that trail", all[3].begin == 18 && all[4].begin == 19); + { + INFO("the no-break space, whole"); + CHECK(all[2].begin == 12); + CHECK(all[2].end == 14); + CHECK(all[2].kind == 'n'); + } + { + INFO("the two that trail"); + CHECK(all[3].begin == 18); + CHECK(all[4].begin == 19); + } e.setShowWhitespace(ALCodeEditor::Whitespace::Trailing); std::vector tail = e.blanksOn(0); - ensure_equals("only what the line ends in", tail.size(), size_t(2)); - ensure("which is where the line ends", tail[0].begin == 18 && tail[1].begin == 19); + UNSCOPED_INFO("only what the line ends in"); + REQUIRE(tail.size() == size_t(2)); + { + INFO("which is where the line ends"); + CHECK(tail[0].begin == 18); + CHECK(tail[1].begin == 19); + } e.setShowWhitespace(ALCodeEditor::Whitespace::None); - ensure("and none is none", e.blanksOn(0).empty()); + UNSCOPED_INFO("and none is none"); + REQUIRE(e.blanksOn(0).empty()); // Under the selection: what the selection covers, and no more. e.setShowWhitespace(ALCodeEditor::Whitespace::Selection); e.setSelection(ALTextRange(ALTextPos(0, 8), ALTextPos(0, 12))); std::vector picked = e.blanksOn(0); - ensure_equals("the one blank in it", picked.size(), size_t(1)); - ensure("the space after the comma", picked[0].begin == 9); + UNSCOPED_INFO("the one blank in it"); + REQUIRE(picked.size() == size_t(1)); + UNSCOPED_INFO("the space after the comma"); + CHECK(picked[0].begin == 9); // A line of nothing but blanks trails all the way: there is // nothing there for them to trail. ALCodeEditor& f = make(" "); f.setShowWhitespace(ALCodeEditor::Whitespace::Trailing); - ensure_equals("all three", f.blanksOn(0).size(), size_t(3)); + UNSCOPED_INFO("all three"); + REQUIRE(f.blanksOn(0).size() == size_t(3)); } - template<> template<> - void alcodeeditor_object::test<19>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a capital typed into a word keeps the list open and asks nothing new", "[alcodeeditor]") { - set_test_name("a capital typed into a word keeps the list up and what was answered about the word"); ALCodeEditor& e = make("integer countDown;\ncou"); e.setCaret(e.document().end()); S32 asked = 0; e.setCompletionRequest([&asked](const ALTextPos&, std::string_view) { ++asked; }); type("n"); - ensure("open", e.completionOpen()); - ensure_equals("asked once about the word", asked, 1); + UNSCOPED_INFO("open"); + CHECK(e.completionOpen()); + UNSCOPED_INFO("asked once about the word"); + CHECK(asked == 1); // As the viewer delivers it: the key with its shift, then the // character. e.handleKeyHere('T', MASK_SHIFT); - ensure("the shift closed nothing", e.completionOpen()); - ensure("char taken", e.handleUnicodeCharHere('t')); + UNSCOPED_INFO("the shift closed nothing"); + CHECK(e.completionOpen()); + UNSCOPED_INFO("char taken"); + CHECK(e.handleUnicodeCharHere('t')); e.handleKeyHere('D', MASK_SHIFT); - ensure("char taken", e.handleUnicodeCharHere('D')); - ensure("still open", e.completionOpen()); - ensure_equals("and not asked again about the same word", asked, 1); + UNSCOPED_INFO("char taken"); + CHECK(e.handleUnicodeCharHere('D')); + UNSCOPED_INFO("still open"); + CHECK(e.completionOpen()); + UNSCOPED_INFO("and not asked again about the same word"); + CHECK(asked == 1); } - template<> template<> - void alcodeeditor_object::test<20>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Return indents under an opener and a closer comes out as it is typed", "[alcodeeditor]") { - set_test_name("Return goes a level in under what opens a block, and what closes one comes out as it is typed"); ALCodeEditor& e = make(""); e.setAutoComplete(false); e.setSoftTabs(true); type("default\n{\nstate_entry()\n{\nllSay(0, \"hi\");\n}\n}"); - ensure_equals("braces in and out", e.text(), + UNSCOPED_INFO("braces in and out"); + CHECK(e.text() == std::string("default\n{\n state_entry()\n {\n llSay(0, \"hi\");\n }\n}")); // Between a bracket and the one that closes it. e.setText("list l = [];"); e.setCaret(ALTextPos(0, 10)); key(KEY_RETURN); - ensure_equals("the closer on a line of its own", e.text(), std::string("list l = [\n \n];")); - ensure("the caret on the line between", e.caret() == ALTextPos(1, 4)); + UNSCOPED_INFO("the closer on a line of its own"); + CHECK(e.text() == std::string("list l = [\n \n];")); + UNSCOPED_INFO("the caret on the line between"); + CHECK(e.caret() == ALTextPos(1, 4)); // A blank line left behind keeps no blanks; a comment after the // brace still opens. e.setText("if (x) { // yes"); e.setCaret(e.document().end()); type("\n\nz"); - ensure_equals("in under the brace, the blank line bare", e.text(), std::string("if (x) { // yes\n\n z")); + UNSCOPED_INFO("in under the brace, the blank line bare"); + CHECK(e.text() == std::string("if (x) { // yes\n\n z")); // A closer typed further in than its opener comes out to it, one // typed further out stays, and one not first on its line is text. e.setText("f(\n a,\n b"); e.setCaret(e.document().end()); type("\n)"); - ensure_equals("level with the line that opened it", e.document().line(3), std::string(")")); + UNSCOPED_INFO("level with the line that opened it"); + CHECK(e.document().line(3) == std::string(")")); e.setText("{\n x;\n"); e.setCaret(e.document().end()); type("y}"); - ensure_equals("not first on the line, left alone", e.document().line(2), std::string("y}")); + UNSCOPED_INFO("not first on the line, left alone"); + CHECK(e.document().line(2) == std::string("y}")); } - template<> template<> - void alcodeeditor_object::test<21>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: in SLua Return indents a block and puts in its end; closers come out", "[alcodeeditor]") { - set_test_name("in SLua a block opens with then, do and function, its end put in by Return where it is not closed, and end, else and elseif come out once the word is whole"); ALCodeEditor& e = make("", "slua"); e.setAutoComplete(false); e.setSoftTabs(true); @@ -809,7 +1143,8 @@ namespace tut key(KEY_DOWN); key(KEY_END); type("\nelse\nreturn \"y\""); - ensure_equals("nested and closed", e.text(), + UNSCOPED_INFO("nested and closed"); + CHECK(e.text() == std::string("local function f(a: number): string\n" " if a then\n" " return \"x\"\n" @@ -825,48 +1160,63 @@ namespace tut e.setText("do\n"); e.setCaret(e.document().end()); type(" endpoint = 1"); - ensure_equals("a name, left where it is", e.document().line(1), std::string(" endpoint = 1")); + UNSCOPED_INFO("a name, left where it is"); + CHECK(e.document().line(1) == std::string(" endpoint = 1")); // A function closed on its own line opens nothing. e.setText("local f = function() return 1 end"); e.setCaret(e.document().end()); key(KEY_RETURN); - ensure_equals("level with it", e.text(), std::string("local f = function() return 1 end\n")); + UNSCOPED_INFO("level with it"); + CHECK(e.text() == std::string("local f = function() return 1 end\n")); // In a text indented with tabs, a level is a tab. e.setText("\tif x then"); e.setCaret(e.document().end()); type("\ny()"); - ensure_equals("tabs in, and the end put in level", e.text(), std::string("\tif x then\n\t\ty()\n\tend")); + UNSCOPED_INFO("tabs in, and the end put in level"); + CHECK(e.text() == std::string("\tif x then\n\t\ty()\n\tend")); } - template<> template<> - void alcodeeditor_object::test<22>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: completion skips comments and strings and ranks by a word's parts", "[alcodeeditor]") { - set_test_name("completion: not in comments or strings, by the parts of a word, the script's own first, the choice kept by its word, its documentation beside it"); // Prose is not completed on its own; asked for, it is. ALCodeEditor& e = make("integer counter;\n"); e.setCaret(e.document().end()); type("// the cou"); - ensure("not in a comment", !e.completionOpen()); + UNSCOPED_INFO("not in a comment"); + CHECK_FALSE(e.completionOpen()); type("\n\"cou"); - ensure("not in a string", !e.completionOpen()); + UNSCOPED_INFO("not in a string"); + CHECK_FALSE(e.completionOpen()); key(' ', MASK_CONTROL); - ensure("asked for, it opens", e.completionOpen()); + UNSCOPED_INFO("asked for, it opens"); + CHECK(e.completionOpen()); key(KEY_ESCAPE); type("\"\ncou"); - ensure("in code, it opens", e.completionOpen()); + UNSCOPED_INFO("in code, it opens"); + CHECK(e.completionOpen()); key(KEY_ESCAPE); // The tiers. - ensure_equals("start as typed", ALCodeEditor::matchTier("llSay", "llS"), 0); - ensure_equals("start in either case", ALCodeEditor::matchTier("llSay", "LLs"), 1); - ensure_equals("a part", ALCodeEditor::matchTier("llSay", "say"), 2); - ensure_equals("a part after an underscore", ALCodeEditor::matchTier("PRIM_POSITION", "pos"), 2); - ensure_equals("the letters of parts", ALCodeEditor::matchTier("llSetPos", "setpos"), 2); - ensure_equals("initials", ALCodeEditor::matchTier("llSetPos", "sp"), 3); - ensure_equals("initials, one needing a second look", ALCodeEditor::matchTier("llSetSpeed", "sp"), 2); - ensure_equals("a member", ALCodeEditor::matchTier("ll.SetPos", "ll.pos"), 3); - ensure_equals("not the middle of a part", ALCodeEditor::matchTier("llSay", "ay"), -1); - ensure_equals("nor a stray letter", ALCodeEditor::matchTier("llSay", "sx"), -1); + UNSCOPED_INFO("start as typed"); + CHECK(ALCodeEditor::matchTier("llSay", "llS") == 0); + UNSCOPED_INFO("start in either case"); + CHECK(ALCodeEditor::matchTier("llSay", "LLs") == 1); + UNSCOPED_INFO("a part"); + CHECK(ALCodeEditor::matchTier("llSay", "say") == 2); + UNSCOPED_INFO("a part after an underscore"); + CHECK(ALCodeEditor::matchTier("PRIM_POSITION", "pos") == 2); + UNSCOPED_INFO("the letters of parts"); + CHECK(ALCodeEditor::matchTier("llSetPos", "setpos") == 2); + UNSCOPED_INFO("initials"); + CHECK(ALCodeEditor::matchTier("llSetPos", "sp") == 3); + UNSCOPED_INFO("initials, one needing a second look"); + CHECK(ALCodeEditor::matchTier("llSetSpeed", "sp") == 2); + UNSCOPED_INFO("a member"); + CHECK(ALCodeEditor::matchTier("ll.SetPos", "ll.pos") == 3); + UNSCOPED_INFO("not the middle of a part"); + CHECK(ALCodeEditor::matchTier("llSay", "ay") == -1); + UNSCOPED_INFO("nor a stray letter"); + CHECK(ALCodeEditor::matchTier("llSay", "sx") == -1); // A provider's words matched by their parts, ranked. e.setText(""); @@ -886,28 +1236,41 @@ namespace tut } }); type("say"); - ensure("open on three letters of a part", e.completionOpen()); + UNSCOPED_INFO("open on three letters of a part"); + CHECK(e.completionOpen()); std::string listed; for (const ALCodeEditor::Completion& c : e.completions()) listed += " " + c.text; - ensure_equals("the start first, then either case, then a part", listed, std::string(" saying SAY_CHANNEL llSay")); - ensure("the chosen one's documentation beside the list", e.findChild("completion_doc") && - e.findChild("completion_doc")->getVisible() && - e.findChild("completion_doc")->text().find("What saying does.") != std::string::npos); + UNSCOPED_INFO("the start first, then either case, then a part"); + CHECK(listed == std::string(" saying SAY_CHANNEL llSay")); + { + INFO("the chosen one's documentation beside the list"); + REQUIRE(e.findChild("completion_doc")); + REQUIRE(e.findChild("completion_doc")->getVisible()); + REQUIRE(e.findChild("completion_doc")->text().find("What saying does.") != std::string::npos); + } key(KEY_DOWN); - ensure("it follows the choice", e.findChild("completion_doc")->text().find("What SAY_CHANNEL does.") != std::string::npos); + UNSCOPED_INFO("it follows the choice"); + REQUIRE(e.findChild("completion_doc")->text().find("What SAY_CHANNEL does.") != std::string::npos); // An answer joining the list keeps the choice on its word. ALCodeEditor::Completion late; late.text = "sayAgain"; late.kind = ALSyntaxKind::Variable; e.supplyCompletions(ALTextPos(0, 0), { late }); - ensure("the answer is in", e.completions().size() == 4 && e.completions()[0].text == "sayAgain"); - ensure_equals("the choice is still SAY_CHANNEL", e.completions()[e.chosenCompletion()].text, std::string("SAY_CHANNEL")); + { + INFO("the answer is in"); + REQUIRE(e.completions().size() == 4); + CHECK(e.completions()[0].text == "sayAgain"); + } + UNSCOPED_INFO("the choice is still SAY_CHANNEL"); + CHECK(e.completions()[e.chosenCompletion()].text == std::string("SAY_CHANNEL")); // More typed starts from the best. type("i"); - ensure_equals("narrowed to the best", e.completions()[e.chosenCompletion()].text, std::string("saying")); + UNSCOPED_INFO("narrowed to the best"); + CHECK(e.completions()[e.chosenCompletion()].text == std::string("saying")); key(KEY_ESCAPE); - ensure("the documentation goes with the list", !e.findChild("completion_doc")->getVisible()); + UNSCOPED_INFO("the documentation goes with the list"); + CHECK_FALSE(e.findChild("completion_doc")->getVisible()); // A deprecated function completes as a function. e.setText(""); @@ -923,15 +1286,18 @@ namespace tut } }); type("llOld"); - ensure("offered", e.completionOpen() && e.completions()[0].deprecated); + { + INFO("offered"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].deprecated); + } key(KEY_RETURN); - ensure_equals("with its brackets and its parameter", e.text(), std::string("llOldThing(n)")); + UNSCOPED_INFO("with its brackets and its parameter"); + CHECK(e.text() == std::string("llOldThing(n)")); } - template<> template<> - void alcodeeditor_object::test<23>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a card over a problem says the problem then the word's head as code", "[alcodeeditor]") { - set_test_name("a card over a problem says the problem and the word both, the word's head coloured as code"); ALCodeEditor& e = make("llSay(0, x);\n"); e.setHoverProvider([](const ALTextPos&, std::string_view word, std::string& text) { if (word != "llSay") @@ -951,10 +1317,17 @@ namespace tut const S32 x = text.mLeft + static_cast(e.layout().xOf(0, 2, &row)) + 1; const S32 y = text.mTop - e.layout().rowHeight() / 2; e.handleHover(x, y, MASK_NONE); - ensure("taken", e.handleToolTip(x, y, MASK_NONE)); + UNSCOPED_INFO("taken"); + CHECK(e.handleToolTip(x, y, MASK_NONE)); const ALTextView& card = *e.card(); - ensure_equals("the problem, a blank, then the word", card.text(), std::string("Too few arguments\n\ninteger llSay(integer channel, string msg)\nSays something.")); - ensure("the problem in its colour", !card.styles().empty() && card.styles()[0].range.begin.line == 0 && card.styles()[0].color == LLColor4::red); + UNSCOPED_INFO("the problem, a blank, then the word"); + CHECK(card.text() == std::string("Too few arguments\n\ninteger llSay(integer channel, string msg)\nSays something.")); + { + INFO("the problem in its colour"); + REQUIRE_FALSE(card.styles().empty()); + CHECK(card.styles()[0].range.begin.line == 0); + CHECK(card.styles()[0].color == LLColor4::red); + } // The head's words: `integer` a type, `llSay` in the text's own // colour or a function's, in the editor's face either way. bool typed = false, faced = true; @@ -967,126 +1340,151 @@ namespace tut faced = faced && style.font == e.getFont(); typed = typed || (style.range.begin.column == 0 && style.color && *style.color == e.colorForKind(ALSyntaxKind::Type)); } - ensure("the head in the editor's face", faced); - ensure("its type coloured as a type", typed); + UNSCOPED_INFO("the head in the editor's face"); + CHECK(faced); + UNSCOPED_INFO("its type coloured as a type"); + CHECK(typed); } - template<> template<> - void alcodeeditor_object::test<24>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a colour the theme names is used and one it does not is mixed", "[alcodeeditor]") { - set_test_name("a colour a theme names is drawn in, and one it does not is mixed from the view's own"); ALCodeEditor& e = make("x\n"); LLUIColorTable& table = LLUIColorTable::instance(); const std::string name = e.colorPrefix() + ALCodeEditor::paintName(ALCodeEditor::Paint::ActiveLineNumber); table.resetToDefault(name); - ensure("mixed: the text's own", e.paint(ALCodeEditor::Paint::ActiveLineNumber) == e.textColor()); - ensure("a mark's colour is the skin's without a theme", e.markColor(ALCodeEditor::Mark::Error) == e.paint(ALCodeEditor::Paint::Error)); + UNSCOPED_INFO("mixed: the text's own"); + CHECK(e.paint(ALCodeEditor::Paint::ActiveLineNumber) == e.textColor()); + UNSCOPED_INFO("a mark's colour is the skin's without a theme"); + CHECK(e.markColor(ALCodeEditor::Mark::Error) == e.paint(ALCodeEditor::Paint::Error)); table.setColor(name, LLColor4::green); - ensure("named: the theme's", e.paint(ALCodeEditor::Paint::ActiveLineNumber) == LLColor4::green); + UNSCOPED_INFO("named: the theme's"); + CHECK(e.paint(ALCodeEditor::Paint::ActiveLineNumber) == LLColor4::green); table.resetToDefault(name); - ensure("taken away: mixed again", e.paint(ALCodeEditor::Paint::ActiveLineNumber) == e.textColor()); + UNSCOPED_INFO("taken away: mixed again"); + CHECK(e.paint(ALCodeEditor::Paint::ActiveLineNumber) == e.textColor()); const std::string error = e.colorPrefix() + ALCodeEditor::paintName(ALCodeEditor::Paint::Error); table.setColor(error, LLColor4::blue); - ensure("a theme's error colour marks errors", e.markColor(ALCodeEditor::Mark::Error) == LLColor4::blue); + UNSCOPED_INFO("a theme's error colour marks errors"); + CHECK(e.markColor(ALCodeEditor::Mark::Error) == LLColor4::blue); table.resetToDefault(error); // The selection is quieter once the keyboard has gone elsewhere. e.setSelectionColor(LLUIColor(LLColor4(0.f, 0.f, 1.f, 1.f))); - ensure("focused, the selection's own", e.selectionDrawColor() == e.selectionColor()); + UNSCOPED_INFO("focused, the selection's own"); + CHECK(e.selectionDrawColor() == e.selectionColor()); gFocusMgr.setKeyboardFocus(nullptr); - ensure("unfocused, a quieter one", e.selectionDrawColor() == e.paint(ALCodeEditor::Paint::SelectionInactive) && e.selectionDrawColor() != e.selectionColor()); + { + INFO("unfocused, a quieter one"); + CHECK(e.selectionDrawColor() == e.paint(ALCodeEditor::Paint::SelectionInactive)); + CHECK(e.selectionDrawColor() != e.selectionColor()); + } } - template<> template<> - void alcodeeditor_object::test<25>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: brackets and quotes pair up except in comments and after a letter", "[alcodeeditor]") { - set_test_name("brackets and quotes typed in pairs: closed, typed over, wrapped, taken away together; not in comments or after a letter"); ALCodeEditor& e = make(""); e.setAutoClose(true); type("llSay("); - ensure_equals("closed", e.text(), std::string("llSay()")); - ensure("the caret between", e.caret() == ALTextPos(0, 6)); + UNSCOPED_INFO("closed"); + CHECK(e.text() == std::string("llSay()")); + UNSCOPED_INFO("the caret between"); + CHECK(e.caret() == ALTextPos(0, 6)); type("0, \""); - ensure_equals("a quote closed too", e.text(), std::string("llSay(0, \"\")")); + UNSCOPED_INFO("a quote closed too"); + CHECK(e.text() == std::string("llSay(0, \"\")")); type("hi\""); - ensure_equals("its closer typed over", e.text(), std::string("llSay(0, \"hi\")")); + UNSCOPED_INFO("its closer typed over"); + CHECK(e.text() == std::string("llSay(0, \"hi\")")); type(")"); - ensure_equals("and the bracket's", e.text(), std::string("llSay(0, \"hi\")")); - ensure("past it", e.caret() == ALTextPos(0, 14)); + UNSCOPED_INFO("and the bracket's"); + CHECK(e.text() == std::string("llSay(0, \"hi\")")); + UNSCOPED_INFO("past it"); + CHECK(e.caret() == ALTextPos(0, 14)); type(";"); // One Backspace between a pair just put in takes both. e.setText(""); type("x = ["); - ensure_equals("opened", e.text(), std::string("x = []")); + UNSCOPED_INFO("opened"); + CHECK(e.text() == std::string("x = []")); key(KEY_BACKSPACE); - ensure_equals("both gone", e.text(), std::string("x = ")); + UNSCOPED_INFO("both gone"); + CHECK(e.text() == std::string("x = ")); // Before a word the bracket is about the word. e.setText("count"); e.setCaret(ALTextPos(0, 0)); type("("); - ensure_equals("not closed before a word", e.text(), std::string("(count")); + UNSCOPED_INFO("not closed before a word"); + CHECK(e.text() == std::string("(count")); // A quote after a letter is an apostrophe; in a comment, prose. e.setText("// it"); e.setCaret(e.document().end()); type("("); - ensure_equals("not in a comment", e.text(), std::string("// it(")); + UNSCOPED_INFO("not in a comment"); + CHECK(e.text() == std::string("// it(")); e.setText("s = \"don"); e.setCaret(e.document().end()); type("\""); - ensure_equals("not after a letter", e.text(), std::string("s = \"don\"")); + UNSCOPED_INFO("not after a letter"); + CHECK(e.text() == std::string("s = \"don\"")); // A selection wrapped in the pair, and still chosen. e.setText("a + b"); e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 5))); type("("); - ensure_equals("wrapped", e.text(), std::string("(a + b)")); - ensure("the inside still chosen", e.selection().normalised() == ALTextRange(ALTextPos(0, 1), ALTextPos(0, 6))); + UNSCOPED_INFO("wrapped"); + CHECK(e.text() == std::string("(a + b)")); + UNSCOPED_INFO("the inside still chosen"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(0, 1), ALTextPos(0, 6))); // A closer the text already had is typed, not gone over. e.setText("f()"); e.setCaret(ALTextPos(0, 2)); type(")"); - ensure_equals("a closer not put in is typed", e.text(), std::string("f())")); + UNSCOPED_INFO("a closer not put in is typed"); + CHECK(e.text() == std::string("f())")); // Off, as it was. e.setAutoClose(false); e.setText(""); type("("); - ensure_equals("off, one bracket", e.text(), std::string("(")); + UNSCOPED_INFO("off, one bracket"); + CHECK(e.text() == std::string("(")); } - template<> template<> - void alcodeeditor_object::test<26>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: completion opens as configured and Return takes it or starts a line", "[alcodeeditor]") { - set_test_name("completion opens as asked: on its own or not, after so many letters, and Return takes it or starts a line"); ALCodeEditor& e = make("integer counter;\n"); e.setCaret(e.document().end()); e.setAutoComplete(false); type("co"); - ensure("off: not on its own", !e.completionOpen()); + UNSCOPED_INFO("off: not on its own"); + CHECK_FALSE(e.completionOpen()); key(' ', MASK_CONTROL); - ensure("but when asked", e.completionOpen()); + UNSCOPED_INFO("but when asked"); + CHECK(e.completionOpen()); key(KEY_ESCAPE); e.setAutoComplete(true); e.setCompleteAfter(4); e.setText("integer counter;\n"); e.setCaret(e.document().end()); type("cou"); - ensure("not at three letters when four are asked", !e.completionOpen()); + UNSCOPED_INFO("not at three letters when four are asked"); + CHECK_FALSE(e.completionOpen()); type("n"); - ensure("at four", e.completionOpen()); + UNSCOPED_INFO("at four"); + CHECK(e.completionOpen()); e.setAcceptOnEnter(false); key(KEY_RETURN); - ensure("Return does not take it", !e.completionOpen()); - ensure_equals("it starts a line", e.text(), std::string("integer counter;\ncoun\n")); + UNSCOPED_INFO("Return does not take it"); + CHECK_FALSE(e.completionOpen()); + UNSCOPED_INFO("it starts a line"); + CHECK(e.text() == std::string("integer counter;\ncoun\n")); } - template<> template<> - void alcodeeditor_object::test<27>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the hover card waits for the rest asked for and can be turned off", "[alcodeeditor]") { - set_test_name("the hover card waits for the rest asked for, and can be turned off"); ALCodeEditor& e = make("llSay(0, x);\n"); e.setHoverProvider([](const ALTextPos&, std::string_view word, std::string& text) { text = "about " + std::string(word); @@ -1098,87 +1496,146 @@ namespace tut const S32 y = text.mTop - e.layout().rowHeight() / 2; e.setHoverDelay(60.f); e.handleHover(x, y, MASK_NONE); - ensure("the tooltip's asking is taken", e.handleToolTip(x, y, MASK_NONE)); - ensure("but no card before the rest", !e.cardShown()); + UNSCOPED_INFO("the tooltip's asking is taken"); + CHECK(e.handleToolTip(x, y, MASK_NONE)); + UNSCOPED_INFO("but no card before the rest"); + CHECK_FALSE(e.cardShown()); e.setHoverDelay(0.f); - ensure("taken", e.handleToolTip(x, y, MASK_NONE)); - ensure("with no wait, the card", e.cardShown()); + UNSCOPED_INFO("taken"); + CHECK(e.handleToolTip(x, y, MASK_NONE)); + UNSCOPED_INFO("with no wait, the card"); + CHECK(e.cardShown()); e.hideCard(); e.setHoverCards(false); e.handleToolTip(x, y, MASK_NONE); - ensure("off: no card", !e.cardShown()); + UNSCOPED_INFO("off: no card"); + CHECK_FALSE(e.cardShown()); } - template<> template<> - void alcodeeditor_object::test<28>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: change bars mark lines unlike the saved text or all if none is known", "[alcodeeditor]") { - set_test_name("a text put back whole is barred where it differs from what was saved, and everywhere where nothing saved is known"); ALCodeEditor& e = make("one\ntwo\nthree\nfour"); e.resetDirty(); - ensure("saved: nothing barred", !e.lineChanged(0) && !e.lineChanged(1) && !e.lineChanged(2) && !e.lineChanged(3)); - ensure("and clean", !e.isDirty()); + { + INFO("saved: nothing barred"); + CHECK_FALSE(e.lineChanged(0)); + CHECK_FALSE(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(2)); + CHECK_FALSE(e.lineChanged(3)); + } + UNSCOPED_INFO("and clean"); + CHECK_FALSE(e.isDirty()); e.markUnsaved(); - ensure("never saved: unsaved", e.isDirty()); - ensure("and every line barred", e.lineChanged(0) && e.lineChanged(1) && e.lineChanged(2) && e.lineChanged(3)); + UNSCOPED_INFO("never saved: unsaved"); + CHECK(e.isDirty()); + { + INFO("and every line barred"); + CHECK(e.lineChanged(0)); + CHECK(e.lineChanged(1)); + CHECK(e.lineChanged(2)); + CHECK(e.lineChanged(3)); + } e.barChangesSince("one\n2\n3\nfour"); - ensure("the lines that differ barred", e.lineChanged(1) && e.lineChanged(2)); - ensure("the ones around them not", !e.lineChanged(0) && !e.lineChanged(3)); + { + INFO("the lines that differ barred"); + CHECK(e.lineChanged(1)); + CHECK(e.lineChanged(2)); + } + { + INFO("the ones around them not"); + CHECK_FALSE(e.lineChanged(0)); + CHECK_FALSE(e.lineChanged(3)); + } e.barChangesSince("one\ntwo\nthree\nfour"); - ensure("the same text bars nothing", !e.lineChanged(0) && !e.lineChanged(1) && !e.lineChanged(2) && !e.lineChanged(3)); + { + INFO("the same text bars nothing"); + CHECK_FALSE(e.lineChanged(0)); + CHECK_FALSE(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(2)); + CHECK_FALSE(e.lineChanged(3)); + } e.barChangesSince("one\nfour"); - ensure("lines put in between barred", e.lineChanged(1) && e.lineChanged(2)); - ensure("and not the lines around them", !e.lineChanged(0) && !e.lineChanged(3)); + { + INFO("lines put in between barred"); + CHECK(e.lineChanged(1)); + CHECK(e.lineChanged(2)); + } + { + INFO("and not the lines around them"); + CHECK_FALSE(e.lineChanged(0)); + CHECK_FALSE(e.lineChanged(3)); + } e.barChangesSince("zero\none\ntwo\nthree\nfour"); - ensure("a line taken away bars the line it was taken from", e.lineChanged(0)); - ensure("and nothing else", !e.lineChanged(1) && !e.lineChanged(2) && !e.lineChanged(3)); + UNSCOPED_INFO("a line taken away bars the line it was taken from"); + CHECK(e.lineChanged(0)); + { + INFO("and nothing else"); + CHECK_FALSE(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(2)); + CHECK_FALSE(e.lineChanged(3)); + } e.resetDirty(); - ensure("a save clears the bars", !e.lineChanged(0)); + UNSCOPED_INFO("a save clears the bars"); + CHECK_FALSE(e.lineChanged(0)); } - template<> template<> - void alcodeeditor_object::test<29>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: snippet stops span lines and nest and mirror a repeated number", "[alcodeeditor]") { - set_test_name("a snippet's stops on several lines stay while the caret is among them; one inside another; $0's text chosen; a number again a mirror"); ALCodeEditor& e = make("", "lsl"); e.insertSnippet("for (${1:i} = 0; ${1} < ${2:n}; ++${1})\n{\n ${3:body}\n}${0:done}"); - ensure_equals("the mirrors show what the first holds", e.document().line(0), std::string("for (i = 0; i < n; ++i)")); - ensure_equals("three stops, not the mirrors", e.placeholders().size(), size_t(3)); + UNSCOPED_INFO("the mirrors show what the first holds"); + CHECK(e.document().line(0) == std::string("for (i = 0; i < n; ++i)")); + UNSCOPED_INFO("three stops, not the mirrors"); + REQUIRE(e.placeholders().size() == size_t(3)); type("k"); - ensure_equals("typed over the first alone", e.document().line(0), std::string("for (k = 0; i < n; ++i)")); + UNSCOPED_INFO("typed over the first alone"); + CHECK(e.document().line(0) == std::string("for (k = 0; i < n; ++i)")); key(KEY_TAB); - ensure_equals("its mirrors made it as it was left", e.document().line(0), std::string("for (k = 0; k < n; ++k)")); - ensure_equals("the next chosen", e.selectedText(), std::string("n")); + UNSCOPED_INFO("its mirrors made it as it was left"); + CHECK(e.document().line(0) == std::string("for (k = 0; k < n; ++k)")); + UNSCOPED_INFO("the next chosen"); + CHECK(e.selectedText() == std::string("n")); key(KEY_TAB); - ensure("on to a stop on another line, the stops still there", e.selectedText() == "body" && e.caret().line == 2 && e.placeholders().size() == 3); + { + INFO("on to a stop on another line, the stops still there"); + CHECK(e.selectedText() == "body"); + CHECK(e.caret().line == 2); + REQUIRE(e.placeholders().size() == 3); + } key(KEY_TAB); - ensure_equals("then $0, its text chosen", e.selectedText(), std::string("done")); - ensure("and done", e.placeholders().empty()); + UNSCOPED_INFO("then $0, its text chosen"); + CHECK(e.selectedText() == std::string("done")); + UNSCOPED_INFO("and done"); + REQUIRE(e.placeholders().empty()); e.undo(); // One inside another, and let go of when the caret leaves them. ALCodeEditor& f = make("x\n", "lsl"); f.insertSnippet("f(${1:a, ${2:b}})"); - ensure_equals("the text", f.document().line(0), std::string("f(a, b)x")); - ensure_equals("the outer chosen whole", f.selectedText(), std::string("a, b")); + UNSCOPED_INFO("the text"); + CHECK(f.document().line(0) == std::string("f(a, b)x")); + UNSCOPED_INFO("the outer chosen whole"); + CHECK(f.selectedText() == std::string("a, b")); key(KEY_TAB); - ensure_equals("then the inner", f.selectedText(), std::string("b")); + UNSCOPED_INFO("then the inner"); + CHECK(f.selectedText() == std::string("b")); f.setCaret(ALTextPos(1, 0)); - ensure("the caret gone from their lines lets them go", f.placeholders().empty()); + UNSCOPED_INFO("the caret gone from their lines lets them go"); + REQUIRE(f.placeholders().empty()); ALCodeEditor& g = make("", "lsl"); g.insertSnippet("a\\$b\\}c ${1:x}"); - ensure_equals("escaped dollar and brace as themselves", g.text(), std::string("a$b}c x")); + UNSCOPED_INFO("escaped dollar and brace as themselves"); + CHECK(g.text() == std::string("a$b}c x")); } - template<> template<> - void alcodeeditor_object::test<30>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: matchTier on a word of many like parts answers at once and rightly", "[alcodeeditor]") { - set_test_name("a word of many parts alike is matched by its parts at once, however near the miss, and still matched right"); std::string word; for (S32 i = 0; i < 60; ++i) { @@ -1187,49 +1644,61 @@ namespace tut word += "b"; // Every way there is through sixty parts, tried one by one, would // not end in anyone's lifetime. - ensure_equals("a near miss is no match", ALCodeEditor::matchTier(word, std::string(30, 'a') + "c"), -1); - ensure_equals("and the letters of its parts a match", ALCodeEditor::matchTier(word, std::string(30, 'a') + "b"), 3); - ensure_equals("by parts as ever", ALCodeEditor::matchTier("llSetPos", "setpos"), 2); - ensure_equals("by the letters of its parts", ALCodeEditor::matchTier("llSetPrimitiveParams", "sprp"), 3); + UNSCOPED_INFO("a near miss is no match"); + CHECK(ALCodeEditor::matchTier(word, std::string(30, 'a') + "c") == -1); + UNSCOPED_INFO("and the letters of its parts a match"); + CHECK(ALCodeEditor::matchTier(word, std::string(30, 'a') + "b") == 3); + UNSCOPED_INFO("by parts as ever"); + CHECK(ALCodeEditor::matchTier("llSetPos", "setpos") == 2); + UNSCOPED_INFO("by the letters of its parts"); + CHECK(ALCodeEditor::matchTier("llSetPrimitiveParams", "sprp") == 3); } - template<> template<> - void alcodeeditor_object::test<31>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the bars go where the text is stepped back to the one saved", "[alcodeeditor]") { - set_test_name("the bars go where the text is stepped back to the one saved"); ALCodeEditor& e = make("one\ntwo"); e.resetDirty(); e.setCaret(ALTextPos(1, 3)); type("x"); - ensure("changed and barred", e.isDirty() && e.lineChanged(1)); + { + INFO("changed and barred"); + CHECK(e.isDirty()); + CHECK(e.lineChanged(1)); + } e.undo(); - ensure("back to the saved text: clean", !e.isDirty()); - ensure("and no bar", !e.lineChanged(1)); + UNSCOPED_INFO("back to the saved text: clean"); + CHECK_FALSE(e.isDirty()); + UNSCOPED_INFO("and no bar"); + CHECK_FALSE(e.lineChanged(1)); e.redo(); - ensure("forward again: barred", e.lineChanged(1)); + UNSCOPED_INFO("forward again: barred"); + CHECK(e.lineChanged(1)); } - template<> template<> - void alcodeeditor_object::test<32>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: lines deleted right above a folded block leave it folded and moved up", "[alcodeeditor]") { - set_test_name("whole lines taken from right above a folded block leave it folded, moved up with them"); ALCodeEditor& e = make("x\ny\ndefault\n{\n state_entry()\n {\n }\n}\nz"); - ensure("folds", e.foldAt(2)); + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(2)); e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(2, 0))); key(KEY_DELETE); - ensure_equals("the lines gone", e.document().line(0), std::string("default")); - ensure("the block still folded, where it is now", e.isFolded(0) && e.layout().hidden(1)); + UNSCOPED_INFO("the lines gone"); + CHECK(e.document().line(0) == std::string("default")); + { + INFO("the block still folded, where it is now"); + CHECK(e.isFolded(0)); + CHECK(e.layout().hidden(1)); + } // An edit into its line is typing on it, and it stays folded; one // that takes the line with others unfolds it. e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0))); type("q"); - ensure("typing on the header keeps it", e.isFolded(0)); + UNSCOPED_INFO("typing on the header keeps it"); + CHECK(e.isFolded(0)); } - template<> template<> - void alcodeeditor_object::test<33>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: with no provider the grammar's words are matched by their parts too", "[alcodeeditor]") { - set_test_name("with nobody to ask, the grammar's words are found by their parts as the document's are"); ALCodeEditor& e = make("", "lsl"); e.highlighter().ownWords().set("function", { "llSetPos", "llSay" }); type("setp"); @@ -1239,13 +1708,15 @@ namespace tut { found = found || c.text == "llSetPos"; } - ensure("llSetPos for setp", e.completionOpen() && found); + { + INFO("llSetPos for setp"); + CHECK(e.completionOpen()); + CHECK(found); + } } - template<> template<> - void alcodeeditor_object::test<34>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a line's fixes come preferred first and previewed and taken by value", "[alcodeeditor]") { - set_test_name("a line's fixes are listed preferred first, previewed as they are chosen, and taken by their value"); ALCodeEditor& e = make("integer a = 1\nllOwnerSay((string)a);\n"); std::vector taken; e.setFixProvider([](S32 line, std::vector& out) { @@ -1270,52 +1741,99 @@ namespace tut out = { suppress, other, semi }; }); e.setFixHandler([&taken](const LLSD& value) { taken.push_back(value); }); - ensure("nothing offered where the line says nothing", !e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("nothing offered where the line says nothing"); + CHECK_FALSE(e.canPerform(ALEditorCommand::QuickFix)); e.setFixable(0, true, true); - ensure("offered on the caret's line", e.canPerform(ALEditorCommand::QuickFix) && e.changesAt(0)); - ensure("listed by Control-.", e.handleKeyHere('.', MASK_CONTROL) && e.fixesOpen()); - ensure_equals("three", e.fixes().size(), static_cast(3)); - ensure_equals("the preferred first", e.fixes().front().title, std::string("Insert ';'")); - ensure_equals("the suppression last", e.fixes().back().title, std::string("Suppress it")); + { + INFO("offered on the caret's line"); + CHECK(e.canPerform(ALEditorCommand::QuickFix)); + CHECK(e.changesAt(0)); + } + { + INFO("listed by Control-."); + CHECK(e.handleKeyHere('.', MASK_CONTROL)); + CHECK(e.fixesOpen()); + } + UNSCOPED_INFO("three"); + REQUIRE(e.fixes().size() == static_cast(3)); + UNSCOPED_INFO("the preferred first"); + CHECK(e.fixes().front().title == std::string("Insert ';'")); + UNSCOPED_INFO("the suppression last"); + CHECK(e.fixes().back().title == std::string("Suppress it")); const ALTextView* box = e.findChild("fix_preview"); - ensure("previewed", box && box->getVisible()); - ensure_equals("as a diff", box->text(), std::string("- integer a = 1\n+ integer a = 1;")); + { + INFO("previewed"); + REQUIRE(box); + CHECK(box->getVisible()); + } + UNSCOPED_INFO("as a diff"); + CHECK(box->text() == std::string("- integer a = 1\n+ integer a = 1;")); key(KEY_DOWN); - ensure_equals("the next, previewed", box->text(), std::string("- integer a = 1\n+ integer b = 1")); + UNSCOPED_INFO("the next, previewed"); + CHECK(box->text() == std::string("- integer a = 1\n+ integer b = 1")); key(KEY_ESCAPE); - ensure("escape lets it go", !e.fixesOpen() && taken.empty() && !box->getVisible()); + { + INFO("escape lets it go"); + CHECK_FALSE(e.fixesOpen()); + REQUIRE(taken.empty()); + CHECK_FALSE(box->getVisible()); + } e.handleKeyHere('.', MASK_CONTROL); key(KEY_RETURN); - ensure("Return takes the chosen one by its value", taken.size() == 1 && taken.front().asString() == "semi" && !e.fixesOpen()); - ensure_equals("and changes nothing itself", e.document().line(0), std::string("integer a = 1")); + { + INFO("Return takes the chosen one by its value"); + REQUIRE(taken.size() == 1); + CHECK(taken.front().asString() == "semi"); + CHECK_FALSE(e.fixesOpen()); + } + UNSCOPED_INFO("and changes nothing itself"); + CHECK(e.document().line(0) == std::string("integer a = 1")); e.handleKeyHere('.', MASK_CONTROL); type("x"); - ensure("typing lets it go", !e.fixesOpen()); - ensure("and the line it edited offers nothing until it is checked again", !e.fixableAt(0) && !e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("typing lets it go"); + CHECK_FALSE(e.fixesOpen()); + { + INFO("and the line it edited offers nothing until it is checked again"); + CHECK_FALSE(e.fixableAt(0)); + CHECK_FALSE(e.canPerform(ALEditorCommand::QuickFix)); + } } - template<> template<> - void alcodeeditor_object::test<35>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a line's fix mark slides with edits and a fix previews as its lines", "[alcodeeditor]") { - set_test_name("what a line offers slides with the edits above it, and a fix reads as the lines it makes"); ALCodeEditor& e = make("one\ntwo\nthree\n"); e.setFixable(1, true, false); - ensure("a suppression only", e.fixableAt(1) && !e.changesAt(1)); + { + INFO("a suppression only"); + CHECK(e.fixableAt(1)); + CHECK_FALSE(e.changesAt(1)); + } // A line put in above it: a bare break, since a word typed would // open the completions, and Return would take one. e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0))); type("\n"); - ensure("moved down with its line", e.fixableAt(2) && !e.fixableAt(1)); + { + INFO("moved down with its line"); + CHECK(e.fixableAt(2)); + CHECK_FALSE(e.fixableAt(1)); + } e.clearMarks(); - ensure("cleared with the marks", !e.fixableAt(2)); + UNSCOPED_INFO("cleared with the marks"); + CHECK_FALSE(e.fixableAt(2)); ALCodeEditor::Fix fix; fix.edits.emplace_back(ALTextRange(ALTextPos(2, 1), ALTextPos(3, 2)), "X"); fix.edits.emplace_back(ALTextRange(ALTextPos(2, 0), ALTextPos(2, 0)), ">"); S32 first = -1, last = -1; - ensure_equals("both edits, in the text's order", ALCodeEditor::fixedLines(e.document(), fix, first, last), std::string(">tXree")); - ensure_equals("the text itself untouched", e.document().line(2), std::string("two")); - ensure("over the lines they touch", first == 2 && last == 3); + UNSCOPED_INFO("both edits, in the text's order"); + CHECK(ALCodeEditor::fixedLines(e.document(), fix, first, last) == std::string(">tXree")); + UNSCOPED_INFO("the text itself untouched"); + CHECK(e.document().line(2) == std::string("two")); + { + INFO("over the lines they touch"); + CHECK(first == 2); + CHECK(last == 3); + } // Something put in where a stretch replaced begins goes before it, // whichever order the fix gives them in, as the editor makes them. for (const bool insertion_first : { true, false }) @@ -1330,7 +1848,8 @@ namespace tut { both.edits.emplace_back(ALTextRange(ALTextPos(2, 0), ALTextPos(2, 0)), "local v = two\n"); } - ensure_equals(insertion_first ? "put in, then replaced" : "replaced, then put in", ALCodeEditor::fixedLines(e.document(), both, first, last), + UNSCOPED_INFO((insertion_first ? "put in, then replaced" : "replaced, then put in")); + REQUIRE(ALCodeEditor::fixedLines(e.document(), both, first, last) == std::string("local v = two\nv")); } // Edits far apart preview as a stretch each, not every line between. @@ -1345,29 +1864,32 @@ namespace tut apart.edits.emplace_back(ALTextRange(ALTextPos(150, 0), ALTextPos(150, 4)), "LINE"); std::vector kinds; const std::string preview = ALCodeEditor::previewOf(farest.document(), apart, kinds); - ensure_equals("two stretches and an ellipsis between", preview, + UNSCOPED_INFO("two stretches and an ellipsis between"); + CHECK(preview == std::string("- line 2\n+ top\n+ line 2\n\u2026\n- line 150\n+ LINE 150")); - ensure("each line's kind", std::string(kinds.begin(), kinds.end()) == "-++ -+"); + UNSCOPED_INFO("each line's kind"); + CHECK(std::string(kinds.begin(), kinds.end()) == "-++ -+"); // Near each other, one stretch. ALCodeEditor::Fix nearest; nearest.edits.emplace_back(ALTextRange(ALTextPos(10, 0), ALTextPos(10, 4)), "A"); nearest.edits.emplace_back(ALTextRange(ALTextPos(12, 0), ALTextPos(12, 4)), "B"); - ensure_equals("together", ALCodeEditor::previewOf(farest.document(), nearest, kinds), + UNSCOPED_INFO("together"); + CHECK(ALCodeEditor::previewOf(farest.document(), nearest, kinds) == std::string("- line 10\n- line 11\n- line 12\n+ A 10\n+ line 11\n+ B 12")); // Deep in a block, the lines' indentation in common taken off. ALCodeEditor& nested = make("{\n {\n one;\n two;\n }\n}\n"); ALCodeEditor::Fix called; called.edits.emplace_back(ALTextRange(ALTextPos(2, 8), ALTextPos(2, 11)), "one()"); - ensure_equals("at the left", ALCodeEditor::previewOf(nested.document(), called, kinds), std::string("- one;\n+ one();")); + UNSCOPED_INFO("at the left"); + CHECK(ALCodeEditor::previewOf(nested.document(), called, kinds) == std::string("- one;\n+ one();")); called.edits.emplace_back(ALTextRange(ALTextPos(3, 12), ALTextPos(3, 15)), "two()"); - ensure_equals("each as far in as it is past the least", ALCodeEditor::previewOf(nested.document(), called, kinds), + UNSCOPED_INFO("each as far in as it is past the least"); + CHECK(ALCodeEditor::previewOf(nested.document(), called, kinds) == std::string("- one;\n- two;\n+ one();\n+ two();")); } - template<> template<> - void alcodeeditor_object::test<36>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a problem's card offers its line's fixes in the list's order", "[alcodeeditor]") { - set_test_name("a problem's card offers its line's fixes, in the list's order"); ALCodeEditor& e = make("integer a = 1\n"); e.setFixProvider([](S32 line, std::vector& out) { ALCodeEditor::Fix suppress; @@ -1380,14 +1902,14 @@ namespace tut }); e.setFixHandler([](const LLSD&) {}); e.showCard(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 7)), std::string(), { { "Missing ';'.", LLColor4::red } }); - ensure("shown", e.cardShown()); - ensure_equals("each a line under the problem", e.card()->text(), std::string("Missing ';'.\nFix: Insert ';'\nFix: Suppress it")); + UNSCOPED_INFO("shown"); + CHECK(e.cardShown()); + UNSCOPED_INFO("each a line under the problem"); + CHECK(e.card()->text() == std::string("Missing ';'.\nFix: Insert ';'\nFix: Suppress it")); } - template<> template<> - void alcodeeditor_object::test<37>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Quick Fix lists refactors between the fixes and the suppressions", "[alcodeeditor]") { - set_test_name("a quick fix asks for the refactors too, and joins them to the line's fixes as they come, after the fixes and before a suppression"); ALCodeEditor& e = make("integer a = 1\nllOwnerSay((string)a);\n"); std::vector asked; std::vector taken; @@ -1409,54 +1931,92 @@ namespace tut out = { suppress, semi }; }); e.setFixHandler([&taken](const LLSD& value) { taken.push_back(value); }); - ensure("nothing offered where the line says nothing", !e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("nothing offered where the line says nothing"); + CHECK_FALSE(e.canPerform(ALEditorCommand::QuickFix)); e.setActionRequest([&asked](const ALTextRange& at) { asked.push_back(at); }); - ensure("offered anywhere once refactors may be asked for", e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("offered anywhere once refactors may be asked for"); + CHECK(e.canPerform(ALEditorCommand::QuickFix)); e.setFixable(0, true, true); - ensure("listed by Control-.", e.handleKeyHere('.', MASK_CONTROL) && e.fixesOpen()); - ensure_equals("the fixes listed meanwhile", e.fixes().size(), static_cast(2)); - ensure("the refactors asked for at the caret", asked.size() == 1 && asked.front() == ALTextRange(e.caret(), e.caret())); + { + INFO("listed by Control-."); + CHECK(e.handleKeyHere('.', MASK_CONTROL)); + CHECK(e.fixesOpen()); + } + UNSCOPED_INFO("the fixes listed meanwhile"); + REQUIRE(e.fixes().size() == static_cast(2)); + { + INFO("the refactors asked for at the caret"); + REQUIRE(asked.size() == 1); + CHECK(asked.front() == ALTextRange(e.caret(), e.caret())); + } key(KEY_DOWN); ALCodeEditor::Fix extract; extract.title = "Put it in a local"; extract.value = "extract"; extract.edits.emplace_back(ALTextRange(ALTextPos(0, 12), ALTextPos(0, 13)), "value"); e.supplyActions(ALTextRange(ALTextPos(1, 0), ALTextPos(1, 0)), { extract }); - ensure_equals("another place's dropped", e.fixes().size(), static_cast(2)); + UNSCOPED_INFO("another place's dropped"); + REQUIRE(e.fixes().size() == static_cast(2)); e.supplyActions(asked.front(), { extract }); - ensure_equals("joined", e.fixes().size(), static_cast(3)); - ensure_equals("after the fixes", e.fixes()[1].title, std::string("Put it in a local")); - ensure("marked a refactor", e.fixes()[1].refactor); - ensure_equals("before the suppression", e.fixes()[2].title, std::string("Suppress it")); + UNSCOPED_INFO("joined"); + REQUIRE(e.fixes().size() == static_cast(3)); + UNSCOPED_INFO("after the fixes"); + CHECK(e.fixes()[1].title == std::string("Put it in a local")); + UNSCOPED_INFO("marked a refactor"); + CHECK(e.fixes()[1].refactor); + UNSCOPED_INFO("before the suppression"); + CHECK(e.fixes()[2].title == std::string("Suppress it")); key(KEY_RETURN); - ensure("the choice stayed where it was", taken.size() == 1 && taken.front().asString() == "suppress"); + { + INFO("the choice stayed where it was"); + REQUIRE(taken.size() == 1); + CHECK(taken.front().asString() == "suppress"); + } // A line with no fixes: the stretch chosen asked about, and the // refactors listed alone when they come, or a word that there is // nothing. e.setSelection(ALTextRange(ALTextPos(1, 0), ALTextPos(1, 10))); e.handleKeyHere('.', MASK_CONTROL); - ensure("nothing listed yet", !e.fixesOpen()); - ensure("the stretch asked about", asked.size() == 2 && asked.back() == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 10))); + UNSCOPED_INFO("nothing listed yet"); + CHECK_FALSE(e.fixesOpen()); + { + INFO("the stretch asked about"); + REQUIRE(asked.size() == 2); + CHECK(asked.back() == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 10))); + } e.supplyActions(asked.back(), {}); - ensure("a word that there is nothing", e.fixesOpen() && e.fixes().size() == 1 && e.fixes().front().value.isUndefined()); + { + INFO("a word that there is nothing"); + CHECK(e.fixesOpen()); + REQUIRE(e.fixes().size() == 1); + REQUIRE(e.fixes().front().value.isUndefined()); + } key(KEY_RETURN); - ensure("which takes nothing", taken.size() == 1 && !e.fixesOpen()); + { + INFO("which takes nothing"); + REQUIRE(taken.size() == 1); + CHECK_FALSE(e.fixesOpen()); + } e.handleKeyHere('.', MASK_CONTROL); e.supplyActions(asked.back(), { extract }); - ensure("listed alone", e.fixesOpen() && e.fixes().size() == 1 && e.fixes().front().refactor); + { + INFO("listed alone"); + CHECK(e.fixesOpen()); + REQUIRE(e.fixes().size() == 1); + CHECK(e.fixes().front().refactor); + } key(KEY_ESCAPE); // An answer for a caret since moved goes nowhere. e.handleKeyHere('.', MASK_CONTROL); e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0))); e.supplyActions(asked.back(), { extract }); - ensure("dropped", !e.fixesOpen()); + UNSCOPED_INFO("dropped"); + CHECK_FALSE(e.fixesOpen()); } - template<> template<> - void alcodeeditor_object::test<38>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a double-click on a hint takes its name and Control writes the hint in", "[alcodeeditor]") { - set_test_name("a double-click on a hint takes the name beside it, a Control-double-click writes it in as one step to undo, and the lightbulb asks for the refactors"); ALCodeEditor& e = make("local count = 5\nprint(count)\n"); ALCodeEditor::InlayHint type; type.at = ALTextPos(0, 11); @@ -1482,26 +2042,40 @@ namespace tut return -1; }; const S32 on_type = pill(0); - ensure("found where it is drawn, past the name", on_type > text.mLeft + static_cast(e.layout().xOf(0, 10, nullptr))); - ensure_equals("the type's", e.inlayAtLocal(on_type + 2, rowY(0)), 0); - ensure("not over the text", e.inlayAtLocal(text.mLeft + 1, rowY(0)) < 0); - ensure("a tip says how it is written in", e.handleToolTip(on_type + 2, rowY(0), MASK_NONE)); + UNSCOPED_INFO("found where it is drawn, past the name"); + CHECK(on_type > text.mLeft + static_cast(e.layout().xOf(0, 10, nullptr))); + UNSCOPED_INFO("the type's"); + CHECK(e.inlayAtLocal(on_type + 2, rowY(0)) == 0); + UNSCOPED_INFO("not over the text"); + CHECK(e.inlayAtLocal(text.mLeft + 1, rowY(0)) < 0); + UNSCOPED_INFO("a tip says how it is written in"); + CHECK(e.handleToolTip(on_type + 2, rowY(0), MASK_NONE)); // A double-click alone meant the name, and changes nothing. - ensure("taken", e.handleDoubleClick(on_type + 2, rowY(0), MASK_NONE)); - ensure_equals("nothing written", e.document().line(0), std::string("local count = 5")); - ensure("the name chosen", e.selection().normalised() == ALTextRange(ALTextPos(0, 6), ALTextPos(0, 11))); - ensure("taken with Control", e.handleDoubleClick(on_type + 2, rowY(0), MASK_CONTROL)); - ensure_equals("written in", e.document().line(0), std::string("local count: number = 5")); - ensure_equals("and the hint gone with its line's edit", e.inlayHints().size(), static_cast(1)); + UNSCOPED_INFO("taken"); + CHECK(e.handleDoubleClick(on_type + 2, rowY(0), MASK_NONE)); + UNSCOPED_INFO("nothing written"); + CHECK(e.document().line(0) == std::string("local count = 5")); + UNSCOPED_INFO("the name chosen"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(0, 6), ALTextPos(0, 11))); + UNSCOPED_INFO("taken with Control"); + CHECK(e.handleDoubleClick(on_type + 2, rowY(0), MASK_CONTROL)); + UNSCOPED_INFO("written in"); + CHECK(e.document().line(0) == std::string("local count: number = 5")); + UNSCOPED_INFO("and the hint gone with its line's edit"); + REQUIRE(e.inlayHints().size() == static_cast(1)); e.undo(); - ensure_equals("one step to undo", e.document().line(0), std::string("local count = 5")); + UNSCOPED_INFO("one step to undo"); + CHECK(e.document().line(0) == std::string("local count = 5")); // A parameter's name: nothing the text can say, Control or not; // the argument it stands before is taken. const S32 on_name = pill(1); - ensure("the name's pill", on_name >= 0); + UNSCOPED_INFO("the name's pill"); + CHECK(on_name >= 0); e.handleDoubleClick(on_name + 2, rowY(1), MASK_CONTROL); - ensure_equals("nothing written", e.document().line(1), std::string("print(count)")); - ensure("the argument chosen", e.selection().normalised() == ALTextRange(ALTextPos(1, 6), ALTextPos(1, 11))); + UNSCOPED_INFO("nothing written"); + CHECK(e.document().line(1) == std::string("print(count)")); + UNSCOPED_INFO("the argument chosen"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(1, 6), ALTextPos(1, 11))); // The lightbulb on the caret's line is Control-.: the refactors // asked for with the fixes. On another line, that line's fixes. @@ -1519,53 +2093,87 @@ namespace tut e.setFixable(0, true, true); e.setFixable(1, true, true); e.setSelection(ALTextRange(ALTextPos(0, 3), ALTextPos(0, 3))); - ensure("the caret's lightbulb pressed", e.handleMouseDown(e.leftEdge() + 2, rowY(0), MASK_NONE) && e.fixesOpen()); - ensure("and the refactors asked for", asked.size() == 1 && asked.front() == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 3))); + { + INFO("the caret's lightbulb pressed"); + CHECK(e.handleMouseDown(e.leftEdge() + 2, rowY(0), MASK_NONE)); + CHECK(e.fixesOpen()); + } + { + INFO("and the refactors asked for"); + REQUIRE(asked.size() == 1); + CHECK(asked.front() == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 3))); + } e.closeFixes(); - ensure("another line's mark pressed", e.handleMouseDown(e.leftEdge() + 2, rowY(1), MASK_NONE) && e.fixesOpen()); - ensure("its fixes alone", asked.size() == 1); + { + INFO("another line's mark pressed"); + CHECK(e.handleMouseDown(e.leftEdge() + 2, rowY(1), MASK_NONE)); + CHECK(e.fixesOpen()); + } + UNSCOPED_INFO("its fixes alone"); + REQUIRE(asked.size() == 1); } - template<> template<> - void alcodeeditor_object::test<39>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the heat strip is a gutter column and heat stays with its line", "[alcodeeditor]") { - set_test_name("the gutter's strip of heat is a column of its own while asked for, and each line's heat stays with what is left of the line"); ALCodeEditor& e = make("a\nb\nc\nd\n"); const S32 plain = e.gutterWidth(); e.setHeatShown(true); - ensure("a column of its own", e.gutterWidth() > plain); + UNSCOPED_INFO("a column of its own"); + CHECK(e.gutterWidth() > plain); const S32 with_heat = e.gutterWidth(); e.setLineHeat({ { 1, 0.5f, "b's" }, { 2, 1.f, "c's" } }); - ensure("as said", e.heatAt(1) == 0.5f && e.heatAt(2) == 1.f && e.heatAt(0) == 0.f); - ensure_equals("the gutter no wider for what is in it", e.gutterWidth(), with_heat); + { + INFO("as said"); + CHECK(e.heatAt(1) == 0.5f); + CHECK(e.heatAt(2) == 1.f); + CHECK(e.heatAt(0) == 0.f); + } + UNSCOPED_INFO("the gutter no wider for what is in it"); + CHECK(e.gutterWidth() == with_heat); // A line made above one pushes its heat down with it. e.document().replace(ALTextRange(ALTextPos(1, 0), ALTextPos(1, 0)), "\n"); - ensure("pushed down", e.heatAt(1) == 0.f && e.heatAt(2) == 0.5f && e.heatAt(3) == 1.f); + { + INFO("pushed down"); + CHECK(e.heatAt(1) == 0.f); + CHECK(e.heatAt(2) == 0.5f); + CHECK(e.heatAt(3) == 1.f); + } // Typing in a line keeps its heat, and breaking it keeps it on // what is left of the line. e.document().replace(ALTextRange(ALTextPos(2, 1), ALTextPos(2, 1)), "x"); - ensure("typed in", e.heatAt(2) == 0.5f); + UNSCOPED_INFO("typed in"); + CHECK(e.heatAt(2) == 0.5f); e.document().replace(ALTextRange(ALTextPos(3, 1), ALTextPos(3, 1)), "\n"); - ensure("broken", e.heatAt(3) == 1.f && e.heatAt(4) == 0.f); + { + INFO("broken"); + CHECK(e.heatAt(3) == 1.f); + CHECK(e.heatAt(4) == 0.f); + } // The lines taken from a line's start take theirs with them, and // the line after is pulled up with its own. e.document().replace(ALTextRange(ALTextPos(1, 0), ALTextPos(3, 0)), ""); - ensure("pulled up", e.heatAt(1) == 1.f && e.heatAt(2) == 0.f); + { + INFO("pulled up"); + CHECK(e.heatAt(1) == 1.f); + CHECK(e.heatAt(2) == 0.f); + } e.setLineHeat({}); - ensure("replaced whole", e.heatAt(1) == 0.f); + UNSCOPED_INFO("replaced whole"); + CHECK(e.heatAt(1) == 0.f); e.setHeatShown(false); - ensure_equals("the column gone", e.gutterWidth(), plain); + UNSCOPED_INFO("the column gone"); + CHECK(e.gutterWidth() == plain); } - template<> template<> - void alcodeeditor_object::test<40>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a line's note is drawn after its end and stays with its line", "[alcodeeditor]") { - set_test_name("a line's note is drawn after its end, says more to the mouse, and stays with what is left of the line"); ALCodeEditor& e = make("integer twice(integer n)\n{\n return n * 2;\n}\n"); e.setLineNotes({ { 0, "28 bytes", "twice: 28 bytes of code" } }); - ensure_equals("as said", e.noteAt(0), std::string("28 bytes")); - ensure("nowhere else", e.noteAt(1).empty()); + UNSCOPED_INFO("as said"); + CHECK(e.noteAt(0) == std::string("28 bytes")); + UNSCOPED_INFO("nowhere else"); + REQUIRE(e.noteAt(1).empty()); const LLRect text = e.textRect(); const S32 row_h = e.layout().rowHeight(); const S32 y = text.mTop - row_h / 2; @@ -1575,23 +2183,31 @@ namespace tut on = e.noteAtLocal(x, y) == 0 ? x : -1; } const S32 end = text.mLeft + static_cast(e.layout().xOf(0, static_cast(e.document().line(0).size()), nullptr)); - ensure("drawn after the line's end", on > end); - ensure("not over the text", e.noteAtLocal(text.mLeft + 2, y) < 0); - ensure("nor over a line with none", e.noteAtLocal(on + 2, y - row_h) < 0); - ensure("says more to the mouse", e.handleToolTip(on + 2, y, MASK_NONE)); + UNSCOPED_INFO("drawn after the line's end"); + CHECK(on > end); + UNSCOPED_INFO("not over the text"); + CHECK(e.noteAtLocal(text.mLeft + 2, y) < 0); + UNSCOPED_INFO("nor over a line with none"); + CHECK(e.noteAtLocal(on + 2, y - row_h) < 0); + UNSCOPED_INFO("says more to the mouse"); + CHECK(e.handleToolTip(on + 2, y, MASK_NONE)); e.document().replace(ALTextRange(ALTextPos(0, 8), ALTextPos(0, 13)), "double"); - ensure_equals("kept through an edit in its line", e.noteAt(0), std::string("28 bytes")); + UNSCOPED_INFO("kept through an edit in its line"); + CHECK(e.noteAt(0) == std::string("28 bytes")); e.document().replace(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "// doubles\n"); - ensure("pushed down with its line", e.noteAt(0).empty() && e.noteAt(1) == "28 bytes"); + { + INFO("pushed down with its line"); + REQUIRE(e.noteAt(0).empty()); + CHECK(e.noteAt(1) == "28 bytes"); + } e.setLineNotes({}); - ensure("replaced whole", e.noteAt(1).empty()); + UNSCOPED_INFO("replaced whole"); + REQUIRE(e.noteAt(1).empty()); } - template<> template<> - void alcodeeditor_object::test<41>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a listener is told of each fix list made and its notes go to that one", "[alcodeeditor]") { - set_test_name("whoever asks is told each time the fix list is made, and notes go after their fixes for that making only"); ALCodeEditor& e = make("integer a = 1\nllOwnerSay((string)a);\n"); e.setFixProvider([](S32 line, std::vector& out) { if (line != 0) @@ -1612,30 +2228,44 @@ namespace tut std::vector> told; e.setFixesShown([&told](U32 shown, const std::vector& fixes) { told.emplace_back(shown, fixes.size()); }); e.setFixable(0, true, true); - ensure("opened", e.openFixes(0)); - ensure("told what it lists", told.size() == 1 && told.back().second == 2); + UNSCOPED_INFO("opened"); + CHECK(e.openFixes(0)); + { + INFO("told what it lists"); + REQUIRE(told.size() == 1); + CHECK(told.back().second == 2); + } e.noteFixes(told.back().first, { "12 bytes lighter on LSO", "" }); - ensure_equals("after its fix", e.fixes()[0].note, std::string("12 bytes lighter on LSO")); - ensure("the other has none", e.fixes()[1].note.empty()); - ensure("still open", e.fixesOpen()); + UNSCOPED_INFO("after its fix"); + CHECK(e.fixes()[0].note == std::string("12 bytes lighter on LSO")); + UNSCOPED_INFO("the other has none"); + REQUIRE(e.fixes()[1].note.empty()); + UNSCOPED_INFO("still open"); + CHECK(e.fixesOpen()); // Made again: told again, and the last making's notes go nowhere. const U32 was = told.back().first; - ensure("opened again", e.openFixes(0)); - ensure("told again", told.size() == 2 && told.back().first != was); + UNSCOPED_INFO("opened again"); + CHECK(e.openFixes(0)); + { + INFO("told again"); + REQUIRE(told.size() == 2); + CHECK(told.back().first != was); + } e.noteFixes(was, { "stale", "stale" }); - ensure("an old making's dropped", e.fixes()[0].note.empty()); + UNSCOPED_INFO("an old making's dropped"); + REQUIRE(e.fixes()[0].note.empty()); e.noteFixes(told.back().first, { "one" }); - ensure("one note for two fixes dropped", e.fixes()[0].note.empty()); + UNSCOPED_INFO("one note for two fixes dropped"); + REQUIRE(e.fixes()[0].note.empty()); e.closeFixes(); e.noteFixes(told.back().first, { "late", "late" }); - ensure("nothing to put them on once closed", e.fixes().empty()); + UNSCOPED_INFO("nothing to put them on once closed"); + REQUIRE(e.fixes().empty()); } - template<> template<> - void alcodeeditor_object::test<42>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a press on the find bar is the bar's even over pinned headers", "[alcodeeditor]") { - set_test_name("a press on the find bar is the bar's though pinned headers are under it, and a line scrolled up to comes out from under them"); std::string text = "default\n{\n state_entry()\n {\n"; for (int i = 0; i < 80; ++i) { @@ -1650,30 +2280,37 @@ namespace tut e.setCaret(ALTextPos(70, 8)); e.setCaret(ALTextPos(40, 8)); const S32 below = e.layout().lineTop(40) - e.scrollY(); - ensure("below the two pinned headers: " + std::to_string(below) + " of " + std::to_string(row_h), below >= 2 * row_h); + UNSCOPED_INFO("below the two pinned headers: " + std::to_string(below) + " of " + std::to_string(row_h)); + CHECK(below >= 2 * row_h); e.showFind(false); e.findBar()->setQuery("line 6"); e.setCaret(ALTextPos(64, 8)); LLView* next = e.findBar()->findChild("next"); const LLRect bar = e.findBar()->getRect(); - ensure("the arrow", next != nullptr); + UNSCOPED_INFO("the arrow"); + REQUIRE(next != nullptr); const S32 x = bar.mLeft + next->getRect().getCenterX(); const S32 y = bar.mBottom + next->getRect().getCenterY(); - ensure("over the pinned headers", y > e.textRect().mTop - 2 * row_h); - ensure("pressed", e.handleMouseDown(x, y, MASK_NONE)); + UNSCOPED_INFO("over the pinned headers"); + CHECK(y > e.textRect().mTop - 2 * row_h); + UNSCOPED_INFO("pressed"); + CHECK(e.handleMouseDown(x, y, MASK_NONE)); e.handleMouseUp(x, y, MASK_NONE); // The next after the caret is further along its own line. - ensure("the next match after the caret, not a header's line: " + std::to_string(e.caret().line), e.caret().line == 64 && e.findCurrent() >= 0); + { + INFO("the next match after the caret, not a header's line: " + std::to_string(e.caret().line)); + CHECK(e.caret().line == 64); + CHECK(e.findCurrent() >= 0); + } e.handleMouseDown(x, y, MASK_NONE); e.handleMouseUp(x, y, MASK_NONE); - ensure("and the one after that on the next press: " + std::to_string(e.caret().line), e.caret().line == 65); + UNSCOPED_INFO("and the one after that on the next press: " + std::to_string(e.caret().line)); + CHECK(e.caret().line == 65); } - template<> template<> - void alcodeeditor_object::test<43>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the wheel over a card with nothing to scroll scrolls the text", "[alcodeeditor]") { - set_test_name("the wheel under a card that has nothing more to show scrolls the text, and brings no card until the mouse moves"); std::string text; for (int i = 0; i < 60; ++i) { @@ -1698,29 +2335,41 @@ namespace tut // A card of a few lines: all of it in sight, nothing for the wheel. said = "llSay(integer channel, string msg)\nSays something."; e.handleHover(x, y, MASK_NONE); - ensure("a card", e.handleToolTip(x, y, MASK_NONE) && e.cardShown()); + { + INFO("a card"); + CHECK(e.handleToolTip(x, y, MASK_NONE)); + CHECK(e.cardShown()); + } const LLRect small = e.card()->getRect(); e.handleHover(small.getCenterX(), small.getCenterY(), MASK_NONE); - ensure("kept, the mouse on it", e.cardShown()); - ensure("the wheel taken", e.handleScrollWheel(small.getCenterX(), small.getCenterY(), down)); - ensure("the card gone", !e.cardShown()); - ensure("the text scrolled", e.scrollY() > 0); + UNSCOPED_INFO("kept, the mouse on it"); + CHECK(e.cardShown()); + UNSCOPED_INFO("the wheel taken"); + CHECK(e.handleScrollWheel(small.getCenterX(), small.getCenterY(), down)); + UNSCOPED_INFO("the card gone"); + CHECK_FALSE(e.cardShown()); + UNSCOPED_INFO("the text scrolled"); + CHECK(e.scrollY() > 0); // Still: whatever is under the mouse now, no card. e.handleToolTip(small.getCenterX(), small.getCenterY(), MASK_NONE); - ensure("none while the mouse is still", !e.cardShown()); + UNSCOPED_INFO("none while the mouse is still"); + CHECK_FALSE(e.cardShown()); const S32 scrolled = e.scrollY(); e.handleScrollWheel(small.getCenterX(), small.getCenterY(), down); - ensure("the text scrolls on", e.scrollY() > scrolled); + UNSCOPED_INFO("the text scrolls on"); + CHECK(e.scrollY() > scrolled); // Moved: a card may come again. e.handleHover(x + 1, y, MASK_NONE); - ensure("the mouse moved: a card again", e.handleToolTip(x + 1, y, MASK_NONE) && e.cardShown()); + { + INFO("the mouse moved: a card again"); + CHECK(e.handleToolTip(x + 1, y, MASK_NONE)); + CHECK(e.cardShown()); + } } - template<> template<> - void alcodeeditor_object::test<44>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: an include's name is followed by F12 and Control-click first", "[alcodeeditor]") { - set_test_name("a place that leads somewhere of itself -- an include's name -- is gone to by F12 and Control-click, before any identifier"); ALCodeEditor& e = make("#include \"lib/util.lsl\"\ninteger x;\n", "lsl"); std::vector followed; std::vector asked; @@ -1736,31 +2385,46 @@ namespace tut return ALTextRange(ALTextPos(0, 9), ALTextPos(0, 23)); }); e.setCaret(ALTextPos(0, 13)); // on the slash: no identifier - ensure("F12 can, with no one to ask about names", e.canPerform(ALEditorCommand::GoToDefinition)); - ensure("not the references", !e.canPerform(ALEditorCommand::FindReferences)); + UNSCOPED_INFO("F12 can, with no one to ask about names"); + CHECK(e.canPerform(ALEditorCommand::GoToDefinition)); + UNSCOPED_INFO("not the references"); + CHECK_FALSE(e.canPerform(ALEditorCommand::FindReferences)); key(KEY_F12); - ensure("gone to", followed == std::vector{ ALTextPos(0, 13) }); + UNSCOPED_INFO("gone to"); + CHECK(followed == std::vector{ ALTextPos(0, 13) }); e.setSymbolRequest([&](ALEditorCommand, const ALTextRange& word) { asked.push_back(word); }); e.setCaret(ALTextPos(1, 8)); key(KEY_F12); - ensure("elsewhere, the identifier asked about", asked.size() == 1 && followed.size() == 1); + { + INFO("elsewhere, the identifier asked about"); + REQUIRE(asked.size() == 1); + REQUIRE(followed.size() == 1); + } e.setCaret(ALTextPos(0, 3)); // in `include`, an identifier key(KEY_F12); - ensure("the link before it", followed.size() == 2 && asked.size() == 1); + { + INFO("the link before it"); + REQUIRE(followed.size() == 2); + REQUIRE(asked.size() == 1); + } const LLRect text = e.textRect(); const S32 x = text.mLeft + 2; const S32 y = text.mTop - e.layout().rowHeight() / 2; - ensure("Control-click on it", e.handleMouseDown(x, y, MASK_CONTROL)); + UNSCOPED_INFO("Control-click on it"); + CHECK(e.handleMouseDown(x, y, MASK_CONTROL)); e.handleMouseUp(x, y, MASK_CONTROL); - ensure("gone to where it was clicked", followed.size() == 3 && followed.back().line == 0 && e.caret().line == 0); + { + INFO("gone to where it was clicked"); + REQUIRE(followed.size() == 3); + CHECK(followed.back().line == 0); + CHECK(e.caret().line == 0); + } } - template<> template<> - void alcodeeditor_object::test<45>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Quick Fix on a misspelled word offers suggestions the editor puts in", "[alcodeeditor]") { - set_test_name("Quick Fix on a misspelled word offers the dictionary's words for it, which the editor puts in itself"); ALCodeEditor& e = make("// teh note\ninteger x;\n", "lsl"); e.setSpellChecker([](const std::string& word) { return word != "teh"; }, [](const std::string&, std::vector& out) { @@ -1769,42 +2433,65 @@ namespace tut }); e.setSpellCheck(true); e.setCaret(ALTextPos(1, 3)); - ensure("elsewhere, nothing to fix", !e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("elsewhere, nothing to fix"); + CHECK_FALSE(e.canPerform(ALEditorCommand::QuickFix)); e.setCaret(ALTextPos(0, 4)); - ensure("on it, with no one else to ask", e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("on it, with no one else to ask"); + CHECK(e.canPerform(ALEditorCommand::QuickFix)); key('.', MASK_CONTROL); - ensure("the list", e.fixesOpen() && e.fixes().size() == 2); - ensure_equals("the first word", e.fixes()[0].title, std::string("Change to \"the\"")); - ensure("its edit, for the preview", e.fixes()[0].edits.size() == 1 && e.fixes()[0].edits[0].second == "the"); + { + INFO("the list"); + CHECK(e.fixesOpen()); + REQUIRE(e.fixes().size() == 2); + } + UNSCOPED_INFO("the first word"); + CHECK(e.fixes()[0].title == std::string("Change to \"the\"")); + { + INFO("its edit, for the preview"); + REQUIRE(e.fixes()[0].edits.size() == 1); + CHECK(e.fixes()[0].edits[0].second == "the"); + } key(KEY_DOWN); key(KEY_RETURN); - ensure_equals("the second put in", e.document().line(0), std::string("// ten note")); - ensure("the list gone", !e.fixesOpen()); + UNSCOPED_INFO("the second put in"); + CHECK(e.document().line(0) == std::string("// ten note")); + UNSCOPED_INFO("the list gone"); + CHECK_FALSE(e.fixesOpen()); } - template<> template<> - void alcodeeditor_object::test<46>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Next and Previous Function go between functions; Select Function grows", "[alcodeeditor]") { - set_test_name("Next Function and Previous Function go between the host's functions; Select Function takes the one around, then the one around that"); ALCodeEditor& e = make("integer f(integer x)\n{\n return x;\n}\ndefault\n{\n state_entry()\n {\n f(1);\n }\n" " touch_start(integer n)\n {\n f(2);\n }\n}\n", "lsl"); - ensure("no host, no functions", !e.canPerform(ALEditorCommand::NextFunction) && !e.canPerform(ALEditorCommand::SelectFunction)); + { + INFO("no host, no functions"); + CHECK_FALSE(e.canPerform(ALEditorCommand::NextFunction)); + CHECK_FALSE(e.canPerform(ALEditorCommand::SelectFunction)); + } e.setFunctionProvider([](std::vector& out) { out = { ALTextRange(ALTextPos(10, 4), ALTextPos(13, 5)), ALTextRange(ALTextPos(0, 0), ALTextPos(3, 1)), ALTextRange(ALTextPos(6, 4), ALTextPos(9, 5)) }; }); e.setCaret(ALTextPos(2, 0)); key(KEY_PAGE_DOWN, MASK_ALT); - ensure("the next's start", e.caret() == ALTextPos(6, 4)); + UNSCOPED_INFO("the next's start"); + CHECK(e.caret() == ALTextPos(6, 4)); key(KEY_PAGE_DOWN, MASK_ALT); - ensure("and the next", e.caret() == ALTextPos(10, 4)); - ensure("none after the last", !e.canPerform(ALEditorCommand::NextFunction)); + UNSCOPED_INFO("and the next"); + CHECK(e.caret() == ALTextPos(10, 4)); + UNSCOPED_INFO("none after the last"); + CHECK_FALSE(e.canPerform(ALEditorCommand::NextFunction)); key(KEY_PAGE_UP, MASK_ALT); - ensure("back", e.caret() == ALTextPos(6, 4)); + UNSCOPED_INFO("back"); + CHECK(e.caret() == ALTextPos(6, 4)); e.setCaret(ALTextPos(8, 9)); - ensure("the one around, whole", - e.perform(ALEditorCommand::SelectFunction) && e.selection() == ALTextRange(ALTextPos(6, 4), ALTextPos(9, 5))); - ensure("nothing around it", !e.canPerform(ALEditorCommand::SelectFunction)); + { + INFO("the one around, whole"); + CHECK(e.perform(ALEditorCommand::SelectFunction)); + CHECK(e.selection() == ALTextRange(ALTextPos(6, 4), ALTextPos(9, 5))); + } + UNSCOPED_INFO("nothing around it"); + CHECK_FALSE(e.canPerform(ALEditorCommand::SelectFunction)); // Nested -- a function inside another, as SLua has them: the inner // first, then the one around it. @@ -1812,15 +2499,20 @@ namespace tut out = { ALTextRange(ALTextPos(4, 0), ALTextPos(14, 1)), ALTextRange(ALTextPos(6, 4), ALTextPos(9, 5)) }; }); e.setCaret(ALTextPos(8, 9)); - ensure("the inner", e.perform(ALEditorCommand::SelectFunction) && e.selection() == ALTextRange(ALTextPos(6, 4), ALTextPos(9, 5))); - ensure("then the one around it", - e.perform(ALEditorCommand::SelectFunction) && e.selection() == ALTextRange(ALTextPos(4, 0), ALTextPos(14, 1))); + { + INFO("the inner"); + CHECK(e.perform(ALEditorCommand::SelectFunction)); + CHECK(e.selection() == ALTextRange(ALTextPos(6, 4), ALTextPos(9, 5))); + } + { + INFO("then the one around it"); + CHECK(e.perform(ALEditorCommand::SelectFunction)); + CHECK(e.selection() == ALTextRange(ALTextPos(4, 0), ALTextPos(14, 1))); + } } - template<> template<> - void alcodeeditor_object::test<47>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the completion list sits under the whole of a tall row", "[alcodeeditor]") { - set_test_name("the completion list stands under the whole of the caret's row where a box has made it taller than the font's line"); ALCodeEditor& e = make("integer count;\n#co"); ALTextView::Atom tall; tall.at = ALTextPos(1, 0); @@ -1830,46 +2522,52 @@ namespace tut e.addAtom(tall); e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("open", e.completionOpen()); - ensure_equals("the row is taller", e.layout().rowHeightOf(1, 0), 3 * e.layout().rowHeight()); + UNSCOPED_INFO("open"); + CHECK(e.completionOpen()); + UNSCOPED_INFO("the row is taller"); + CHECK(e.layout().rowHeightOf(1, 0) == 3 * e.layout().rowHeight()); const LLView* list = e.findChild("completions"); - ensure("the list", list != nullptr); - ensure_equals("under the row, not over its text", list->getRect().mTop, + UNSCOPED_INFO("the list"); + REQUIRE(list != nullptr); + UNSCOPED_INFO("under the row, not over its text"); + CHECK(list->getRect().mTop == e.textRect().mTop - e.layout().lineTop(1) - e.layout().rowHeightOf(1, 0)); } - template<> template<> - void alcodeeditor_object::test<48>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: in a 50000-line script a key at the top is one change and lexes little", "[alcodeeditor]") { - set_test_name("in a script of 50,000 lines, a key typed at the top is one change, and the lines after it are not lexed again"); ALCodeEditor& e = make(""); e.setText(ll_test::bigLSL(50000)); - ensure("the script is big", e.document().lineCount() > 49900); + UNSCOPED_INFO("the script is big"); + CHECK(e.document().lineCount() > 49900); const S32 last = e.document().lineCount() - 1; e.highlighter().tokens(last); - ensure_equals("every line lexed at first", e.highlighter().lastLexed(), last + 1); + UNSCOPED_INFO("every line lexed at first"); + CHECK(e.highlighter().lastLexed() == last + 1); ll_test::EditCount edits(e.document()); e.setCaret(ALTextPos(0, 0)); type("x"); - ensure_equals("one change", edits.count(), 1); + UNSCOPED_INFO("one change"); + REQUIRE(edits.count() == 1); e.highlighter().tokens(last); - ensure("the lines after it are not lexed again", e.highlighter().lastLexed() <= 1); + UNSCOPED_INFO("the lines after it are not lexed again"); + CHECK(e.highlighter().lastLexed() <= 1); edits.reset(); e.setCaret(ALTextPos(0, 0)); type("/*"); e.highlighter().tokens(last); - ensure_equals("a change for each key", edits.count(), 2); + UNSCOPED_INFO("a change for each key"); + REQUIRE(edits.count() == 2); // The script's first block comment is on its sixth line: what is // after it lexes as it did. - ensure("a comment opened lexes on to where the next one ends, not to the end", e.highlighter().lastLexed() < 10); + UNSCOPED_INFO("a comment opened lexes on to where the next one ends, not to the end"); + CHECK(e.highlighter().lastLexed() < 10); } - template<> template<> - void alcodeeditor_object::test<49>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: an edit drops awaited refactors and closes an open fix list", "[alcodeeditor]") { - set_test_name("an edit lets go of refactors still awaited while nothing is listed, and of a list that is open"); ALCodeEditor& e = make("integer x;\ninteger y;\n"); std::vector asked; e.setFixHandler([](const LLSD&) {}); @@ -1881,22 +2579,27 @@ namespace tut e.setCaret(ALTextPos(0, 8)); e.handleKeyHere('.', MASK_CONTROL); - ensure("asked, and nothing listed yet", asked.size() == 1 && !e.fixesOpen()); + { + INFO("asked, and nothing listed yet"); + REQUIRE(asked.size() == 1); + CHECK_FALSE(e.fixesOpen()); + } e.document().insert(ALTextPos(1, 0), "// "); e.supplyActions(asked.back(), { extract }); - ensure("an edit elsewhere, the caret where it was: the answer is for a text that is gone", !e.fixesOpen()); + UNSCOPED_INFO("an edit elsewhere, the caret where it was: the answer is for a text that is gone"); + CHECK_FALSE(e.fixesOpen()); e.handleKeyHere('.', MASK_CONTROL); e.supplyActions(asked.back(), { extract }); - ensure("listed", e.fixesOpen()); + UNSCOPED_INFO("listed"); + CHECK(e.fixesOpen()); e.document().insert(ALTextPos(1, 0), "// "); - ensure("an edit closes it", !e.fixesOpen()); + UNSCOPED_INFO("an edit closes it"); + CHECK_FALSE(e.fixesOpen()); } - template<> template<> - void alcodeeditor_object::test<50>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a hint added above others leaves each pill with its own hint", "[alcodeeditor]") { - set_test_name("a hint added above others leaves each pill below its own: found, drawn and written as itself"); ALCodeEditor& e = make("local a = 1\nlocal b = 2\nlocal c = 3\n", "slua"); const auto hint = [](S32 line, const char* type) { ALCodeEditor::InlayHint h; @@ -1921,48 +2624,67 @@ namespace tut }; e.setInlayHints({ hint(1, ": two"), hint(2, ": three") }); // The lines laid out with their pills, as drawing them does. - ensure("the second line's", pill(1) >= 0 && e.inlayAtLocal(pill(1) + 2, rowY(1)) == 0); - ensure("the third's", pill(2) >= 0 && e.inlayAtLocal(pill(2) + 2, rowY(2)) == 1); + { + INFO("the second line's"); + CHECK(pill(1) >= 0); + CHECK(e.inlayAtLocal(pill(1) + 2, rowY(1)) == 0); + } + { + INFO("the third's"); + CHECK(pill(2) >= 0); + CHECK(e.inlayAtLocal(pill(2) + 2, rowY(2)) == 1); + } // One more, above both: their lines are not laid out again. e.setInlayHints({ hint(0, ": one"), hint(1, ": two"), hint(2, ": three") }); - ensure_equals("the third line's pill is the third hint", e.inlayAtLocal(pill(2) + 2, rowY(2)), 2); - ensure("taken with Control", e.handleDoubleClick(pill(2) + 2, rowY(2), MASK_CONTROL)); - ensure_equals("its own text written, on its own line", e.text(), std::string("local a = 1\nlocal b = 2\nlocal c: three = 3\n")); + UNSCOPED_INFO("the third line's pill is the third hint"); + CHECK(e.inlayAtLocal(pill(2) + 2, rowY(2)) == 2); + UNSCOPED_INFO("taken with Control"); + CHECK(e.handleDoubleClick(pill(2) + 2, rowY(2), MASK_CONTROL)); + UNSCOPED_INFO("its own text written, on its own line"); + CHECK(e.text() == std::string("local a = 1\nlocal b = 2\nlocal c: three = 3\n")); } - template<> template<> - void alcodeeditor_object::test<51>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: clearHandlers leaves nothing a host gave the editor to call", "[alcodeeditor]") { - set_test_name("handlers cleared: nothing a host gave the editor is called on the way out"); ALCodeEditor& e = make("integer x;\n"); S32 asked = 0; e.setFixProvider([&asked](S32, std::vector&) { ++asked; }); e.setFixHandler([&asked](const LLSD&) { ++asked; }); e.setActionRequest([&asked](const ALTextRange&) { ++asked; }); e.setFixable(0, true, true); - ensure("offered while held", e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("offered while held"); + CHECK(e.canPerform(ALEditorCommand::QuickFix)); e.clearHandlers(); - ensure("nothing offered once let go of", !e.canPerform(ALEditorCommand::QuickFix)); + UNSCOPED_INFO("nothing offered once let go of"); + CHECK_FALSE(e.canPerform(ALEditorCommand::QuickFix)); e.handleKeyHere('.', MASK_CONTROL); - ensure_equals("and nothing asked", asked, 0); + UNSCOPED_INFO("and nothing asked"); + CHECK(asked == 0); } - template<> template<> - void alcodeeditor_object::test<52>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: highlight layers clear apart and slide; decorations list as given", "[alcodeeditor]") { - set_test_name("highlights by what lit them: one layer cleared leaves the others; an edit slides each; decorations listed as given, drawn as they lie"); ALCodeEditor& e = make("integer count = 1;\ncount = count + 1;\n"); typedef ALCodeEditor::Highlight H; e.setHighlights(H::References, { ALTextRange(ALTextPos(0, 8), ALTextPos(0, 13)) }); e.setHighlights(H::Search, { ALTextRange(ALTextPos(1, 0), ALTextPos(1, 5)) }); e.clearHighlights(H::Search); - ensure("the search's gone, the name's kept", e.highlights(H::Search).empty() && e.highlights(H::References).size() == 1); + { + INFO("the search's gone, the name's kept"); + REQUIRE(e.highlights(H::Search).empty()); + REQUIRE(e.highlights(H::References).size() == 1); + } e.setHighlights(H::Block, { ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13)) }); e.setCaret(ALTextPos(0, 0)); type("\n"); - ensure("each slid", e.highlights(H::References)[0].begin == ALTextPos(1, 8) && e.highlights(H::Block)[0].begin == ALTextPos(2, 8)); + { + INFO("each slid"); + CHECK(e.highlights(H::References)[0].begin == ALTextPos(1, 8)); + CHECK(e.highlights(H::Block)[0].begin == ALTextPos(2, 8)); + } e.clearHighlights(); - ensure("all cleared", e.highlights().empty()); + UNSCOPED_INFO("all cleared"); + REQUIRE(e.highlights().empty()); ALCodeEditor::Decoration worst, later; worst.range = ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13)); @@ -1970,28 +2692,41 @@ namespace tut later.range = ALTextRange(ALTextPos(1, 0), ALTextPos(1, 13)); later.message = "later"; e.setDecorations({ worst, later }); - ensure("as they lie", e.decorations()[0].message == "later"); + UNSCOPED_INFO("as they lie"); + CHECK(e.decorations()[0].message == "later"); const std::vector listed = e.decorationsOn(1); - ensure("as given", listed.size() == 2 && listed[0]->message == "worst" && listed[1]->message == "later"); - ensure("none on another line", e.decorationsOn(2).empty()); + { + INFO("as given"); + REQUIRE(listed.size() == 2); + REQUIRE(listed[0]->message == "worst"); + CHECK(listed[1]->message == "later"); + } + UNSCOPED_INFO("none on another line"); + REQUIRE(e.decorationsOn(2).empty()); } - template<> template<> - void alcodeeditor_object::test<53>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: in a 50000-line script Select All and Tab is one change and one undo", "[alcodeeditor]") { - set_test_name("in a script of 50,000 lines, Select All and Tab is one change and its undo another; what lies between a batch's stretches stays"); ALCodeEditor& e = make(""); e.setText(ll_test::bigLSL(50000)); const std::string was = e.document().line(1); ll_test::EditCount edits(e.document()); e.perform(ALEditorCommand::SelectAll); - ensure("taken", e.handleKeyHere(KEY_TAB, MASK_NONE)); - ensure_equals("one change", edits.count(), 1); + UNSCOPED_INFO("taken"); + CHECK(e.handleKeyHere(KEY_TAB, MASK_NONE)); + UNSCOPED_INFO("one change"); + REQUIRE(edits.count() == 1); const std::string& now = e.document().line(1); - ensure("a line indented", !was.empty() && now.size() > was.size() && now.compare(now.size() - was.size(), was.size(), was) == 0); + { + INFO("a line indented"); + REQUIRE_FALSE(was.empty()); + REQUIRE(now.size() > was.size()); + REQUIRE(now.compare(now.size() - was.size(), was.size(), was) == 0); + } edits.reset(); e.undo(); - ensure_equals("undone as one", edits.count(), 1); + UNSCOPED_INFO("undone as one"); + REQUIRE(edits.count() == 1); ALCodeEditor& f = make("integer total;\nf()\n{\n total = 1;\n}\n// a note\ntotal = total + 1;\n"); f.setMark(5, ALCodeEditor::Mark::Warning); @@ -2000,35 +2735,42 @@ namespace tut { folded = f.foldAt(line) ? line : -1; } - ensure("a block folded", folded >= 0); + UNSCOPED_INFO("a block folded"); + CHECK(folded >= 0); std::vector> renames = { { ALTextRange(ALTextPos(0, 8), ALTextPos(0, 13)), "count" }, { ALTextRange(ALTextPos(6, 0), ALTextPos(6, 5)), "count" }, { ALTextRange(ALTextPos(6, 8), ALTextPos(6, 13)), "count" } }; ll_test::EditCount renamed(f.document()); - ensure("replaced", f.replaceAll(std::move(renames))); - ensure_equals("as one change", renamed.count(), 1); - ensure_equals("the text", f.text(), std::string("integer count;\nf()\n{\n total = 1;\n}\n// a note\ncount = count + 1;\n")); - ensure("the mark on a line between kept", f.markAt(5) == ALCodeEditor::Mark::Warning); - ensure("the fold between kept", f.isFolded(folded) && f.layout().hidden(3)); + UNSCOPED_INFO("replaced"); + CHECK(f.replaceAll(std::move(renames))); + UNSCOPED_INFO("as one change"); + REQUIRE(renamed.count() == 1); + UNSCOPED_INFO("the text"); + CHECK(f.text() == std::string("integer count;\nf()\n{\n total = 1;\n}\n// a note\ncount = count + 1;\n")); + UNSCOPED_INFO("the mark on a line between kept"); + CHECK(f.markAt(5) == ALCodeEditor::Mark::Warning); + { + INFO("the fold between kept"); + CHECK(f.isFolded(folded)); + CHECK(f.layout().hidden(3)); + } } - template<> template<> - void alcodeeditor_object::test<54>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a line typed with pairs closed and typed over is one undo step", "[alcodeeditor]") { - set_test_name("a line typed with its pairs closed and typed over and ended with Return is one step to undo: one run of typing"); ALCodeEditor& e = make(""); type("llSay(0, \"hi\");\n"); - ensure_equals("typed, the pairs closed and typed over", e.text(), std::string("llSay(0, \"hi\");\n")); + UNSCOPED_INFO("typed, the pairs closed and typed over"); + CHECK(e.text() == std::string("llSay(0, \"hi\");\n")); e.undo(); - ensure_equals("one step takes all of it back", e.text(), std::string()); - ensure("and there is nothing before it", !e.undoJournal().canUndo()); + UNSCOPED_INFO("one step takes all of it back"); + CHECK(e.text() == std::string()); + UNSCOPED_INFO("and there is nothing before it"); + CHECK_FALSE(e.undoJournal().canUndo()); } - template<> template<> - void alcodeeditor_object::test<55>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Return takes a completion once moved through or if it changes the text", "[alcodeeditor]") { - set_test_name("Return takes a completion once the list is moved through, or where taking it changes the text; else it is a new line; " - "the provider asked once while a word is typed"); ALCodeEditor& e = make(""); S32 asked = 0; e.setCompletionProvider([&asked](const ALTextPos&, std::string_view prefix, std::vector& out) { @@ -2045,74 +2787,95 @@ namespace tut } }); type("cou"); - ensure("open", e.completionOpen()); + UNSCOPED_INFO("open"); + CHECK(e.completionOpen()); type("nt"); - ensure_equals("asked once for the word, narrowed as it grew", asked, 1); - ensure_equals("the word typed out first", e.completions()[0].text, std::string("count")); + UNSCOPED_INFO("asked once for the word, narrowed as it grew"); + CHECK(asked == 1); + UNSCOPED_INFO("the word typed out first"); + CHECK(e.completions()[0].text == std::string("count")); key(KEY_RETURN); - ensure("a word typed out whole and Return: the list gone", !e.completionOpen()); - ensure_equals("and a new line", e.text(), std::string("count\n")); + UNSCOPED_INFO("a word typed out whole and Return: the list gone"); + CHECK_FALSE(e.completionOpen()); + UNSCOPED_INFO("and a new line"); + CHECK(e.text() == std::string("count\n")); type("cou"); key(KEY_DOWN); key(KEY_RETURN); - ensure_equals("moved through: the one chosen", e.text(), std::string("count\ncountdown")); + UNSCOPED_INFO("moved through: the one chosen"); + CHECK(e.text() == std::string("count\ncountdown")); key(KEY_RETURN); type("cou"); key(KEY_RETURN); - ensure_equals("not moved, but it changes what is typed: taken", e.text(), std::string("count\ncountdown\ncount")); - ensure_equals("asked once for each word", asked, 3); + UNSCOPED_INFO("not moved, but it changes what is typed: taken"); + CHECK(e.text() == std::string("count\ncountdown\ncount")); + UNSCOPED_INFO("asked once for each word"); + CHECK(asked == 3); e.setAcceptOnEnter(false); key(KEY_RETURN); type("cou"); key(KEY_DOWN); key(KEY_RETURN); - ensure_equals("never, where Return is not to take one", e.text(), std::string("count\ncountdown\ncount\ncou\n")); + UNSCOPED_INFO("never, where Return is not to take one"); + CHECK(e.text() == std::string("count\ncountdown\ncount\ncou\n")); } - template<> template<> - void alcodeeditor_object::test<56>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Control-Space lists with nothing typed and a colon asks for methods", "[alcodeeditor]") { - set_test_name("Control-Space with nothing typed lists what could go there; a colon asks for SLua's methods"); ALCodeEditor& e = make("integer total;\n"); e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("listed with nothing typed", e.completionOpen() && !e.completions().empty()); + { + INFO("listed with nothing typed"); + CHECK(e.completionOpen()); + REQUIRE_FALSE(e.completions().empty()); + } key(KEY_ESCAPE); ALCodeEditor& f = make("", "slua"); std::vector> asked; f.setCompletionRequest([&asked](const ALTextPos& at, std::string_view prefix) { asked.emplace_back(at, std::string(prefix)); }); type("obj:"); - ensure("asked after the colon with nothing typed", !asked.empty() && asked.back().first == ALTextPos(0, 4) && asked.back().second.empty()); + { + INFO("asked after the colon with nothing typed"); + REQUIRE_FALSE(asked.empty()); + CHECK(asked.back().first == ALTextPos(0, 4)); + REQUIRE(asked.back().second.empty()); + } ALCodeEditor::Completion method; method.text = "method"; method.kind = ALSyntaxKind::Function; f.supplyCompletions(ALTextPos(0, 4), { method }); - ensure("the answer listed", f.completionOpen() && f.completions().size() == 1 && f.completions()[0].text == "method"); + { + INFO("the answer listed"); + CHECK(f.completionOpen()); + REQUIRE(f.completions().size() == 1); + CHECK(f.completions()[0].text == "method"); + } } - template<> template<> - void alcodeeditor_object::test<57>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Go to Matching Bracket goes to the partner or the enclosing closer", "[alcodeeditor]") { - set_test_name("Go to Matching Bracket: to the partner of the bracket at the caret and back; else to the closer of the pair around it"); ALCodeEditor& e = make("f(a, [1, 2], \"(\")\n"); e.setCaret(ALTextPos(0, 1)); key('\\', MASK_CONTROL | MASK_SHIFT); - ensure("to the partner, past the one in a string", e.caret() == ALTextPos(0, 16)); + UNSCOPED_INFO("to the partner, past the one in a string"); + CHECK(e.caret() == ALTextPos(0, 16)); key('\\', MASK_CONTROL | MASK_SHIFT); - ensure("and back", e.caret() == ALTextPos(0, 1)); + UNSCOPED_INFO("and back"); + CHECK(e.caret() == ALTextPos(0, 1)); e.setCaret(ALTextPos(0, 7)); - ensure("offered", e.canPerform(ALEditorCommand::GoToMatchingBracket)); + UNSCOPED_INFO("offered"); + CHECK(e.canPerform(ALEditorCommand::GoToMatchingBracket)); e.perform(ALEditorCommand::GoToMatchingBracket); - ensure("inside a pair, at no bracket: to its closer", e.caret() == ALTextPos(0, 10)); + UNSCOPED_INFO("inside a pair, at no bracket: to its closer"); + CHECK(e.caret() == ALTextPos(0, 10)); } - template<> template<> - void alcodeeditor_object::test<58>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a pasted block is reindented as its own undo step", "[alcodeeditor]") { - set_test_name("a paste of lines into a line's indentation brought to where they go, as a step of its own that Undo takes back first"); ALCodeEditor& e = make("f()\n{\n x;\n}", "lsl"); e.setSoftTabs(true); e.setReindentsPaste(true); @@ -2120,61 +2883,74 @@ namespace tut LLClipboard::instance().copyToClipboard(copied, 0, static_cast(copied.size())); e.setCaret(ALTextPos(2, 4)); e.paste(); - ensure_equals("where it goes", e.text(), std::string("f()\n{\n if (y)\n {\n z;\n }\n x;\n}")); + UNSCOPED_INFO("where it goes"); + CHECK(e.text() == std::string("f()\n{\n if (y)\n {\n z;\n }\n x;\n}")); e.undo(); - ensure_equals("Undo: as it was copied", e.text(), std::string("f()\n{\n if (y)\n{\n z;\n}\nx;\n}")); + UNSCOPED_INFO("Undo: as it was copied"); + CHECK(e.text() == std::string("f()\n{\n if (y)\n{\n z;\n}\nx;\n}")); e.undo(); - ensure_equals("and again: before the paste", e.text(), std::string("f()\n{\n x;\n}")); + UNSCOPED_INFO("and again: before the paste"); + CHECK(e.text() == std::string("f()\n{\n x;\n}")); e.setReindentsPaste(false); e.setCaret(ALTextPos(2, 4)); e.paste(); - ensure_equals("not asked: as copied", e.text(), std::string("f()\n{\n if (y)\n{\n z;\n}\nx;\n}")); + UNSCOPED_INFO("not asked: as copied"); + CHECK(e.text() == std::string("f()\n{\n if (y)\n{\n z;\n}\nx;\n}")); } - template<> template<> - void alcodeeditor_object::test<59>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: LSL typing indents a braceless head and continues a doc comment", "[alcodeeditor]") { - set_test_name("LSL typed: a head with no brace sends one line in, its brace comes back level, and past its statement Return comes out; a doc comment goes on with its star"); ALCodeEditor& e = make("", "lsl"); e.setAutoComplete(false); e.setAutoClose(false); e.setSoftTabs(true); type("if (x)\n{\nllSay(0, \"a\");\n}\nif (y)\nllOwnerSay(\"b\");\nz = 1;"); - ensure_equals("as written by hand", e.text(), + UNSCOPED_INFO("as written by hand"); + CHECK(e.text() == std::string("if (x)\n{\n llSay(0, \"a\");\n}\nif (y)\n llOwnerSay(\"b\");\nz = 1;")); e.setText(" /**"); e.setCaret(e.document().end()); type("\nWhat it does.\n/"); - ensure_equals("each line starred, and closed", e.text(), std::string(" /**\n * What it does.\n */")); + UNSCOPED_INFO("each line starred, and closed"); + CHECK(e.text() == std::string(" /**\n * What it does.\n */")); } - template<> template<> - void alcodeeditor_object::test<60>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Copy and Cut with nothing selected take the line and paste it above", "[alcodeeditor]") { - set_test_name("Copy and Cut with nothing selected take the caret's line, and a paste with nothing selected puts it in above the caret's line"); ALCodeEditor& e = make("one\ntwo\nthree", "lsl"); e.setCaret(ALTextPos(1, 1)); - ensure("Copy offered with nothing selected", e.canCopy() && e.canCut()); + { + INFO("Copy offered with nothing selected"); + CHECK(e.canCopy()); + CHECK(e.canCut()); + } e.copy(); std::string held; LLClipboard::instance().pasteFromClipboard(held); - ensure_equals("the line, with its break", held, std::string("two\n")); + UNSCOPED_INFO("the line, with its break"); + CHECK(held == std::string("two\n")); e.setCaret(ALTextPos(0, 2)); e.paste(); - ensure_equals("in above the caret's line", e.text(), std::string("two\none\ntwo\nthree")); - ensure("the caret where it was in its line", e.caret() == ALTextPos(1, 2)); + UNSCOPED_INFO("in above the caret's line"); + CHECK(e.text() == std::string("two\none\ntwo\nthree")); + UNSCOPED_INFO("the caret where it was in its line"); + CHECK(e.caret() == ALTextPos(1, 2)); e.setCaret(ALTextPos(3, 3)); e.cut(); - ensure_equals("cut: the last line gone, with the break before it", e.text(), std::string("two\none\ntwo")); - ensure("the caret on the line before", e.caret() == ALTextPos(2, 3)); + UNSCOPED_INFO("cut: the last line gone, with the break before it"); + CHECK(e.text() == std::string("two\none\ntwo")); + UNSCOPED_INFO("the caret on the line before"); + CHECK(e.caret() == ALTextPos(2, 3)); e.setSelection(ALTextRange(ALTextPos(1, 0), ALTextPos(1, 2))); e.paste(); - ensure_equals("over a selection, as text", e.text(), std::string("two\nthree\ne\ntwo")); + UNSCOPED_INFO("over a selection, as text"); + CHECK(e.text() == std::string("two\nthree\ne\ntwo")); e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 3))); e.copy(); e.setCaret(ALTextPos(3, 0)); e.paste(); - ensure_equals("a selection copied goes in where the caret is", e.text(), std::string("two\nthree\ne\ntwotwo")); + UNSCOPED_INFO("a selection copied goes in where the caret is"); + CHECK(e.text() == std::string("two\nthree\ne\ntwotwo")); // Several, one selecting something: what is copied is what a cut // takes, the selections' text, not a caret's line too. @@ -2182,44 +2958,59 @@ namespace tut e.setSelections(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 2)), { ALTextRange(ALTextPos(1, 1), ALTextPos(1, 1)) }); e.copy(); LLClipboard::instance().pasteFromClipboard(held); - ensure_equals("the selection's alone", held, std::string("al")); + UNSCOPED_INFO("the selection's alone"); + CHECK(held == std::string("al")); e.cut(); - ensure_equals("and the cut takes that alone", e.text(), std::string("pha\nbeta")); + UNSCOPED_INFO("and the cut takes that alone"); + CHECK(e.text() == std::string("pha\nbeta")); // A jump to a line leaves one caret there. e.setSelections(ALTextRange(ALTextPos(0, 1), ALTextPos(0, 1)), { ALTextRange(ALTextPos(1, 1), ALTextPos(1, 1)) }); e.goToLine(1); - ensure("one caret, at the line", !e.hasOtherSelections() && e.caret() == ALTextPos(1, 0)); + { + INFO("one caret, at the line"); + CHECK_FALSE(e.hasOtherSelections()); + CHECK(e.caret() == ALTextPos(1, 0)); + } } - template<> template<> - void alcodeeditor_object::test<61>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Control-Return inserts lines below or above and Control-L selects lines", "[alcodeeditor]") { - set_test_name("a line inserted below, indented where it goes, or above, as far in as the caret's; the line selected, and the next with it"); ALCodeEditor& e = make("default\n{\n state_entry()\n {\n }\n}", "lsl"); e.setSoftTabs(true); e.setCaret(ALTextPos(3, 2)); key(KEY_RETURN, MASK_CONTROL); - ensure_equals("below, a level in under the brace, the brace line whole", e.document().line(4), std::string(" ")); - ensure("the caret on it", e.caret() == ALTextPos(4, 8) && e.document().line(3) == " {"); + UNSCOPED_INFO("below, a level in under the brace, the brace line whole"); + CHECK(e.document().line(4) == std::string(" ")); + { + INFO("the caret on it"); + CHECK(e.caret() == ALTextPos(4, 8)); + CHECK(e.document().line(3) == " {"); + } e.setCaret(ALTextPos(2, 6)); key(KEY_RETURN, MASK_CONTROL | MASK_SHIFT); - ensure_equals("above, as far in", e.document().line(2), std::string(" ")); - ensure("the caret on it, the line below as it was", e.caret() == ALTextPos(2, 4) && e.document().line(3) == " state_entry()"); + UNSCOPED_INFO("above, as far in"); + CHECK(e.document().line(2) == std::string(" ")); + { + INFO("the caret on it, the line below as it was"); + CHECK(e.caret() == ALTextPos(2, 4)); + CHECK(e.document().line(3) == " state_entry()"); + } e.setCaret(ALTextPos(1, 0)); key('L', MASK_CONTROL); - ensure("the line selected with its break", e.selection().normalised() == ALTextRange(ALTextPos(1, 0), ALTextPos(2, 0))); + UNSCOPED_INFO("the line selected with its break"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(1, 0), ALTextPos(2, 0))); key('L', MASK_CONTROL); - ensure("and the next", e.selection().normalised() == ALTextRange(ALTextPos(1, 0), ALTextPos(3, 0))); + UNSCOPED_INFO("and the next"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(1, 0), ALTextPos(3, 0))); e.setCaret(ALTextPos(7, 1)); key('L', MASK_CONTROL); - ensure("the last, to the end", e.selection().normalised() == ALTextRange(ALTextPos(7, 0), ALTextPos(7, 1))); + UNSCOPED_INFO("the last, to the end"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(7, 0), ALTextPos(7, 1))); } - template<> template<> - void alcodeeditor_object::test<62>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: word keys move by code names and subword keys by a name's parts", "[alcodeeditor]") { - set_test_name("the word keys go by code's names and marks in code, as a double-click takes a name; the subword keys by a name's parts"); ALCodeEditor& e = make("ll.Say(0, llSetPos);", "slua"); #if LL_DARWIN const MASK word = MASK_ALT; @@ -2230,12 +3021,15 @@ namespace tut #endif e.setCaret(ALTextPos(0, 0)); key(KEY_RIGHT, word); - ensure("to the dot, not past ll.Say", e.caret() == ALTextPos(0, 2)); + UNSCOPED_INFO("to the dot, not past ll.Say"); + CHECK(e.caret() == ALTextPos(0, 2)); key(KEY_RIGHT, word | MASK_SHIFT); - ensure("selecting the mark", e.selection().normalised() == ALTextRange(ALTextPos(0, 2), ALTextPos(0, 3))); + UNSCOPED_INFO("selecting the mark"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(0, 2), ALTextPos(0, 3))); e.setCaret(ALTextPos(0, 10)); key(KEY_RIGHT, part); - ensure("a part", e.caret() == ALTextPos(0, 12)); + UNSCOPED_INFO("a part"); + CHECK(e.caret() == ALTextPos(0, 12)); #if LL_DARWIN key(KEY_RIGHT, part | MASK_SHIFT); #else @@ -2243,101 +3037,141 @@ namespace tut // arrow grows a column elsewhere. e.perform(ALEditorCommand::SelectSubwordRight); #endif - ensure("and a part selected",e.selection().normalised() == ALTextRange(ALTextPos(0, 12), ALTextPos(0, 15))); + UNSCOPED_INFO("and a part selected"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(0, 12), ALTextPos(0, 15))); e.setCaret(ALTextPos(0, 18)); key(KEY_BACKSPACE, word); - ensure_equals("a word taken back is the name", e.text(), std::string("ll.Say(0, );")); + UNSCOPED_INFO("a word taken back is the name"); + CHECK(e.text() == std::string("ll.Say(0, );")); } - template<> template<> - void alcodeeditor_object::test<63>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Expand Selection grows by syntax and Shrink Selection goes back", "[alcodeeditor]") { - set_test_name("Expand Selection grows by the name, the member, the string, the brackets' inside and the brackets, the lines, the text; Shrink goes back"); ALCodeEditor& e = make("f()\n{\n ll.Say(0, \"hi there\");\n}", "slua"); const auto grown = [&]() { - ensure("grows", e.perform(ALEditorCommand::ExpandSelection)); + UNSCOPED_INFO("grows"); + REQUIRE(e.perform(ALEditorCommand::ExpandSelection)); return e.selectedText(); }; e.setCaret(ALTextPos(2, 8)); - ensure_equals("the name", grown(), std::string("Say")); - ensure_equals("with what it is a member of", grown(), std::string("ll.Say")); - ensure_equals("the line's text", grown(), std::string(" ll.Say(0, \"hi there\");")); - ensure_equals("the braces' inside", grown(), std::string("\n ll.Say(0, \"hi there\");\n")); - ensure_equals("with the braces", grown(), std::string("{\n ll.Say(0, \"hi there\");\n}")); - ensure("shrinks back", e.perform(ALEditorCommand::ShrinkSelection) && e.selectedText() == "\n ll.Say(0, \"hi there\");\n"); + UNSCOPED_INFO("the name"); + CHECK(grown() == std::string("Say")); + UNSCOPED_INFO("with what it is a member of"); + CHECK(grown() == std::string("ll.Say")); + UNSCOPED_INFO("the line's text"); + CHECK(grown() == std::string(" ll.Say(0, \"hi there\");")); + UNSCOPED_INFO("the braces' inside"); + CHECK(grown() == std::string("\n ll.Say(0, \"hi there\");\n")); + UNSCOPED_INFO("with the braces"); + CHECK(grown() == std::string("{\n ll.Say(0, \"hi there\");\n}")); + { + INFO("shrinks back"); + CHECK(e.perform(ALEditorCommand::ShrinkSelection)); + CHECK(e.selectedText() == "\n ll.Say(0, \"hi there\");\n"); + } e.perform(ALEditorCommand::ShrinkSelection); e.perform(ALEditorCommand::ShrinkSelection); e.perform(ALEditorCommand::ShrinkSelection); - ensure_equals("to the name", e.selectedText(), std::string("Say")); - ensure("and to the caret", e.perform(ALEditorCommand::ShrinkSelection) && e.caret() == ALTextPos(2, 8) && !e.hasSelection()); - ensure("no further", !e.canPerform(ALEditorCommand::ShrinkSelection)); + UNSCOPED_INFO("to the name"); + CHECK(e.selectedText() == std::string("Say")); + { + INFO("and to the caret"); + CHECK(e.perform(ALEditorCommand::ShrinkSelection)); + CHECK(e.caret() == ALTextPos(2, 8)); + CHECK_FALSE(e.hasSelection()); + } + UNSCOPED_INFO("no further"); + CHECK_FALSE(e.canPerform(ALEditorCommand::ShrinkSelection)); e.setCaret(ALTextPos(2, 17)); - ensure_equals("in a string: a word", grown(), std::string("hi")); - ensure_equals("its inside", grown(), std::string("hi there")); - ensure_equals("with its quotes", grown(), std::string("\"hi there\"")); - ensure_equals("the call's arguments", grown(), std::string("0, \"hi there\"")); - ensure_equals("with the brackets", grown(), std::string("(0, \"hi there\")")); + UNSCOPED_INFO("in a string: a word"); + CHECK(grown() == std::string("hi")); + UNSCOPED_INFO("its inside"); + CHECK(grown() == std::string("hi there")); + UNSCOPED_INFO("with its quotes"); + CHECK(grown() == std::string("\"hi there\"")); + UNSCOPED_INFO("the call's arguments"); + CHECK(grown() == std::string("0, \"hi there\"")); + UNSCOPED_INFO("with the brackets"); + CHECK(grown() == std::string("(0, \"hi there\")")); e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(1, 0))); - ensure("moved by hand: nothing to go back through", !e.canPerform(ALEditorCommand::ShrinkSelection)); + UNSCOPED_INFO("moved by hand: nothing to go back through"); + CHECK_FALSE(e.canPerform(ALEditorCommand::ShrinkSelection)); // Selected by hand from inside a pair to past where it closes: the // pair around all of it, not the line. e.setText("f((a) + b)"); e.setSelection(ALTextRange(ALTextPos(0, 3), ALTextPos(0, 9))); - ensure_equals("past the pair it crosses, the inside of the one around it", grown(), std::string("(a) + b")); - ensure_equals("then with its brackets", grown(), std::string("((a) + b)")); + UNSCOPED_INFO("past the pair it crosses, the inside of the one around it"); + CHECK(grown() == std::string("(a) + b")); + UNSCOPED_INFO("then with its brackets"); + CHECK(grown() == std::string("((a) + b)")); } - template<> template<> - void alcodeeditor_object::test<64>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the name under the caret lights its other places in code only", "[alcodeeditor]") { - set_test_name("the name under the caret: its other places in code lit, not in a string or a comment, nor a longer name; put out as the caret leaves it or the text changes"); ALCodeEditor& e = make("local x = x + xy\nprint(x) -- x\nprint(\"x\")", "slua"); typedef ALCodeEditor::Highlight H; e.setCaret(ALTextPos(0, 6)); e.lightOccurrences(); const std::vector& lit = e.highlights(H::Occurrences); - ensure_equals("three in code", lit.size(), size_t(3)); - ensure("where they are", lit[0] == ALTextRange(ALTextPos(0, 6), ALTextPos(0, 7)) && lit[1] == ALTextRange(ALTextPos(0, 10), ALTextPos(0, 11)) && - lit[2] == ALTextRange(ALTextPos(1, 6), ALTextPos(1, 7))); + UNSCOPED_INFO("three in code"); + REQUIRE(lit.size() == size_t(3)); + { + INFO("where they are"); + CHECK(lit[0] == ALTextRange(ALTextPos(0, 6), ALTextPos(0, 7))); + CHECK(lit[1] == ALTextRange(ALTextPos(0, 10), ALTextPos(0, 11))); + CHECK(lit[2] == ALTextRange(ALTextPos(1, 6), ALTextPos(1, 7))); + } e.setCaret(ALTextPos(0, 11)); - ensure("still on one: kept", e.highlights(H::Occurrences).size() == 3); + UNSCOPED_INFO("still on one: kept"); + REQUIRE(e.highlights(H::Occurrences).size() == 3); e.setCaret(ALTextPos(0, 8)); - ensure("off it: put out", e.highlights(H::Occurrences).empty()); + UNSCOPED_INFO("off it: put out"); + REQUIRE(e.highlights(H::Occurrences).empty()); e.setCaret(ALTextPos(0, 14)); e.lightOccurrences(); - ensure("a name that stands once: nothing", e.highlights(H::Occurrences).empty()); + UNSCOPED_INFO("a name that stands once: nothing"); + REQUIRE(e.highlights(H::Occurrences).empty()); e.setCaret(ALTextPos(1, 12)); e.lightOccurrences(); - ensure("in a comment: nothing", e.highlights(H::Occurrences).empty()); + UNSCOPED_INFO("in a comment: nothing"); + REQUIRE(e.highlights(H::Occurrences).empty()); e.setCaret(ALTextPos(0, 6)); e.lightOccurrences(); e.insertText("z"); - ensure("the text changed: put out", e.highlights(H::Occurrences).empty()); + UNSCOPED_INFO("the text changed: put out"); + REQUIRE(e.highlights(H::Occurrences).empty()); e.setLightsOccurrences(false); e.setCaret(ALTextPos(1, 6)); e.lightOccurrences(); - ensure("not asked: nothing", e.highlights(H::Occurrences).empty()); + UNSCOPED_INFO("not asked: nothing"); + REQUIRE(e.highlights(H::Occurrences).empty()); } - template<> template<> - void alcodeeditor_object::test<65>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Select Next Occurrence takes each place in turn and typing edits all", "[alcodeeditor]") { - set_test_name("Select Next Occurrence takes the name, then its places in turn, going round, each a selection; what is typed goes into every place taken, one step; Change All selects every place"); ALCodeEditor& e = make("local x = 1\nx = x + xy\nprint(x)", "slua"); e.setAutoComplete(false); e.setAutoClose(false); e.setCaret(ALTextPos(1, 0)); key('D', MASK_CONTROL); - ensure_equals("the name", e.selectedText(), std::string("x")); + UNSCOPED_INFO("the name"); + CHECK(e.selectedText() == std::string("x")); key('D', MASK_CONTROL); - ensure("the next place taken, the main selection now", e.selection().normalised() == ALTextRange(ALTextPos(1, 4), ALTextPos(1, 5))); - ensure("the first kept beside it", e.otherSelections().size() == 1 && e.otherSelections()[0] == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 1))); + UNSCOPED_INFO("the next place taken, the main selection now"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(1, 4), ALTextPos(1, 5))); + { + INFO("the first kept beside it"); + REQUIRE(e.otherSelections().size() == 1); + CHECK(e.otherSelections()[0] == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 1))); + } type("count"); - ensure_equals("typed into both, not into the longer name", e.text(), std::string("local x = 1\ncount = count + xy\nprint(x)")); + UNSCOPED_INFO("typed into both, not into the longer name"); + CHECK(e.text() == std::string("local x = 1\ncount = count + xy\nprint(x)")); e.undo(); - ensure_equals("one step back takes back both", e.text(), std::string("local x = 1\nx = x + xy\nprint(x)")); + UNSCOPED_INFO("one step back takes back both"); + CHECK(e.text() == std::string("local x = 1\nx = x + xy\nprint(x)")); e.setCaret(ALTextPos(2, 6)); e.singleSelection(); @@ -2345,26 +3179,30 @@ namespace tut key('D', MASK_CONTROL); key('D', MASK_CONTROL); type("n"); - ensure_equals("going round from the last to the first", e.text(), std::string("local n = 1\nn = x + xy\nprint(n)")); + UNSCOPED_INFO("going round from the last to the first"); + CHECK(e.text() == std::string("local n = 1\nn = x + xy\nprint(n)")); key(KEY_ESCAPE); - ensure("Escape lets go", !e.hasOtherSelections()); + UNSCOPED_INFO("Escape lets go"); + CHECK_FALSE(e.hasOtherSelections()); e.setText("local x = 1\nx = x + xy\nprint(\"x\")"); e.setCaret(ALTextPos(0, 6)); key('L', MASK_CONTROL | MASK_SHIFT); - ensure_equals("Change All: every place selected", e.otherSelections().size(), static_cast(3)); + UNSCOPED_INFO("Change All: every place selected"); + REQUIRE(e.otherSelections().size() == static_cast(3)); type("n"); - ensure_equals("every place of the name, as written", e.text(), std::string("local n = 1\nn = n + xy\nprint(\"n\")")); + UNSCOPED_INFO("every place of the name, as written"); + CHECK(e.text() == std::string("local n = 1\nn = n + xy\nprint(\"n\")")); e.goTo(ALTextPos(2, 0)); - ensure("gone elsewhere: one caret", !e.hasOtherSelections()); + UNSCOPED_INFO("gone elsewhere: one caret"); + CHECK_FALSE(e.hasOtherSelections()); type("-- "); - ensure_equals("and typing is typing again", e.document().line(2), std::string("-- print(\"n\")")); + UNSCOPED_INFO("and typing is typing again"); + CHECK(e.document().line(2) == std::string("-- print(\"n\")")); } - template<> template<> - void alcodeeditor_object::test<66>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: signature help follows the caret's argument and steps through overloads", "[alcodeeditor]") { - set_test_name("signature help stays inside a call across its lines, follows the caret's argument itself, asks again only as a bracket or a comma changes, and steps through overloads"); ALCodeEditor& e = make("f(a,\n b, c)\nx\n", "lsl"); e.setAutoComplete(false); e.setAutoClose(false); @@ -2376,36 +3214,45 @@ namespace tut e.setCaret(ALTextPos(0, 2)); e.showSignature(ALTextPos(0, 2), sig); e.setCaret(ALTextPos(1, 3)); - ensure("shown on the call's next line", e.signatureShown()); - ensure_equals("the second argument, by the comma above", e.signature()->active, 1); + UNSCOPED_INFO("shown on the call's next line"); + CHECK(e.signatureShown()); + UNSCOPED_INFO("the second argument, by the comma above"); + CHECK(e.signature()->active == 1); e.setCaret(ALTextPos(1, 5)); - ensure_equals("past another comma: the third", e.signature()->active, 2); + UNSCOPED_INFO("past another comma: the third"); + CHECK(e.signature()->active == 2); type("zz"); - ensure_equals("typing within an argument asks nothing", asked, 0); + UNSCOPED_INFO("typing within an argument asks nothing"); + CHECK(asked == 0); key(KEY_BACKSPACE); - ensure_equals("nor taking a letter back", asked, 0); + UNSCOPED_INFO("nor taking a letter back"); + CHECK(asked == 0); type(","); - ensure_equals("a comma asks", asked, 1); + UNSCOPED_INFO("a comma asks"); + CHECK(asked == 1); key(KEY_BACKSPACE); - ensure_equals("and taking it back", asked, 2); + UNSCOPED_INFO("and taking it back"); + CHECK(asked == 2); e.setCaret(ALTextPos(2, 0)); - ensure("out of the call: gone", !e.signatureShown()); + UNSCOPED_INFO("out of the call: gone"); + CHECK_FALSE(e.signatureShown()); ALCodeEditor::Signature forms = sig; forms.overloads = { { sig.label, sig.parameters }, { "f(string text)", { { 2, 13 } } } }; e.setCaret(ALTextPos(0, 2)); e.showSignature(ALTextPos(0, 2), forms); key(KEY_DOWN); - ensure_equals("Down: the next form", e.signature()->label, std::string("f(string text)")); - ensure("the caret where it was", e.caret() == ALTextPos(0, 2)); + UNSCOPED_INFO("Down: the next form"); + CHECK(e.signature()->label == std::string("f(string text)")); + UNSCOPED_INFO("the caret where it was"); + CHECK(e.caret() == ALTextPos(0, 2)); key(KEY_DOWN); - ensure_equals("round to the first", e.signature()->label, sig.label); + UNSCOPED_INFO("round to the first"); + CHECK(e.signature()->label == sig.label); } - template<> template<> - void alcodeeditor_object::test<67>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: folding follows LSL braces and SLua blocks and region comments", "[alcodeeditor]") { - set_test_name("folding follows the syntax: LSL's braces, a brace alone its header's; SLua's words, each arm of an if; a region the comments mark"); const auto regions = [](ALCodeEditor& e) { std::string out; for (const ALFoldModel::Region& region : e.foldRegions()) @@ -2415,16 +3262,16 @@ namespace tut return out; }; ALCodeEditor& lsl = make("default\n{\n touch_start(integer n) {\n llSay(0, \"{\"); // }\n }\n}\n", "lsl"); - ensure_equals("braces that are code, from the header", regions(lsl), std::string("0-5 2-4")); + UNSCOPED_INFO("braces that are code, from the header"); + CHECK(regions(lsl) == std::string("0-5 2-4")); ALCodeEditor& lua = make("local function f()\n if a then\n x()\n elseif b then\n y()\n else\n z()\n end\nend\n-- #region notes\nlocal t = {\n 1,\n}\n-- #endregion\n", "slua"); - ensure_equals("the function, each arm, the table, the region", regions(lua), std::string("0-8 1-2 3-4 5-7 9-13 10-12")); + UNSCOPED_INFO("the function, each arm, the table, the region"); + CHECK(regions(lua) == std::string("0-8 1-2 3-4 5-7 9-13 10-12")); } - template<> template<> - void alcodeeditor_object::test<68>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the analyzer's hover text comes before the definitions' text", "[alcodeeditor]") { - set_test_name("the analyzer's word on a word comes before the definitions', which are said where the analyzer says nothing"); ALCodeEditor& e = make("print(value)\n"); e.setHoverProvider([](const ALTextPos&, std::string_view word, std::string& text) { text = "the definitions on " + std::string(word); @@ -2439,23 +3286,34 @@ namespace tut const S32 y = text.mTop - e.layout().rowHeight() / 2; e.handleHover(print_x, y, MASK_NONE); e.handleToolTip(print_x, y, MASK_NONE); - ensure("asked, nothing shown before the answer", asked.size() == 1 && !e.cardShown()); + { + INFO("asked, nothing shown before the answer"); + REQUIRE(asked.size() == 1); + CHECK_FALSE(e.cardShown()); + } e.supplyHover(asked[0], "local print: the script's own"); - ensure("the analyzer's shown", e.cardShown() && e.card()->text() == "local print: the script's own"); + { + INFO("the analyzer's shown"); + REQUIRE(e.cardShown()); + CHECK(e.card()->text() == "local print: the script's own"); + } e.handleHover(text.mRight - 5, text.mBottom + 5, MASK_NONE); e.handleHover(value_x, y, MASK_NONE); e.handleToolTip(value_x, y, MASK_NONE); - ensure_equals("asked of the other", asked.size(), size_t(2)); + UNSCOPED_INFO("asked of the other"); + REQUIRE(asked.size() == size_t(2)); e.supplyHover(asked[1], std::string()); - ensure("the analyzer saying nothing: the definitions'", e.cardShown() && e.card()->text() == "the definitions on value"); + { + INFO("the analyzer saying nothing: the definitions'"); + REQUIRE(e.cardShown()); + CHECK(e.card()->text() == "the definitions on value"); + } } - template<> template<> - void alcodeeditor_object::test<69>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a row's colours match the whole line's when wrapped or clipped", "[alcodeeditor]") { - set_test_name("a row's colours are the whole line's, however much of it is drawn: wrapped, or cut down to what is in sight"); if (!ui.ok()) { - skip("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } // A long line with brackets nested in code, in a string and in a // comment, and a name lit by the analyzer wherever it is written: @@ -2498,10 +3356,12 @@ namespace tut }; const auto same = [&](const std::string& what, const std::vector& all, const ALTextLayout::Row& row) { const std::vector colours = ll_test::TextViewProbe::colours(*editor, 0, row); - ensure_equals(what + ": as many", colours.size(), row.glyphEnd - row.glyphBegin); + UNSCOPED_INFO(what + ": as many"); + REQUIRE(colours.size() == row.glyphEnd - row.glyphBegin); for (size_t k = 0; k < colours.size(); ++k) { - ensure(what + ": glyph " + std::to_string(row.glyphBegin + k), colours[k] == all[row.glyphBegin + k]); + UNSCOPED_INFO(what + ": glyph " + std::to_string(row.glyphBegin + k)); + REQUIRE(colours[k] == all[row.glyphBegin + k]); } }; @@ -2510,14 +3370,19 @@ namespace tut // analyzer's colour in code, and the grammar's in the string. const S32 first_open = static_cast(text.find('(')); const S32 in_string = static_cast(text.find("(x)") + 1); - ensure("the outer bracket in the first colour", all[static_cast(first_open)] == LLColor4U(LLColor4::red)); - ensure("the inner one in the second", all[static_cast(first_open) + 2] == LLColor4U(LLColor4::green)); - ensure("a name in code in the analyzer's colour", all[0] == LLColor4U(editor->colorForKind(ALSyntaxKind::Constant))); - ensure("the name in the string in the string's", all[static_cast(in_string)] == all[static_cast(in_string) - 1]); + UNSCOPED_INFO("the outer bracket in the first colour"); + CHECK(all[static_cast(first_open)] == LLColor4U(LLColor4::red)); + UNSCOPED_INFO("the inner one in the second"); + CHECK(all[static_cast(first_open) + 2] == LLColor4U(LLColor4::green)); + UNSCOPED_INFO("a name in code in the analyzer's colour"); + CHECK(all[0] == LLColor4U(editor->colorForKind(ALSyntaxKind::Constant))); + UNSCOPED_INFO("the name in the string in the string's"); + CHECK(all[static_cast(in_string)] == all[static_cast(in_string) - 1]); editor->setWordWrap(true); const ALTextLayout::Line& wrapped = editor->layout().line(0); - ensure("wrapped into rows", wrapped.rows.size() > 10); + UNSCOPED_INFO("wrapped into rows"); + REQUIRE(wrapped.rows.size() > 10); for (size_t r = 0; r < wrapped.rows.size(); ++r) { same("wrapped row " + std::to_string(r), all, wrapped.rows[r]); @@ -2536,7 +3401,8 @@ namespace tut editor->setCaret(ALTextPos(0, 0)); editor->insertText("("); const std::vector deeper = whole_of(); - ensure("the outer bracket one deeper", deeper[static_cast(first_open) + 1] == LLColor4U(LLColor4::green)); + UNSCOPED_INFO("the outer bracket one deeper"); + CHECK(deeper[static_cast(first_open) + 1] == LLColor4U(LLColor4::green)); editor->setWordWrap(true); const ALTextLayout::Line& again = editor->layout().line(0); for (size_t r = 0; r < again.rows.size(); ++r) @@ -2545,18 +3411,27 @@ namespace tut } } - template<> template<> - void alcodeeditor_object::test<70>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the completion and fix lists are made when first shown", "[alcodeeditor]") { - set_test_name("an editor makes its completion and fix lists the first time each is shown, not before"); ALCodeEditor& e = make("integer count;\nllSay(0, co"); - ensure("no completion list yet", e.findChild("completions") == nullptr); - ensure("no fix list yet", e.findChild("fixes") == nullptr); - ensure("neither open", !e.completionOpen() && !e.fixesOpen()); + UNSCOPED_INFO("no completion list yet"); + REQUIRE(e.findChild("completions") == nullptr); + UNSCOPED_INFO("no fix list yet"); + REQUIRE(e.findChild("fixes") == nullptr); + { + INFO("neither open"); + CHECK_FALSE(e.completionOpen()); + CHECK_FALSE(e.fixesOpen()); + } e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("the completions listed", e.completionOpen() && e.findChild("completions") != nullptr); - ensure("and still no fix list", e.findChild("fixes") == nullptr); + { + INFO("the completions listed"); + CHECK(e.completionOpen()); + REQUIRE(e.findChild("completions") != nullptr); + } + UNSCOPED_INFO("and still no fix list"); + REQUIRE(e.findChild("fixes") == nullptr); key(KEY_ESCAPE); e.setFixProvider([](S32 line, std::vector& out) { ALCodeEditor::Fix semi; @@ -2566,12 +3441,15 @@ namespace tut out = { semi }; }); e.setFixable(1, true, true); - ensure("the fixes listed", e.handleKeyHere('.', MASK_CONTROL) && e.fixesOpen() && e.findChild("fixes") != nullptr); + { + INFO("the fixes listed"); + CHECK(e.handleKeyHere('.', MASK_CONTROL)); + CHECK(e.fixesOpen()); + REQUIRE(e.findChild("fixes") != nullptr); + } } - template<> template<> - void alcodeeditor_object::test<71>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the next occurrence taken comes out from under the pinned headers", "[alcodeeditor]") { - set_test_name("the next occurrence taken comes out from under the pinned headers, not scrolled to the top under them"); std::string text = "default\n{\n state_entry()\n {\n"; for (int i = 0; i < 80; ++i) { @@ -2587,39 +3465,53 @@ namespace tut e.perform(ALEditorCommand::SelectNextOccurrence); e.perform(ALEditorCommand::SelectNextOccurrence); const S32 below = e.layout().lineTop(4) - e.scrollY(); - ensure("in sight", below < e.textRect().getHeight()); - ensure("below the handler's state pinned over it: " + std::to_string(below) + " of " + std::to_string(row_h), below >= row_h); + UNSCOPED_INFO("in sight"); + CHECK(below < e.textRect().getHeight()); + UNSCOPED_INFO("below the handler's state pinned over it: " + std::to_string(below) + " of " + std::to_string(row_h)); + CHECK(below >= row_h); } - template<> template<> - void alcodeeditor_object::test<72>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: completion and fix popups are see-through and as large as the editor", "[alcodeeditor]") { - set_test_name("the completion list and the box beside it are one child view, as large as the editor and seen through: a press " - "on the list is the list's, one beside it the text's; the fixes have one of their own"); ALCodeEditor& e = make("integer count;\ninteger total;\nllSay(0, co"); e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("open", e.completionOpen()); + UNSCOPED_INFO("open"); + CHECK(e.completionOpen()); ALChoicePopup* popup = e.findChild("completion_popup", false); - ensure("found by its name among the editor's children", popup != nullptr); - ensure("as large as the editor", popup->getRect() == e.getLocalRect()); - ensure("holding the list", popup->findChild("completions", false) == &popup->list()); + UNSCOPED_INFO("found by its name among the editor's children"); + REQUIRE(popup != nullptr); + UNSCOPED_INFO("as large as the editor"); + CHECK(popup->getRect() == e.getLocalRect()); + UNSCOPED_INFO("holding the list"); + CHECK(popup->findChild("completions", false) == &popup->list()); const LLRect list = popup->list().getRect(); const LLRect text = e.textRect(); const S32 row = e.layout().rowHeight(); // On the list: the list's, the list staying open. const ALTextPos caret = e.caret(); - ensure("a press on the list is taken", e.handleMouseDown(list.mLeft + 8, list.mTop - row / 2, MASK_NONE)); + UNSCOPED_INFO("a press on the list is taken"); + CHECK(e.handleMouseDown(list.mLeft + 8, list.mTop - row / 2, MASK_NONE)); e.handleMouseUp(list.mLeft + 8, list.mTop - row / 2, MASK_NONE); - ensure("by the list, the text as it was", e.completionOpen() && e.caret() == caret); + { + INFO("by the list, the text as it was"); + CHECK(e.completionOpen()); + CHECK(e.caret() == caret); + } // On the first line, well above the list: the text's. const S32 x = text.mLeft + 2; const S32 y = text.mTop - row / 2; - ensure("the first line is not under the list", !list.pointInRect(x, y)); + UNSCOPED_INFO("the first line is not under the list"); + CHECK_FALSE(list.pointInRect(x, y)); e.handleMouseDown(x, y, MASK_NONE); e.handleMouseUp(x, y, MASK_NONE); - ensure("a press beside the list is the text's", !e.completionOpen() && e.caret().line == 0); - ensure("the popup hidden with its list", !popup->getVisible()); + { + INFO("a press beside the list is the text's"); + CHECK_FALSE(e.completionOpen()); + CHECK(e.caret().line == 0); + } + UNSCOPED_INFO("the popup hidden with its list"); + CHECK_FALSE(popup->getVisible()); e.setFixProvider([](S32 line, std::vector& out) { ALCodeEditor::Fix semi; @@ -2629,41 +3521,64 @@ namespace tut out = { semi }; }); e.setFixable(0, true, true); - ensure("the fixes listed", e.handleKeyHere('.', MASK_CONTROL) && e.fixesOpen()); + { + INFO("the fixes listed"); + CHECK(e.handleKeyHere('.', MASK_CONTROL)); + CHECK(e.fixesOpen()); + } ALChoicePopup* fixes = e.findChild("fix_popup", false); - ensure("on a popup of their own", fixes && fixes != popup && fixes->findChild("fixes", false) == &fixes->list()); - ensure("previewed in its own box", fixes->sideShown() && e.findChild("fix_preview") == &fixes->side()); + { + INFO("on a popup of their own"); + REQUIRE(fixes); + REQUIRE(fixes != popup); + CHECK(fixes->findChild("fixes", false) == &fixes->list()); + } + { + INFO("previewed in its own box"); + REQUIRE(fixes->sideShown()); + CHECK(e.findChild("fix_preview") == &fixes->side()); + } key(KEY_ESCAPE); - ensure("and the box goes with the list", !fixes->sideShown() && !fixes->getVisible()); + { + INFO("and the box goes with the list"); + REQUIRE_FALSE(fixes->sideShown()); + CHECK_FALSE(fixes->getVisible()); + } } - template<> template<> - void alcodeeditor_object::test<73>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: pairs close and type over and delete together at every caret", "[alcodeeditor]") { - set_test_name("pairs at every caret: closed, typed over and taken away together at each, a selection wrapped; a character typed as ever where it pairs nothing"); ALCodeEditor& e = make("a\nb\nc"); e.setAutoClose(true); e.setSelections(ALTextRange(ALTextPos(0, 1), ALTextPos(0, 1)), { ALTextRange(ALTextPos(1, 1), ALTextPos(1, 1)), ALTextRange(ALTextPos(2, 1), ALTextPos(2, 1)) }); type("("); - ensure_equals("closed at each", e.text(), std::string("a()\nb()\nc()")); - ensure("the main caret between", e.caret() == ALTextPos(0, 2)); + UNSCOPED_INFO("closed at each"); + CHECK(e.text() == std::string("a()\nb()\nc()")); + UNSCOPED_INFO("the main caret between"); + CHECK(e.caret() == ALTextPos(0, 2)); type("x)"); - ensure_equals("each closer typed over", e.text(), std::string("a(x)\nb(x)\nc(x)")); - ensure("past it", e.caret() == ALTextPos(0, 4)); + UNSCOPED_INFO("each closer typed over"); + CHECK(e.text() == std::string("a(x)\nb(x)\nc(x)")); + UNSCOPED_INFO("past it"); + CHECK(e.caret() == ALTextPos(0, 4)); type("["); key(KEY_BACKSPACE); - ensure_equals("one Backspace takes each pair", e.text(), std::string("a(x)\nb(x)\nc(x)")); + UNSCOPED_INFO("one Backspace takes each pair"); + CHECK(e.text() == std::string("a(x)\nb(x)\nc(x)")); e.undo(); - ensure_equals("the Backspace undone", e.text(), std::string("a(x)[]\nb(x)[]\nc(x)[]")); + UNSCOPED_INFO("the Backspace undone"); + CHECK(e.text() == std::string("a(x)[]\nb(x)[]\nc(x)[]")); e.setText("one two"); e.setSelections(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 3)), { ALTextRange(ALTextPos(0, 4), ALTextPos(0, 7)) }); type("\""); - ensure_equals("each selection wrapped", e.text(), std::string("\"one\" \"two\"")); + UNSCOPED_INFO("each selection wrapped"); + CHECK(e.text() == std::string("\"one\" \"two\"")); e.setText("x\n// y"); e.setSelections(ALTextRange(ALTextPos(0, 1), ALTextPos(0, 1)), { ALTextRange(ALTextPos(1, 4), ALTextPos(1, 4)) }); type("("); - ensure_equals("closed where it is code, typed as ever in the comment", e.text(), std::string("x()\n// y(")); + UNSCOPED_INFO("closed where it is code, typed as ever in the comment"); + CHECK(e.text() == std::string("x()\n// y(")); // A closer typed over at one caret and typed plain at another: the // plain one comes out as it would alone. @@ -2672,26 +3587,36 @@ namespace tut type("{"); e.setSelections(ALTextRange(ALTextPos(2, 2), ALTextPos(2, 2)), { ALTextRange(ALTextPos(1, 8), ALTextPos(1, 8)) }); type("}"); - ensure_equals("typed over at one", e.document().line(2), std::string("x{}")); - ensure_equals("brought out at the other", e.document().line(1), std::string("}")); + UNSCOPED_INFO("typed over at one"); + CHECK(e.document().line(2) == std::string("x{}")); + UNSCOPED_INFO("brought out at the other"); + CHECK(e.document().line(1) == std::string("}")); } - template<> template<> - void alcodeeditor_object::test<74>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Select Next Occurrence and completion work at several carets", "[alcodeeditor]") { - set_test_name("Select Next Occurrence at several carets takes the word at each, then the next place as the main one; a completion accepted goes in at each caret, its first parameter chosen at each"); ALCodeEditor& e = make("one\none two\none", "slua"); e.setAutoComplete(false); e.setAutoClose(false); const auto at = [](S32 line, S32 column) { return ALTextRange(ALTextPos(line, column), ALTextPos(line, column)); }; e.setSelections(at(0, 1), { at(2, 1) }); key('D', MASK_CONTROL); - ensure("the word at the main caret", e.selection() == ALTextRange(ALTextPos(0, 0), ALTextPos(0, 3))); - ensure("and at the other", e.otherSelections().size() == 1 && e.otherSelections()[0] == ALTextRange(ALTextPos(2, 0), ALTextPos(2, 3))); + UNSCOPED_INFO("the word at the main caret"); + CHECK(e.selection() == ALTextRange(ALTextPos(0, 0), ALTextPos(0, 3))); + { + INFO("and at the other"); + REQUIRE(e.otherSelections().size() == 1); + CHECK(e.otherSelections()[0] == ALTextRange(ALTextPos(2, 0), ALTextPos(2, 3))); + } key('D', MASK_CONTROL); - ensure("the next place not taken, the main one", e.selection() == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 3)) && e.otherSelections().size() == 2); + { + INFO("the next place not taken, the main one"); + CHECK(e.selection() == ALTextRange(ALTextPos(1, 0), ALTextPos(1, 3))); + REQUIRE(e.otherSelections().size() == 2); + } type("1"); - ensure_equals("typed at each", e.text(), std::string("1\n1 two\n1")); + UNSCOPED_INFO("typed at each"); + CHECK(e.text() == std::string("1\n1 two\n1")); e.setText("myC\n myC"); e.setCompletionProvider([](const ALTextPos&, std::string_view, std::vector& out) { @@ -2703,103 +3628,170 @@ namespace tut }); e.setSelections(at(0, 3), { at(1, 5) }); key(' ', MASK_CONTROL); - ensure("the list at the main caret, the other kept", e.completionOpen() && e.hasOtherSelections()); - ensure("the call offered", !e.completions().empty() && e.completions()[0].text == "myCall"); + { + INFO("the list at the main caret, the other kept"); + CHECK(e.completionOpen()); + CHECK(e.hasOtherSelections()); + } + { + INFO("the call offered"); + REQUIRE_FALSE(e.completions().empty()); + CHECK(e.completions()[0].text == "myCall"); + } key(KEY_RETURN); - ensure_equals("in at each, over what was typed", e.text(), std::string("myCall(channel, msg)\n myCall(channel, msg)")); - ensure("the first parameter chosen at the main caret", e.selection() == ALTextRange(ALTextPos(0, 7), ALTextPos(0, 14))); - ensure("and at the other", e.otherSelections().size() == 1 && e.otherSelections()[0] == ALTextRange(ALTextPos(1, 9), ALTextPos(1, 16))); + UNSCOPED_INFO("in at each, over what was typed"); + CHECK(e.text() == std::string("myCall(channel, msg)\n myCall(channel, msg)")); + UNSCOPED_INFO("the first parameter chosen at the main caret"); + CHECK(e.selection() == ALTextRange(ALTextPos(0, 7), ALTextPos(0, 14))); + { + INFO("and at the other"); + REQUIRE(e.otherSelections().size() == 1); + CHECK(e.otherSelections()[0] == ALTextRange(ALTextPos(1, 9), ALTextPos(1, 16))); + } e.undo(); - ensure_equals("one step", e.text(), std::string("myC\n myC")); + UNSCOPED_INFO("one step"); + CHECK(e.text() == std::string("myC\n myC")); } - template<> template<> - void alcodeeditor_object::test<75>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: folding moves other carets out and a caret in a fold opens it", "[alcodeeditor]") { - set_test_name("a caret besides the main one put on a line folded away opens it, as the main one does; folding moves the others out to the fold's line, as it does the main one"); ALCodeEditor& e = make("default\n{\n state_entry()\n {\n llSay(0, \"a\");\n llSay(0, \"b\");\n }\n}\nx"); const auto caretAt = [](S32 line, S32 column) { return ALTextRange(ALTextPos(line, column), ALTextPos(line, column)); }; - ensure("folds", e.foldAt(2)); + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(2)); e.setSelections(caretAt(8, 0), { caretAt(4, 8) }); - ensure("the other caret's line opened", !e.isFolded(2) && !e.layout().hidden(4)); + { + INFO("the other caret's line opened"); + CHECK_FALSE(e.isFolded(2)); + CHECK_FALSE(e.layout().hidden(4)); + } e.setSelections(caretAt(8, 0), { caretAt(4, 8), ALTextRange(ALTextPos(5, 8), ALTextPos(5, 13)) }); - ensure("folds again", e.foldAt(2)); - ensure("the main one where it was", e.selection() == caretAt(8, 0)); - ensure("the others out of the fold, at the end of its line, as one", e.otherSelections().size() == 1 && e.otherSelections()[0] == caretAt(2, 17)); - ensure("and the fold kept", e.isFolded(2)); + UNSCOPED_INFO("folds again"); + CHECK(e.foldAt(2)); + UNSCOPED_INFO("the main one where it was"); + CHECK(e.selection() == caretAt(8, 0)); + { + INFO("the others out of the fold, at the end of its line, as one"); + REQUIRE(e.otherSelections().size() == 1); + CHECK(e.otherSelections()[0] == caretAt(2, 17)); + } + UNSCOPED_INFO("and the fold kept"); + CHECK(e.isFolded(2)); e.unfoldAll(); e.setSelections(caretAt(8, 0), { caretAt(4, 8) }); e.foldAll(); - ensure("folding all moves them out as well", e.otherSelections().size() == 1 && !e.layout().hidden(e.otherSelections()[0].end.line)); - ensure("into sight above", e.otherSelections()[0] == caretAt(0, 7)); + { + INFO("folding all moves them out as well"); + REQUIRE(e.otherSelections().size() == 1); + CHECK_FALSE(e.layout().hidden(e.otherSelections()[0].end.line)); + } + UNSCOPED_INFO("into sight above"); + CHECK(e.otherSelections()[0] == caretAt(0, 7)); // The main one folded away too: every block folds, none opened // again for a caret in it. e.setText("f()\n{\n llSay(0, \"a\");\n}\ng()\n{\n llSay(0, \"b\");\n}\n"); e.setSelections(caretAt(2, 4), { caretAt(6, 4) }); e.foldAll(); - ensure("both folded", e.isFolded(0) && e.isFolded(4)); - ensure("the main one at its fold's line", e.selection() == caretAt(0, 3)); - ensure("the other at its own", e.otherSelections().size() == 1 && e.otherSelections()[0] == caretAt(4, 3)); + { + INFO("both folded"); + CHECK(e.isFolded(0)); + CHECK(e.isFolded(4)); + } + UNSCOPED_INFO("the main one at its fold's line"); + CHECK(e.selection() == caretAt(0, 3)); + { + INFO("the other at its own"); + REQUIRE(e.otherSelections().size() == 1); + CHECK(e.otherSelections()[0] == caretAt(4, 3)); + } } - template<> template<> - void alcodeeditor_object::test<76>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a host's hidden lines are kept apart from the folds' and revealed by it", "[alcodeeditor]") { - set_test_name("a host's hidden lines and the folds' apart: unfolding leaves the host's; the caret landing in them asks the host, else shows the line"); ALCodeEditor& e = make("default\n{\n state_entry()\n {\n a();\n b();\n }\n}"); typedef ALTextLayout::HiddenBy By; e.layout().setHidden(By::Host, 4, 5, true); e.foldAll(); - ensure("folded too", e.layout().hiddenBy(3, By::Folds) || e.layout().hiddenBy(4, By::Folds)); + UNSCOPED_INFO("folded too"); + CHECK((e.layout().hiddenBy(3, By::Folds) || e.layout().hiddenBy(4, By::Folds))); e.unfoldAll(); - ensure("unfolded: the host's still hidden", e.layout().hidden(4) && e.layout().hidden(5) && !e.layout().hiddenBy(4, By::Folds)); + { + INFO("unfolded: the host's still hidden"); + CHECK(e.layout().hidden(4)); + CHECK(e.layout().hidden(5)); + CHECK_FALSE(e.layout().hiddenBy(4, By::Folds)); + } S32 asked = -1; e.setLineRevealer([&](S32 line) { asked = line; e.layout().setHidden(By::Host, 4, 5, false); }); e.goTo(ALTextPos(5, 0)); - ensure("the host asked, and showed its run", asked == 5 && !e.layout().hidden(4) && !e.layout().hidden(5)); + { + INFO("the host asked, and showed its run"); + CHECK(asked == 5); + CHECK_FALSE(e.layout().hidden(4)); + CHECK_FALSE(e.layout().hidden(5)); + } e.layout().setHidden(By::Host, 4, 5, true); e.setLineRevealer(nullptr); e.goTo(ALTextPos(4, 0)); - ensure("nobody to ask: the line shown, the rest left", !e.layout().hidden(4) && e.layout().hidden(5)); + { + INFO("nobody to ask: the line shown, the rest left"); + CHECK_FALSE(e.layout().hidden(4)); + CHECK(e.layout().hidden(5)); + } } - template<> template<> - void alcodeeditor_object::test<77>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a changed line's bar is found by the mouse before the gutter's tips", "[alcodeeditor]") { - set_test_name("a changed line's bar is found by the mouse: taken on hover, and says what a press does, ahead of the gutter's own tips"); ALCodeEditor& e = make("one\ntwo\nthree"); e.resetDirty(); e.goTo(ALTextPos(1, 0)); e.insertText("2"); - ensure("barred", e.lineChanged(1) && !e.lineChanged(0)); + { + INFO("barred"); + CHECK(e.lineChanged(1)); + CHECK_FALSE(e.lineChanged(0)); + } const LLRect text = e.textRect(); const auto rowY = [&](S32 line) { return text.mTop - (e.layout().lineTop(line) - e.scrollY()) - e.layout().lineHeight(line) / 2; }; const S32 bar = e.leftEdge() + 1; - ensure_equals("on the bar", e.changeBarAt(bar, rowY(1)), 1); - ensure_equals("a line not changed", e.changeBarAt(bar, rowY(0)), -1); - ensure_equals("past the bar", e.changeBarAt(e.leftEdge() + 5, rowY(1)), -1); - ensure("hovered: taken", e.handleHover(bar, rowY(1), MASK_NONE)); - ensure("a tip, not a card", e.handleToolTip(bar, rowY(1), MASK_NONE) && !e.cardShown()); - ensure("a press peeks", e.handleMouseDown(bar, rowY(1), MASK_NONE) && e.changePeek() && e.changePeek()->isOpen()); + UNSCOPED_INFO("on the bar"); + CHECK(e.changeBarAt(bar, rowY(1)) == 1); + UNSCOPED_INFO("a line not changed"); + CHECK(e.changeBarAt(bar, rowY(0)) == -1); + UNSCOPED_INFO("past the bar"); + CHECK(e.changeBarAt(e.leftEdge() + 5, rowY(1)) == -1); + UNSCOPED_INFO("hovered: taken"); + CHECK(e.handleHover(bar, rowY(1), MASK_NONE)); + { + INFO("a tip, not a card"); + CHECK(e.handleToolTip(bar, rowY(1), MASK_NONE)); + CHECK_FALSE(e.cardShown()); + } + { + INFO("a press peeks"); + REQUIRE(e.handleMouseDown(bar, rowY(1), MASK_NONE)); + REQUIRE(e.changePeek()); + CHECK(e.changePeek()->isOpen()); + } e.closePeek(); // No gutter: nothing to point at. e.setShowLineNumbers(false); e.setShowFoldMarkers(false); - ensure_equals("no gutter", e.gutterWidth(), 0); - ensure_equals("no bar", e.changeBarAt(e.leftEdge() + 1, rowY(1)), -1); + UNSCOPED_INFO("no gutter"); + CHECK(e.gutterWidth() == 0); + UNSCOPED_INFO("no bar"); + CHECK(e.changeBarAt(e.leftEdge() + 1, rowY(1)) == -1); } - template<> template<> - void alcodeeditor_object::test<78>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a completion's brackets go where the answer says or as its kind reads", "[alcodeeditor]") { - set_test_name("a completion's brackets go where whoever answered says: none, after an empty pair, or between them; else as its kind reads"); ALCodeEditor& e = make("", "lsl"); ALCompletion::Brackets brackets = ALCompletion::Brackets::Guess; e.setCompletionProvider([&brackets](const ALTextPos&, std::string_view prefix, std::vector& out) { @@ -2820,22 +3812,30 @@ namespace tut e.setText(""); asks.clear(); type("hand"); - ensure("offered", e.completionOpen()); + UNSCOPED_INFO("offered"); + REQUIRE(e.completionOpen()); key(KEY_TAB); return e.text(); }; - ensure_equals("guessed: a function is called, its parameter to fill", take(ALCompletion::Brackets::Guess), std::string("handler(n)")); - ensure_equals("none: passed as it is", take(ALCompletion::Brackets::None), std::string("handler")); - ensure("and no signature asked", asks.empty()); - ensure_equals("after: an empty pair", take(ALCompletion::Brackets::After), std::string("handler()")); - ensure("the caret after it, and no signature asked", e.caret() == ALTextPos(0, 9) && asks.empty()); - ensure_equals("inside: its parameter to fill", take(ALCompletion::Brackets::Inside), std::string("handler(n)")); + UNSCOPED_INFO("guessed: a function is called, its parameter to fill"); + CHECK(take(ALCompletion::Brackets::Guess) == std::string("handler(n)")); + UNSCOPED_INFO("none: passed as it is"); + CHECK(take(ALCompletion::Brackets::None) == std::string("handler")); + UNSCOPED_INFO("and no signature asked"); + REQUIRE(asks.empty()); + UNSCOPED_INFO("after: an empty pair"); + CHECK(take(ALCompletion::Brackets::After) == std::string("handler()")); + { + INFO("the caret after it, and no signature asked"); + CHECK(e.caret() == ALTextPos(0, 9)); + REQUIRE(asks.empty()); + } + UNSCOPED_INFO("inside: its parameter to fill"); + CHECK(take(ALCompletion::Brackets::Inside) == std::string("handler(n)")); } - template<> template<> - void alcodeeditor_object::test<79>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a comment the grammar says completes opens the list on its own", "[alcodeeditor]") { - set_test_name("a comment the grammar says completes, as SLua's --! does, opens the list on its own; any other comment does not"); ALCodeEditor& e = make("", "slua"); e.setCompletionProvider([](const ALTextPos&, std::string_view prefix, std::vector& out) { for (const char* word : { "strict", "nonstrict" }) @@ -2850,21 +3850,26 @@ namespace tut } }); type("--!st"); - ensure("open after --!", e.completionOpen() && e.completions()[0].text == "strict"); + { + INFO("open after --!"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "strict"); + } key(KEY_TAB); - ensure_equals("taken", e.text(), std::string("--!strict")); + UNSCOPED_INFO("taken"); + CHECK(e.text() == std::string("--!strict")); e.setText(""); type("-- st"); - ensure("not in a comment that says nothing of how the script is checked", !e.completionOpen()); + UNSCOPED_INFO("not in a comment that says nothing of how the script is checked"); + CHECK_FALSE(e.completionOpen()); e.setText(""); type("x = 1 --!st"); - ensure("nor after code on its line", !e.completionOpen()); + UNSCOPED_INFO("nor after code on its line"); + CHECK_FALSE(e.completionOpen()); } - template<> template<> - void alcodeeditor_object::test<80>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: in a string naming a file the list offers paths and folders", "[alcodeeditor]") { - set_test_name("in a string that names a file the list opens at its quote and after each slash, narrows on the name typed, and puts a path or a folder in place"); ALCodeEditor& e = make("", "slua"); std::vector asked; e.setPathProvider([&asked](const ALTextPos&, std::string_view path, std::vector& out) { @@ -2893,66 +3898,114 @@ namespace tut e.setPathRequest([&requested](const ALTextPos&, std::string_view path) { requested.emplace_back(path); }); e.setAutoClose(true); type("require(\""); - ensure_equals("the quote closed for it", e.text(), std::string("require(\"\")")); - ensure("open at the quote, asked of nothing yet", e.completionOpen() && e.completions().size() == 3 && asked.back().empty()); + UNSCOPED_INFO("the quote closed for it"); + CHECK(e.text() == std::string("require(\"\")")); + { + INFO("open at the quote, asked of nothing yet"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 3); + REQUIRE(asked.back().empty()); + } type("li"); - ensure("narrowed to the folder", e.completions().size() == 1 && e.completions()[0].text == "lib"); + { + INFO("narrowed to the folder"); + REQUIRE(e.completions().size() == 1); + CHECK(e.completions()[0].text == "lib"); + } key(KEY_TAB); - ensure_equals("the folder, and a slash after it", e.text(), std::string("require(\"lib/\")")); - ensure("the list again, for what is in it", e.completionOpen() && asked.back() == "lib/"); - ensure("the request too of the whole path typed, not of the name after its slash", !requested.empty() && requested.back() == "lib/"); + UNSCOPED_INFO("the folder, and a slash after it"); + CHECK(e.text() == std::string("require(\"lib/\")")); + { + INFO("the list again, for what is in it"); + CHECK(e.completionOpen()); + CHECK(asked.back() == "lib/"); + } + { + INFO("the request too of the whole path typed, not of the name after its slash"); + REQUIRE_FALSE(requested.empty()); + CHECK(requested.back() == "lib/"); + } type("ut"); - ensure_equals("narrowed in the folder", e.completions()[0].text, std::string("util")); + UNSCOPED_INFO("narrowed in the folder"); + CHECK(e.completions()[0].text == std::string("util")); key(KEY_TAB); - ensure_equals("the whole path in place, the quote kept", e.text(), std::string("require(\"./lib/util\")")); - ensure("and the list gone", !e.completionOpen()); + UNSCOPED_INFO("the whole path in place, the quote kept"); + CHECK(e.text() == std::string("require(\"./lib/util\")")); + UNSCOPED_INFO("and the list gone"); + CHECK_FALSE(e.completionOpen()); e.setText(""); type("require(\"qu"); key(KEY_TAB); - ensure_equals("a path given escaped stays escaped", e.text(), std::string("require(\"./a\\\"b\")")); + UNSCOPED_INFO("a path given escaped stays escaped"); + CHECK(e.text() == std::string("require(\"./a\\\"b\")")); e.setText(""); type("print(\"li"); - ensure("not in a string that names nothing", !e.completionOpen()); + UNSCOPED_INFO("not in a string that names nothing"); + CHECK_FALSE(e.completionOpen()); e.setText(""); type("--#include \"m"); - ensure("an include's name too", e.completionOpen() && e.completions()[0].text == "main"); + { + INFO("an include's name too"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "main"); + } key(KEY_ESCAPE); // The opening quote taken back: the list goes with the string, // rather than become one of every word. e.setText(""); type("require(\""); - ensure("open at the quote", e.completionOpen()); + UNSCOPED_INFO("open at the quote"); + CHECK(e.completionOpen()); key(KEY_BACKSPACE); - ensure_equals("the pair taken", e.text(), std::string("require()")); - ensure("and the list with it", !e.completionOpen()); + UNSCOPED_INFO("the pair taken"); + CHECK(e.text() == std::string("require()")); + UNSCOPED_INFO("and the list with it"); + CHECK_FALSE(e.completionOpen()); // Not closed: the path runs to the line's end, and what was typed of // it is put in place, the quote closed after it. e.setAutoClose(false); e.setText(""); type("require(\"ma"); - ensure("open in a string not closed", e.completionOpen() && e.completions()[0].text == "main"); + { + INFO("open in a string not closed"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "main"); + } key(KEY_TAB); - ensure_equals("the path in place, closed", e.text(), std::string("require(\"./main\"")); - ensure("past its quote", e.caret() == ALTextPos(0, 16)); + UNSCOPED_INFO("the path in place, closed"); + CHECK(e.text() == std::string("require(\"./main\"")); + UNSCOPED_INFO("past its quote"); + CHECK(e.caret() == ALTextPos(0, 16)); // What follows the caret there is the script's, not the string's. e.setText("local m = require(, 1)"); e.setCaret(ALTextPos(0, 18)); type("\"ma"); - ensure("open before what follows", e.completionOpen() && e.completions()[0].text == "main"); + { + INFO("open before what follows"); + CHECK(e.completionOpen()); + CHECK(e.completions()[0].text == "main"); + } key(KEY_TAB); - ensure_equals("what follows kept", e.text(), std::string("local m = require(\"./main\", 1)")); + UNSCOPED_INFO("what follows kept"); + CHECK(e.text() == std::string("local m = require(\"./main\", 1)")); // A folder put in so: the quote closed after its slash, the caret // inside it for the list again. e.setText(""); type("require(\"li"); key(KEY_TAB); - ensure_equals("the folder, closed", e.text(), std::string("require(\"lib/\"")); - ensure("inside the quote, the list open again", e.caret() == ALTextPos(0, 13) && e.completionOpen() && asked.back() == "lib/"); + UNSCOPED_INFO("the folder, closed"); + CHECK(e.text() == std::string("require(\"lib/\"")); + { + INFO("inside the quote, the list open again"); + CHECK(e.caret() == ALTextPos(0, 13)); + CHECK(e.completionOpen()); + CHECK(asked.back() == "lib/"); + } // Its closing quote typed: the list goes, and nobody is asked for // the words past it. e.setText(""); @@ -2960,13 +4013,15 @@ namespace tut size_t words_asked = 0; e.setCompletionRequest([&words_asked](const ALTextPos&, std::string_view) { ++words_asked; }); type("\""); - ensure("closed with the string, the words not asked for", !e.completionOpen() && words_asked == 0); + { + INFO("closed with the string, the words not asked for"); + CHECK_FALSE(e.completionOpen()); + CHECK(words_asked == 0); + } } - template<> template<> - void alcodeeditor_object::test<81>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: in a string the host names things for the list offers its names", "[alcodeeditor]") { - set_test_name("in any other string the host names something for, the list opens at its quote, narrows on all the string holds, and puts what was named in place whole; elsewhere a string is prose"); ALCodeEditor& e = make(""); std::vector asked; e.setStringProvider([&e, &asked](const ALTextPos& at, std::string_view typed, std::vector& out) { @@ -2990,41 +4045,62 @@ namespace tut }); e.setAutoClose(true); type("llPlaySound(\""); - ensure_equals("the quote closed for it", e.text(), std::string("llPlaySound(\"\")")); - ensure("open at the quote, asked of nothing yet", e.completionOpen() && e.completions().size() == 3 && asked.back().empty()); + UNSCOPED_INFO("the quote closed for it"); + CHECK(e.text() == std::string("llPlaySound(\"\")")); + { + INFO("open at the quote, asked of nothing yet"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 3); + REQUIRE(asked.back().empty()); + } type("Door "); - ensure("narrowed on all it holds, its space too", e.completionOpen() && e.completions().size() == 2); + { + INFO("narrowed on all it holds, its space too"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 2); + } const size_t asks = asked.size(); type("C"); - ensure("narrowed again, not asked again", - e.completions().size() == 1 && e.completions()[0].text == "Door Close" && asked.size() == asks); + { + INFO("narrowed again, not asked again"); + REQUIRE(e.completions().size() == 1); + CHECK(e.completions()[0].text == "Door Close"); + REQUIRE(asked.size() == asks); + } key(KEY_TAB); - ensure_equals("the name in place, the quote kept", e.text(), std::string("llPlaySound(\"Door Close\")")); - ensure("and the list gone", !e.completionOpen()); + UNSCOPED_INFO("the name in place, the quote kept"); + CHECK(e.text() == std::string("llPlaySound(\"Door Close\")")); + UNSCOPED_INFO("and the list gone"); + CHECK_FALSE(e.completionOpen()); e.setText(""); type("llPlaySound(\"Sa"); key(KEY_TAB); - ensure_equals("a name put in escaped", e.text(), std::string("llPlaySound(\"Say \\\"hi\\\"\")")); + UNSCOPED_INFO("a name put in escaped"); + CHECK(e.text() == std::string("llPlaySound(\"Say \\\"hi\\\"\")")); // A name of several words found by any of them. e.setText(""); type("llPlaySound(\"clo"); - ensure("by a later word", e.completionOpen() && e.completions().size() == 1 && e.completions()[0].text == "Door Close"); + { + INFO("by a later word"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 1); + CHECK(e.completions()[0].text == "Door Close"); + } key(KEY_ESCAPE); e.setText(""); type("llSay(0, \"Do"); - ensure("not where the host names nothing: prose", !e.completionOpen()); + UNSCOPED_INFO("not where the host names nothing: prose"); + CHECK_FALSE(e.completionOpen()); type("\""); - ensure("nor once the string is closed", !e.completionOpen()); + UNSCOPED_INFO("nor once the string is closed"); + CHECK_FALSE(e.completionOpen()); } - template<> template<> - void alcodeeditor_object::test<82>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a string name goes in for what was typed and closes the string", "[alcodeeditor]") { - set_test_name("a name for a string not closed goes in for what was typed up to the caret, the quote closed after it; the list goes as the caret " - "leaves the string; a name matched as the string's escapes read; at several carets nothing named for a string"); ALCodeEditor& e = make(""); e.setStringProvider([&e](const ALTextPos& at, std::string_view, std::vector& out) { if (e.document().line(at.line).find("llPlaySound(") == std::string::npos) @@ -3051,46 +4127,79 @@ namespace tut e.setText("llPlaySound(, 1.0);"); e.setCaret(ALTextPos(0, 12)); type("\"Do"); - ensure("open before what follows", e.completionOpen() && e.completions().size() == 2); + { + INFO("open before what follows"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 2); + } key(KEY_TAB); - ensure_equals("the name in place of what was typed, closed, what follows kept", e.text(), std::string("llPlaySound(\"Door Close\", 1.0);")); - ensure("past its quote", e.caret() == ALTextPos(0, 24)); + UNSCOPED_INFO("the name in place of what was typed, closed, what follows kept"); + CHECK(e.text() == std::string("llPlaySound(\"Door Close\", 1.0);")); + UNSCOPED_INFO("past its quote"); + CHECK(e.caret() == ALTextPos(0, 24)); // A quote typed before a word is not closed for it: the word stays, // after the name. e.setAutoClose(true); e.setText("llPlaySound(snd, 1.0);"); e.setCaret(ALTextPos(0, 12)); type("\""); - ensure("open at the quote", e.completionOpen() && e.completions().size() == 3); + { + INFO("open at the quote"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 3); + } key(KEY_TAB); - ensure_equals("nothing after the caret taken", e.text(), std::string("llPlaySound(\"Door Close\"snd, 1.0);")); + UNSCOPED_INFO("nothing after the caret taken"); + CHECK(e.text() == std::string("llPlaySound(\"Door Close\"snd, 1.0);")); // The opening quote taken back, or the closing one typed: the list // goes with the string, rather than become one of every word. e.setText(""); type("llPlaySound(\""); - ensure("open at the quote", e.completionOpen()); + UNSCOPED_INFO("open at the quote"); + CHECK(e.completionOpen()); size_t before = words_asked; key(KEY_BACKSPACE); - ensure_equals("the pair taken", e.text(), std::string("llPlaySound()")); - ensure("and the list with it, the words not asked for", !e.completionOpen() && words_asked == before); + UNSCOPED_INFO("the pair taken"); + CHECK(e.text() == std::string("llPlaySound()")); + { + INFO("and the list with it, the words not asked for"); + CHECK_FALSE(e.completionOpen()); + CHECK(words_asked == before); + } e.setAutoClose(false); e.setText(""); type("llPlaySound(\"Do"); - ensure("open in the string", e.completionOpen()); + UNSCOPED_INFO("open in the string"); + CHECK(e.completionOpen()); before = words_asked; type("\""); - ensure("closed with the string, the words not asked for", !e.completionOpen() && words_asked == before); + { + INFO("closed with the string, the words not asked for"); + CHECK_FALSE(e.completionOpen()); + CHECK(words_asked == before); + } // Matched as the string's escapes read: `Say \"` is `Say "`, and an // escape only begun is nothing yet. e.setText(""); type("llPlaySound(\"Say \\"); - ensure("an escape begun", e.completionOpen() && e.completions().size() == 1 && e.completions()[0].text == "Say \"hi\""); + { + INFO("an escape begun"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 1); + CHECK(e.completions()[0].text == "Say \"hi\""); + } type("\""); - ensure("and ended", e.completionOpen() && e.completions().size() == 1 && e.completions()[0].text == "Say \"hi\""); + { + INFO("and ended"); + CHECK(e.completionOpen()); + REQUIRE(e.completions().size() == 1); + CHECK(e.completions()[0].text == "Say \"hi\""); + } key(KEY_TAB); - ensure_equals("put in escaped, and closed", e.text(), std::string("llPlaySound(\"Say \\\"hi\\\"\"")); + UNSCOPED_INFO("put in escaped, and closed"); + CHECK(e.text() == std::string("llPlaySound(\"Say \\\"hi\\\"\"")); // At several carets nothing is named for a string, which would go in // at each as a name rather than as the string holds it. @@ -3102,59 +4211,96 @@ namespace tut { named = named || !c.path.empty(); } - ensure("nothing named for the string", !named); + UNSCOPED_INFO("nothing named for the string"); + CHECK_FALSE(named); e.closeCompletion(); } - template<> template<> - void alcodeeditor_object::test<83>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a folded block stays hidden through a rename and goes with its header", "[alcodeeditor]") { - set_test_name("a folded block's lines stay hidden through a rename inside it, and a fold taken with its header leaves none of them hidden"); ALCodeEditor& e = make("integer n;\nf()\n{\n n = 1;\n llSay(0, (string)n);\n}\ng()\n{\n n = 2;\n}"); - ensure("folds", e.foldAt(1)); - ensure("its lines hidden", e.layout().hidden(2) && e.layout().hidden(5) && !e.layout().hidden(6)); + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(1)); + { + INFO("its lines hidden"); + CHECK(e.layout().hidden(2)); + CHECK(e.layout().hidden(5)); + CHECK_FALSE(e.layout().hidden(6)); + } e.setCaret(ALTextPos(6, 0)); // A name inside it replaced, as a rename or Replace All does: the // layout shows the line it made, which the fold still holds. - ensure("replaced", e.replaceAll({ { ALTextRange(ALTextPos(3, 4), ALTextPos(3, 5)), "total" } })); - ensure_equals("the line", e.document().line(3), std::string(" total = 1;")); - ensure("still folded, the line replaced hidden with the rest", e.isFolded(1) && e.layout().hidden(2) && e.layout().hidden(3) && e.layout().hidden(5)); - ensure_equals("six rows in sight", e.layout().totalHeight(), 6 * e.layout().rowHeight()); + UNSCOPED_INFO("replaced"); + CHECK(e.replaceAll({ { ALTextRange(ALTextPos(3, 4), ALTextPos(3, 5)), "total" } })); + UNSCOPED_INFO("the line"); + CHECK(e.document().line(3) == std::string(" total = 1;")); + { + INFO("still folded, the line replaced hidden with the rest"); + CHECK(e.isFolded(1)); + CHECK(e.layout().hidden(2)); + CHECK(e.layout().hidden(3)); + CHECK(e.layout().hidden(5)); + } + UNSCOPED_INFO("six rows in sight"); + CHECK(e.layout().totalHeight() == 6 * e.layout().rowHeight()); // The header taken with the line above it: the fold goes, and every // line it hid, moved up a line, is in sight. e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(1, 3))); key(KEY_DELETE); - ensure_equals("the header gone", e.document().line(1), std::string("{")); - ensure("nothing folded", !e.isFolded(0) && !e.isFolded(1)); - ensure("nothing hidden", !e.layout().anyHidden() && !e.layout().hidden(1)); + UNSCOPED_INFO("the header gone"); + CHECK(e.document().line(1) == std::string("{")); + { + INFO("nothing folded"); + CHECK_FALSE(e.isFolded(0)); + CHECK_FALSE(e.isFolded(1)); + } + { + INFO("nothing hidden"); + CHECK_FALSE(e.layout().anyHidden()); + CHECK_FALSE(e.layout().hidden(1)); + } } - template<> template<> - void alcodeeditor_object::test<84>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a folded header deleted with lines below folds nothing in its place", "[alcodeeditor]") { - set_test_name("a folded header taken with the whole lines below it does not fold the block that comes up in its place; Return at its start takes the fold down with it"); ALCodeEditor& e = make("foo()\n{\n x();\n}\nbar()\n{\n y();\n}"); - ensure("folds", e.foldAt(0)); + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(0)); // From the header's start to the next line in sight, deleted. e.setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(4, 0))); key(KEY_DELETE); - ensure_equals("the block gone", e.document().line(0), std::string("bar()")); - ensure("the one in its place not folded", !e.isFolded(0) && !e.layout().anyHidden()); + UNSCOPED_INFO("the block gone"); + CHECK(e.document().line(0) == std::string("bar()")); + { + INFO("the one in its place not folded"); + CHECK_FALSE(e.isFolded(0)); + CHECK_FALSE(e.layout().anyHidden()); + } ALCodeEditor& f = make("foo()\n{\n x();\n}\nbar()"); - ensure("folds", f.foldAt(0)); + UNSCOPED_INFO("folds"); + CHECK(f.foldAt(0)); f.setCaret(ALTextPos(0, 0)); key(KEY_RETURN); - ensure_equals("a line put in above", f.document().line(1), std::string("foo()")); - ensure("the fold down with its header", f.isFolded(1) && !f.isFolded(0)); - ensure("hiding its block there", !f.layout().hidden(1) && f.layout().hidden(2) && f.layout().hidden(4) && !f.layout().hidden(5)); + UNSCOPED_INFO("a line put in above"); + CHECK(f.document().line(1) == std::string("foo()")); + { + INFO("the fold down with its header"); + CHECK(f.isFolded(1)); + CHECK_FALSE(f.isFolded(0)); + } + { + INFO("hiding its block there"); + CHECK_FALSE(f.layout().hidden(1)); + CHECK(f.layout().hidden(2)); + CHECK(f.layout().hidden(4)); + CHECK_FALSE(f.layout().hidden(5)); + } } - template<> template<> - void alcodeeditor_object::test<85>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a line scrolled up to from an unpinned spot clears the pinned headers", "[alcodeeditor]") { - set_test_name("a line scrolled up to from where nothing is pinned comes out from under the headers pinned over it there"); std::string text = "default\n{\n state_entry()\n {\n"; for (int i = 0; i < 80; ++i) { @@ -3171,16 +4317,16 @@ namespace tut // At the end, past every block, where nothing is pinned; then up // into the handler, where its state and it are. e.setCaret(ALTextPos(185, 0)); - ensure("the view past the blocks", e.scrollY() > e.layout().lineTop(86)); + UNSCOPED_INFO("the view past the blocks"); + CHECK(e.scrollY() > e.layout().lineTop(86)); e.setCaret(ALTextPos(40, 8)); const S32 below = e.layout().lineTop(40) - e.scrollY(); - ensure("below the two pinned headers: " + std::to_string(below) + " of " + std::to_string(row_h), below >= 2 * row_h); + UNSCOPED_INFO("below the two pinned headers: " + std::to_string(below) + " of " + std::to_string(row_h)); + CHECK(below >= 2 * row_h); } - template<> template<> - void alcodeeditor_object::test<86>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a gutter mark's card stays while the mouse rests on its row", "[alcodeeditor]") { - set_test_name("a gutter mark's card stays while the mouse rests on its row there, and goes as it leaves the row"); ALCodeEditor& e = make("one\ntwo\nthree\n"); ALCodeEditor::Decoration d; d.range = ALTextRange(ALTextPos(1, 0), ALTextPos(1, 3)); @@ -3192,18 +4338,23 @@ namespace tut const S32 x = e.leftEdge() + 8; const S32 y = text.mTop - row_h - row_h / 2; e.handleHover(x, y, MASK_NONE); - ensure("the gutter's card", e.handleToolTip(x, y, MASK_NONE) && e.cardShown() && e.card()->text() == "something is wrong here"); + { + INFO("the gutter's card"); + REQUIRE(e.handleToolTip(x, y, MASK_NONE)); + REQUIRE(e.cardShown()); + CHECK(e.card()->text() == "something is wrong here"); + } // The next frame, the mouse still. e.handleHover(x, y, MASK_NONE); - ensure("kept, the mouse on the line's row in the gutter", e.cardShown()); + UNSCOPED_INFO("kept, the mouse on the line's row in the gutter"); + CHECK(e.cardShown()); e.handleHover(x, y - 2 * row_h, MASK_NONE); - ensure("gone from another row", !e.cardShown()); + UNSCOPED_INFO("gone from another row"); + CHECK_FALSE(e.cardShown()); } - template<> template<> - void alcodeeditor_object::test<87>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a card a key put away stays away until the mouse moves", "[alcodeeditor]") { - set_test_name("a card a key put away stays away under a still mouse, a word a page brings under it brings none, and the mouse moving brings one again"); std::string text; for (int i = 0; i < 60; ++i) { @@ -3223,27 +4374,37 @@ namespace tut const S32 x = text_rect.mLeft + static_cast(e.layout().xOf(0, 2, &row)) + 1; const S32 y = text_rect.mTop - e.layout().rowHeight() / 2; e.handleHover(x, y, MASK_NONE); - ensure("a card", e.handleToolTip(x, y, MASK_NONE) && e.cardShown()); + { + INFO("a card"); + CHECK(e.handleToolTip(x, y, MASK_NONE)); + CHECK(e.cardShown()); + } key(KEY_DOWN); - ensure("a key put it away", !e.cardShown()); + UNSCOPED_INFO("a key put it away"); + CHECK_FALSE(e.cardShown()); // The frames after, the mouse still. e.handleHover(x, y, MASK_NONE); e.handleToolTip(x, y, MASK_NONE); - ensure("not back while the mouse is still", !e.cardShown()); + UNSCOPED_INFO("not back while the mouse is still"); + CHECK_FALSE(e.cardShown()); key(KEY_PAGE_DOWN); - ensure("other lines under the mouse", e.scrollY() > 0); + UNSCOPED_INFO("other lines under the mouse"); + CHECK(e.scrollY() > 0); e.handleHover(x, y, MASK_NONE); e.handleToolTip(x, y, MASK_NONE); - ensure("none for the word scrolled under it", !e.cardShown()); + UNSCOPED_INFO("none for the word scrolled under it"); + CHECK_FALSE(e.cardShown()); // Moved: a card may come again. e.handleHover(x + 1, y, MASK_NONE); - ensure("the mouse moved: a card again", e.handleToolTip(x + 1, y, MASK_NONE) && e.cardShown()); + { + INFO("the mouse moved: a card again"); + CHECK(e.handleToolTip(x + 1, y, MASK_NONE)); + CHECK(e.cardShown()); + } } - template<> template<> - void alcodeeditor_object::test<88>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the card is worked out once per rest and not at every tooltip pass", "[alcodeeditor]") { - set_test_name("the card is worked out once a rest, not again at every tooltip pass the mouse stays still for, on a word or on a mark"); ALCodeEditor& e = make("llSay(0, x);\nsecond line\n"); size_t hovered = 0; e.setHoverProvider([&hovered](const ALTextPos&, std::string_view word, std::string& out) { @@ -3269,8 +4430,10 @@ namespace tut e.handleHover(x, y, MASK_NONE); e.handleToolTip(x, y, MASK_NONE); } - ensure("a card", e.cardShown()); - ensure_equals("the word asked about once", hovered, size_t(1)); + UNSCOPED_INFO("a card"); + CHECK(e.cardShown()); + UNSCOPED_INFO("the word asked about once"); + CHECK(hovered == size_t(1)); // And on the next line's mark, past the change bar. const S32 gutter_x = e.leftEdge() + 8; const S32 gutter_y = text.mTop - row_h - row_h / 2; @@ -3279,14 +4442,17 @@ namespace tut e.handleHover(gutter_x, gutter_y, MASK_NONE); e.handleToolTip(gutter_x, gutter_y, MASK_NONE); } - ensure("the mark's card", e.cardShown() && e.card()->text().find("something is wrong here") != std::string::npos); - ensure_equals("its fixes asked for once", fixed, size_t(1)); + { + INFO("the mark's card"); + REQUIRE(e.cardShown()); + REQUIRE(e.card()->text().find("something is wrong here") != std::string::npos); + } + UNSCOPED_INFO("its fixes asked for once"); + CHECK(fixed == size_t(1)); } - template<> template<> - void alcodeeditor_object::test<89>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the band of pinned headers acts for the header drawn there", "[alcodeeditor]") { - set_test_name("the gutter, the mouse and the notes on the band of pinned headers go by the header drawn there, not the line hidden under it"); std::string text = "default\n{\n state_entry()\n {\n"; for (int i = 0; i < 80; ++i) { @@ -3314,31 +4480,47 @@ namespace tut e.setCaret(ALTextPos(70, 8)); const S32 y = text_rect.mTop - row_h - row_h / 2; const S32 hidden = e.posAtLocal(text_rect.mLeft, y, false).line; - ensure("a line of the handler's under the band: " + std::to_string(hidden), hidden > 3); + UNSCOPED_INFO("a line of the handler's under the band: " + std::to_string(hidden)); + CHECK(hidden > 3); // Its number pressed chooses the header's line. const S32 number_x = e.leftEdge() + 18; - ensure("pressed", e.handleMouseDown(number_x, y, MASK_NONE)); + UNSCOPED_INFO("pressed"); + CHECK(e.handleMouseDown(number_x, y, MASK_NONE)); e.handleMouseUp(number_x, y, MASK_NONE); const ALTextRange chosen = e.selection().normalised(); - ensure("the header's line chosen: " + std::to_string(chosen.begin.line), chosen.begin == ALTextPos(2, 0) && chosen.end == ALTextPos(3, 0)); + { + INFO("the header's line chosen: " + std::to_string(chosen.begin.line)); + CHECK(chosen.begin == ALTextPos(2, 0)); + CHECK(chosen.end == ALTextPos(3, 0)); + } // The mouse on its number: the header's problems, kept there. e.setCaret(ALTextPos(70, 8)); e.handleHover(number_x, y, MASK_NONE); e.handleToolTip(number_x, y, MASK_NONE); - ensure("the header's card from the gutter", e.cardShown() && e.card()->text().find("a problem with the handler") != std::string::npos); + { + INFO("the header's card from the gutter"); + REQUIRE(e.cardShown()); + REQUIRE(e.card()->text().find("a problem with the handler") != std::string::npos); + } e.handleHover(number_x, y, MASK_NONE); - ensure("kept on the band's row in the gutter", e.cardShown()); + UNSCOPED_INFO("kept on the band's row in the gutter"); + CHECK(e.cardShown()); // The mouse on the header's name drawn there: the name's card. S32 row; const S32 name_x = text_rect.mLeft + static_cast(e.layout().xOf(2, 8, &row)) + 1; e.handleHover(name_x, y, MASK_NONE); e.handleToolTip(name_x, y, MASK_NONE); - ensure("the header's name", e.cardShown() && e.card()->text() == "the state_entry event"); + { + INFO("the header's name"); + REQUIRE(e.cardShown()); + CHECK(e.card()->text() == "the state_entry event"); + } e.handleHover(name_x, y, MASK_NONE); - ensure("kept on the band's row", e.cardShown()); + UNSCOPED_INFO("kept on the band's row"); + CHECK(e.cardShown()); e.hideCard(); // The note of the line hidden is not found over it; one in sight is. @@ -3353,21 +4535,24 @@ namespace tut } return on; }; - ensure_equals("the note in sight", note_on(seen_y), seen); - ensure_equals("none on the band", note_on(y), -1); + UNSCOPED_INFO("the note in sight"); + CHECK(note_on(seen_y) == seen); + UNSCOPED_INFO("none on the band"); + CHECK(note_on(y) == -1); // The fold column there folds the header's block. const S32 fold_x = e.leftEdge() + e.gutterWidth() - 6; e.handleMouseDown(fold_x, y, MASK_NONE); e.handleMouseUp(fold_x, y, MASK_NONE); - ensure("the header's block folded", e.isFolded(2) && !e.isFolded(hidden)); + { + INFO("the header's block folded"); + CHECK(e.isFolded(2)); + CHECK_FALSE(e.isFolded(hidden)); + } } - template<> template<> - void alcodeeditor_object::test<90>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: a folded script edited throughout settles its folds once lexed", "[alcodeeditor]") { - set_test_name("a folded script edited so that every line after the edit starts otherwise: the folds settled once it is lexed to its end, a " - "slice a frame, not the whole of it lexed in the frame of the edit"); std::string text = "default\n{\n state_entry()\n {\n integer x = 1;\n }\n"; for (S32 i = 0; i < 3000; ++i) { @@ -3378,74 +4563,116 @@ namespace tut e.setText(text); const S32 last = e.document().lineCount() - 1; e.setCaret(ALTextPos(0, 0)); - ensure("folds", e.foldAt(2)); - ensure("its lines hidden", e.layout().hidden(3) && e.layout().hidden(5) && !e.layout().hidden(6)); + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(2)); + { + INFO("its lines hidden"); + CHECK(e.layout().hidden(3)); + CHECK(e.layout().hidden(5)); + CHECK_FALSE(e.layout().hidden(6)); + } // A string opened at the top runs on to the end: every line after // it starts in it, and the block is gone. e.document().insert(ALTextPos(0, 0), "\""); e.pump(); e.highlighter().tokens(last); - ensure("the frame after the edit left most of the text to lex: " + std::to_string(e.highlighter().lastLexed()) + " of " + - std::to_string(last + 1), - e.highlighter().lastLexed() > last / 2); - ensure("the fold as the edit left it meanwhile", e.isFolded(2) && e.layout().hidden(3)); + UNSCOPED_INFO("the frame after the edit left most of the text to lex: " + std::to_string(e.highlighter().lastLexed()) + " of " + + std::to_string(last + 1)); + CHECK(e.highlighter().lastLexed() > last / 2); + { + INFO("the fold as the edit left it meanwhile"); + CHECK(e.isFolded(2)); + CHECK(e.layout().hidden(3)); + } e.pump(); - ensure("lexed to its end, the next frame lets go of the fold whose block went", !e.isFolded(2) && !e.layout().anyHidden()); + { + INFO("lexed to its end, the next frame lets go of the fold whose block went"); + CHECK_FALSE(e.isFolded(2)); + CHECK_FALSE(e.layout().anyHidden()); + } } - template<> template<> - void alcodeeditor_object::test<91>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: Return at a folded header's end opens the block and shows the caret", "[alcodeeditor]") { - set_test_name("Return at the end of a folded header opens the block the line it makes goes into, the caret on that line in sight"); ALCodeEditor& e = make("foo() {\n x();\n}\nbar()"); - ensure("folds", e.foldAt(0)); - ensure("its lines hidden", e.layout().hidden(1) && e.layout().hidden(2) && !e.layout().hidden(3)); + UNSCOPED_INFO("folds"); + CHECK(e.foldAt(0)); + { + INFO("its lines hidden"); + CHECK(e.layout().hidden(1)); + CHECK(e.layout().hidden(2)); + CHECK_FALSE(e.layout().hidden(3)); + } e.setCaret(e.document().lineEnd(0)); key(KEY_RETURN); - ensure_equals("the header as it was", e.document().line(0), std::string("foo() {")); - ensure_equals("the caret on the line made", e.caret().line, 1); - ensure("the block opened", !e.isFolded(0)); - ensure("the caret's line in sight, and the rest of the block", !e.layout().hidden(1) && !e.layout().anyHidden()); + UNSCOPED_INFO("the header as it was"); + CHECK(e.document().line(0) == std::string("foo() {")); + UNSCOPED_INFO("the caret on the line made"); + CHECK(e.caret().line == 1); + UNSCOPED_INFO("the block opened"); + CHECK_FALSE(e.isFolded(0)); + { + INFO("the caret's line in sight, and the rest of the block"); + CHECK_FALSE(e.layout().hidden(1)); + CHECK_FALSE(e.layout().anyHidden()); + } // The brace under the header: the line made between them is the // block's too, the header found past it. ALCodeEditor& f = make("f()\n{\n x();\n}\ng()"); - ensure("folds", f.foldAt(0)); - ensure("its lines hidden", f.layout().hidden(1) && f.layout().hidden(3) && !f.layout().hidden(4)); + UNSCOPED_INFO("folds"); + CHECK(f.foldAt(0)); + { + INFO("its lines hidden"); + CHECK(f.layout().hidden(1)); + CHECK(f.layout().hidden(3)); + CHECK_FALSE(f.layout().hidden(4)); + } f.setCaret(f.document().lineEnd(0)); key(KEY_RETURN); - ensure_equals("the brace a line down", f.document().line(2), std::string("{")); - ensure_equals("the caret on the line made", f.caret().line, 1); - ensure("the block opened", !f.isFolded(0)); - ensure("the caret's line in sight, and the rest of the block", !f.layout().hidden(1) && !f.layout().anyHidden()); + UNSCOPED_INFO("the brace a line down"); + CHECK(f.document().line(2) == std::string("{")); + UNSCOPED_INFO("the caret on the line made"); + CHECK(f.caret().line == 1); + UNSCOPED_INFO("the block opened"); + CHECK_FALSE(f.isFolded(0)); + { + INFO("the caret's line in sight, and the rest of the block"); + CHECK_FALSE(f.layout().hidden(1)); + CHECK_FALSE(f.layout().anyHidden()); + } } - template<> template<> - void alcodeeditor_object::test<92>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: the marks' revision moves on only when an edit moves or takes a mark", "[alcodeeditor]") { - set_test_name("the marks' revision, which the ruler's list of marked lines is kept by, moves on where an edit moves a mark or takes one, " - "and not for one within a line without"); ALCodeEditor& e = make("one\ntwo\nthree"); e.setMark(2, ALCodeEditor::Mark::Error); const ALTextFeatures* features = e.features(); - ensure("the editor's own", features != nullptr); + UNSCOPED_INFO("the editor's own"); + REQUIRE(features != nullptr); U32 was = features->marksRevision(); e.document().insert(ALTextPos(0, 3), "!"); - ensure_equals("typed within a line without a mark: the same", features->marksRevision(), was); + UNSCOPED_INFO("typed within a line without a mark: the same"); + CHECK(features->marksRevision() == was); e.document().insert(ALTextPos(0, 0), "zero\n"); - ensure("the mark gone down with its line", e.markAt(3) == ALCodeEditor::Mark::Error && e.markAt(2) == ALCodeEditor::Mark::None); - ensure("a line made above it: moved on", features->marksRevision() != was); + { + INFO("the mark gone down with its line"); + CHECK(e.markAt(3) == ALCodeEditor::Mark::Error); + CHECK(e.markAt(2) == ALCodeEditor::Mark::None); + } + UNSCOPED_INFO("a line made above it: moved on"); + CHECK(features->marksRevision() != was); was = features->marksRevision(); e.document().insert(ALTextPos(3, 0), "x"); - ensure("its own line edited, the mark gone with what it said", e.markAt(3) == ALCodeEditor::Mark::None); - ensure("and moved on", features->marksRevision() != was); + UNSCOPED_INFO("its own line edited, the mark gone with what it said"); + CHECK(e.markAt(3) == ALCodeEditor::Mark::None); + UNSCOPED_INFO("and moved on"); + CHECK(features->marksRevision() != was); } - template<> template<> - void alcodeeditor_object::test<93>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: with no provider a word from a table of a kind is offered as that kind", "[alcodeeditor]") { - set_test_name("with nobody to ask, a word from a table named for any kind is offered as that kind"); ALCodeEditor& e = make("", "lsl"); e.highlighter().ownWords().set("namespace", { "Vehicles" }); type("Vehi"); @@ -3455,105 +4682,150 @@ namespace tut { found = c.text == "Vehicles" ? &c : found; } - ensure("offered", e.completionOpen() && found); - ensure("as a namespace", found->kind == ALSyntaxKind::Namespace); - ensure_equals("and said to be one", found->detail, std::string("namespace")); + { + INFO("offered"); + CHECK(e.completionOpen()); + REQUIRE(found); + } + UNSCOPED_INFO("as a namespace"); + CHECK(found->kind == ALSyntaxKind::Namespace); + UNSCOPED_INFO("and said to be one"); + CHECK(found->detail == std::string("namespace")); } - template<> template<> - void alcodeeditor_object::test<94>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: occurrences are whole words in prose and whole names in code", "[alcodeeditor]") { - set_test_name("in prose the occurrences of the word at the caret are taken, whole words as Whole Word finds them; in code whole names, " - "or every place a selection reads, none over another"); // Größe, then Gr alone, at the start of Grün, and alone again. const std::string grosse = "Gr\xC3\xB6\xC3\x9F" "e"; ALCodeEditor& e = make((grosse + " Gr Gr\xC3\xBC" "n Gr").c_str(), "text"); e.setCaret(ALTextPos(0, 1)); - ensure("a word taken", e.selectNextOccurrence()); - ensure_equals("the whole of it", e.selectedText(), grosse); + UNSCOPED_INFO("a word taken"); + CHECK(e.selectNextOccurrence()); + UNSCOPED_INFO("the whole of it"); + CHECK(e.selectedText() == grosse); e.setCaret(ALTextPos(0, 9)); - ensure("a word standing alone", e.selectNextOccurrence() && e.selection().normalised() == ALTextRange(ALTextPos(0, 8), ALTextPos(0, 10))); - ensure("its next place", e.selectNextOccurrence()); - ensure("the next whole word, not the start of another", - e.selection().normalised() == ALTextRange(ALTextPos(0, 17), ALTextPos(0, 19)) && e.otherSelections().size() == 1); + { + INFO("a word standing alone"); + CHECK(e.selectNextOccurrence()); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(0, 8), ALTextPos(0, 10))); + } + UNSCOPED_INFO("its next place"); + CHECK(e.selectNextOccurrence()); + { + INFO("the next whole word, not the start of another"); + CHECK(e.selection().normalised() == ALTextRange(ALTextPos(0, 17), ALTextPos(0, 19))); + REQUIRE(e.otherSelections().size() == 1); + } e.goTo(ALTextPos(0, 9)); - ensure("every place", e.changeAllOccurrences()); - ensure("the one other whole word only", e.otherSelections().size() == 1 && e.otherSelections().front().normalised() == + UNSCOPED_INFO("every place"); + CHECK(e.changeAllOccurrences()); + { + INFO("the one other whole word only"); + REQUIRE(e.otherSelections().size() == 1); + CHECK(e.otherSelections().front().normalised() == ALTextRange(ALTextPos(0, 17), ALTextPos(0, 19))); + } ALCodeEditor& c = make("integer aa = aaaa + aa;"); c.setCaret(ALTextPos(0, 9)); - ensure("in code, every place", c.changeAllOccurrences()); - ensure("of the whole name", c.otherSelections().size() == 1 && c.otherSelections().front().normalised() == + UNSCOPED_INFO("in code, every place"); + CHECK(c.changeAllOccurrences()); + { + INFO("of the whole name"); + REQUIRE(c.otherSelections().size() == 1); + CHECK(c.otherSelections().front().normalised() == ALTextRange(ALTextPos(0, 20), ALTextPos(0, 22))); + } c.setSelection(ALTextRange(ALTextPos(0, 8), ALTextPos(0, 10))); - ensure("chosen by hand, every place it reads", c.changeAllOccurrences()); + UNSCOPED_INFO("chosen by hand, every place it reads"); + CHECK(c.changeAllOccurrences()); const std::vector& places = c.otherSelections(); - ensure_equals("in and out of names, none over another", places.size(), size_t(3)); - ensure("where they are", places[0].normalised() == ALTextRange(ALTextPos(0, 13), ALTextPos(0, 15)) && - places[1].normalised() == ALTextRange(ALTextPos(0, 15), ALTextPos(0, 17)) && - places[2].normalised() == ALTextRange(ALTextPos(0, 20), ALTextPos(0, 22))); + UNSCOPED_INFO("in and out of names, none over another"); + REQUIRE(places.size() == size_t(3)); + { + INFO("where they are"); + CHECK(places[0].normalised() == ALTextRange(ALTextPos(0, 13), ALTextPos(0, 15))); + CHECK(places[1].normalised() == ALTextRange(ALTextPos(0, 15), ALTextPos(0, 17))); + CHECK(places[2].normalised() == ALTextRange(ALTextPos(0, 20), ALTextPos(0, 22))); + } } - template<> template<> - void alcodeeditor_object::test<95>() + TEST_CASE_METHOD(alcodeeditor_data, "alcodeeditor: what a string holds is not code to occurrences or argument counting", "[alcodeeditor]") { - set_test_name("what a string says is not code to the lit occurrences and the call's arguments: an attribute's value, an escape"); ALCodeEditor& x = make("