diff --git a/indra/cmake/ALAddTestBinary.cmake b/indra/cmake/ALAddTestBinary.cmake index ac294aa44c..872e5c4d8e 100644 --- a/indra/cmake/ALAddTestBinary.cmake +++ b/indra/cmake/ALAddTestBinary.cmake @@ -148,9 +148,6 @@ function(al_add_test_binary name) add_executable(${target} ${arg_SOURCES} ${arg_UNIT}) target_link_libraries(${target} PRIVATE al::flags al_test_support ${arg_SUPPORT} ${arg_LIBRARIES}) target_include_directories(${target} PRIVATE ${arg_INCLUDES} ${INDRA_SOURCE_DIR}/test) - # For the helpers in test/ that TUT tests still share, so each fails a - # test the way its framework does. - target_compile_definitions(${target} PRIVATE AL_TEST_CATCH2=1) if(arg_UNIT) target_include_directories( ${target} @@ -324,7 +321,9 @@ function(al_add_test_binary name) # Every case no file's tag names: a filter of nothing but exclusions # leaves the hidden cases out, as the files' entries do, and one that # matches nothing exits 2. The ISA gate is not asked: nothing runs, and a - # listing already needs the binary to start on any host. + # listing already needs the binary to start on any host. Disabled where + # the files' entries are: a run starts its checks whatever it matches, + # and a GL binary's make the context the host has none of. set(strays) foreach(stem IN LISTS stems) string(APPEND strays "~[#${stem}]") @@ -338,7 +337,11 @@ function(al_add_test_binary name) ) set_tests_properties( ${target}/stray_cases - PROPERTIES ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" LABELS "${labels}" TIMEOUT 60 + PROPERTIES + ENVIRONMENT_MODIFICATION "${AL_TEST_ENVIRONMENT}" + LABELS "${labels}" + DISABLED ${disabled} + TIMEOUT 60 ) list(LENGTH stems file_count) diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index a5348deb66..8db20427ce 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -562,207 +562,183 @@ 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 - PROJECT llui - LIBRARIES ${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\"" + # What llui's test binaries set up for the process, put back after every + # case, and give the state guard to check: once for the binary with no GL, + # once for the one that draws. + add_library(llui_test_checks OBJECT tests/llui_test_checks.cpp) + add_library(llui_gl_test_checks OBJECT tests/llui_test_checks.cpp) + target_compile_definitions(llui_gl_test_checks PRIVATE LLUI_TEST_GL=1) + foreach(checks llui_test_checks llui_gl_test_checks) + target_link_libraries(${checks} PRIVATE al::flags al_test_support llui) + target_compile_definitions( + ${checks} + PRIVATE "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" ) + set_target_properties(${checks} PROPERTIES FOLDER "Tests/llui") endforeach() - # What the text view draws, read back from llrender's hidden window. - al_add_test( - altextview_gl - GL + # llui's tests with no GL but the colour table's: 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/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 llui_test_checks llrender_test_checks llcommon_test_checks + 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 + # The colour table's own tests, alone: each loads documents of its own, + # and a name, once in the table, stays there for every case after it. + al_add_test_binary( + lluicolortable_test PROJECT llui - LIBRARIES ${test_libs} + SOURCES tests/lluicolortable_test.cpp + SUPPORT llui_test_fakes llrender_test_checks llcommon_test_checks + LIBRARIES llui ${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 + # 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 llui_gl_test_checks llrender_test_checks llcommon_test_checks + LIBRARIES llui ${test_libs} DEFINES "LLUI_TEST_APP_DIR=\"${INDRA_SOURCE_DIR}/newview\"" + GL ) + # 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. al_add_bench( diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index f9605adefa..490a3447b0 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -246,6 +246,13 @@ void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, bool lock, bool mKeyboardFocus = new_focus; mKeyboardFocusView = new_focus_view; + // A view that died with focus, or under it, can tell nothing, and its + // dead handle would stay at the chain's root end, where the ancestors + // the old focus shares with the new are looked for: none would be + // found, and every view above the old focus would be told it lost a + // focus it still has. + mImpl->mCachedKeyboardFocusList.remove_if([](const LLHandle& handle) { return handle.isDead(); }); + // list of the focus and it's ancestors view_handle_list_t old_focus_list = mImpl->mCachedKeyboardFocusList; view_handle_list_t new_focus_list; @@ -375,6 +382,14 @@ void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLFocusableElement* f { mKeyboardFocus = nullptr; mKeyboardFocusView = nullptr; + // Nothing is told it lost focus, so nothing is left as having it: the + // next change of focus starts from no chain. Left, the chain's views + // die (this is called from destructors) and their dead handles stay + // at its root end for good, where every later change looks for the + // ancestors it shares with the old focus -- so it would find none, + // and tell every view above the old focus that it lost focus, + // whatever still had it. + mImpl->mCachedKeyboardFocusList.clear(); } } diff --git a/indra/llui/tests/alanchoredranges_test.cpp b/indra/llui/tests/alanchoredranges_test.cpp index bbf2354716..11b6c2a2e9 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,37 @@ 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")); + 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"); + REQUIRE(gone == size_t(1)); + 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 +94,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 +105,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 +135,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..7776f54f1e 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,54 @@ 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")); + 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")); + 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()); + CHECK(commits == 1); + 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 +112,12 @@ 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")); + 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..ab24cee74d 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 { @@ -51,7 +51,7 @@ namespace tut // Line and block comments, strings, and brackets as punctuation; // and with `paths`, a string one word long with a dot or a slash in // it a path, as LSL's grammar has it. - static std::shared_ptr grammar(bool paths = false) + static std::shared_ptr require_grammar(bool paths = false) { LLSD description; description["name"] = "brackets"; @@ -71,7 +71,9 @@ namespace tut description["states"]["line_comment"] = line_comment; auto loaded = std::make_shared(); std::string error; - ensure("grammar loads: " + error, loaded->load(description, error)); + const bool ok = loaded->load(description, error); + INFO("grammar loads: " << error); + REQUIRE(ok); return loaded; } @@ -82,7 +84,7 @@ namespace tut void make(const char* text, bool paths = false) { doc.setText(text); - highlighter.setGrammar(grammar(paths)); + highlighter.setGrammar(require_grammar(paths)); highlighter.attach(&doc); index.attach(&doc); } @@ -98,42 +100,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 +148,56 @@ 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); + CHECK(matched(0, 0) == std::string("1:1")); + 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..7e1c393010 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,120 @@ 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); + CHECK(canvas->getRect().getWidth() == ROOM_W); + 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); + CHECK(x == 50); + 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 +258,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 +276,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 +296,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 +336,26 @@ 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); + 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 +383,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 +406,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(); + + CAPTURE(again); + UNSCOPED_INFO("it stayed where it was put, across"); + REQUIRE(root->getRect().mLeft == want_left); + UNSCOPED_INFO("and down from the top"); + REQUIRE(canvas->surfaceHeight() - root->getRect().mTop == want_down); + { + INFO("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 +531,99 @@ 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); + CAPTURE(pass); + UNSCOPED_INFO("the row is where it was, across"); + REQUIRE(row->getRect().getWidth() == settled.getWidth()); + UNSCOPED_INFO("and down"); + REQUIRE(row->getRect().getHeight() == settled.getHeight()); + UNSCOPED_INFO("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())); + CHECK(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) + { + CAPTURE(frame); + LLRect snapped; + LLView* to = root->findSnapRect(snapped, LLCoordGL(0, -1), + LLView::SNAP_PARENT_AND_SIBLINGS, SNAP_FROM); + UNSCOPED_INFO("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 +631,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()); @@ -601,7 +659,7 @@ namespace tut for (S32 turn = 0; turn < 6; ++turn) { - const std::string which = "turn " + std::to_string(turn) + ": "; + CAPTURE(turn); // The band folding away and coming back. area->reshape(ROOM_W, (turn % 2) ? ROOM_H : ROOM_H / 2); @@ -612,12 +670,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("the surface is inside the row, " + where(canvas->getRect()) + + " in " + where(row->getLocalRect())); + REQUIRE(row->getLocalRect().contains(canvas->getRect())); + UNSCOPED_INFO("and the window is on the surface, " + where(root->getRect()) + + " on " + where(canvas->getLocalRect())); + REQUIRE(canvas->getLocalRect().contains(root->getRect())); } area->die(); } @@ -628,12 +686,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 +721,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 +730,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 +741,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,47 +805,52 @@ 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, - canvas->surfaceHeight() - root->getRect().mTop); + UNSCOPED_INFO("once something has been put on it, it has"); + CHECK(canvas->keptPlace(left, down)); + 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. const S32 was_left = left; @@ -797,58 +858,23 @@ 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)); + CHECK(left == was_left); + 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 +888,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 +958,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 +992,44 @@ 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()); + CHECK(canvas->getRect().getWidth() == room.getWidth()); + 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(); + + CHECK(canvas->empty()); + UNSCOPED_INFO("life size reads as a hundred"); + CHECK(canvas->legend() == std::string("100%")); + place(canvas, 200, 100); + CHECK_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); @@ -980,46 +1040,28 @@ namespace tut canvas->setZoom(zoom); for (S32 room = 640; room < 652; ++room) { + CAPTURE(zoom, 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"); + 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 +1088,9 @@ 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); + 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 +1098,18 @@ 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); + CHECK(after_x == before_x); + 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 +1123,120 @@ namespace tut canvas->refresh(); area->getContentWindowRect(); - ensure("not panning yet", !canvas->panning()); + CHECK_FALSE(canvas->panning()); canvas->beginPan(100, 100); - ensure("panning", canvas->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()); + 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"); + CHECK(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"); + CHECK(canvas->contentOriginX() == 50.f); + CHECK(canvas->contentOriginY() == -25.f); + } const auto contentAt = [canvas](S32 x, S32 y) { canvas->toContent(x, y); @@ -1165,22 +1244,24 @@ 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); + 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 +1269,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"); + CHECK(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 +1285,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 +1299,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 +1316,49 @@ 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); + CHECK(screen.mLeft == sx + ll_round(ox - 40.f)); + CHECK(screen.mRight == sx + ll_round(ox + 40.f)); + CHECK(screen.mTop == sy + ll_round(oy + 60.f)); + 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"); + CHECK(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"); + CHECK(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..9308c83424 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,194 @@ 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()); + 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"); + CHECK(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)); + 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); + } + CHECK(peek.said().find("1 of 3") != std::string::npos); + CHECK(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"); + CHECK(peek.changeShown() == 1); + CHECK(peek.gapLine() == 4); + CHECK_FALSE(peek.savedText()->getVisible()); + } + UNSCOPED_INFO("said as put in"); + CHECK(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"); + CHECK(peek.changeShown() == 2); + CHECK(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()); + CHECK(e.changePeek()->isOpen()); + CHECK(e.changePeek()->changeShown() == 0); + } + { + INFO("Escape closes it"); + CHECK(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 +275,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"); + CHECK(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"); + CHECK(f.highlights(ALCodeEditor::Highlight::Change).empty()); + CHECK(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 +316,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"); + CHECK(e.caret().line == 4); + CHECK(e.changePeek()->isOpen()); + CHECK(e.changePeek()->changeShown() == 1); + } + { + INFO("as the editor steps without vim"); + CHECK(e.stepChange(false)); + CHECK(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 +415,104 @@ 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)); + 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); + CHECK(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"); + CHECK(e.stepChange(true)); + CHECK(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); + CHECK(edited != first); + CHECK(edited->version == e.document().version()); + CHECK(edited->now.front() == "ya"); + } + { + INFO("undone: again, three"); + CHECK(e.perform(ALEditorCommand::Undo)); + CHECK(e.changesSinceSaved() != edited); + CHECK(e.changesSinceSaved()->changes.size() == 3); + } e.resetDirty(); - ensure("saved: none", e.changesSinceSaved() && e.changesSinceSaved()->changes.empty()); + { + INFO("saved: none"); + REQUIRE(e.changesSinceSaved()); + CHECK(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 +524,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"); + CHECK((!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..8ce683ebb7 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,67 @@ 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)); + 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"); + CHECK(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 +154,81 @@ 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)); + 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..27a885f75e 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,159 @@ 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, - "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 } }); + CHECK(card.text == "unknown global 'x'\ntwo\nlines\nFix: Declare 'x'\n\nlocal x: number\nA number.\n" + ALCodeCards::deprecatedNote() + + "\ndeclared at line 3"); + 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); + CHECK(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)); + 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); + 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(), {})); + CHECK(cards.answer().empty()); + } + UNSCOPED_INFO("the word's, taken"); + CHECK(cards.heard(ALTextPos(2, 4), 7, "local n: number", { { "declared here", "", LLSD(1) } })); + CHECK(cards.answer() == "local n: number"); + CHECK(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"); + CHECK(cards.answer().empty()); + CHECK(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))); + CHECK_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..7990ca5def 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,139 +85,186 @@ 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) + { + INFO("key " << static_cast(k) << ", mask " << m); + REQUIRE(editor->handleKeyHere(k, m)); + } void type(const char* text) { for (const char* c = text; *c; ++c) { + CAPTURE(text, *c); if (*c == '\n') { key(KEY_RETURN); } else { - ensure("char taken", editor->handleUnicodeCharHere(static_cast(static_cast(*c)))); + 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); + 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); + 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)); + 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"); + CHECK(e.decorations().empty()); e.clearMarks(); - ensure("cleared", e.markAt(2) == ALCodeEditor::Mark::None); + 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")); + 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")); + 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)); + 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)); + 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); + } + + CHECK(e.foldAt(2)); + CHECK(e.isFolded(2)); + 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); + 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()); + CHECK_FALSE(e.layout().anyHidden()); // Command-Option with a bracket on the Mac, Control-Shift elsewhere. #if LL_DARWIN @@ -227,46 +274,73 @@ 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)); + 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"); + 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()); + 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 +349,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 +371,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 +386,127 @@ 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)); + 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"); + CHECK(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); + 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"); + CHECK(signature_asks.size() == 1); e.hideSignature(); - ensure("hidden", !e.signatureShown()); + 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)); + CHECK(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"); + CHECK(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 +515,48 @@ 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)); + CHECK(e.replaceAll(edits)); + 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")); + e.setReadOnly(true); + UNSCOPED_INFO("read-only, a rename cannot be asked"); + CHECK_FALSE(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"); + CHECK(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"); + CHECK(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 +564,42 @@ 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)); + 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)); + CHECK(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)); + } + 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 +612,79 @@ 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"); + CHECK(ALCodeEditor::parameterNames("function").empty()); + CHECK(ALCodeEditor::parameterNames("() -> ()").empty()); + } type("llS"); - ensure("offered", e.completionOpen()); + 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")); + CHECK(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)); + CHECK(e.placeholders().empty()); + } + 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")); + CHECK(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)); + CHECK(e.placeholders().empty()); + } ALCodeEditor& f = make("", "lsl"); f.setCompletionProvider([](const ALTextPos&, std::string_view prefix, std::vector& out) { @@ -503,16 +699,16 @@ namespace tut } }); type("fo"); - ensure("offered", f.completionOpen()); + 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 +720,89 @@ 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)); + 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); + 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); + CHECK(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)); + 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 +816,55 @@ 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)); + REQUIRE(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); + CHECK(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()); + 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 +874,40 @@ 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"); + CHECK(e.stringAt(ALTextPos(0, whole.end.column)).empty()); + UNSCOPED_INFO("nor of a name"); + CHECK(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")); + 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 +915,18 @@ 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); + 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 +936,160 @@ namespace tut e.setTextColor(ink); e.setCaret(e.document().end()); key(' ', MASK_CONTROL); - ensure("the list is open", e.completionOpen()); + 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"); + CHECK(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"); + CHECK(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()); + CHECK(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)); + CHECK(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); + 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()); + 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); + CHECK(e.handleUnicodeCharHere('D')); + 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 +1099,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 +1116,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 +1192,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")); + CHECK(e.findChild("completion_doc")->getVisible()); + CHECK(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"); + CHECK(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 +1242,15 @@ namespace tut } }); type("llOld"); - ensure("offered", e.completionOpen() && e.completions()[0].deprecated); + 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 +1270,16 @@ 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)); + 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 +1292,148 @@ 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)); + 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 = []")); + 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))); + 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 +1445,143 @@ 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()); + 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"); + CHECK(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); + CHECK(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")); + CHECK(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")); + 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"); + CHECK(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 +1590,60 @@ 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)); + 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 +1653,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 +1686,98 @@ 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()); + } + 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()); + CHECK(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 +1792,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 +1808,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 +1846,13 @@ 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")); + REQUIRE(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 +1874,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"); + CHECK(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"); + CHECK(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); + CHECK(e.fixes().front().value.isUndefined()); + } key(KEY_RETURN); - ensure("which takes nothing", taken.size() == 1 && !e.fixesOpen()); + { + INFO("which takes nothing"); + CHECK(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 +1985,38 @@ 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)); + 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)); + CHECK(e.document().line(0) == std::string("local count: number = 5")); + UNSCOPED_INFO("and the hint gone with its line's edit"); + CHECK(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 +2034,83 @@ 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"); + CHECK(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); + 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()); + CHECK(e.noteAt(0) == std::string("28 bytes")); + UNSCOPED_INFO("nowhere else"); + CHECK(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 +2120,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"); + CHECK(e.noteAt(0).empty()); + CHECK(e.noteAt(1) == "28 bytes"); + } e.setLineNotes({}); - ensure("replaced whole", e.noteAt(1).empty()); + UNSCOPED_INFO("replaced whole"); + CHECK(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 +2165,42 @@ 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); + CHECK(e.openFixes(0)); + { + INFO("told what it lists"); + REQUIRE(told.size() == 1); + REQUIRE(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"); + CHECK(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); + 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"); + CHECK(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"); + CHECK(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"); + CHECK(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 +2215,36 @@ 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"); + 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); + 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"); + 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"); + 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 +2269,37 @@ 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()); + CHECK(e.handleToolTip(x, y, MASK_NONE)); + REQUIRE(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()); + 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 +2315,45 @@ 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) }); + 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"); + CHECK(asked.size() == 1); + CHECK(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"); + CHECK(followed.size() == 2); + CHECK(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 +2362,61 @@ 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"); + 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")); + 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 +2424,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 +2447,49 @@ 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()); + 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, + 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); + CHECK(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"); + CHECK(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 +2501,26 @@ 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()); + 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 +2545,66 @@ 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"); + CHECK(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()); + CHECK(e.highlights().empty()); ALCodeEditor::Decoration worst, later; worst.range = ALTextRange(ALTextPos(1, 8), ALTextPos(1, 13)); @@ -1970,28 +2612,39 @@ 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); + CHECK(listed[0]->message == "worst"); + CHECK(listed[1]->message == "later"); + } + UNSCOPED_INFO("none on another line"); + CHECK(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); + CHECK(e.handleKeyHere(KEY_TAB, MASK_NONE)); + CHECK(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"); + CHECK_FALSE(was.empty()); + REQUIRE(now.size() > was.size()); + CHECK(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"); + CHECK(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 +2653,40 @@ 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)); + CHECK(f.replaceAll(std::move(renames))); + UNSCOPED_INFO("as one change"); + CHECK(renamed.count() == 1); + 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 +2703,93 @@ namespace tut } }); type("cou"); - ensure("open", e.completionOpen()); + 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()); + CHECK_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)); + CHECK(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)); + 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 +2797,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 +2872,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 +2935,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 +2951,161 @@ 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)); + 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"); + { + INFO("the name"); + CHECK(grown() == std::string("Say")); + } + { + INFO("with what it is a member of"); + CHECK(grown() == std::string("ll.Say")); + } + { + INFO("the line's text"); + CHECK(grown() == std::string(" ll.Say(0, \"hi there\");")); + } + { + INFO("the braces' inside"); + CHECK(grown() == std::string("\n ll.Say(0, \"hi there\");\n")); + } + { + 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\")")); + { + INFO("in a string: a word"); + CHECK(grown() == std::string("hi")); + } + { + INFO("its inside"); + CHECK(grown() == std::string("hi there")); + } + { + INFO("with its quotes"); + CHECK(grown() == std::string("\"hi there\"")); + } + { + INFO("the call's arguments"); + CHECK(grown() == std::string("0, \"hi there\"")); + } + { + 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)")); + { + INFO("past the pair it crosses, the inside of the one around it"); + CHECK(grown() == std::string("(a) + b")); + } + { + 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)); + 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"); + CHECK(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"); + CHECK(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"); + CHECK(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"); + CHECK(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"); + CHECK(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"); + CHECK(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 +3113,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"); + CHECK(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 +3148,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"); + REQUIRE(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 +3196,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 +3220,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: @@ -2496,12 +3288,14 @@ namespace tut whole.end = editor->document().lineLength(0); return ll_test::TextViewProbe::colours(*editor, 0, whole); }; - const auto same = [&](const std::string& what, const std::vector& all, const ALTextLayout::Row& row) { + const auto require_same_colours = [&](const char* 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); + INFO(what); + 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]); + INFO("glyph " << row.glyphBegin + k); + REQUIRE(colours[k] == all[row.glyphBegin + k]); } }; @@ -2510,25 +3304,32 @@ 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"); + CHECK(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]); + CAPTURE(r); + require_same_colours("a wrapped row", all, wrapped.rows[r]); } editor->setWordWrap(false); const ALTextLayout::Line& one = editor->layout().line(0); for (F32 from = 0.f; from < one.width; from += 1500.f) { + CAPTURE(from); const ALTextLayout::Row seen = ALTextLayout::rowWithin(one, one.rows[0], from, from + 400.f); - same("in sight from " + std::to_string(from), all, seen); + require_same_colours("the part in sight", all, seen); } // An opener typed at the start: every bracket after it one deeper, @@ -2536,27 +3337,38 @@ 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) { - same("wrapped row " + std::to_string(r) + " after the opener", deeper, again.rows[r]); + CAPTURE(r); + require_same_colours("a wrapped row after the opener", deeper, again.rows[r]); } } - 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"); + CHECK(e.findChild("completions") == nullptr); + UNSCOPED_INFO("no fix list yet"); + CHECK(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()); + CHECK(e.findChild("completions") != nullptr); + } + UNSCOPED_INFO("and still no fix list"); + CHECK(e.findChild("fixes") == nullptr); key(KEY_ESCAPE); e.setFixProvider([](S32 line, std::vector& out) { ALCodeEditor::Fix semi; @@ -2566,12 +3378,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()); + CHECK(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 +3402,52 @@ 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"); + 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()); + 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 +3457,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); + CHECK(fixes != popup); + CHECK(fixes->findChild("fixes", false) == &fixes->list()); + } + { + INFO("previewed in its own box"); + CHECK(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"); + CHECK_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 +3523,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))); + CHECK(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 +3564,159 @@ 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)); + 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)); + 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)); + 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)); + 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); + 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"); + CHECK(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); + CHECK(e.gutterWidth() == 0); + 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 +3737,37 @@ namespace tut e.setText(""); asks.clear(); type("hand"); - ensure("offered", e.completionOpen()); + 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)")); + { + INFO("guessed: a function is called, its parameter to fill"); + CHECK(take(ALCompletion::Brackets::Guess) == std::string("handler(n)")); + } + { + INFO("none: passed as it is"); + CHECK(take(ALCompletion::Brackets::None) == std::string("handler")); + } + UNSCOPED_INFO("and no signature asked"); + CHECK(asks.empty()); + { + 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)); + CHECK(asks.empty()); + } + { + 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 +3782,25 @@ 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")); + 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 +3829,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()); + CHECK(e.completions().size() == 3); + CHECK(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 +3944,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 +3976,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()); + CHECK(e.completions().size() == 3); + CHECK(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()); + CHECK(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"); + CHECK(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 +4058,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()); + CHECK(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()); + CHECK(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 +4142,82 @@ 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)); + CHECK(e.foldAt(1)); + 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()); + CHECK(e.replaceAll({ { ALTextRange(ALTextPos(3, 4), ALTextPos(3, 5)), "total" } })); + 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("{")); + CHECK_FALSE(e.isFolded(0)); + CHECK_FALSE(e.isFolded(1)); + 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)); + 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)); + 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 +4234,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"); + 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 +4255,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"); + CHECK(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 +4291,34 @@ 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()); + 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 +4344,9 @@ 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)); + 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 +4355,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()); + CHECK(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 +4393,46 @@ 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"); + 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)); + 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()); + CHECK(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 +4447,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 +4475,101 @@ 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)); + CHECK(e.foldAt(2)); + 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"); + 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)); + CHECK(e.foldAt(0)); + 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); + 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)); + CHECK(f.foldAt(0)); + 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); + 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 +4579,142 @@ 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")); + 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); + 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))); + CHECK(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() == + 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)); + 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("