diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index c8320cc21f..29e93efa68 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -217,10 +217,14 @@ void LLComboBox::onCommit() mTextEntry->setValue(label); mTextEntry->setTentative(false); } + // Chosen or typed: the value is the control's own now, whoever had + // written it before. + mUnset = false; setControlValue(item->getValue()); } else if (mAllowTextEntry) { + mUnset = false; setControlValue(mTextEntry->getValue()); } else @@ -465,6 +469,10 @@ void LLComboBox::setUnset(bool unset) void LLComboBox::updateLabel() { + // An item chosen is shown as settled, and is: whoever had written the + // value before, it is the control's own now. + mUnset = false; + // Update the combo editor with the selected // item label. if (mTextEntry) diff --git a/indra/llui/tests/alanchoredranges_test.cpp b/indra/llui/tests/alanchoredranges_test.cpp index 11b6c2a2e9..aca1307e85 100644 --- a/indra/llui/tests/alanchoredranges_test.cpp +++ b/indra/llui/tests/alanchoredranges_test.cpp @@ -137,6 +137,7 @@ namespace [&gone](Hint& h) { gone.push_back(h.name); }); { INFO("before stays, after moves"); + REQUIRE(hints.size() == 3); CHECK(hints[0].name == "inside"); CHECK(hints[1].name == "before"); CHECK(hints[1].at == ALTextPos(0, 4)); @@ -168,6 +169,7 @@ namespace { INFO("past the edit's line, not looked at, and where it was"); CHECK(current == 3); + REQUIRE(matches.size() == 4); CHECK(matches[3] == on(1, 3, 5)); } matches.apply(doc.replace(on(1, 3, 5), "E"), ¤t); diff --git a/indra/llui/tests/alcanvasview_test.cpp b/indra/llui/tests/alcanvasview_test.cpp index 7e1c393010..14bf422030 100644 --- a/indra/llui/tests/alcanvasview_test.cpp +++ b/indra/llui/tests/alcanvasview_test.cpp @@ -1147,8 +1147,11 @@ namespace UNSCOPED_INFO("and down"); CHECK(area->getDocPosVertical() == 40); + // panGesture() is whether a left press pans, which the space bar + // held makes it too: asked before the surface says it drags and + // after, it shows the saying is what changed it. UNSCOPED_INFO("a plain surface does not drag by the left button"); - CHECK_FALSE(canvas->dragPans()); + CHECK_FALSE(canvas->panGesture()); canvas->setDragPans(true); UNSCOPED_INFO("until it says so"); CHECK(canvas->panGesture()); diff --git a/indra/llui/tests/alchangepeek_test.cpp b/indra/llui/tests/alchangepeek_test.cpp index 9308c83424..957a809567 100644 --- a/indra/llui/tests/alchangepeek_test.cpp +++ b/indra/llui/tests/alchangepeek_test.cpp @@ -189,7 +189,7 @@ namespace TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: take_back reverts a change as one undo step and other edits close it", "[alchangepeek]") { ALCodeEditor& e = make("a\nb\nc\nd\ne", "a\nB\nc\nx\nd"); - e.peekChange(1); + CHECK(e.peekChange(1)); ALChangePeek& peek = *e.changePeek(); press(peek, "take_back"); UNSCOPED_INFO("made as saved"); @@ -269,7 +269,7 @@ namespace TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: changed words are marked in the peek and the editor until it closes", "[alchangepeek]") { ALCodeEditor& e = make("integer count = 1;\nstring s;", "integer total = 2;\nstring s;"); - e.peekChange(0); + CHECK(e.peekChange(0)); ALChangePeek& peek = *e.changePeek(); const auto has = [](const ALTextRange& range, S32 line, S32 begin, S32 end) { return range.begin.line == line && range.begin.column <= begin && range.end.column >= end; @@ -294,7 +294,7 @@ namespace editor->die(); editor = nullptr; ALCodeEditor& f = make("a\nb", "a\nput in\nb"); - f.peekChange(1); + CHECK(f.peekChange(1)); { INFO("nothing marked"); CHECK(f.highlights(ALCodeEditor::Highlight::Change).empty()); @@ -497,6 +497,7 @@ namespace INFO("undone: again, three"); CHECK(e.perform(ALEditorCommand::Undo)); CHECK(e.changesSinceSaved() != edited); + REQUIRE(e.changesSinceSaved()); CHECK(e.changesSinceSaved()->changes.size() == 3); } e.resetDirty(); @@ -507,8 +508,11 @@ namespace } // Changed by its document, which no journal is told of: again. e.document().replace(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)), "z"); - UNSCOPED_INFO("the text as it is"); - CHECK(e.changesSinceSaved()->now.front() == "za"); + { + INFO("the text as it is"); + REQUIRE(e.changesSinceSaved()); + CHECK(e.changesSinceSaved()->now.front() == "za"); + } } TEST_CASE_METHOD(alchangepeek_data, "alchangepeek: an edit above a peek that adds or takes lines closes it leaving no gap", "[alchangepeek]") @@ -606,8 +610,8 @@ namespace CHECK_FALSE(peek.isOpen()); CHECK(e.lineAnnotation(2).gap == 0); } - UNSCOPED_INFO("closed, F7 is the text's again"); - CHECK_FALSE(peek.isOpen()); + UNSCOPED_INFO("closed, F7 is the text's again: with no misspelling to go to, nothing takes it"); + CHECK_FALSE(e.handleKeyHere(KEY_F7, MASK_NONE)); e.setModalKeymap(std::make_unique()); { diff --git a/indra/llui/tests/alchoicelist_test.cpp b/indra/llui/tests/alchoicelist_test.cpp index 8ce683ebb7..6db013df5c 100644 --- a/indra/llui/tests/alchoicelist_test.cpp +++ b/indra/llui/tests/alchoicelist_test.cpp @@ -101,7 +101,7 @@ namespace 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"); + UNSCOPED_INFO("a style a line, the note's face: no choice here has an ink of its own"); CHECK(v.styles().size() == size_t(3)); v.moveChoice(1, true); UNSCOPED_INFO("down one"); @@ -206,6 +206,7 @@ namespace 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 ")); + REQUIRE(v.choices().size() == size_t(3)); UNSCOPED_INFO("the words handed back as given"); CHECK(v.choices()[0].note == std::string("for each\nitem in a list")); S32 x, y; diff --git a/indra/llui/tests/alcodeeditor_test.cpp b/indra/llui/tests/alcodeeditor_test.cpp index 7990ca5def..4a3aa80a0b 100644 --- a/indra/llui/tests/alcodeeditor_test.cpp +++ b/indra/llui/tests/alcodeeditor_test.cpp @@ -185,6 +185,7 @@ namespace CHECK(e.markAt(2) == ALCodeEditor::Mark::Error); CHECK(e.markAt(1) == ALCodeEditor::Mark::None); } + REQUIRE_FALSE(e.decorations().empty()); UNSCOPED_INFO("the decoration slid"); CHECK(e.decorations().front().range.begin.line == 3); e.document().replace(ALTextRange(ALTextPos(3, 0), ALTextPos(3, 2)), "TH"); @@ -318,7 +319,7 @@ namespace type("u"); { INFO("still open, narrowed"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "count"); } key(KEY_RETURN); @@ -351,7 +352,7 @@ namespace type(" cu"); { INFO("the provider's word"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "custom"); CHECK(e.completions()[0].detail == "made up"); } @@ -394,7 +395,7 @@ namespace type("a"); { INFO("narrowing keeps it"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "abacus"); } UNSCOPED_INFO("no new request while narrowing"); @@ -1218,10 +1219,12 @@ namespace REQUIRE(e.completions().size() == 4); CHECK(e.completions()[0].text == "sayAgain"); } + REQUIRE(e.chosenCompletion() >= 0); 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"); + REQUIRE(e.chosenCompletion() >= 0); UNSCOPED_INFO("narrowed to the best"); CHECK(e.completions()[e.chosenCompletion()].text == std::string("saying")); key(KEY_ESCAPE); @@ -1242,7 +1245,7 @@ namespace } }); type("llOld"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].deprecated); key(KEY_RETURN); UNSCOPED_INFO("with its brackets and its parameter"); @@ -1271,6 +1274,7 @@ namespace const S32 y = text.mTop - e.layout().rowHeight() / 2; e.handleHover(x, y, MASK_NONE); CHECK(e.handleToolTip(x, y, MASK_NONE)); + REQUIRE(e.cardShown()); const ALTextView& card = *e.card(); 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.")); @@ -2188,6 +2192,7 @@ namespace CHECK(told.back().first != was); } e.noteFixes(was, { "stale", "stale" }); + REQUIRE(e.fixes().size() == 2); UNSCOPED_INFO("an old making's dropped"); CHECK(e.fixes()[0].note.empty()); e.noteFixes(told.back().first, { "one" }); @@ -2600,7 +2605,9 @@ namespace type("\n"); { INFO("each slid"); + REQUIRE_FALSE(e.highlights(H::References).empty()); CHECK(e.highlights(H::References)[0].begin == ALTextPos(1, 8)); + REQUIRE_FALSE(e.highlights(H::Block).empty()); CHECK(e.highlights(H::Block)[0].begin == ALTextPos(2, 8)); } e.clearHighlights(); @@ -2612,6 +2619,7 @@ namespace later.range = ALTextRange(ALTextPos(1, 0), ALTextPos(1, 13)); later.message = "later"; e.setDecorations({ worst, later }); + REQUIRE(e.decorations().size() == 2); UNSCOPED_INFO("as they lie"); CHECK(e.decorations()[0].message == "later"); const std::vector listed = e.decorationsOn(1); @@ -2707,6 +2715,7 @@ namespace type("nt"); UNSCOPED_INFO("asked once for the word, narrowed as it grew"); CHECK(asked == 1); + REQUIRE_FALSE(e.completions().empty()); UNSCOPED_INFO("the word typed out first"); CHECK(e.completions()[0].text == std::string("count")); key(KEY_RETURN); @@ -3176,11 +3185,13 @@ namespace e.setCaret(ALTextPos(0, 2)); e.showSignature(ALTextPos(0, 2), forms); key(KEY_DOWN); + REQUIRE(e.signature() != nullptr); 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); + REQUIRE(e.signature() != nullptr); UNSCOPED_INFO("round to the first"); CHECK(e.signature()->label == sig.label); } @@ -3268,6 +3279,7 @@ namespace p.bracket_color_2 = LLUIColor(LLColor4::green); p.bracket_color_3 = LLUIColor(LLColor4::blue); editor = LLUICtrlFactory::create(p); + made.push_back(editor); editor->setFont(LLFontGL::getFontMonospace()); std::vector lit; for (size_t at = text.find('x'); at != std::string::npos; at = text.find('x', at + 1)) @@ -3299,7 +3311,10 @@ namespace } }; + // A colour for each character: the line is ASCII in the monospace + // face, one glyph to a character. const std::vector all = whole_of(); + REQUIRE(all.size() == text.size()); // What the line is coloured with: a bracket at each depth, the // analyzer's colour in code, and the grammar's in the string. const S32 first_open = static_cast(text.find('(')); @@ -3325,6 +3340,7 @@ namespace editor->setWordWrap(false); const ALTextLayout::Line& one = editor->layout().line(0); + REQUIRE_FALSE(one.rows.empty()); for (F32 from = 0.f; from < one.width; from += 1500.f) { CAPTURE(from); @@ -3337,6 +3353,7 @@ namespace editor->setCaret(ALTextPos(0, 0)); editor->insertText("("); const std::vector deeper = whole_of(); + REQUIRE(deeper.size() == text.size() + 1); UNSCOPED_INFO("the outer bracket one deeper"); CHECK(deeper[static_cast(first_open) + 1] == LLColor4U(LLColor4::green)); editor->setWordWrap(true); @@ -3784,7 +3801,7 @@ namespace type("--!st"); { INFO("open after --!"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "strict"); } key(KEY_TAB); @@ -3835,6 +3852,7 @@ namespace INFO("open at the quote, asked of nothing yet"); CHECK(e.completionOpen()); CHECK(e.completions().size() == 3); + REQUIRE_FALSE(asked.empty()); CHECK(asked.back().empty()); } type("li"); @@ -3857,6 +3875,7 @@ namespace CHECK(requested.back() == "lib/"); } type("ut"); + REQUIRE_FALSE(e.completions().empty()); UNSCOPED_INFO("narrowed in the folder"); CHECK(e.completions()[0].text == std::string("util")); key(KEY_TAB); @@ -3880,7 +3899,7 @@ namespace type("--#include \"m"); { INFO("an include's name too"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "main"); } key(KEY_ESCAPE); @@ -3904,7 +3923,7 @@ namespace type("require(\"ma"); { INFO("open in a string not closed"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "main"); } key(KEY_TAB); @@ -3918,7 +3937,7 @@ namespace type("\"ma"); { INFO("open before what follows"); - CHECK(e.completionOpen()); + REQUIRE(e.completionOpen()); CHECK(e.completions()[0].text == "main"); } key(KEY_TAB); @@ -3982,6 +4001,7 @@ namespace INFO("open at the quote, asked of nothing yet"); CHECK(e.completionOpen()); CHECK(e.completions().size() == 3); + REQUIRE_FALSE(asked.empty()); CHECK(asked.back().empty()); } type("Door "); @@ -5033,18 +5053,23 @@ namespace } } } + // The first line the layout hides for the folds where no folded + // block does, or shows where one does: one check a step, not one + // a line. S32 hidden = 0; + S32 wrong = -1; for (S32 l = 0; l < lines; ++l) { const bool by_folds = e.layout().hiddenBy(l, By::Folds); const bool wanted = folded[static_cast(l)] != 0; hidden += by_folds ? 1 : 0; - if (by_folds != wanted) + if (by_folds != wanted && wrong < 0) { - CAPTURE(l); - REQUIRE(by_folds == wanted); + wrong = l; } } + UNSCOPED_INFO("each line hidden for the folds just where a folded block hides it"); + REQUIRE(wrong == -1); UNSCOPED_INFO("as many hidden for the folds as the layout counts"); REQUIRE(e.layout().hiddenCount(By::Folds) == hidden); folded_steps += hidden > 0 ? 1 : 0; diff --git a/indra/llui/tests/alcolorfield_test.cpp b/indra/llui/tests/alcolorfield_test.cpp index c2fde422c1..d4ce2cbbd3 100644 --- a/indra/llui/tests/alcolorfield_test.cpp +++ b/indra/llui/tests/alcolorfield_test.cpp @@ -308,8 +308,11 @@ namespace }); field->setValue("$Accent"); LLColor4 shown; - UNSCOPED_INFO("the field resolves its own name"); - CHECK(field->resolved(shown)); + { + INFO("the field resolves its own name"); + CHECK(field->resolved(shown)); + CHECK(shown == LLColor4(0.f, 0.5f, 1.f, 1.f)); + } field->handleMouseDown(4, 10, MASK_NONE); ALColorPicker* picker = gFloaterView->findChild("picker", true); @@ -323,13 +326,16 @@ namespace // The first swatch is the caller's own name. grid->handleMouseDown(5 + 14, grid->getRect().getHeight() - 5 - 14, MASK_NONE); LLFloater* popover = grid->getParentByType(); + UNSCOPED_INFO("the grid is in the popover"); + REQUIRE(popover != nullptr); UNSCOPED_INFO("the caller's name comes first"); CHECK(popover->getTitle() == std::string("$Accent")); field->die(); } - // A name means what the table says now: the swatch follows a colour - // changed elsewhere without the text having changed. + // A name means what the table says now: what the field resolves it to, + // which is what its swatch is drawn in, follows a colour changed + // elsewhere without the text having changed. TEST_CASE_METHOD(alcolorfield_data, "alcolorfield: a name resolves to its colour as the table has it now", "[alcolorfield]") { if (!ui.ok()) diff --git a/indra/llui/tests/alcolorsheet_test.cpp b/indra/llui/tests/alcolorsheet_test.cpp index 0cee6c5e90..dff236a939 100644 --- a/indra/llui/tests/alcolorsheet_test.cpp +++ b/indra/llui/tests/alcolorsheet_test.cpp @@ -165,6 +165,7 @@ namespace { INFO("which shadows the default's"); REQUIRE(base->shadowed >= 0); + REQUIRE(base->shadowed < static_cast(sheet.declarations().size())); CHECK(sheet.fileOf(sheet.declarations()[base->shadowed]) == "default.xml"); } } diff --git a/indra/llui/tests/alcolortablepanel_test.cpp b/indra/llui/tests/alcolortablepanel_test.cpp index de8a1f66e3..77e7b3ef96 100644 --- a/indra/llui/tests/alcolortablepanel_test.cpp +++ b/indra/llui/tests/alcolortablepanel_test.cpp @@ -26,6 +26,7 @@ #include "alcolortablepanel.h" +#include "alcolorfield.h" #include "alpropertygrid.h" #include "llcallbacklist.h" #include "../llfloater.h" @@ -163,9 +164,15 @@ namespace panel->die(); } - // What a value is written as, and read back from. - TEST_CASE_METHOD(alcolortablepanel_data, "alcolortablepanel: valueText writes four numbers with commas", "[alcolortablepanel]") + // What a value is written as, and read back from: a row's commit reads + // it as a colour field reads its text. + TEST_CASE_METHOD(alcolortablepanel_data, "alcolortablepanel: valueText writes four numbers with commas that read back as the colour", "[alcolortablepanel]") { - CHECK(ALColorTablePanel::valueText(LLColor4(1.f, 0.5f, 0.f, 1.f)) == std::string("1, 0.5, 0, 1")); + const LLColor4 colour(1.f, 0.5f, 0.f, 1.f); + const std::string text = ALColorTablePanel::valueText(colour); + CHECK(text == std::string("1, 0.5, 0, 1")); + LLColor4 back; + CHECK(ALColorField::textToColor(text, back)); + CHECK(back == colour); } } diff --git a/indra/llui/tests/alcompletionmodel_test.cpp b/indra/llui/tests/alcompletionmodel_test.cpp index 43b5a8ce35..2b22144631 100644 --- a/indra/llui/tests/alcompletionmodel_test.cpp +++ b/indra/llui/tests/alcompletionmodel_test.cpp @@ -45,15 +45,6 @@ namespace c.detail = detail; return c; } - static std::vector texts(const std::vector& list) - { - std::vector out; - for (const ALCompletion& c : list) - { - out.push_back(c.text); - } - return out; - } static std::string joined(const std::vector& list) { std::string out; @@ -225,6 +216,7 @@ namespace const ALTextDocument text("pr\n"); const ALTextPos start(0, 0); model.narrow(start, ALTextPos(0, 2), "pr", std::string(), { word("print"), word("pairs") }, text); + REQUIRE_FALSE(model.list().empty()); UNSCOPED_INFO("guessed until told"); CHECK(model.list()[0].brackets == ALCompletion::Brackets::Guess); ALCompletion told = word("print"); @@ -234,6 +226,7 @@ namespace model.narrow(start, ALTextPos(0, 2), "pr", std::string(), {}, text); { INFO("told it fits, and where its brackets go"); + REQUIRE_FALSE(model.list().empty()); CHECK(model.list()[0].text == "print"); CHECK(model.list()[0].fits); CHECK(model.list()[0].brackets == ALCompletion::Brackets::None); diff --git a/indra/llui/tests/alcornerfield_test.cpp b/indra/llui/tests/alcornerfield_test.cpp index 8b0aee14ab..24efd52051 100644 --- a/indra/llui/tests/alcornerfield_test.cpp +++ b/indra/llui/tests/alcornerfield_test.cpp @@ -147,8 +147,8 @@ namespace } { INFO("beside the picture, which is at the left"); - CHECK(tl.mLeft > 0); CHECK(tl.mLeft >= 72); + CHECK(bl.mLeft >= 72); } UNSCOPED_INFO("the link beside them"); CHECK(f->getChild("link")->getRect().mLeft > tr.mRight); diff --git a/indra/llui/tests/aldifflexer_test.cpp b/indra/llui/tests/aldifflexer_test.cpp index 485d2a3f05..fbef66fa13 100644 --- a/indra/llui/tests/aldifflexer_test.cpp +++ b/indra/llui/tests/aldifflexer_test.cpp @@ -87,6 +87,8 @@ namespace TEST_CASE_METHOD(aldifflexer_data, "aldifflexer: an edit is read again only until a line starts as it did", "[aldifflexer]") { + UNSCOPED_INFO("the LSL grammar"); + REQUIRE(lsl != nullptr); std::vector text; for (S32 n = 0; n < 200; ++n) { diff --git a/indra/llui/tests/aldiffmodel_test.cpp b/indra/llui/tests/aldiffmodel_test.cpp index d7799a146f..847cd4583d 100644 --- a/indra/llui/tests/aldiffmodel_test.cpp +++ b/indra/llui/tests/aldiffmodel_test.cpp @@ -334,6 +334,7 @@ namespace CHECK(m.changeStep(Column::Left, 1, true) == -1); CHECK(m.changeStep(Column::Left, 1, false) == -1); } + REQUIRE(m.changeCount() == 1); UNSCOPED_INFO("the caret for its row: under its gap"); CHECK(m.caretLineOfRow(Column::Left, m.changeFirst(Layout::Sides, 0)) == 1); } @@ -489,6 +490,7 @@ namespace UNSCOPED_INFO("the gap now the right's"); CHECK(paddingOf(Column::Right) == std::vector{ 0, 0, 0, 1 }); CHECK(static_cast(m.rightColumn()) == static_cast(Column::Left)); + REQUIRE(m.changeCount() == 2); const ALDiffModel::ChangeLines& last = m.changeLines(1); { INFO("the line put in still the right's"); @@ -509,6 +511,7 @@ namespace m.setSwapped(false); m.setTexts(lines(30).c_str(), lines(30, { { 2, "line two" }, { 27, "line 27 changed" } }).c_str()); + REQUIRE(m.foldCount() == 1); m.setFoldOpen(0, true); m.setSwapped(true); { @@ -573,6 +576,7 @@ namespace { const std::string right = lines(30, { { 2, "line two" }, { 27, "line 27 changed" } }); m.setTexts(lines(30).c_str(), right.c_str()); + REQUIRE(m.foldCount() == 1); m.setFoldOpen(0, true); const ALDiffModel::LineMap map = m.setRightText("inserted\n" + right); { @@ -695,6 +699,7 @@ namespace CHECK(beside(5, 5)); UNSCOPED_INFO("two rows of nothing over it on the left, beside the if's own"); CHECK(m.line(Column::Left, 5).padding == 2); + REQUIRE(m.ranges().size() == 5); // The state's first line beside nothing, the handler having the // SLua's first; the call in the if on the if's own line, which is // beside the if's head: neither lined up, so the if is bracketed, @@ -728,6 +733,7 @@ namespace m.setRightText(std::string("-- a line more\n") + slua); { INFO("carried with the right's lines"); + REQUIRE(m.ranges().size() == 5); CHECK(m.ranges()[2].rightFirst == 3); CHECK(m.ranges()[2].rightLast == 5); CHECK(beside(4, 3)); @@ -1136,6 +1142,7 @@ namespace // SLua's then and end, with the blanks beside them. { INFO("the if: the LSL's brackets"); + REQUIRE_FALSE(m.line(Column::Left, 1).words.empty()); CHECK(marked(Column::Left, 1) <= 3); CHECK(m.line(Column::Left, 1).words.front() == std::make_pair(3, 4)); } @@ -1840,6 +1847,7 @@ namespace // Lines put in above on the right, then on the left: carried. m.setRightText("-- a note\n" + right); + REQUIRE(m.pairs().size() == 2); UNSCOPED_INFO("the right's lines moved along"); CHECK(m.pairs()[0].rightFirst == 6); { @@ -1848,6 +1856,7 @@ namespace CHECK(beside(4, 9)); } m.setLeftText("-- a note\n" + left); + REQUIRE(m.pairs().size() == 2); UNSCOPED_INFO("the left's too"); CHECK(m.pairs()[0].leftFirst == 1); CHECK(beside(1, 6)); @@ -2016,6 +2025,7 @@ namespace m.setAlgorithm(ALTextDiff::Algorithm::Histogram); { INFO("and the other way about"); + REQUIRE(m.foldCount() == 2); CHECK(m.foldOpen(0)); CHECK_FALSE(m.foldOpen(1)); } diff --git a/indra/llui/tests/aldiffview_gl_test.cpp b/indra/llui/tests/aldiffview_gl_test.cpp index 72713d9242..f12f87db47 100644 --- a/indra/llui/tests/aldiffview_gl_test.cpp +++ b/indra/llui/tests/aldiffview_gl_test.cpp @@ -24,6 +24,7 @@ #include "linden_common.h" +#include "llfonttextcache.h" #include "llglheaders.h" #include "aluigl_fixture.h" @@ -39,6 +40,7 @@ #include #include +#include #include namespace @@ -104,27 +106,35 @@ namespace } } - // Forty lines, the third changed and the thirty-first, a line put - // in after the fifth: the run between folded. The sides in colours - // of their own, which the headless colour table has none of. - ALDiffView& make() + // Forty lines, the third changed and the thirty-first -- or the one + // asked for -- and a line put in after the fifth: the run between + // folded. + static std::pair texts(S32 changed = 30) { - // The fonts' glyphs in textures, once there is a context. - if (!ll_test::HeadlessUI::get(/*gl_textures=*/true).ok()) - { - SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); - } std::string left; std::string right; for (S32 i = 0; i < 40; ++i) { left += "line " + std::to_string(i) + "\n"; - right += (i == 2 ? std::string("two") : i == 30 ? std::string("thirty") : "line " + std::to_string(i)) + "\n"; + right += (i == 2 ? std::string("two") : i == changed ? std::string("thirty") : "line " + std::to_string(i)) + "\n"; if (i == 4) { right += "added\n"; } } + return { left, right }; + } + + // The texts above, the sides in colours of their own, which the + // headless colour table has none of. + ALDiffView& make() + { + // The fonts' glyphs in textures, once there is a context. + if (!ll_test::HeadlessUI::get(/*gl_textures=*/true).ok()) + { + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + } + const auto [left, right] = texts(); ALCodeEditor::Params side(LLUICtrlFactory::getDefaultParams()); const LLColor4 ink(0.85f, 0.85f, 0.85f, 1.f); const LLColor4 ground(0.12f, 0.12f, 0.14f, 1.f); @@ -198,7 +208,7 @@ namespace CHECK(count(plain, b.left + 4, b.right - 4, b.top - 2, b.top + 1, yellow) < (b.right - b.left) / 4); } - TEST_CASE_METHOD(aldiffview_gl_data, "aldiffview_gl: a folded run's gap row is written over and goes once the run opens", "[aldiffview_gl]") + TEST_CASE_METHOD(aldiffview_gl_data, "aldiffview_gl: a folded run's gap row says how many lines it stands for and goes once the run opens", "[aldiffview_gl]") { ALDiffView& d = make(); CHECK(d.foldedCount() == 1); @@ -218,6 +228,43 @@ namespace const std::vector folded = drawn(d); UNSCOPED_INFO("words over it"); CHECK(count(folded, b.left, b.right, b.bottom + 1, b.top - 1, inked) > 20); + + // The run a line longer, its row the gap above the line after it: + // the words over that row say another count, so they differ. + const S32 lower = under + 1; + { + const auto [left, right] = texts(31); + d.setTexts(left, right); + } + CHECK(layout.gapRows(lower) == 1); + Band c = band(d.right(), lower, lower); + c.bottom = c.top; + c.top = c.top + layout.gapHeight(lower); + const std::vector longer = drawn(d); + // Pixel for pixel up the two rows from their bottoms. + S32 differing = 0; + for (S32 up = 1; up < b.top - b.bottom; ++up) + { + const S32 was = b.bottom + up; + const S32 now = c.bottom + up; + if (was < 0 || was >= H || now < 0 || now >= H) + { + continue; + } + for (S32 x = llmax(0, b.left); x < llmin(W, b.right); ++x) + { + const size_t i = (static_cast(was) * W + x) * 4; + const size_t j = (static_cast(now) * W + x) * 4; + differing += folded[i] != longer[j] || folded[i + 1] != longer[j + 1] || folded[i + 2] != longer[j + 2] ? 1 : 0; + } + } + UNSCOPED_INFO("another count over a run a line longer"); + CHECK(differing > 0); + { + const auto [left, right] = texts(); + d.setTexts(left, right); + } + d.right()->goTo(ALTextPos(12, 0)); UNSCOPED_INFO("opened"); CHECK(d.foldedCount() == 0); @@ -313,7 +360,8 @@ namespace TEST_CASE_METHOD(aldiffview_gl_data, "aldiffview_gl: range bands draw in the same number of batches however many there are", "[aldiffview_gl]") { - ALDiffView& d = make(); + ALDiffView& d = make(); + const S32 narrow = d.right()->getRect().mLeft - d.left()->getRect().mRight; // Eight lines the same on each side, ranges of two lines each, level: // a band each, which line up nothing the lines would not, so the // sides are drawn alike whatever bands there are. @@ -322,20 +370,57 @@ namespace { text += (n ? "\n" : "") + ("same " + std::to_string(n)); } - // How many draws the comparison makes, drawn once already: what is - // put in a texture the first time it is drawn, put in. - const auto draws = [&](const ALTextDiff::ranges_t& ranges) { - d.setTexts(text, text, ranges); - drawn(d); + // How many draws a frame of the comparison makes, and what it drew. + std::vector shown; + const auto frame = [&]() { gGL.pushUIMatrix(); const U32 before = LLRender::sUICalls; - drawn(d); - const U32 made = LLRender::sUICalls - before; + shown = drawn(d); + const U32 made = LLRender::sUICalls - before; gGL.popUIMatrix(); return made; }; - const U32 two = draws({ { 0, 1, 0, 1 }, { 4, 5, 4, 5 } }); - const U32 four = draws({ { 0, 1, 0, 1 }, { 2, 3, 2, 3 }, { 4, 5, 4, 5 }, { 6, 7, 6, 7 } }); + // How many a frame makes once no text in it is put together anew: + // drawn until a frame rebuilds none, at most eight times. A frame + // that adds glyphs to the font's cache moves it under every text + // drawn before them, and each of those is rebuilt on the next frame, + // so a frame can make as many draws as the one before and still not + // be the frame that settles. + const auto draws = [&](const ALTextDiff::ranges_t& ranges) { + d.setTexts(text, text, ranges); + for (S32 tries = 0; tries < 8; ++tries) + { + const U64 rebuilt = LLFontTextCache::regenCount(); + const U32 made = frame(); + if (LLFontTextCache::regenCount() == rebuilt) + { + return made; + } + } + FAIL("no frame drew without rebuilding a text"); + return 0U; + }; + // The bands drawn in the frame counted: the gap widened for them, and + // its columns lit beside each band's rows. + const auto lit = [](U8 r, U8 g, U8 b) { return r > 8 || g > 8 || b > 8; }; + const auto banded = [&](const ALTextDiff::ranges_t& ranges) { + UNSCOPED_INFO("the gap widened for the bands"); + CHECK(d.right()->getRect().mLeft - d.left()->getRect().mRight > narrow); + const S32 from = d.left()->getRect().mRight + 1; + const S32 to = d.right()->getRect().mLeft - 1; + for (const ALTextDiff::Range& range : ranges) + { + const Band rows = band(d.right(), range.rightFirst, range.rightLast); + UNSCOPED_INFO("a band beside the right's lines from " << range.rightFirst); + CHECK(count(shown, from, to, rows.bottom + 2, rows.top - 2, lit) > (to - from) * 4); + } + }; + const ALTextDiff::ranges_t two_bands = { { 0, 1, 0, 1 }, { 4, 5, 4, 5 } }; + const ALTextDiff::ranges_t four_bands = { { 0, 1, 0, 1 }, { 2, 3, 2, 3 }, { 4, 5, 4, 5 }, { 6, 7, 6, 7 } }; + const U32 two = draws(two_bands); + banded(two_bands); + const U32 four = draws(four_bands); + banded(four_bands); CHECK(two > 0); UNSCOPED_INFO("as many draws for four bands as for two"); CHECK(four == two); diff --git a/indra/llui/tests/aldiffview_test.cpp b/indra/llui/tests/aldiffview_test.cpp index 762368d587..8dd406a10d 100644 --- a/indra/llui/tests/aldiffview_test.cpp +++ b/indra/llui/tests/aldiffview_test.cpp @@ -86,7 +86,15 @@ namespace return *view; } - static void press(ALDiffView& d, const char* name) { ALViewType::as(d.bar()->getChild(name))->press(); } + // The bar's button of that name. getChild makes a stand-in for a + // name the bar lacks, which is no button. + static ALFlatButton& require_button(ALDiffView& d, const char* name) + { + ALFlatButton* button = ALViewType::as(d.bar()->getChild(name)); + REQUIRE(button != nullptr); + return *button; + } + static void press(ALDiffView& d, const char* name) { require_button(d, name).press(); } static bool enabled(ALDiffView& d, const char* name) { return d.bar()->getChild(name)->getEnabled(); } // The lines "line 0" on, so many, with some of them said otherwise. @@ -540,7 +548,7 @@ namespace S32 said = -1; d.setOnInline([&said](bool inlined) { said = inlined ? 1 : 0; }); - ALFlatButton* inlined = ALViewType::as(d.bar()->getChild("inline")); + ALFlatButton* inlined = &require_button(d, "inline"); UNSCOPED_INFO("side by side: not lit"); CHECK_FALSE(inlined->getToggleState()); inlined->press(); @@ -570,7 +578,7 @@ namespace { INFO("swapped, and lit"); CHECK(d.isSwapped()); - CHECK(ALViewType::as(d.bar()->getChild("swap"))->getToggleState()); + CHECK(require_button(d, "swap").getToggleState()); } UNSCOPED_INFO("the right's text on the left"); CHECK(d.left()->text() == std::string("a\nb = 2\nc\nd")); @@ -852,7 +860,7 @@ namespace } { INFO("the gap"); - CHECK(gapsOf(d.right())[16] == 1); + CHECK(d.right()->layout().gapRows(16) == 1); CHECK(d.right()->layout().lineTop(16) == d.right()->layout().rowHeight()); } @@ -885,7 +893,7 @@ namespace INFO("folded, inline"); CHECK(hidden(d.inlined(), 1)); CHECK_FALSE(hidden(d.inlined(), 16)); - CHECK(gapsOf(d.inlined())[16] == 1); + CHECK(d.inlined()->layout().gapRows(16) == 1); } d.inlined()->goTo(ALTextPos(16, 0)); d.setInline(false); @@ -1145,7 +1153,7 @@ namespace { INFO("on, and the bar's button lit"); CHECK(d.ignores("whitespace")); - CHECK(ALViewType::as(d.bar()->getChild("ignore"))->getToggleState()); + CHECK(require_button(d, "ignore").getToggleState()); } UNSCOPED_INFO("one line changed: the word, not its indent"); CHECK(d.changeCount() == 1); @@ -1291,27 +1299,30 @@ namespace LLContextMenu* menu = holder->findChild("menu_diff_ignore"); UNSCOPED_INFO("shown"); REQUIRE(menu != nullptr); - if (menu) - { - menu->arrangeAndClear(); - UNSCOPED_INFO("comments offered, with LSL's grammar"); - CHECK(menu->findChild("comments")->getVisible()); - UNSCOPED_INFO("case not, for code"); - CHECK_FALSE(menu->findChild("case")->getVisible()); - LLMenuItemGL* trailing = menu->findChild("trailing"); - trailing->onCommit(); - { - INFO("an item turns its own on"); - CHECK(d.ignores("trailing")); - CHECK_FALSE(d.ignores("whitespace")); - } + menu->arrangeAndClear(); + LLMenuItemGL* comments = menu->findChild("comments"); + LLMenuItemGL* letter_case = menu->findChild("case"); + LLMenuItemGL* trailing = menu->findChild("trailing"); + REQUIRE(comments != nullptr); + REQUIRE(letter_case != nullptr); + REQUIRE(trailing != nullptr); + UNSCOPED_INFO("comments offered, with LSL's grammar"); + CHECK(comments->getVisible()); + UNSCOPED_INFO("case not, for code"); + CHECK_FALSE(letter_case->getVisible()); + trailing->onCommit(); + { + INFO("an item turns its own on"); + CHECK(d.ignores("trailing")); + CHECK_FALSE(d.ignores("whitespace")); } d.setIgnore("comments", true); d.setIgnore("blank_lines", true); UNSCOPED_INFO("a comment reworded, a blank line gone and blanks at an end: no change"); CHECK(d.changeCount() == 0); + const ALFlatButton& ignore = require_button(d, "ignore"); UNSCOPED_INFO("lit"); - CHECK(ALViewType::as(d.bar()->getChild("ignore"))->getToggleState()); + CHECK(ignore.getToggleState()); menus.reset(); LLMortician::updateClass(); delete holder; @@ -1750,7 +1761,7 @@ namespace INFO("comments let go of: no change, the bar lit"); CHECK(d.ignores("comments")); CHECK(d.changeCount() == 0); - CHECK(ALViewType::as(d.bar()->getChild("ignore"))->getToggleState()); + CHECK(require_button(d, "ignore").getToggleState()); } d.setGrammar(nullptr); { @@ -1758,7 +1769,7 @@ namespace CHECK_FALSE(d.ignores("comments")); CHECK_FALSE(d.offersIgnore("comments")); CHECK(d.changeCount() == 1); - CHECK_FALSE(ALViewType::as(d.bar()->getChild("ignore"))->getToggleState()); + CHECK_FALSE(require_button(d, "ignore").getToggleState()); } } @@ -2120,7 +2131,7 @@ namespace INFO("opened, and the button so"); CHECK_FALSE(d.foldsSame()); CHECK(d.foldedCount() == 0); - CHECK_FALSE(ALViewType::as(d.bar()->getChild("fold"))->getToggleState()); + CHECK_FALSE(require_button(d, "fold").getToggleState()); } d.right()->handleKey('F', MASK_ALT, false); { @@ -2296,7 +2307,7 @@ namespace d.setMergeBase(base); d.setVersions(3, 1, [](S32) {}); LLView* count = d.bar()->getChild("count"); - const auto glyph = [&d](const char* name) { return ALViewType::as(d.bar()->getChild(name))->glyph(); }; + const ALFlatButton& take_theirs = require_button(d, "take_theirs"); const auto nothing_past_left = [&d]() { for (LLView* child : *d.bar()->getChildList()) { @@ -2311,7 +2322,7 @@ namespace { INFO("wide: all of it"); CHECK(count->getVisible()); - CHECK(glyph("take_theirs") == "Theirs"); + CHECK(take_theirs.glyph() == "Theirs"); CHECK(d.bar()->getChild("versions")->getVisible()); } S32 lost_count = 0; @@ -2324,7 +2335,7 @@ namespace UNSCOPED_INFO("nothing past the left edge"); REQUIRE(nothing_past_left()); lost_count = !count->getVisible() && !lost_count ? width : lost_count; - lost_words = glyph("take_theirs") == "T" && !lost_words ? width : lost_words; + lost_words = take_theirs.glyph() == "T" && !lost_words ? width : lost_words; lost_slider = !d.bar()->getChild("versions")->getVisible() && !lost_slider ? width : lost_slider; } { @@ -2337,7 +2348,7 @@ namespace { INFO("and all back"); CHECK(count->getVisible()); - CHECK(glyph("take_theirs") == "Theirs"); + CHECK(take_theirs.glyph() == "Theirs"); CHECK(d.bar()->getChild("versions")->getVisible()); CHECK(d.bar()->getChild("take_theirs")->getVisible()); CHECK(d.bar()->getChild("done")->getVisible()); diff --git a/indra/llui/tests/aldockpanel_test.cpp b/indra/llui/tests/aldockpanel_test.cpp index b714e152cf..5fda7c8e6a 100644 --- a/indra/llui/tests/aldockpanel_test.cpp +++ b/indra/llui/tests/aldockpanel_test.cpp @@ -59,6 +59,10 @@ namespace // there. ~aldockpanel_data() { + if (!ui.ok()) + { + return; + } std::vector> out; for (LLView* child : *gFloaterView->getChildList()) { @@ -77,9 +81,6 @@ namespace LLMortician::updateClass(); } - // A window with a stack of two panes across it, the way a tool is - // laid out: a side pane of a fixed width and a middle that takes - // whatever is left. // A window that says which keys it was asked about, and takes only // Control-F. struct Home : public LLFloater @@ -102,8 +103,15 @@ namespace LLButton* inside = nullptr; // something to look for by name }; - static Window build(S32 width = 600, S32 height = 400, S32 side_width = 200) + // A window with a stack of two panes across it, the way a tool is + // laid out: a side pane of a fixed width and a middle that takes + // whatever is left. + Window build(S32 width = 600, S32 height = 400, S32 side_width = 200) { + if (!ui.ok()) + { + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + } Window w; LLFloater::Params fp(LLFloater::getDefaultParams()); fp.name = "tool"; @@ -167,6 +175,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); const S32 was = w.middle->getRect().getWidth(); pane->popOut(); @@ -191,6 +200,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); const S32 dim = w.side->getTargetDim(); pane->popOut(); @@ -220,6 +230,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); pane->popOut(); ALPanelFloater* window = pane->getParentByType(); @@ -239,6 +250,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); pane->dock(); UNSCOPED_INFO("still where it was declared"); @@ -259,6 +271,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); pane->popOut(); ALPanelFloater* window = pane->getParentByType(); @@ -291,6 +304,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); const S32 was = pane->getRect().getHeight(); pane->popOut(); @@ -318,9 +332,11 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); pane->popOut(); LLFloater* window = pane->getParentByType(); + REQUIRE(window != nullptr); const LLRect moved(300, 700, 800, 300); window->setShape(moved); pane->dock(); @@ -328,8 +344,10 @@ namespace UNSCOPED_INFO("where it was is remembered"); CHECK(pane->floatingRect().getWidth() == moved.getWidth()); pane->popOut(); + window = pane->getParentByType(); + REQUIRE(window != nullptr); UNSCOPED_INFO("and it comes back out there"); - CHECK(pane->getParentByType()->getRect().getWidth() == moved.getWidth()); + CHECK(window->getRect().getWidth() == moved.getWidth()); w.floater->die(); } @@ -340,6 +358,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); pane->popOut(); w.stack->updateLayout(); @@ -442,6 +461,7 @@ namespace { Window w = build(); ALDockPanel* pane = ALDockPanel::wrap(w.side, "Side"); + REQUIRE(pane != nullptr); const LLHandle held = pane->getHandle(); pane->popOut(); @@ -470,7 +490,9 @@ namespace REQUIRE(pane->poppedOut()); CHECK_FALSE(pane->outAtQuit()); } - pane->getParentByType()->closeFloater(); + window = pane->getParentByType(); + REQUIRE(window != nullptr); + window->closeFloater(); { INFO("closed by hand, it is simply home"); CHECK_FALSE(pane->poppedOut()); diff --git a/indra/llui/tests/alemptystate_test.cpp b/indra/llui/tests/alemptystate_test.cpp index 6b8e8a8dca..d68c871b91 100644 --- a/indra/llui/tests/alemptystate_test.cpp +++ b/indra/llui/tests/alemptystate_test.cpp @@ -127,14 +127,13 @@ namespace CHECK(part(state, "sentence")->getVisible()); // And it is in the middle of the room, not under where a headline - // would have been. + // would have been: as much room above it as below, but for the odd + // pixel of halving. const LLRect sentence = part(state, "sentence")->getRect(); - const S32 middle = state->getRect().getHeight() / 2; - { - INFO("centred on its own " << where(sentence)); - CHECK(sentence.mTop >= middle); - CHECK(sentence.mBottom <= middle); - } + const S32 above = state->getRect().getHeight() - sentence.mTop; + const S32 below = sentence.mBottom; + CAPTURE(above, below); + CHECK(llabs(above - below) <= 1); state->die(); } diff --git a/indra/llui/tests/alfixlistmodel_test.cpp b/indra/llui/tests/alfixlistmodel_test.cpp index 0d92b5b1bd..8accd108e1 100644 --- a/indra/llui/tests/alfixlistmodel_test.cpp +++ b/indra/llui/tests/alfixlistmodel_test.cpp @@ -79,6 +79,7 @@ namespace { INFO("this showing's"); CHECK(model.note(second, { "-12 bytes", "" })); + REQUIRE(model.fixes().size() == 2); CHECK(model.fixes()[0].note == "-12 bytes"); } model.close(); @@ -115,6 +116,7 @@ namespace model.close(); model.ask(at, caret); joined = model.join(at, caret, { fix("Extract") }, false, -1); + REQUIRE(joined.has_value()); UNSCOPED_INFO("alone, the list having closed"); CHECK(titles(joined->fixes) == std::string("Extract")); model.ask(at, caret); diff --git a/indra/llui/tests/alfollowscontrol_test.cpp b/indra/llui/tests/alfollowscontrol_test.cpp index bbba4fdb22..1766984f8a 100644 --- a/indra/llui/tests/alfollowscontrol_test.cpp +++ b/indra/llui/tests/alfollowscontrol_test.cpp @@ -45,8 +45,12 @@ namespace // Wide enough for the picture and both thumbnails, so the picture is // a square of SIDE at the left of it. - static ALFollowsControl* build(S32 width = SIDE * 3 + 16, S32 height = SIDE) + ALFollowsControl* build(S32 width = SIDE * 3 + 16, S32 height = SIDE) { + if (!ui.ok()) + { + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + } ALFollowsControl::Params p(LLUICtrlFactory::getDefaultParams()); p.name = "follows"; p.rect = LLRect(0, height, width, 0); diff --git a/indra/llui/tests/alfontfield_test.cpp b/indra/llui/tests/alfontfield_test.cpp index b43e403a74..ff74d2b4d9 100644 --- a/indra/llui/tests/alfontfield_test.cpp +++ b/indra/llui/tests/alfontfield_test.cpp @@ -116,9 +116,11 @@ namespace CHECK(field->style() == std::string("BOLD")); UNSCOPED_INFO("a style does not change the value"); CHECK(field->getValue().asString() == std::string("SansSerif")); + const LLFontGL* styled = field->font(); UNSCOPED_INFO("a style is a different face"); - CHECK(field->font() != plain); - CHECK((field->font()->getFontDesc().getStyle() & LLFontGL::BOLD) != 0); + CHECK(styled != plain); + REQUIRE(styled != nullptr); + CHECK((styled->getFontDesc().getStyle() & LLFontGL::BOLD) != 0); field->setSize("Large"); CHECK(field->size() == std::string("Large")); @@ -206,9 +208,11 @@ namespace UNSCOPED_INFO("opened again"); REQUIRE(popover != nullptr); bold = popover->findChild("bold", true); + REQUIRE(bold != nullptr); bold->setValue(true); bold->onCommit(); LLComboBox* sizes = popover->findChild("size", true); + REQUIRE(sizes != nullptr); sizes->setValue("Large"); sizes->onCommit(); CHECK(popover->handleKeyHere(KEY_RETURN, MASK_NONE)); @@ -250,6 +254,7 @@ namespace CHECK(gFocusMgr.getKeyboardFocus() != elsewhere); } LLCheckBoxCtrl* bold = popover->findChild("bold", true); + REQUIRE(bold != nullptr); bold->setValue(true); bold->onCommit(); field->die(); diff --git a/indra/llui/tests/alhistorylist_test.cpp b/indra/llui/tests/alhistorylist_test.cpp index 776989958a..d71edff32b 100644 --- a/indra/llui/tests/alhistorylist_test.cpp +++ b/indra/llui/tests/alhistorylist_test.cpp @@ -62,9 +62,13 @@ namespace { "label on title", "floater_a.xml" } }; } - static LLScrollListCtrl* listOf(ALHistoryList* history) + // The history's list. Found, not got: getChild makes a stand-in for + // a list the history lacks, which reads as an empty list. + static LLScrollListCtrl* require_list(ALHistoryList* history) { - return history->getChild("steps"); + LLScrollListCtrl* list = history->findChild("steps"); + REQUIRE(list != nullptr); + return list; } }; @@ -82,8 +86,7 @@ namespace CHECK(history->count() == 3u); CHECK(history->inForce() == 3u); - LLScrollListCtrl* list = listOf(history); - REQUIRE(list != nullptr); + LLScrollListCtrl* list = require_list(history); REQUIRE(list->getItemCount() == 3); UNSCOPED_INFO("newest first"); CHECK(list->getFirstData()->getValue().asInteger() == 2); @@ -105,7 +108,7 @@ namespace UNSCOPED_INFO("all three are still listed"); CHECK(history->count() == 3u); CHECK(history->inForce() == 1u); - CHECK(listOf(history)->getItemCount() == 3); + CHECK(require_list(history)->getItemCount() == 3); // More in force than there are steps is not a thing a stack can be. history->setSteps(three(), 99); @@ -129,7 +132,7 @@ namespace std::vector asked; history->onGoTo([&asked](size_t want) { asked.push_back(want); }); - LLScrollListCtrl* list = listOf(history); + LLScrollListCtrl* list = require_list(history); list->setSelectedByValue(LLSD(0), true); // the oldest step history->goToSelected(); REQUIRE(asked.size() == 1u); @@ -159,7 +162,7 @@ namespace // Selecting is what says it: the list commits on a change of // selection, since a row chosen is the whole of the gesture. - listOf(history)->setSelectedByValue(LLSD(1), true); + require_list(history)->setSelectedByValue(LLSD(1), true); REQUIRE(chosen.size() == 1u); CHECK(chosen.front() == 1u); delete history; @@ -175,7 +178,7 @@ namespace } ALHistoryList* history = make(); history->setSteps({}, 0); - CHECK(listOf(history)->getItemCount() == 0); + CHECK(require_list(history)->getItemCount() == 0); ALPaneList* pane = history->getChild("steps"); CHECK(pane->saysEmpty()); @@ -190,16 +193,16 @@ namespace CHECK(asked == std::vector{ 1 }); } - LLScrollListCtrl* list = listOf(history); + LLScrollListCtrl* list = require_list(history); list->setSelectedByValue(LLSD(1), true); std::vector more = three(); more.push_back({ "name on title", "floater_a.xml" }); history->setSteps(more, 4); - CHECK(listOf(history)->getItemCount() == 4); + CHECK(list->getItemCount() == 4); UNSCOPED_INFO("the row a person was on is still the row they are on"); - REQUIRE(listOf(history)->getFirstSelected() != nullptr); - CHECK(listOf(history)->getFirstSelected()->getValue().asInteger() == 1); + REQUIRE(list->getFirstSelected() != nullptr); + CHECK(list->getFirstSelected()->getValue().asInteger() == 1); delete history; } @@ -217,15 +220,18 @@ namespace std::vector chosen; history->onStepChosen([&chosen](size_t at) { chosen.push_back(at); }); - listOf(history)->setSelectedByValue(LLSD(1), true); + require_list(history)->setSelectedByValue(LLSD(1), true); UNSCOPED_INFO("choosing a row says so"); REQUIRE(chosen.size() == 1u); std::vector more = three(); more.push_back({ "name on title", "floater_a.xml" }); history->setSteps(more, 4); - UNSCOPED_INFO("the row is kept"); - CHECK(listOf(history)->getFirstSelected()->getValue().asInteger() == 1); + { + INFO("the row is kept"); + REQUIRE(require_list(history)->getFirstSelected() != nullptr); + CHECK(require_list(history)->getFirstSelected()->getValue().asInteger() == 1); + } UNSCOPED_INFO("and not said again"); CHECK(chosen.size() == 1u); delete history; @@ -239,7 +245,7 @@ namespace } ALHistoryList* history = make(); history->setSteps({ { "width on close_btn", "" }, { "height on close_btn", "" } }, 2); - LLScrollListColumn* where = listOf(history)->getColumn("where"); + LLScrollListColumn* where = require_list(history)->getColumn("where"); REQUIRE(where != nullptr); UNSCOPED_INFO("no room when nothing names a document"); CHECK(where->getWidth() == 0); @@ -262,7 +268,7 @@ namespace } ALHistoryList* history = make("As the files were opened"); history->setSteps(three(), 2); - LLScrollListCtrl* list = listOf(history); + LLScrollListCtrl* list = require_list(history); UNSCOPED_INFO("a row a step, and the start"); REQUIRE(list->getItemCount() == 4); UNSCOPED_INFO("still three steps"); @@ -274,9 +280,10 @@ namespace history->onStepChosen([&pointed](size_t index) { pointed.push_back(index); }); const std::vector rows = list->getAllData(); + const LLScrollListCell* words = rows.back()->getColumn(1); + REQUIRE(words != nullptr); UNSCOPED_INFO("at the bottom, under the oldest"); - CHECK(rows.back()->getColumn(1)->getValue().asString() == - std::string("As the files were opened")); + CHECK(words->getValue().asString() == std::string("As the files were opened")); list->selectNthItem(3); UNSCOPED_INFO("pointed at, it tells nobody of a step"); CHECK(pointed.empty()); @@ -292,8 +299,12 @@ namespace history->setSteps(three(), 0); UNSCOPED_INFO("still chosen after a refill"); CHECK(list->getFirstSelectedIndex() == 3); + const std::vector refilled = list->getAllData(); + REQUIRE_FALSE(refilled.empty()); + const LLScrollListCell* mark = refilled.back()->getColumn(0); + REQUIRE(mark != nullptr); UNSCOPED_INFO("the present is the start"); - CHECK_FALSE(list->getAllData().back()->getColumn(0)->getValue().asString().empty()); + CHECK_FALSE(mark->getValue().asString().empty()); history->goToSelected(); CHECK(asked.size() == 1u); @@ -304,8 +315,9 @@ namespace history = make(); history->setSteps(three(), 3); + list = require_list(history); UNSCOPED_INFO("no words, no start"); - CHECK(listOf(history)->getItemCount() == 3); + CHECK(list->getItemCount() == 3); delete history; } } diff --git a/indra/llui/tests/alimagefield_test.cpp b/indra/llui/tests/alimagefield_test.cpp index 833c9e2d7b..5377c3e4c2 100644 --- a/indra/llui/tests/alimagefield_test.cpp +++ b/indra/llui/tests/alimagefield_test.cpp @@ -175,7 +175,9 @@ namespace ALSpecimenList* gallery = open(); REQUIRE(gallery != nullptr); gallery->choose("Icon_Close"); - gallery->getParentByType()->escape(); + ALPopover* popover = gallery->getParentByType(); + REQUIRE(popover != nullptr); + popover->escape(); { INFO("escaped: no change, no commit"); CHECK(commits == 0); @@ -183,8 +185,11 @@ namespace } gallery = open(); + REQUIRE(gallery != nullptr); gallery->choose("Icon_Close"); - gallery->getParentByType()->settle(); + popover = gallery->getParentByType(); + REQUIRE(popover != nullptr); + popover->settle(); { INFO("settled: the one chosen, committed"); CHECK(commits == 1); @@ -193,6 +198,7 @@ namespace elsewhere->setFocus(true); gallery = open(); + REQUIRE(gallery != nullptr); UNSCOPED_INFO("the popover has the keyboard"); CHECK(gFocusMgr.getKeyboardFocus() != elsewhere); gallery->choose("PushButton_Over"); diff --git a/indra/llui/tests/aljumpbar_test.cpp b/indra/llui/tests/aljumpbar_test.cpp index cf88db1644..86d601a40b 100644 --- a/indra/llui/tests/aljumpbar_test.cpp +++ b/indra/llui/tests/aljumpbar_test.cpp @@ -88,8 +88,10 @@ namespace bar->setPath(path()); CHECK(crumbsIn(bar) == 4); + const LLView* first = bar->findChild("crumb_0", false); + REQUIRE(first != nullptr); UNSCOPED_INFO("a step with nowhere else to be is a plain button"); - CHECK(bar->getChild("crumb_0")->as() == nullptr); + CHECK(first->as() != nullptr); UNSCOPED_INFO("and one with siblings offers them"); REQUIRE(bar->getChild("crumb_2")->as() != nullptr); @@ -292,10 +294,12 @@ namespace LLTextBox* again = bar->findChild("trailer_0", true); REQUIRE(again == line); CHECK(again->getText() == std::string("Ln 120, Col 14")); + const LLTextBox* last = bar->findChild("trailer_2", true); + REQUIRE(last != nullptr); UNSCOPED_INFO("still in order"); - CHECK(again->getRect().mRight <= bar->findChild("trailer_2", true)->getRect().mLeft); + CHECK(again->getRect().mRight <= last->getRect().mLeft); UNSCOPED_INFO("against the far edge"); - CHECK(bar->findChild("trailer_2", true)->getRect().mRight <= bar->getChild("trailer")->getRect().getWidth()); + CHECK(last->getRect().mRight <= bar->getChild("trailer")->getRect().getWidth()); // Another piece, and the trailer is built again. moved.push_back({ "1 warning", "problems", "The problems" }); @@ -358,8 +362,10 @@ namespace REQUIRE(size); CHECK(size->getColor().get() == LLColor4::yellow); } + const LLTextBox* line = bar->findChild("trailer_0", true); + REQUIRE(line != nullptr); UNSCOPED_INFO("the others as they were"); - CHECK(bar->findChild("trailer_0", true)->getColor().get() == quiet); + CHECK(line->getColor().get() == quiet); parts[1].color = LLColor4::red; bar->setTrailer(parts); @@ -401,7 +407,11 @@ namespace const S32 two = font->getWidth(parts[0].text) + font->getWidth(dot) + font->getWidth(parts[4].text); ALJumpBar* bar = make(two + 12 + 6 + 4); bar->setTrailer(parts); - const auto said = [&](size_t i) { return bar->findChild("trailer_" + std::to_string(i), true)->getVisible(); }; + const auto said = [&](size_t i) { + const LLTextBox* piece = bar->findChild("trailer_" + std::to_string(i), true); + REQUIRE(piece != nullptr); + return piece->getVisible(); + }; { INFO("the place and the errors, one separator between"); CHECK(said(0)); @@ -415,8 +425,10 @@ namespace CHECK_FALSE(said(2)); CHECK_FALSE(said(6)); } + const LLTextBox* errors = bar->findChild("trailer_4", true); + REQUIRE(errors != nullptr); UNSCOPED_INFO("against the far edge"); - CHECK(bar->findChild("trailer_4", true)->getRect().mRight <= bar->getChild("trailer")->getRect().getWidth()); + CHECK(errors->getRect().mRight <= bar->getChild("trailer")->getRect().getWidth()); bar->reshape(two * 4, 22); for (size_t i = 0; i < parts.size(); ++i) diff --git a/indra/llui/tests/alkeycapture_test.cpp b/indra/llui/tests/alkeycapture_test.cpp index ffa4ee4e67..46a8cba636 100644 --- a/indra/llui/tests/alkeycapture_test.cpp +++ b/indra/llui/tests/alkeycapture_test.cpp @@ -155,6 +155,7 @@ namespace TEST_CASE_METHOD(alkeycapture_data, "alkeycapture: a one-key capture keeps the last key and Escape gives nothing", "[alkeycapture]") { ALKeyCapture* capture = show(false); + REQUIRE(capture != nullptr); { INFO("Return with nothing pressed gives nothing"); CHECK(ALKeyChords::takeKey(KEY_RETURN, MASK_NONE)); @@ -167,6 +168,7 @@ namespace CHECK(capture->pressed() == (ALKeyChord{ 'S', MASK_NONE })); ALKeyChords::takeChar('s'); ALPopover* up = capture->popover(); + REQUIRE(up != nullptr); CHECK(ALKeyChords::takeKey(KEY_ESCAPE, MASK_NONE)); CHECK(chosen.empty()); { diff --git a/indra/llui/tests/almenuslot_test.cpp b/indra/llui/tests/almenuslot_test.cpp index f122c57153..101de118ff 100644 --- a/indra/llui/tests/almenuslot_test.cpp +++ b/indra/llui/tests/almenuslot_test.cpp @@ -112,6 +112,7 @@ namespace { ALMenuSlot slot; LLContextMenu* menu = make(slot); + REQUIRE(menu); menu->setVisible(true); slot.close(); { @@ -121,6 +122,7 @@ namespace CHECK(slot.get() == nullptr); } open = make(slot); + REQUIRE(open); open->setVisible(true); } { diff --git a/indra/llui/tests/aloutputview_test.cpp b/indra/llui/tests/aloutputview_test.cpp index 8235367333..dc01fbcb9d 100644 --- a/indra/llui/tests/aloutputview_test.cpp +++ b/indra/llui/tests/aloutputview_test.cpp @@ -289,8 +289,11 @@ namespace CHECK(v.shows(v.entries().back())); CHECK_FALSE(v.shows(v.entries().front())); } - UNSCOPED_INFO("the links laid again where the entry moved"); - CHECK(v.substitutions()[1].range == ALTextRange(ALTextPos(2, 2), ALTextPos(2, 16))); + { + INFO("the links laid again where the entry moved"); + REQUIRE(v.substitutions().size() == size_t(3)); + CHECK(v.substitutions()[1].range == ALTextRange(ALTextPos(2, 2), ALTextPos(2, 16))); + } } TEST_CASE_METHOD(aloutputview_data, "aloutputview: each lane keeps its own fill so one lane does not push out another", "[aloutputview]") diff --git a/indra/llui/tests/alpanefolds_test.cpp b/indra/llui/tests/alpanefolds_test.cpp index d5572b6572..1acab3db1e 100644 --- a/indra/llui/tests/alpanefolds_test.cpp +++ b/indra/llui/tests/alpanefolds_test.cpp @@ -206,6 +206,7 @@ namespace folds.bind(w.floater, panes()); folds.toggleOut("page"); LLFloater* window = folds.window("page"); + REQUIRE(window != nullptr); window->setRect(LLRect(700, 500, 1000, 300)); folds.save(state); UNSCOPED_INFO("the side's fold is written"); diff --git a/indra/llui/tests/alpanelist_test.cpp b/indra/llui/tests/alpanelist_test.cpp index 3988dc3eb9..04562e2861 100644 --- a/indra/llui/tests/alpanelist_test.cpp +++ b/indra/llui/tests/alpanelist_test.cpp @@ -103,8 +103,11 @@ namespace return value["group"].asString() + (folded ? (*folded ? " shut" : " open") : " turned"); }; l.setFocus(true); + // With no fold to ask, left on the row chosen goes on to the list, + // which takes it as any list does. + l.selectFirstItem(); UNSCOPED_INFO("no fold: left is the list's"); - CHECK((!l.handleKeyHere(KEY_LEFT, MASK_NONE) || asked.empty())); + CHECK(l.handleKeyHere(KEY_LEFT, MASK_NONE)); l.setFold([&](const LLSD& value, std::optional folded) { asked.push_back(said(value, folded)); }, [](const LLScrollListItem*, S32 x) { return x < 20; }); l.deselectAllItems(); @@ -115,6 +118,7 @@ namespace { INFO("left shuts"); CHECK(l.handleKeyHere(KEY_LEFT, MASK_NONE)); + REQUIRE_FALSE(asked.empty()); CHECK(asked.back() == "0 shut"); } { @@ -126,17 +130,22 @@ namespace UNSCOPED_INFO("with a key held, the list's"); REQUIRE(asked.size() == size_t(2)); l.handleKeyHere(KEY_DOWN, MASK_NONE); - UNSCOPED_INFO("another key is the list's: it moved on"); - CHECK(l.getFirstSelected()->getColumn(0)->getValue().asString() == - std::string("two")); + { + INFO("another key is the list's: it moved on"); + REQUIRE(l.getFirstSelected()); + CHECK(l.getFirstSelected()->getColumn(0)->getValue().asString() == std::string("two")); + } const LLRect first = l.getCellRect(0, 0); { INFO("a press on the arrow turns its row"); CHECK(l.handleMouseDown(first.mLeft + 5, first.getCenterY(), MASK_NONE)); CHECK(asked.back() == "0 turned"); } - UNSCOPED_INFO("and chooses nothing"); - CHECK(l.getFirstSelected()->getColumn(0)->getValue().asString() == std::string("two")); + { + INFO("and chooses nothing"); + REQUIRE(l.getFirstSelected()); + CHECK(l.getFirstSelected()->getColumn(0)->getValue().asString() == std::string("two")); + } l.handleMouseDown(first.mLeft + 40, first.getCenterY(), MASK_NONE); l.handleMouseUp(first.mLeft + 40, first.getCenterY(), MASK_NONE); { @@ -168,8 +177,9 @@ namespace l.sortByColumnIndex(0, false); UNSCOPED_INFO("down, within each group, the groups as they were"); CHECK(order() == std::string("Zed b2 b1 Alpha a2 a1")); - // The rows' own comparison, where the owner gives one: by length, - // then by words. + // The rows' own comparison, where the owner gives one: here the + // words the other way round, so that a sort up comes out within + // each group as the words' own order would down. l.setComparison([](S32, const LLScrollListItem* a, const LLScrollListItem* b) { const std::string x = a->getColumn(0)->getValue().asString(); const std::string y = b->getColumn(0)->getValue().asString(); @@ -417,6 +427,7 @@ namespace value["columns"][3]["column"] = "empty"; value["columns"][3]["value"] = ""; LLScrollListItem* item = l.addElement(value); + REQUIRE(item); item->setEnabled(enabled); }; row("A heading", "", false); @@ -598,9 +609,12 @@ namespace }; l.setRows({ row("a", "alpha"), row("b", "beta"), row("c", "gamma") }); CHECK(order() == std::string("alpha beta gamma")); - LLScrollListItem* b = l.rowWithKey("b"); + LLScrollListItem* b = l.rowWithKey("b"); + LLScrollListItem* c = l.rowWithKey("c"); + REQUIRE(b); + REQUIRE(c); LLScrollListCell* b_cell = b->getColumn(0); - LLScrollListCell* c_cell = l.rowWithKey("c")->getColumn(0); + LLScrollListCell* c_cell = c->getColumn(0); l.selectNthItem(1); l.setRows({ row("a", "alpha"), row("b", "beta"), row("c", "gamma 2") }); { @@ -610,7 +624,9 @@ namespace } { INFO("the changed cell made again"); - CHECK(l.rowWithKey("c")->getColumn(0) != c_cell); + const LLScrollListItem* c_now = l.rowWithKey("c"); + REQUIRE(c_now); + CHECK(c_now->getColumn(0) != c_cell); CHECK(order() == "alpha beta gamma 2"); } UNSCOPED_INFO("still chosen"); diff --git a/indra/llui/tests/alpopover_test.cpp b/indra/llui/tests/alpopover_test.cpp index bd1fcf97f1..f3e1fbdbc2 100644 --- a/indra/llui/tests/alpopover_test.cpp +++ b/indra/llui/tests/alpopover_test.cpp @@ -83,7 +83,8 @@ namespace SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } LLPanel* over = anchor(); - ALPopover* popover = ALPopover::show(over, content(160, 90)); + LLPanel* body = content(160, 90); + ALPopover* popover = ALPopover::show(over, body); REQUIRE(popover != nullptr); CHECK(popover->getVisible()); @@ -100,7 +101,7 @@ namespace CHECK(where.mTop == screen.mBottom); UNSCOPED_INFO("the content is in it"); - REQUIRE(popover->getChild("content", true) != nullptr); + CHECK(popover->findChild("content", true) == body); popover->closeFloater(); over->die(); } @@ -136,6 +137,7 @@ namespace { LLPanel* over = anchor(); ALPopover* popover = ALPopover::show(over, content()); + REQUIRE(popover != nullptr); std::vector said; popover->onClosed([&said](bool escaped) { said.push_back(escaped); }); popover->settle(); @@ -149,6 +151,7 @@ namespace { LLPanel* over = anchor(); ALPopover* popover = ALPopover::show(over, content()); + REQUIRE(popover != nullptr); std::vector said; popover->onClosed([&said](bool escaped) { said.push_back(escaped); }); CHECK(popover->handleKeyHere(KEY_ESCAPE, MASK_NONE)); @@ -163,6 +166,7 @@ namespace { LLPanel* over = anchor(); ALPopover* popover = ALPopover::show(over, content()); + REQUIRE(popover != nullptr); std::vector said; popover->onClosed([&said](bool escaped) { said.push_back(escaped); }); popover->onFocusLost(); @@ -181,8 +185,12 @@ namespace { SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); } + LLPanel* orphan = content(); + const LLHandle given = orphan->getHandle(); UNSCOPED_INFO("no anchor, no popover"); - CHECK(ALPopover::show(nullptr, content()) == nullptr); + CHECK(ALPopover::show(nullptr, orphan) == nullptr); + UNSCOPED_INFO("and the content given deleted, not leaked"); + CHECK(given.isDead()); LLPanel* over = anchor(); UNSCOPED_INFO("and no content, none either"); CHECK(ALPopover::show(over, nullptr) == nullptr); @@ -238,6 +246,7 @@ namespace home->addChild(over); ALPopover* popover = ALPopover::show(over, content()); + REQUIRE(popover != nullptr); UNSCOPED_INFO("it knows where home is"); CHECK(popover->home() == home); UNSCOPED_INFO("and asks before the viewer's own menu does"); @@ -259,6 +268,7 @@ namespace // menu's keys. LLPanel* loose = anchor(); ALPopover* alone = ALPopover::show(loose, content()); + REQUIRE(alone != nullptr); UNSCOPED_INFO("no home"); CHECK(alone->home() == nullptr); UNSCOPED_INFO("no claim"); @@ -337,6 +347,7 @@ namespace LLPanel* over = anchor(); window->addChild(over); ALPopover* popover = ALPopover::show(over, content()); + REQUIRE(popover != nullptr); std::vector said; popover->onClosed([&said](bool escaped) { said.push_back(escaped); }); UNSCOPED_INFO("its window's dependent"); @@ -353,6 +364,7 @@ namespace LLPanel* under = anchor(); again->addChild(under); ALPopover* first = ALPopover::show(under, content()); + REQUIRE(first != nullptr); first->escape(); CHECK(first->isDead()); again->closeFloater(); @@ -542,6 +554,7 @@ namespace ALPopoverSlot slot; std::vector said; ALPopover* popover = ALPopover::show(over, content()); + REQUIRE(popover != nullptr); slot.hold(popover, [&said](bool) { said.push_back(1); }); slot.hold(popover, [&said](bool) { said.push_back(2); }); { @@ -557,6 +570,7 @@ namespace const auto dead = [](const LLHandle& handle) { return !handle.get() || handle.get()->isDead(); }; said.clear(); ALPopover* dropped = ALPopover::show(over, content()); + REQUIRE(dropped != nullptr); const LLHandle handle = dropped->getDerivedHandle(); slot.hold(dropped, [&said](bool) { said.push_back(3); }); slot.drop(); @@ -575,6 +589,7 @@ namespace { ALPopoverSlot gone; ALPopover* held = ALPopover::show(over, content()); + REQUIRE(held != nullptr); scoped = held->getDerivedHandle(); gone.hold(held, [&said](bool) { said.push_back(4); }); } diff --git a/indra/llui/tests/alprefixsums_test.cpp b/indra/llui/tests/alprefixsums_test.cpp index a8e90590a0..8092df4629 100644 --- a/indra/llui/tests/alprefixsums_test.cpp +++ b/indra/llui/tests/alprefixsums_test.cpp @@ -35,16 +35,7 @@ namespace { struct alprefixsums_data { - // What the sums should say, added up the long way. - static S32 before(const std::vector& values, size_t index) - { - S32 sum = 0; - for (size_t i = 0; i < index && i < values.size(); ++i) - { - sum += values[i]; - } - return sum; - } + // The place a sum reaches, found the long way. static size_t reach(const std::vector& values, S32 sum) { size_t at = 0; diff --git a/indra/llui/tests/alpropertygrid_test.cpp b/indra/llui/tests/alpropertygrid_test.cpp index 292320efaa..ebc1964d6b 100644 --- a/indra/llui/tests/alpropertygrid_test.cpp +++ b/indra/llui/tests/alpropertygrid_test.cpp @@ -106,7 +106,11 @@ namespace const LLRect& r = labels[i]; CAPTURE(i, r, within); REQUIRE(within.contains(r)); - if (i > 0) + if (i == 0) + { + REQUIRE(r.mTop == within.mTop); + } + else { REQUIRE(labels[i - 1].mTop - r.mTop == ROW); } @@ -114,7 +118,7 @@ namespace } }; - TEST_CASE_METHOD(alpropertygrid_data, "alpropertygrid: freshly filled rows stack one row apart inside their section", "[alpropertygrid]") + TEST_CASE_METHOD(alpropertygrid_data, "alpropertygrid: freshly filled rows stack one row apart from the top of their section", "[alpropertygrid]") { if (!ui.ok()) { @@ -135,7 +139,7 @@ namespace // And they are still stacked from its top after the grid is resized, which // is the whole reason a resize no longer rebuilds them. - TEST_CASE_METHOD(alpropertygrid_data, "alpropertygrid: rows stay stacked inside their section after a resize", "[alpropertygrid]") + TEST_CASE_METHOD(alpropertygrid_data, "alpropertygrid: rows stay stacked from the top of their section after a resize", "[alpropertygrid]") { if (!ui.ok()) { @@ -406,11 +410,14 @@ namespace } // A number is stepped by its arrows, by the wheel over it and by a scrub - // along it, and all three take the same step. A field carrying no - // decimals rounds whatever the step leaves it at, so a step of less than - // one lands back on the number it started from: the arrows write what is - // already there and only the keyboard can change anything. - TEST_CASE_METHOD(alpropertygrid_data, "alpropertygrid: a whole number steps by at least one and a real by less", "[alpropertygrid]") + // along it, and all three take the one step its spinner is given, which + // is what this checks. The gestures are LLSpinCtrl's own, and its arrows + // and wheel read the keyboard's modifiers, which a headless test has no + // keyboard for. A field carrying no decimals rounds whatever the step + // leaves it at, so a step of less than one would land back on the number + // it started from: the arrows would write what is already there and only + // the keyboard could change anything. + TEST_CASE_METHOD(alpropertygrid_data, "alpropertygrid: a whole number is given a step of at least one and a real less", "[alpropertygrid]") { if (!ui.ok()) { @@ -582,6 +589,8 @@ namespace two->setFields(both); UNSCOPED_INFO("a heading over the first"); CHECK(two->getChild("first", true)->getHeaderHeight() > 0); + UNSCOPED_INFO("and one over the second"); + CHECK(two->getChild("second", true)->getHeaderHeight() > 0); one->die(); two->die(); @@ -788,6 +797,7 @@ namespace CHECK(editor->getToolTip() == label->getToolTip()); UNSCOPED_INFO("and it is the same editor, not a new one in its place"); CHECK(row->getChild("width", true) == editor); + REQUIRE(grid->fields().size() == 1u); UNSCOPED_INFO("which the grid remembers"); CHECK(grid->fields().front().authored); @@ -883,10 +893,11 @@ namespace grid->setFields(fields); LLPanel* row = grid->getChild("size_rows", true)->getChild("width_row", true); - LLView* back = row->getChild("width_remove", true); + LLButton* back = row->findChild("width_remove", true); + REQUIRE(back != nullptr); UNSCOPED_INFO("the way back is there for the half that is written"); CHECK(back->getVisible()); - back->as()->onCommit(); + back->onCommit(); UNSCOPED_INFO("and takes out that half alone"); REQUIRE(removed.size() == 1u); UNSCOPED_INFO("which is the second"); @@ -900,7 +911,7 @@ namespace UNSCOPED_INFO("until the first is written"); CHECK(back->getVisible()); removed.clear(); - back->as()->onCommit(); + back->onCommit(); UNSCOPED_INFO("and then it takes out the first"); REQUIRE(removed.size() == 1u); CHECK(removed.front() == std::string("width")); @@ -1044,6 +1055,7 @@ namespace } CHECK(label->getText() == std::string("After")); CHECK(width->getValue().asInteger() == 20); + REQUIRE(grid->fields().size() == 2u); UNSCOPED_INFO("and the grid knows it"); CHECK(grid->fields()[1].value == std::string("20")); @@ -1075,6 +1087,7 @@ namespace fields.back().authored = false; fields.push_back(field("border", 0)); grid->setFields(fields); + REQUIRE(grid->fields().size() == 3u); CHECK(grid->fields()[0].name == std::string("radius")); CHECK(grid->fields()[1].name == std::string("corners")); UNSCOPED_INFO("the written one stays where it was"); diff --git a/indra/llui/tests/alquickopen_test.cpp b/indra/llui/tests/alquickopen_test.cpp index 4c7f49fb72..9c379963e2 100644 --- a/indra/llui/tests/alquickopen_test.cpp +++ b/indra/llui/tests/alquickopen_test.cpp @@ -107,10 +107,13 @@ namespace UNSCOPED_INFO("and that is what comes back first"); CHECK(best("panel") == std::string("panel_people.xml")); - // `flbuy` is initials-and-more; the file that is only about buying - // wins over the one that is about buying currency. - UNSCOPED_INFO("the tighter of two"); + // `floater_buy` begins both, and `flbuy` is initials-and-more of + // both; either way the file that is only about buying wins over the + // one that is about buying currency. + UNSCOPED_INFO("the tighter of two prefixes"); CHECK(best("floater_buy") == std::string("floater_buy.xml")); + UNSCOPED_INFO("the tighter of two by initials"); + CHECK(best("flbuy") == std::string("floater_buy.xml")); } // Nothing typed answers everything, in the order it was given, because @@ -270,7 +273,9 @@ namespace REQUIRE(list->count() == 1); quick->setHint("Go to line 12."); CHECK(list->choices()[0].text == std::string("Go to line 12.")); - quick->findChild("query")->onCommit(); + LLLineEditor* field = quick->findChild("query"); + REQUIRE(field != nullptr); + field->onCommit(); REQUIRE(chosen.size() == 1); CHECK(chosen[0] == "12"); quick->die(); @@ -297,10 +302,11 @@ namespace } quick->setCandidates(many); const ALChoiceList* list = quick->findChild("matches"); + REQUIRE(list != nullptr); UNSCOPED_INFO("nothing typed: the first as many as were asked for"); REQUIRE(list->count() == 3); UNSCOPED_INFO("in the order given"); - CHECK(quick->listed().front() == "llFunction00"); + CHECK(quick->listed() == std::vector({ "llFunction00", "llFunction01", "llFunction02" })); quick->setQuery("func"); UNSCOPED_INFO("typed: as many as were asked for"); CHECK(list->count() == 3); @@ -310,7 +316,7 @@ namespace CHECK(quick->listed().size() == 3); } UNSCOPED_INFO("and nothing to say about it"); - CHECK(quick->findChild("matches")->placeholder().empty()); + CHECK(list->placeholder().empty()); quick->setQuery("zzz"); CHECK(list->count() == 0); UNSCOPED_INFO("and the list says so"); @@ -411,12 +417,15 @@ namespace p.name = "quick_open"; p.rect = LLRect(0, 60, 300, 0); ALQuickOpen* quick = LLUICtrlFactory::create(p); + // Found before the popover is up: one left up by a failed REQUIRE + // would be escaped after the case, into a callback holding `said`. + LLLineEditor* field = quick->findChild("query"); + REQUIRE(field != nullptr); ALPopover* popover = ALPopover::showOver(window, quick, "Go to line"); REQUIRE(popover != nullptr); std::vector said; popover->onClosed([&said](bool escaped) { said.push_back(escaped); }); quick->takeFocus(); - LLLineEditor* field = quick->findChild("query"); CHECK(gFocusMgr.getKeyboardFocus() == field); field->handleKey(KEY_ESCAPE, MASK_NONE, false); CHECK((popover->isDead() || !popover->getVisible())); @@ -462,7 +471,9 @@ namespace CHECK(quick->handleKeyHere(KEY_UP, MASK_NONE)); CHECK(quick->chosenRow() == 1); } - quick->findChild("query")->onCommit(); + LLLineEditor* field = quick->findChild("query"); + REQUIRE(field != nullptr); + field->onCommit(); { INFO("return takes the one chosen"); REQUIRE(chosen.size() == 1); @@ -470,6 +481,7 @@ namespace } ALChoiceList* list = quick->findChild("matches"); + REQUIRE(list != nullptr); const LLRect text = list->textRect(); const S32 y = text.mTop - list->layout().lineTop(2) - list->layout().rowHeightOf(2, 0) / 2 + list->scrollY(); CHECK(list->handleDoubleClick(text.mLeft + 4, y, MASK_NONE)); diff --git a/indra/llui/tests/alscopebar_test.cpp b/indra/llui/tests/alscopebar_test.cpp index c11829924a..d68a8a8ffc 100644 --- a/indra/llui/tests/alscopebar_test.cpp +++ b/indra/llui/tests/alscopebar_test.cpp @@ -143,12 +143,19 @@ namespace UNSCOPED_INFO("everything fits in the bar"); CHECK(scope->getRect().mRight <= 500); - // Narrower, and the field is what gives up the room. + // Narrower, and the field is what gives up the room: the choices keep + // theirs. + const LLComboBox* what = bar->getChild("field"); + const LLComboBox* how = bar->getChild("how"); const S32 was = field->getRect().getWidth(); + const S32 what_was = what->getRect().getWidth(); + const S32 how_was = how->getRect().getWidth(); + const S32 scope_was = scope->getRect().getWidth(); bar->reshape(360, 30); CHECK(field->getRect().getWidth() < was); - UNSCOPED_INFO("the last choice still has some width"); - CHECK(scope->getRect().getWidth() > 0); + CHECK(what->getRect().getWidth() == what_was); + CHECK(how->getRect().getWidth() == how_was); + CHECK(scope->getRect().getWidth() == scope_was); delete bar; } diff --git a/indra/llui/tests/alsettingrow_test.cpp b/indra/llui/tests/alsettingrow_test.cpp index 6b4d5bbff6..92942127d6 100644 --- a/indra/llui/tests/alsettingrow_test.cpp +++ b/indra/llui/tests/alsettingrow_test.cpp @@ -465,9 +465,12 @@ namespace ALSettingRow* large = row(std::string(), 0.f, 10.f, 3); LLSliderCtrl* plain_large = slider_like(10.f); + LLLineEditor* large_box = valueBoxOf(large->getSlider()); + LLLineEditor* plain_large_box = valueBoxOf(plain_large); + REQUIRE(large_box); + REQUIRE(plain_large_box); UNSCOPED_INFO("over 1, exactly a slider's"); - CHECK(valueBoxOf(large->getSlider())->getRect().getWidth() == - valueBoxOf(plain_large)->getRect().getWidth()); + CHECK(large_box->getRect().getWidth() == plain_large_box->getRect().getWidth()); small->die(); plain_small->die(); @@ -593,14 +596,20 @@ namespace ALSettingRow* r = LLUICtrlFactory::create(p); gFloaterView->addChild(r); - LLSlider* bar = nullptr; - for (LLView* child : *r->getSlider()->getChildList()) + // The track a row's slider presses on. + const auto barOf = [](ALSettingRow* of) { - if (LLSlider* candidate = ALViewType::as(child)) + LLSlider* bar = nullptr; + for (LLView* child : *of->getSlider()->getChildList()) { - bar = candidate; + if (LLSlider* candidate = ALViewType::as(child)) + { + bar = candidate; + } } - } + return bar; + }; + LLSlider* bar = barOf(r); REQUIRE(bar != nullptr); // On the track and off the thumb, which sits at the middle for 0.5: @@ -615,9 +624,19 @@ namespace CHECK(released == 1); CHECK(pressed == 1); - // A row that names neither is a row like every other. + // A row that names neither is a row like every other: a press and a + // release on its track are heard by neither callback. ALSettingRow* plain = row("SettingRowTestHeld"); - CHECK_FALSE(params("SettingRowTestHeld").mouse_down_callback.isProvided()); + gFloaterView->addChild(plain); + LLSlider* plain_bar = barOf(plain); + REQUIRE(plain_bar != nullptr); + plain_bar->handleMouseDown(x, plain_bar->getRect().getHeight() / 2, MASK_NONE); + plain_bar->handleMouseUp(x, plain_bar->getRect().getHeight() / 2, MASK_NONE); + { + INFO("the plain row's ends are not heard"); + CHECK(pressed == 1); + CHECK(released == 1); + } plain->die(); r->die(); } @@ -717,8 +736,10 @@ namespace p.components = "X Y"; p.letter_width = 18; ALSettingRow* r = LLUICtrlFactory::create(p); + const LLSpinCtrl* x_box = r->findChild("centre.X"); + REQUIRE(x_box != nullptr); const LLTextBox* letter = nullptr; - for (LLView* child : *r->findChild("centre.X")->getChildList()) + for (LLView* child : *x_box->getChildList()) { letter = letter ? letter : ALViewType::as(child); } @@ -746,8 +767,10 @@ namespace r->getResetButton()->onCommit(); UNSCOPED_INFO("the whole setting back"); CHECK(sameParts(control->getValue(), 0.0, 0.0, 0.0)); + const LLSpinCtrl* y_box = r->findChild("centre.Y"); + REQUIRE(y_box != nullptr); UNSCOPED_INFO("the boxes show it"); - CHECK(closeTo(r->findChild("centre.Y")->getValue().asReal(), 0.0)); + CHECK(closeTo(y_box->getValue().asReal(), 0.0)); r->die(); } diff --git a/indra/llui/tests/alsnippetsession_test.cpp b/indra/llui/tests/alsnippetsession_test.cpp index 45ec8c850a..d2f1909b2b 100644 --- a/indra/llui/tests/alsnippetsession_test.cpp +++ b/indra/llui/tests/alsnippetsession_test.cpp @@ -85,6 +85,7 @@ namespace session.slide(doc.replace(range(0, 2, 3), "alpha")); { INFO("typed over"); + REQUIRE(session.stops().size() == 2); CHECK(doc.text(session.stops()[0]) == "alpha"); CHECK(doc.text(session.stops()[1]) == "b"); } @@ -93,6 +94,7 @@ namespace session.slide(doc.replace(range(0, 0, 0), "x")); { INFO("moved with the text"); + REQUIRE(session.stops().size() == 2); CHECK(doc.text(session.stops()[0]) == "alpha"); CHECK(doc.text(session.stops()[1]) == "b"); } @@ -131,6 +133,7 @@ namespace session.setSyncing(false); { INFO("each brought up whole"); + REQUIRE(session.mirrors().size() == 2); CHECK(doc.text(session.mirrors()[0].range) == "count"); CHECK(doc.text(session.mirrors()[1].range) == "count"); } @@ -202,6 +205,7 @@ namespace CHECK(whole.stops[1] == whole.mirrors[0].range); } session.start(whole.stops, whole.landing.begin, whole.mirrors); + REQUIRE(session.stops().size() == 2); session.slide(doc.replace(session.stops()[0], "json")); sync(doc, 0); CHECK(doc.text() == std::string("local json = require(\"json\")")); @@ -215,6 +219,7 @@ namespace const ALSnippetSession::Expansion around = ALSnippetSession::expand("${1:x} ${2:the $1 thing}", ALTextPos(0, 0), ""); ALTextDocument text(around.text); + REQUIRE(around.stops.size() == 2); session.start(around.stops, around.landing.begin, around.mirrors); session.slide(text.replace(session.stops()[0], "abc")); sync(text, 0); diff --git a/indra/llui/tests/alspecimenlist_test.cpp b/indra/llui/tests/alspecimenlist_test.cpp index 394454808d..9143977fd5 100644 --- a/indra/llui/tests/alspecimenlist_test.cpp +++ b/indra/llui/tests/alspecimenlist_test.cpp @@ -142,19 +142,28 @@ namespace } ALSpecimenList* list = make(); list->setSpecimens(three()); + // Filtering shows and hides; it builds nothing, so these stay. + const LLView* check_box = list->findChild("row_check_box", true); + const LLView* panel = list->findChild("row_panel", true); + const LLView* containers = list->findChild("heading_Containers", true); + const LLView* controls = list->findChild("heading_Controls", true); + REQUIRE(check_box != nullptr); + REQUIRE(panel != nullptr); + REQUIRE(containers != nullptr); + REQUIRE(controls != nullptr); list->filter("CHECK"); CHECK(list->shown() == 1u); - CHECK(list->findChild("row_check_box", true)->getVisible()); - CHECK_FALSE(list->findChild("row_panel", true)->getVisible()); - CHECK_FALSE(list->findChild("heading_Containers", true)->getVisible()); - CHECK(list->findChild("heading_Controls", true)->getVisible()); + CHECK(check_box->getVisible()); + CHECK_FALSE(panel->getVisible()); + CHECK_FALSE(containers->getVisible()); + CHECK(controls->getVisible()); // A heading is a word to filter by as well: everything under it. list->filter("containers"); UNSCOPED_INFO("the group matched"); CHECK(list->shown() == 1u); - CHECK(list->findChild("row_panel", true)->getVisible()); + CHECK(panel->getVisible()); list->filter(std::string()); CHECK(list->shown() == 3u); @@ -172,10 +181,15 @@ namespace ALSpecimenList* list = make(); list->setSpecimens(three()); + const LLView* scroller = list->findChild("scroller", true); + const LLView* empty = list->findChild("empty", true); + REQUIRE(scroller != nullptr); + REQUIRE(empty != nullptr); + list->filter("qqzzxw"); CHECK(list->shown() == 0u); - CHECK_FALSE(list->findChild("scroller", true)->getVisible()); - CHECK(list->findChild("empty", true)->getVisible()); + CHECK_FALSE(scroller->getVisible()); + CHECK(empty->getVisible()); delete list; } @@ -191,13 +205,15 @@ namespace ALSpecimenList* list = make(); list->setSpecimens(three()); LLView* row = list->findChild("row_check_box", true); + REQUIRE(row != nullptr); const LLRect r = row->getLocalRect(); const S32 x = r.getCenterX(); const S32 y = r.getCenterY(); - // Without a starter, moving the press carries nothing. + // Without a starter, moving the press carries nothing: there is no + // starter to call, and calling the empty one would throw. row->handleMouseDown(x, y, MASK_NONE); - row->handleHover(x + 20, y, MASK_NONE); + CHECK_NOTHROW(row->handleHover(x + 20, y, MASK_NONE)); row->handleMouseUp(x + 20, y, MASK_NONE); std::vector carried; @@ -286,8 +302,10 @@ namespace CHECK(list->shown() == size_t(1)); list->filter("zzz"); CHECK(list->shown() == size_t(0)); + const LLView* empty = list->findChild("empty", true); + REQUIRE(empty != nullptr); UNSCOPED_INFO("the pane says so"); - CHECK(list->findChild("empty", true)->getVisible()); + CHECK(empty->getVisible()); delete list; } @@ -404,6 +422,7 @@ namespace // The cells sit across under their heading: B is the second, at // the tiles' own height less the heading and half a cell. LLView* tiles = list->findChild("tiles", true); + REQUIRE(tiles != nullptr); const S32 x = 150; const S32 y = tiles->getRect().getHeight() - 20 - 30; list->cellPressed(0, MASK_NONE); @@ -455,6 +474,7 @@ namespace UNSCOPED_INFO("both sides are"); CHECK(list->cells()); LLView* tiles = list->findChild("tiles", true); + REQUIRE(tiles != nullptr); { INFO("the picture stayed with the row"); CHECK(tiles->findChild("picture", true) == nullptr); @@ -489,10 +509,15 @@ namespace CHECK(sample->getText() == "Aa Bb 0123"); } CHECK(sample->getFont() == LLFontGL::getFontSansSerif()); + const LLView* mono = list->findChild("row_Monospace", true); + const LLTextBox* mono_sample = mono ? mono->findChild("sample") : nullptr; + REQUIRE(mono_sample != nullptr); UNSCOPED_INFO("the other row in its own"); - CHECK(list->findChild("row_Monospace", true)->findChild("sample")->getFont() == LLFontGL::getFontMonospace()); + CHECK(mono_sample->getFont() == LLFontGL::getFontMonospace()); + const LLTextBox* label = sans->findChild("label"); + REQUIRE(label != nullptr); UNSCOPED_INFO("past the label"); - CHECK(sample->getRect().mLeft > sans->findChild("label")->getRect().mRight - 1); + CHECK(sample->getRect().mLeft > label->getRect().mRight - 1); list->filter("monospace"); UNSCOPED_INFO("found by its name, not only its label"); diff --git a/indra/llui/tests/alstudiofloater_test.cpp b/indra/llui/tests/alstudiofloater_test.cpp index 25bb59a45c..c1ef2d0e11 100644 --- a/indra/llui/tests/alstudiofloater_test.cpp +++ b/indra/llui/tests/alstudiofloater_test.cpp @@ -104,10 +104,20 @@ namespace return made; } + // Every popover still up escaped while what its callbacks count is still + // there: a REQUIRE that ends a case with one up would otherwise leave the + // harness to escape it after the case, into a frame that has gone. + struct EscapeOnExit + { + ~EscapeOnExit() { ll_test::escapePopovers(); } + }; + // Return, as the field gives it: the best answer taken. void choose(ALQuickOpen* quick) { - quick->findChild("query")->onCommit(); + LLLineEditor* query = quick->findChild("query"); + REQUIRE(query != nullptr); + query->onCommit(); } } @@ -139,11 +149,14 @@ namespace TestStudio* window = studio(); std::vector symbols, commands; S32 symbols_escaped = 0; + EscapeOnExit escape_on_exit; ALQuickOpen* first = window->quickOpen(candidates({ "state_entry", "touch_start" }), "Type a symbol", "Go to Symbol", [&](const std::string& v) { symbols.push_back(v); }, nullptr, 0, 0, [&]() { ++symbols_escaped; }); REQUIRE(first != nullptr); - const LLHandle first_up = first->getParentByType()->getHandle(); + LLFloater* first_popover = first->getParentByType(); + REQUIRE(first_popover != nullptr); + const LLHandle first_up = first_popover->getHandle(); ALQuickOpen* second = window->quickOpen(candidates({ "save", "compile" }), "Type a command", "Commands", [&](const std::string& v) { commands.push_back(v); }); @@ -178,10 +191,11 @@ namespace std::vector first_heard, second_heard; ALQuickOpen* first = window->quickOpen(candidates({ "alpha", "beta" }), "Type a name", "Tabs", [&](const std::string& v) { first_heard.push_back(v); }); + REQUIRE(first != nullptr); first->setQuery("be"); ALQuickOpen* again = window->quickOpen(candidates({ "alpha", "beta", "beetle" }), "Type a name", "Tabs", [&](const std::string& v) { second_heard.push_back(v); }); - CHECK(again == first); + REQUIRE(again == first); CHECK(again->query() == std::string("be")); choose(again); REQUIRE(second_heard.size() == 1); @@ -205,11 +219,15 @@ namespace S32 escaped = 0; S32 left = 0; std::vector chosen; + EscapeOnExit escape_on_exit; const auto ask = [&]() { return window->quickOpen(candidates({ "one" }), "Type", "Ways out", [&](const std::string& v) { chosen.push_back(v); }, nullptr, 0, 0, [&]() { ++escaped; }, {}, [&]() { ++left; }); }; - const auto popoverOf = [](ALQuickOpen* quick) { return ALViewType::as(quick->getParentByType()); }; + const auto popoverOf = [](ALQuickOpen* quick) + { + return quick ? ALViewType::as(quick->getParentByType()) : nullptr; + }; ALQuickOpen* quick = ask(); REQUIRE(popoverOf(quick) != nullptr); @@ -220,12 +238,14 @@ namespace CHECK(chosen.empty()); quick = ask(); + REQUIRE(popoverOf(quick) != nullptr); popoverOf(quick)->escape(); CHECK(escaped == 1); UNSCOPED_INFO("escaping is not leaving"); CHECK(left == 1); quick = ask(); + REQUIRE(quick != nullptr); choose(quick); CHECK(chosen.size() == (size_t)1); { @@ -380,6 +400,7 @@ namespace { INFO("a second key, not theirs to give, still does"); CHECK(window->runCommandKey('S', MASK_CONTROL | MASK_SHIFT)); + REQUIRE_FALSE(ran.empty()); CHECK(ran.back() == "save again"); } window->closeFloater(); @@ -452,7 +473,9 @@ namespace // A popover's field, whose keys come home to the window. ALQuickOpen* quick = window->quickOpen(candidates({ "one" }), "Type", "Popover", [](const std::string&) {}); + REQUIRE(quick != nullptr); LLLineEditor* query = quick->findChild("query"); + REQUIRE(query != nullptr); query->setFocus(true); quick->setQuery("world"); { diff --git a/indra/llui/tests/alsyntaxgrammars_test.cpp b/indra/llui/tests/alsyntaxgrammars_test.cpp index 5d51d961bb..58b2de4eac 100644 --- a/indra/llui/tests/alsyntaxgrammars_test.cpp +++ b/indra/llui/tests/alsyntaxgrammars_test.cpp @@ -46,8 +46,9 @@ namespace for (const char* name : { "lsl", "slua", "xml", "json", "text", "config" }) { std::string error; - UNSCOPED_INFO(std::string(name) + " loads: " + error); - REQUIRE(library.loadFile(std::string(LLUI_TEST_APP_DIR) + "/app_settings/syntax/" + name + ".xml", error)); + const bool loaded = library.loadFile(std::string(LLUI_TEST_APP_DIR) + "/app_settings/syntax/" + name + ".xml", error); + INFO(name << " loads: " << error); + REQUIRE(loaded); } } @@ -85,10 +86,17 @@ namespace TEST_CASE_METHOD(alsyntaxgrammars_data, "alsyntaxgrammars: the six grammars load and answer to their extensions", "[alsyntaxgrammars]") { REQUIRE(library.names().size() == size_t(6)); - CHECK(library.forExtension(".lsl")->name() == std::string("lsl")); - CHECK(library.forExtension("luau")->name() == std::string("slua")); - CHECK(library.forExtension("xui")->name() == std::string("xml")); - CHECK(library.forExtension("ini")->name() == std::string("config")); + const auto named = [this](std::string_view extension) + { + std::shared_ptr grammar = library.forExtension(extension); + CAPTURE(extension); + REQUIRE(grammar); + return grammar->name(); + }; + CHECK(named(".lsl") == std::string("lsl")); + CHECK(named("luau") == std::string("slua")); + CHECK(named("xui") == std::string("xml")); + CHECK(named("ini") == std::string("config")); REQUIRE_FALSE(library.forExtension("exe")); } @@ -258,8 +266,9 @@ namespace description["states"] = states; ALSyntaxGrammar grammar; std::string error; - UNSCOPED_INFO("loads: " + error); - REQUIRE(grammar.load(description, error)); + const bool loaded = grammar.load(description, error); + UNSCOPED_INFO("loads: " << error); + REQUIRE(loaded); ALSyntaxWords words; // Nested repeats over a long line, which Boost.Regex gave up on: @@ -307,8 +316,9 @@ namespace regex_description["name"] = "tested"; regex_description["states"] = regex_states; ALSyntaxGrammar regex_grammar; - UNSCOPED_INFO("loads: " + error); - REQUIRE(regex_grammar.load(regex_description, error)); + const bool regex_loaded = regex_grammar.load(regex_description, error); + UNSCOPED_INFO("loads: " << error); + REQUIRE(regex_loaded); state = regex_grammar.initialState(); regex_grammar.lexLine("<(); std::string error; - UNSCOPED_INFO("grammar loads: " + error); - REQUIRE(grammar->load(description, error)); + const bool loaded = grammar->load(description, error); + INFO("grammar loads: " << error); + REQUIRE(loaded); return grammar; } @@ -150,6 +151,7 @@ namespace UNSCOPED_INFO("the longer operator first"); CHECK(lexed(*grammar, "a==b", state, words) == std::string("text:a|operator:==|text:b")); REQUIRE(grammar->wordTables().size() == size_t(2)); + REQUIRE_FALSE(grammar->extensions().empty()); CHECK(grammar->extensions().front() == std::string("mini")); } @@ -189,6 +191,7 @@ namespace UNSCOPED_INFO("level two ends at level two"); CHECK(lexed(*grammar, "[==[ a ]=] b ]==] c", state, words) == std::string("string:[==[ a ]=] b ]==]|text: c")); CHECK(lexed(*grammar, "[=[ x", state, words) == std::string("string:[=[ x")); + REQUIRE_FALSE(state.frames.empty()); UNSCOPED_INFO("with the level in the state"); CHECK(state.frames.back().payload == std::string("=")); UNSCOPED_INFO("and closed on the next"); @@ -243,8 +246,9 @@ namespace LLSD good = mini(); good["states"]["main"] = rules({ LLSD().with("regex", "(x)y").with("consume", 1).with("not_after_chars", "a-z").with("kind", "keyword") }); + const bool both = grammar.load(good, error); UNSCOPED_INFO("both on a regex: " << error); - CHECK(grammar.load(good, error)); + CHECK(both); } TEST_CASE_METHOD(alsyntaxhighlighter_data, "alsyntaxhighlighter: the highlighter follows a document and re-lexes only what an edit reaches", "[alsyntaxhighlighter]") @@ -484,10 +488,12 @@ namespace LLSD good = mini(); good["states"]["main"] = rules({ LLSD().with("span", "\"").with("end", "\"").with("escape", "\\").with("multiline", false).with("unless_after", "\\"), LLSD().with("eol", true).with("unless_after", "\\").with("pop", true) }); + const bool backslashed = grammar.load(good, error); UNSCOPED_INFO("a span its line ends, but for a backslash, and an eol that says the same: " << error); - CHECK(grammar.load(good, error)); + CHECK(backslashed); + const bool little = grammar.load(mini(), error); UNSCOPED_INFO("and the little language: " << error); - CHECK(grammar.load(mini(), error)); + CHECK(little); } TEST_CASE_METHOD(alsyntaxhighlighter_data, "alsyntaxhighlighter: a line's revision moves on only when its tokens may have changed", "[alsyntaxhighlighter]") diff --git a/indra/llui/tests/altabstrip_test.cpp b/indra/llui/tests/altabstrip_test.cpp index 586f2eaf9b..2bd970e21c 100644 --- a/indra/llui/tests/altabstrip_test.cpp +++ b/indra/llui/tests/altabstrip_test.cpp @@ -480,6 +480,7 @@ namespace const LLRect second = strip->rectOf(1); strip->handleMouseDown(first.getCenterX(), first.getCenterY(), MASK_NONE); strip->handleHover(second.getCenterX() + 4, second.getCenterY(), MASK_NONE); + REQUIRE(strip->tabs().size() == 3u); UNSCOPED_INFO("dragged past its neighbour, it moved"); CHECK(strip->tabs()[1].value == "a"); UNSCOPED_INFO("nothing said while it is held"); @@ -577,6 +578,7 @@ namespace strip->handleMouseUp(strip->rectOf(1).getCenterX() + 4, HEIGHT / 2, MASK_NONE); { INFO("nothing moved"); + REQUIRE(strip->tabs().size() == 2u); REQUIRE(strip->tabs()[0].value == "b"); CHECK(strip->tabs()[1].value == "c"); } @@ -706,6 +708,7 @@ namespace { INFO("which is still chosen, where it went"); REQUIRE(strip->chosen() == "a"); + REQUIRE(strip->tabs().size() == 3u); CHECK(strip->tabs()[1].value == "a"); } strip->handleKeyHere(KEY_LEFT, MASK_SHIFT); @@ -713,6 +716,7 @@ namespace { INFO("back, and no further than the first"); REQUIRE(orders.size() == 2); + REQUIRE(strip->tabs().size() == 3u); CHECK(strip->tabs()[0].value == "a"); } @@ -768,6 +772,7 @@ namespace CHECK(torn.front() == std::string("b")); S32 screen_x = 0, screen_y = 0; strip->localPointToScreen(second.getCenterX() + 5, -120, &screen_x, &screen_y); + REQUIRE(where.size() == size_t(1)); UNSCOPED_INFO("where, on the screen"); CHECK(where.front() == std::make_pair(screen_x, screen_y)); UNSCOPED_INFO("no longer tearing"); @@ -837,6 +842,7 @@ namespace dirty.badge = LLColor4::red; UNSCOPED_INFO("found"); CHECK(strip->setTab(dirty)); + REQUIRE(strip->tabs().size() == 3u); { INFO("changed in place"); REQUIRE(strip->tabs()[1].dirty); @@ -934,7 +940,7 @@ namespace REQUIRE(cut.find("\xE2\x80\xA6") != std::string::npos); } UNSCOPED_INFO("the extension kept: " << cut); - REQUIRE(cut.compare(cut.size() - 4, 4, ".lsl") == 0); + REQUIRE(cut.ends_with(".lsl")); UNSCOPED_INFO("no mark after the ellipsis, parted from its e: " << cut); REQUIRE(cut.find("\xE2\x80\xA6\xCC") == std::string::npos); UNSCOPED_INFO("the e kept with both its marks: " << cut); diff --git a/indra/llui/tests/altextdiff_test.cpp b/indra/llui/tests/altextdiff_test.cpp index 93a196d63e..4a67438a95 100644 --- a/indra/llui/tests/altextdiff_test.cpp +++ b/indra/llui/tests/altextdiff_test.cpp @@ -72,6 +72,8 @@ namespace REQUIRE(run.right == r); if (run.kind == Kind::Same) { + UNSCOPED_INFO("a Same run inside both texts"); + REQUIRE((l + run.count <= static_cast(left.size()) && r + run.count <= static_cast(right.size()))); for (S32 i = 0; i < run.count; ++i) { UNSCOPED_INFO("a Same run's lines alike"); @@ -739,6 +741,7 @@ namespace { INFO("the operator whole"); REQUIRE_FALSE(left.empty()); + REQUIRE_FALSE(right.empty()); CHECK(left[0] == std::make_pair(6, 8)); CHECK(right[0] == std::make_pair(6, 8)); } @@ -753,6 +756,7 @@ namespace { INFO("without them, by bytes: = and ! alone"); REQUIRE_FALSE(left.empty()); + REQUIRE_FALSE(right.empty()); CHECK(left[0] == std::make_pair(6, 7)); CHECK(right[0] == std::make_pair(6, 7)); } @@ -835,8 +839,9 @@ namespace } return -1; }; - const std::vector patience = ALTextDiff::lines(was, now, by(A::Patience)); - const std::vector minimal = ALTextDiff::lines(was, now, by(A::Minimal)); + const std::vector patience = ALTextDiff::lines(was, now, by(A::Patience)); + const std::vector minimal = ALTextDiff::lines(was, now, by(A::Minimal)); + const std::vector histogram = ALTextDiff::lines(was, now, by(A::Histogram)); require_walks(was, now, patience); require_walks(was, now, minimal); { @@ -850,8 +855,8 @@ namespace CHECK(require_walks(was, now, minimal) <= require_walks(was, now, patience)); } { - INFO("all three alike here: minimal no more"); - CHECK(require_walks(was, now, minimal) <= require_walks(was, now, patience)); + INFO("minimal: fewer or as few as histogram"); + CHECK(require_walks(was, now, minimal) <= require_walks(was, now, histogram)); } // Where they differ. A line moved from the top to the bottom past @@ -1204,6 +1209,14 @@ namespace } return true; }; + // Each line of each text has its marks and says whether it was read + // as tokens: what the checks below index. + const auto require_each_line = [](const Result& r, size_t left, size_t right) { + REQUIRE(r.leftMarks.size() == left); + REQUIRE(r.leftByTokens.size() == left); + REQUIRE(r.rightMarks.size() == right); + REQUIRE(r.rightByTokens.size() == right); + }; const std::vector call = { "default", "{", " llSay(0, \"a\" + b);", "}" }; const std::vector flown = { "default", "{", " llSay(", " 0,", " \"a\" + b", " );", "}" }; const Result reflowed = ALStructuralDiff::compare(call, flown, ALTextDiff::Options()); @@ -1214,6 +1227,7 @@ namespace CHECK(unmarked(reflowed)); CHECK_FALSE(reflowed.tooLarge); } + require_each_line(reflowed, call.size(), flown.size()); { INFO("its lines read as tokens, those the same not"); REQUIRE(reflowed.leftByTokens[2]); @@ -1241,6 +1255,7 @@ namespace const std::vector changed = { "default", "{", " llSay(", " 1,", " \"a\" + b", " );", "}" }; const Result real = ALStructuralDiff::compare(call, changed, ALTextDiff::Options()); require_walks(call, changed, real.runs); + require_each_line(real, call.size(), changed.size()); { INFO("the change inside the reformatting marked, and nothing else"); CHECK(real.leftMarks[2] == ALTextDiff::spans_t{ { 10, 11 } }); @@ -1253,6 +1268,7 @@ namespace const std::vector one = { "f(a)(b)" }; const std::vector two = { "f(a(b))" }; const Result nested = ALStructuralDiff::compare(one, two, ALTextDiff::Options()); + require_each_line(nested, one.size(), two.size()); { INFO("a bracket whose other went elsewhere marked"); REQUIRE_FALSE(nested.leftMarks[0].empty()); @@ -1260,6 +1276,7 @@ namespace } // f's ( kept by the tokens alone, though its ) is not: let go of. const Result closed = ALStructuralDiff::compare({ "f(a); g(b);" }, { "f(a; g(b));" }, ALTextDiff::Options()); + require_each_line(closed, 1, 1); { INFO("an opening bracket whose closing one was not kept, marked"); REQUIRE_FALSE(closed.leftMarks[0].empty()); @@ -1278,6 +1295,7 @@ namespace const std::vector mine = numbered(ALStructuralDiff::MOST_TOKENS / 2 + 1, "mine "); const std::vector theirs = numbered(ALStructuralDiff::MOST_TOKENS / 2 + 1, "theirs "); const Result big = ALStructuralDiff::compare(mine, theirs, ALTextDiff::Options()); + require_each_line(big, mine.size(), theirs.size()); { INFO("too large: by lines, said so"); CHECK(big.tooLarge); @@ -1306,7 +1324,7 @@ namespace kinds.push_back(hunk.kind); } UNSCOPED_INFO("each kind, in order"); - CHECK(kinds == std::vector{ K::Same, K::Ours, K::Same, K::Theirs, K::Same, K::Both, K::Same, K::Conflict }); + REQUIRE(kinds == std::vector{ K::Same, K::Ours, K::Same, K::Theirs, K::Same, K::Both, K::Same, K::Conflict }); { INFO("a hunk's lines in each"); CHECK(hunks[7] == H{ K::Conflict, 7, 1, 7, 1, 7, 1 }); @@ -1361,10 +1379,14 @@ namespace std::vector in_two = base; in_one.insert(in_one.begin() + 4, "mine"); in_two.insert(in_two.begin() + 4, "theirs"); + const ALTextMerge::hunks_t put_in = ALTextMerge::merge(base, in_one, in_two); + REQUIRE(put_in.size() >= 2u); UNSCOPED_INFO("put in at one place"); - CHECK(ALTextMerge::merge(base, in_one, in_two)[1].kind == K::Conflict); + CHECK(put_in[1].kind == K::Conflict); + const ALTextMerge::hunks_t put_in_alike = ALTextMerge::merge(base, in_one, in_one); + REQUIRE(put_in_alike.size() >= 2u); UNSCOPED_INFO("put in alike at one place"); - CHECK(ALTextMerge::merge(base, in_one, in_one)[1].kind == K::Both); + CHECK(put_in_alike[1].kind == K::Both); // A block moved down by ours, a line in it changed by theirs where // it was: a conflict, left so. @@ -1498,6 +1520,8 @@ namespace } if (run.kind == Kind::Same) { + UNSCOPED_INFO("a Same run inside both texts"); + REQUIRE((left + run.count <= static_cast(a.size()) && right + run.count <= static_cast(b.size()))); for (S32 i = 0; i < run.count; ++i) { UNSCOPED_INFO("kept the same"); diff --git a/indra/llui/tests/altextdocument_test.cpp b/indra/llui/tests/altextdocument_test.cpp index 876155369d..7a67d8de81 100644 --- a/indra/llui/tests/altextdocument_test.cpp +++ b/indra/llui/tests/altextdocument_test.cpp @@ -133,8 +133,10 @@ namespace CHECK(doc.version() == before); UNSCOPED_INFO("nobody told"); CHECK(heard.empty()); - UNSCOPED_INFO("clamped past the end"); - CHECK(doc.insert(ALTextPos(7, 7), "").nothing()); + const ALTextDocument::Edit past = doc.insert(ALTextPos(7, 7), ""); + CHECK(past.nothing()); + UNSCOPED_INFO("answered where it was asked for, clamped to the end"); + CHECK(past.range == ALTextRange(ALTextPos(0, 3), ALTextPos(0, 3))); } TEST_CASE_METHOD(altextdocument_data, "altextdocument: offsets and positions agree and the whole text kept follows edits", "[altextdocument]") diff --git a/indra/llui/tests/altextediting_test.cpp b/indra/llui/tests/altextediting_test.cpp index e1dcd4d4b9..2609d799de 100644 --- a/indra/llui/tests/altextediting_test.cpp +++ b/indra/llui/tests/altextediting_test.cpp @@ -160,10 +160,14 @@ namespace CHECK(applied(text, *j) == std::string("call(a, b)\n\nend")); UNSCOPED_INFO("the caret where the first join is"); CHECK(j->caret == ALTextPos(0, 7)); + const std::optional blank = joinLines(doc, 2, 4, false); + REQUIRE(blank.has_value()); UNSCOPED_INFO("a blank line takes no space"); - CHECK(applied(text, *joinLines(doc, 2, 4, false)) == std::string("call(a,\n b\n ) end")); + CHECK(applied(text, *blank) == std::string("call(a,\n b\n ) end")); + const std::optional kept = joinLines(doc, 0, 1, true); + REQUIRE(kept.has_value()); UNSCOPED_INFO("the blanks kept"); - CHECK(applied(text, *joinLines(doc, 0, 1, true)) == std::string("call(a, b\n )\n\nend")); + CHECK(applied(text, *kept) == std::string("call(a, b\n )\n\nend")); { INFO("one line is nothing to join"); CHECK_FALSE(joinLines(doc, 4, 4, false)); @@ -188,18 +192,22 @@ namespace CHECK(down->caret == ALTextPos(3, 0)); const std::optional up = moveLinesTo(doc, 3, 4, 0); + REQUIRE(up.has_value()); UNSCOPED_INFO("under a"); CHECK(applied(text, *up) == std::string("a\nd\ne\nb\nc")); UNSCOPED_INFO("the caret on e"); CHECK(up->caret == ALTextPos(2, 0)); const std::optional top = moveLinesTo(doc, 2, 2, -1); + REQUIRE(top.has_value()); UNSCOPED_INFO("to the top"); CHECK(applied(text, *top) == std::string("c\na\nb\nd\ne")); UNSCOPED_INFO("the caret on c"); CHECK(top->caret == ALTextPos(0, 0)); + const std::optional end = moveLinesTo(doc, 0, 0, 4); + REQUIRE(end.has_value()); UNSCOPED_INFO("to the end"); - CHECK(applied(text, *moveLinesTo(doc, 0, 0, 4)) == std::string("b\nc\nd\ne\na")); + CHECK(applied(text, *end) == std::string("b\nc\nd\ne\na")); UNSCOPED_INFO("into themselves, nothing"); CHECK_FALSE(moveLinesTo(doc, 1, 3, 2)); @@ -340,14 +348,21 @@ namespace { const std::string text = "a \nb\n\nc\t\n d\n\n\ne"; const ALTextDocument doc(text); + const std::optional trailing = joinLines(doc, 0, 1, false); + REQUIRE(trailing.has_value()); UNSCOPED_INFO("a line's trailing blanks stand for the space"); - CHECK(applied(text, *joinLines(doc, 0, 1, false)) == std::string("a b\n\nc\t\n d\n\n\ne")); + CHECK(applied(text, *trailing) == std::string("a b\n\nc\t\n d\n\n\ne")); + const std::optional tab = joinLines(doc, 3, 4, false); + REQUIRE(tab.has_value()); UNSCOPED_INFO("a tab as well"); - CHECK(applied(text, *joinLines(doc, 3, 4, false)) == std::string("a \nb\n\nc\td\n\n\ne")); + CHECK(applied(text, *tab) == std::string("a \nb\n\nc\td\n\n\ne")); + const std::optional onto_empty = joinLines(doc, 5, 7, false); + REQUIRE(onto_empty.has_value()); UNSCOPED_INFO("nothing joined onto yet takes no space"); - CHECK(applied(text, *joinLines(doc, 5, 7, false)) == std::string("a \nb\n\nc\t\n d\ne")); + CHECK(applied(text, *onto_empty) == std::string("a \nb\n\nc\t\n d\ne")); + const std::optional across_empty = joinLines(doc, 1, 3, false); + REQUIRE(across_empty.has_value()); UNSCOPED_INFO("an empty line between joins what comes after it with a space"); - CHECK(applied(text, *joinLines(doc, 1, 3, false)) == - std::string("a \nb c\t\n d\n\n\ne")); + CHECK(applied(text, *across_empty) == std::string("a \nb c\t\n d\n\n\ne")); } } diff --git a/indra/llui/tests/altextgotoline_test.cpp b/indra/llui/tests/altextgotoline_test.cpp index 79c8b51129..dfb16a34b9 100644 --- a/indra/llui/tests/altextgotoline_test.cpp +++ b/indra/llui/tests/altextgotoline_test.cpp @@ -106,9 +106,11 @@ namespace return *view; } - // Go to Line asked over the text, by lines counted as `base` says. - void ask(S32 base = 0) + // Go to Line asked over the text, by lines counted as `base` says: + // its field up, in a popover. + void require_asked(S32 base = 0) { + quick = nullptr; ALTextGoToLine::ask( [this](std::function chose, std::function escaped, std::function left) { quick = asker.ask({}, "Line, or line:column", "Go to Line", std::move(chose), view, 420, ALQuickOpen::heightForRows(1), @@ -122,12 +124,20 @@ namespace return name; }, [this](const ALTextPos& was) { went.push_back(was); }); + INFO("asked"); + REQUIRE(quick != nullptr); + REQUIRE(popover() != nullptr); } ALPopover* popover() const { return quick ? ALViewType::as(quick->getParentByType()) : nullptr; } // Return, as the field gives it. - void choose() { quick->findChild("query")->onCommit(); } + void require_chosen() + { + LLLineEditor* field = quick->findChild("query"); + REQUIRE(field != nullptr); + field->onCommit(); + } }; TEST_CASE_METHOD(altextgotoline_data, "altextgotoline: placeTyped reads line or line:column past blanks and a comma or as none", "[altextgotoline]") @@ -163,12 +173,7 @@ namespace { ALTextView& v = make("zero\none\ntwo\n\tthree\nfour\n"); v.setCaret(ALTextPos(1, 2)); - ask(); - { - INFO("asked"); - REQUIRE(quick != nullptr); - REQUIRE(popover() != nullptr); - } + require_asked(); UNSCOPED_INFO("nothing typed: says how many"); CHECK(word == std::string("GoToLineHint")); UNSCOPED_INFO("the first line, counted from one"); @@ -192,7 +197,7 @@ namespace UNSCOPED_INFO("nothing typed: back where it was"); CHECK(v.caret() == ALTextPos(1, 2)); quick->setQuery("5"); - choose(); + require_chosen(); UNSCOPED_INFO("kept"); CHECK(v.caret() == ALTextPos(4, 0)); { @@ -208,7 +213,7 @@ namespace { ALTextView& v = make("a\nb\nc\nd\n"); v.setCaret(ALTextPos(0, 1)); - ask(); + require_asked(); quick->setQuery("3"); popover()->escape(); UNSCOPED_INFO("escaped: back"); @@ -216,7 +221,7 @@ namespace UNSCOPED_INFO("nothing told"); CHECK(went.empty()); - ask(); + require_asked(); quick->setQuery("4"); popover()->onFocusLost(); UNSCOPED_INFO("looked away: it stands"); @@ -231,7 +236,7 @@ namespace TEST_CASE_METHOD(altextgotoline_data, "altextgotoline: lines counted from 0 as in a notecard make 0 the first line", "[altextgotoline]") { ALTextView& v = make("zero\none\ntwo\n"); - ask(-1); + require_asked(-1); UNSCOPED_INFO("the first line said as 0"); CHECK(said["[FIRST]"]() == std::string("0")); UNSCOPED_INFO("the last line, counted from 0"); @@ -253,7 +258,7 @@ namespace { ALTextView& v = make("zero\none\ntwo\n"); v.setCaret(ALTextPos(2, 1)); - ask(-1); + require_asked(-1); for (const char* typed : { "x", ":", "-3" }) { CAPTURE(typed); @@ -263,7 +268,7 @@ namespace } UNSCOPED_INFO("said as typed"); CHECK(said["[LINE]"]() == std::string("-3")); - choose(); + require_chosen(); UNSCOPED_INFO("Return leaves it there"); CHECK(v.caret() == ALTextPos(2, 1)); UNSCOPED_INFO("and tells of no way back"); @@ -274,7 +279,7 @@ namespace { ALTextView& v = make("zero\none\ntwo\n"); v.setCaret(ALTextPos(2, 1)); - ask(); + require_asked(); quick->setQuery("4294967298"); UNSCOPED_INFO("no such line"); CHECK(word == std::string("GoToLineNone")); @@ -292,7 +297,7 @@ namespace // The most a number holds, counted from 0: past the last line, not // round past the least. - ask(-1); + require_asked(-1); quick->setQuery("2147483647"); UNSCOPED_INFO("no such line, from 0"); CHECK(word == std::string("GoToLineNone")); @@ -307,7 +312,7 @@ namespace ALTextView& v = make("zero\none\ntwo\nthree\n"); const ALTextRange block(ALTextPos(0, 1), ALTextPos(1, 2)); v.setSelections(block, { ALTextRange(ALTextPos(3, 0), ALTextPos(3, 0)) }); - ask(); + require_asked(); UNSCOPED_INFO("opened: the selection as it was"); CHECK(v.selection() == block); UNSCOPED_INFO("and the caret besides it"); @@ -318,7 +323,7 @@ namespace UNSCOPED_INFO("and the other caret"); CHECK(v.otherSelections().size() == size_t(1)); - ask(); + require_asked(); quick->setQuery("3"); UNSCOPED_INFO("gone to the line typed"); CHECK(v.caret() == ALTextPos(2, 0)); diff --git a/indra/llui/tests/altextindent_test.cpp b/indra/llui/tests/altextindent_test.cpp index 256118bff0..5e2a5fc7ca 100644 --- a/indra/llui/tests/altextindent_test.cpp +++ b/indra/llui/tests/altextindent_test.cpp @@ -77,9 +77,10 @@ namespace } // A command at one selection, as its groups make it: the text left, // and where the selection goes, where its group places it. - static std::string applied(const std::string& text, const std::vector& groups, std::optional& placed) + static std::string require_applied(const std::string& text, const std::vector& groups, std::optional& placed) { const ALTextEditing::Combined combined = ALTextEditing::combine(groups, 1); + REQUIRE(combined.selections.size() == 1); ALTextDocument doc(text); std::vector> edits; for (const Replacement& one : combined.replacements) @@ -127,20 +128,26 @@ namespace const std::string text = "a\n\nb\n c"; const ALTextDocument doc(text); std::optional placed; - UNSCOPED_INFO("each line with anything on it a level in"); - CHECK(applied(text, indentLines(doc, { ALTextRange(ALTextPos(0, 1), ALTextPos(3, 3)) }, true, spaces), placed) == - std::string(" a\n\n b\n c")); + { + INFO("each line with anything on it a level in"); + CHECK(require_applied(text, indentLines(doc, { ALTextRange(ALTextPos(0, 1), ALTextPos(3, 3)) }, true, spaces), placed) == + std::string(" a\n\n b\n c")); + } UNSCOPED_INFO("the anchor moved with its line, and the caret too"); CHECK(placed == ALTextRange(ALTextPos(0, 5), ALTextPos(3, 7))); - UNSCOPED_INFO("a tab where tabs are typed"); - CHECK(applied(text, indentLines(doc, { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)) }, true, tabs), placed) == - std::string("\ta\n\nb\n c")); + { + INFO("a tab where tabs are typed"); + CHECK(require_applied(text, indentLines(doc, { ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0)) }, true, tabs), placed) == + std::string("\ta\n\nb\n c")); + } const std::string deep = "\tx\n y\n z"; const ALTextDocument deep_doc(deep); - UNSCOPED_INFO("a tab, or up to a level of spaces, out"); - CHECK(applied(deep, indentLines(deep_doc, { ALTextRange(ALTextPos(0, 0), ALTextPos(2, 1)) }, false, spaces), placed) == - std::string("x\n y\nz")); + { + INFO("a tab, or up to a level of spaces, out"); + CHECK(require_applied(deep, indentLines(deep_doc, { ALTextRange(ALTextPos(0, 0), ALTextPos(2, 1)) }, false, spaces), placed) == + std::string("x\n y\nz")); + } { INFO("the caret never before its line's start"); REQUIRE(placed); @@ -239,6 +246,7 @@ namespace return true; }; const Outdent closed = outdentAsTyped(braces_doc, ALTextPos(2, 9), ALTextPos(2, 9), '}', lsl, matched, AutoOutdent(), spaces); + REQUIRE(closed.replacement); UNSCOPED_INFO("level with the bracket's line"); CHECK(applied(braces, *closed.replacement) == std::string(" f() {\n x;\n }")); UNSCOPED_INFO("a bracket is kept for nothing"); @@ -273,16 +281,20 @@ namespace { const std::string code = "\tif x\n \ty\n z\nw"; const ALTextDocument indented(code); + const std::optional to_spaces = convertIndentation(indented, 0, 3, true, 4); + REQUIRE(to_spaces.has_value()); UNSCOPED_INFO("to spaces, a tab to its stop"); - CHECK(applied(code, *convertIndentation(indented, 0, 3, true, 4)) == - std::string(" if x\n y\n z\nw")); + CHECK(applied(code, *to_spaces) == std::string(" if x\n y\n z\nw")); + const std::optional to_tabs = convertIndentation(indented, 0, 3, false, 4); + REQUIRE(to_tabs.has_value()); UNSCOPED_INFO("to tabs, as far as they go"); - CHECK(applied(code, *convertIndentation(indented, 0, 3, false, 4)) == - std::string("\tif x\n\ty\n\t z\nw")); + CHECK(applied(code, *to_tabs) == std::string("\tif x\n\ty\n\t z\nw")); UNSCOPED_INFO("nothing to change is nothing"); CHECK_FALSE(convertIndentation(ALTextDocument(" a\nb"), 0, 1, true, 4)); + const std::optional measured = convertIndentation(ALTextDocument("\ta"), 0, 0, false, 4, 2); + REQUIRE(measured.has_value()); UNSCOPED_INFO("measured as the tabs were: a tab of two, two spaces at four"); - CHECK(applied("\ta", *convertIndentation(ALTextDocument("\ta"), 0, 0, false, 4, 2)) == std::string(" a")); + CHECK(applied("\ta", *measured) == std::string(" a")); } TEST_CASE_METHOD(altextindent_data, "altextindent: detect reads tabs or spaces and the level from the commonest step", "[altextindent]") @@ -333,8 +345,10 @@ namespace CHECK(from(0, 9) == std::string("one")); Options wide = spaces; wide.tabWidth = 8; + const std::optional wide_back = backspaceFrom(doc, ALTextPos(0, 8), wide); + REQUIRE(wide_back.has_value()); UNSCOPED_INFO("a level as wide as the tab"); - CHECK(backspaceFrom(doc, ALTextPos(0, 8), wide)->column == 0); + CHECK(wide_back->column == 0); } TEST_CASE_METHOD(altextindent_data, "altextindent: pasted lines go where they belong keeping their depth from the first", "[altextindent]") diff --git a/indra/llui/tests/altextlayout_test.cpp b/indra/llui/tests/altextlayout_test.cpp index 11958649e6..3231427d09 100644 --- a/indra/llui/tests/altextlayout_test.cpp +++ b/indra/llui/tests/altextlayout_test.cpp @@ -197,6 +197,7 @@ namespace UNSCOPED_INFO("a tab has width"); CHECK(space > 0.f); const ALTextLayout::Line& line = layout.line(0); + REQUIRE_FALSE(line.placed.empty()); UNSCOPED_INFO("the tab draws nothing"); CHECK(line.placed.front().face == nullptr); // 'ab' then a tab to the next stop: twice the first tab's width. @@ -394,6 +395,7 @@ namespace const ALTextLayout::Line& second = layout.line(1); UNSCOPED_INFO("'ab', the box, 'cd', the tab, 'z'"); REQUIRE(second.glyphs.size() == size_t(2 + 1 + 2 + 1 + 1)); + REQUIRE(second.placed.size() > 2u); UNSCOPED_INFO("the box draws nothing"); CHECK(second.placed[2].face == nullptr); UNSCOPED_INFO("the box is as wide as asked"); @@ -441,7 +443,7 @@ namespace UNSCOPED_INFO("the label is not broken across rows"); CHECK(layout.rowOf(0, 4) == layout.rowOf(0, 31)); UNSCOPED_INFO("but the text after it goes to the next row"); - CHECK((layout.rowOf(0, 32) > layout.rowOf(0, 4) || layout.rowCount(0) >= 2)); + CHECK(layout.rowOf(0, 32) > layout.rowOf(0, 4)); } TEST_CASE_METHOD(altextlayout_data, "altextlayout: a stretch in a font of its own makes its row as tall as that font", "[altextlayout]") @@ -469,6 +471,8 @@ namespace runs = { run }; layout.invalidateLine(0); const ALTextLayout::Line& line = layout.line(0); + REQUIRE_FALSE(line.rows.empty()); + REQUIRE(line.placed.size() > 17u); UNSCOPED_INFO("the row's text is as tall as the larger font"); CHECK(line.rows[0].textHeight == big->getLineSpacing()); UNSCOPED_INFO("and its ascent the larger"); @@ -724,6 +728,7 @@ namespace } ready(text.c_str()); const ALTextLayout::Line& line = layout.line(0); + REQUIRE_FALSE(line.rows.empty()); const ALTextLayout::Row& row = line.rows[0]; const ALTextLayout::Row all = ALTextLayout::rowWithin(line, row, -100.f, line.width + 100.f); { @@ -756,6 +761,7 @@ namespace REQUIRE(k < seen.glyphEnd); } } + REQUIRE(seen.glyphBegin + 2 <= seen.glyphEnd); UNSCOPED_INFO("one more before, no more"); CHECK(line.glyphs[seen.glyphBegin + 1].pen + line.glyphs[seen.glyphBegin + 1].advance - row.xStart >= from); UNSCOPED_INFO("one more after, no more"); @@ -1253,6 +1259,7 @@ namespace // A word after the text before 'b', which the caret at b's column // stands before. const ALTextLayout::Line& line = layout.line(0); + REQUIRE_FALSE(line.rows.empty()); F32 b_left = -1.f; for (const ALTextLayout::Glyph& glyph : line.glyphs) { diff --git a/indra/llui/tests/altextsearch_test.cpp b/indra/llui/tests/altextsearch_test.cpp index 0987cd429b..b75fb9e03d 100644 --- a/indra/llui/tests/altextsearch_test.cpp +++ b/indra/llui/tests/altextsearch_test.cpp @@ -89,6 +89,7 @@ namespace UNSCOPED_INFO("and says why"); CHECK_FALSE(error.empty()); const std::vector found = ALTextSearch::matches(doc, "([a-z])(\\d+)", options); + REQUIRE(found.size() == size_t(3)); UNSCOPED_INFO("groups filled"); CHECK(ALTextSearch::replacement(doc, found[1], "([a-z])(\\d+)", options, "$2$1") == std::string("22b")); options.regex = false; @@ -131,6 +132,7 @@ namespace UNSCOPED_INFO("mixed, as typed"); CHECK(ALTextSearch::replacement(doc, found[3], "hello", options, "wORld") == std::string("wORld")); const std::vector digits = ALTextSearch::matches(doc, "123", options); + REQUIRE(digits.size() == size_t(1)); UNSCOPED_INFO("no letters, as typed"); CHECK(ALTextSearch::replacement(doc, digits[0], "123", options, "Abc") == std::string("Abc")); options.preserveCase = false; @@ -335,12 +337,18 @@ namespace std::thread other([&]() { for (int round = 0; round < 200; ++round) { - wrong = wrong || ALTextSearch::matches(doc, "be\\w+", regex).size() != 3; + if (ALTextSearch::matches(doc, "be\\w+", regex).size() != 3) + { + wrong = true; + } } }); for (int round = 0; round < 200; ++round) { - wrong = wrong || ALTextSearch::matches(doc, "al\\w+|ga\\w+", regex).size() != 3; + if (ALTextSearch::matches(doc, "al\\w+|ga\\w+", regex).size() != 3) + { + wrong = true; + } } other.join(); UNSCOPED_INFO("each thread its own answers"); diff --git a/indra/llui/tests/altextundo_test.cpp b/indra/llui/tests/altextundo_test.cpp index 361da58978..8653cea67b 100644 --- a/indra/llui/tests/altextundo_test.cpp +++ b/indra/llui/tests/altextundo_test.cpp @@ -79,6 +79,7 @@ namespace } CHECK_FALSE(undo.canUndo()); caret = undo.redo(); + REQUIRE(caret.has_value()); UNSCOPED_INFO("redone"); CHECK(doc.text() == std::string("abc")); { @@ -148,7 +149,7 @@ namespace undo.markSaved(); UNSCOPED_INFO("saved is pristine"); CHECK(undo.isPristine()); - ALTextPos at = type(ALTextPos(0, 1), "y"); + type(ALTextPos(0, 1), "y"); UNSCOPED_INFO("changed"); CHECK_FALSE(undo.isPristine()); undo.undo(); @@ -199,44 +200,51 @@ namespace CHECK_FALSE(undo.canUndo()); } + // Steps of 300 KB, three of which the steps back hold: a fourth lets the + // oldest go, and the saved mark, counted from the bottom, moves down + // with it -- until the step that led to the saved text is let go too, + // and stepping back can never reach it, and the journal must not say it + // has. TEST_CASE_METHOD(altextundo_data, "altextundo: the saved mark follows forgotten steps and is lost once out of reach", "[altextundo]") { - // Five steps, saved, then more than the stack keeps: the steps - // that led to the saved text are gone, so stepping back can never - // reach it, and the journal must not say it has. - ALTextPos at = ALTextPos(0, 0); - for (S32 i = 0; i < 5; ++i) - { - at = type(at, "a", 2.0); - } + const std::string part(300 * 1024, 'a'); + ALTextPos at(0, 0); + at = type(at, part.c_str(), 2.0); + at = type(at, part.c_str(), 2.0); undo.markSaved(); UNSCOPED_INFO("saved"); CHECK(undo.isPristine()); - for (S32 i = 0; i < 100; ++i) - { - at = type(at, "b", 2.0); - } + at = type(at, part.c_str(), 2.0); + at = type(at, part.c_str(), 2.0); UNSCOPED_INFO("changed since"); CHECK_FALSE(undo.isPristine()); - for (S32 i = 0; i < 95; ++i) - { - undo.undo(); - } - UNSCOPED_INFO("five steps in force again, but not the saved five"); + undo.undo(); + UNSCOPED_INFO("two steps in force again, but not the saved two"); CHECK_FALSE(undo.isPristine()); - // Saved again with a full stack: another step drops one, and the - // mark with it keeps counting from the bottom. - for (S32 i = 0; i < 95; ++i) + undo.undo(); + UNSCOPED_INFO("back to the saved text, the mark having moved down"); + CHECK(undo.isPristine()); + undo.undo(); + UNSCOPED_INFO("and one step back past it, the oldest having gone"); + CHECK_FALSE(undo.canUndo()); + + // Forward again, and on until the step that led to the saved text is + // let go too. + for (S32 i = 0; i < 3; ++i) { undo.redo(); } - undo.markSaved(); - at = type(at, "c", 2.0); - UNSCOPED_INFO("changed"); + at = type(at, part.c_str(), 2.0); + UNSCOPED_INFO("the saved text at the bottom of the stack, still reached"); + CHECK(undo.savedText().has_value()); + type(at, part.c_str(), 2.0); + UNSCOPED_INFO("the step that led to it gone, and the saved text with it"); + CHECK_FALSE(undo.savedText().has_value()); + while (undo.undo()) + { + } + UNSCOPED_INFO("stepping back as far as it goes never finds it"); CHECK_FALSE(undo.isPristine()); - undo.undo(); - UNSCOPED_INFO("back to the saved text, the mark having moved down"); - CHECK(undo.isPristine()); } // A save point is taken when a text is sent to be saved, and marks that @@ -383,7 +391,10 @@ namespace CHECK(other.text() == std::string("alpha gamma")); // A saved text among the steps forward is found forward. + undo.markSaved(); undo.undo(); + UNSCOPED_INFO("saved a step forward"); + CHECK(undo.savedText() == std::optional("alpha beta")); undo.markSaved(); undo.redo(); UNSCOPED_INFO("saved a step back"); @@ -696,8 +707,10 @@ namespace put(1.0); big.markSaved(); put(3.0); - UNSCOPED_INFO("two of 400 KB kept"); + big.undo(); + UNSCOPED_INFO("two of 400 KB kept: one still back past the newest"); CHECK(big.canUndo()); + big.redo(); put(5.0); UNSCOPED_INFO("three written"); CHECK(big_doc.text().size() == part.size() * 3); diff --git a/indra/llui/tests/altextview_gl_test.cpp b/indra/llui/tests/altextview_gl_test.cpp index de6b2be60d..9c4e464beb 100644 --- a/indra/llui/tests/altextview_gl_test.cpp +++ b/indra/llui/tests/altextview_gl_test.cpp @@ -32,10 +32,10 @@ #include "altextruler.h" #include "altextview.h" #include "../llui.h" -#include "../llfocusmgr.h" #include "../lluictrlfactory.h" #include "alheadlessui_fixture.h" +#include "aluistatescope.h" #include @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -78,6 +79,49 @@ namespace return out; } + // Where two frames, or two strips of them, part: how many pixels differ + // and the first of them, so that a failure names a place rather than + // printing both whole. A row is width pixels of channels bytes, bottom + // row first; frames of different sizes differ by what the longer has + // over the shorter. + struct FrameDiff + { + size_t pixels = 0; + S32 x = -1; + S32 y = -1; + + bool operator==(const FrameDiff&) const = default; + }; + + std::ostream& operator<<(std::ostream& out, const FrameDiff& d) + { + if (d.pixels == 0) + { + return out << "no pixel"; + } + return out << d.pixels << " pixels, the first at (" << d.x << ", " << d.y << ")"; + } + + FrameDiff frameDiff(const std::vector& a, const std::vector& b, S32 width = W, S32 channels = 4) + { + FrameDiff d; + const size_t step = static_cast(channels); + const size_t common = std::min(a.size(), b.size()); + for (size_t i = 0; i + step <= common; i += step) + { + if (!std::equal(a.begin() + i, a.begin() + i + step, b.begin() + i)) + { + if (d.pixels++ == 0) + { + d.x = static_cast((i / step) % static_cast(width)); + d.y = static_cast((i / step) / static_cast(width)); + } + } + } + d.pixels += (std::max(a.size(), b.size()) - common + step - 1) / step; + return d; + } + // A keymap that is always typing the same line, and counts how often // the view asks it what the line is. struct TypingKeymap : public ALModalKeymap @@ -143,12 +187,33 @@ namespace { struct altextview_gl_data { + // Focus as the case found it, whatever it leaves it on. + ll_test::FocusScope focus; + // The views a case made, dying when it ends however it ends: a + // failed REQUIRE skips whatever comes after it. + std::vector views; + altextview_gl_data() { ll_test::uiGL(); ll_test::installWhiteTexture(); } + ~altextview_gl_data() + { + for (LLView* view : views) + { + view->die(); + } + } + + template + T* keep(T* view) + { + views.push_back(view); + return view; + } + // What a squiggle from x0 at height y covers of a pixel, worked out // afresh: how near the wave comes to the pixel's middle, the line // solid for three quarters of a point either side and fading over @@ -217,8 +282,8 @@ namespace const Difference d = differ(was, now, 40, 140); { CAPTURE(d.most, d.covered, d.compared); - UNSCOPED_INFO("the wave covers something"); - CHECK(d.covered > 0); + UNSCOPED_INFO("the squiggle covers something"); + CHECK(d.compared > 0); UNSCOPED_INFO("no pixel far from the wave's"); CHECK(d.most <= 6); UNSCOPED_INFO("as much covered as the wave"); @@ -290,8 +355,8 @@ namespace scale = was_scale; const Difference d = differ(was, now, 20, 220); CAPTURE(d.most, d.covered, d.compared); - UNSCOPED_INFO("the wave covers something"); - CHECK(d.covered > 0); + UNSCOPED_INFO("the squiggle covers something"); + CHECK(d.compared > 0); UNSCOPED_INFO("no pixel far from the wave's"); CHECK(d.most <= 6); UNSCOPED_INFO("as much covered as the wave"); @@ -316,7 +381,7 @@ namespace p.name = "view"; p.rect = LLRect(0, H, W, 0); p.default_text = text; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); view->setScrollMap(true); // The map's columns, at the right of the view. @@ -342,14 +407,13 @@ namespace UNSCOPED_INFO("the map draws its lines"); REQUIRE(std::adjacent_find(first.begin(), first.end(), std::not_equal_to()) != first.end()); UNSCOPED_INFO("the same map when nothing changed"); - CHECK(map_of() == first); + CHECK(frameDiff(map_of(), first, W - map_left) == FrameDiff()); view->document().replace(ALTextRange(ALTextPos(3, 4), ALTextPos(3, 5)), "\t"); const std::vector tabbed = map_of(); UNSCOPED_INFO("a space made a tab changes the map"); - CHECK(tabbed != first); + CHECK(frameDiff(tabbed, first, W - map_left).pixels > 0); UNSCOPED_INFO("and it stays as it now is"); - CHECK(map_of() == tabbed); - view->die(); + CHECK(frameDiff(map_of(), tabbed, W - map_left) == FrameDiff()); } // The code editor draws its rows in passes -- what is behind the text, // every row's glyphs in one call, then what is over it -- and each @@ -374,7 +438,7 @@ namespace p.rect = LLRect(0, H, W, 0); p.default_text = text; p.syntax = "lsl"; - ALCodeEditor* editor = LLUICtrlFactory::create(p); + ALCodeEditor* editor = keep(LLUICtrlFactory::create(p)); editor->setFont(LLFontGL::getFontMonospace()); // With the keyboard, so that a selection is drawn as the one being // worked in; read-only, so that no caret blinks between frames. @@ -422,13 +486,13 @@ namespace const std::vector plain = frame(); UNSCOPED_INFO("the same frame again"); - CHECK(frame() == plain); + CHECK(frameDiff(frame(), plain) == FrameDiff()); editor->setSelection(ALTextRange(ALTextPos(2, 0), ALTextPos(2, 20))); require_only_on("a selection", plain, frame(), 2); editor->setSelection(ALTextRange(ALTextPos(0, 0), ALTextPos(0, 0))); UNSCOPED_INFO("the selection gone"); - CHECK(frame() == plain); + CHECK(frameDiff(frame(), plain) == FrameDiff()); // A colour of its own over part of a line: each row's glyphs carry // their own colours into the one call. @@ -439,7 +503,7 @@ namespace require_only_on("a colour of its own", plain, frame(), 5); editor->clearStyles(); UNSCOPED_INFO("the colour gone"); - CHECK(frame() == plain); + CHECK(frameDiff(frame(), plain) == FrameDiff()); ALCodeEditor::Decoration d; d.range = ALTextRange(ALTextPos(7, 8), ALTextPos(7, 13)); @@ -459,8 +523,6 @@ namespace glFinish(); UNSCOPED_INFO("the frame is a handful of draws"); CHECK(capture.size() <= 25); - gFocusMgr.setKeyboardFocus(nullptr); - editor->die(); } // The preview of the lines under the mouse on the map is drawn over the @@ -482,13 +544,14 @@ namespace p.name = "view"; p.rect = LLRect(0, H, W, 0); p.default_text = text; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); view->setScrollMap(true); view->setScrollMapWidth(60); view->setBackgroundColor(LLColor4(0.1f, 0.1f, 0.12f, 1.f)); view->setTextColor(LLColor4(0.9f, 0.9f, 0.9f, 1.f)); ALTextRuler* map = view->findChild("ruler"); + REQUIRE(map != nullptr); // Every pixel of the view, as it draws. const auto drawn = [&]() { ll_test::uiGL().clearFramebuffer(); @@ -525,7 +588,6 @@ namespace UNSCOPED_INFO("the preview over the text, the map on the " << (left ? "left" : "right")); REQUIRE(changed(plain, shown, text_from, text_to) > 1000); } - view->die(); } // A row's squiggle is under its text: nothing at or above the baseline, @@ -585,7 +647,7 @@ namespace p.default_text = text; // The skin's colours are not loaded here: the caret's is given. p.cursor_color = LLUIColor(LLColor4::white); - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); view->setSelectionColor(LLUIColor(LLColor4(0.2f, 0.4f, 0.9f, 0.6f))); // Shown, and still from frame to frame. @@ -636,7 +698,7 @@ namespace require_only_on("another caret", one, two, 2); view->setSelections(at(2, 5), { at(4, 0) }); UNSCOPED_INFO("the two the other way round, the same frame"); - CHECK(frame() == two); + CHECK(frameDiff(frame(), two) == FrameDiff()); const ALTextRange span(ALTextPos(7, 2), ALTextPos(7, 10)); view->setSelections(at(4, 0), { span }); @@ -644,7 +706,7 @@ namespace require_only_on("another selection, its caret at its end", one, other_span, 7); view->setSelections(span, { at(4, 0) }); UNSCOPED_INFO("the same as the main selection"); - CHECK(frame() == other_span); + CHECK(frameDiff(frame(), other_span) == FrameDiff()); // A caret on every line: the carets are one draw, not one each. std::vector many; @@ -669,8 +731,6 @@ namespace glFinish(); UNSCOPED_INFO("twelve carets cost what one does"); CHECK(with_many == capture.size()); - gFocusMgr.setKeyboardFocus(nullptr); - view->die(); } // A gap: its tint across the text and nothing else in it, the line // under it drawn as far down as the gap is tall, and the rows below @@ -690,7 +750,7 @@ namespace // black. p.text_color = LLUIColor(LLColor4::white); p.bg_visible = false; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); const auto frame = [&]() { ll_test::uiGL().clearFramebuffer(); @@ -745,7 +805,6 @@ namespace CHECK(lit(after, 5, 1, 1, 200) >= width * (row_h - 2)); CHECK(lit(after, 6, 1, 1, 200) == 0); } - view->die(); } // What goes by row is cut at the text's edge: a code editor scrolled @@ -769,7 +828,7 @@ namespace { text += "MMMM " + std::to_string(n) + "\n"; } - ALCodeEditor* editor = LLUICtrlFactory::create(p); + ALCodeEditor* editor = keep(LLUICtrlFactory::create(p)); editor->setFont(LLFontGL::getFontMonospace()); editor->setText(text); const S32 row_h = editor->layout().rowHeight(); @@ -800,7 +859,6 @@ namespace // A clip takes in the row its top edge names, as a rect is drawn. UNSCOPED_INFO("nothing of it above the text"); CHECK(drawnIn(with_atom, x0, x0 + 40, editor->getRect().mBottom + area.mTop + 1, H) == 0); - editor->die(); } // The ruler's marks cost what one does, however many lines have one: a @@ -821,7 +879,7 @@ namespace p.name = "view"; p.rect = LLRect(0, H, W, 0); p.default_text = text; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); ALTextRuler* ruler = view->findChild("ruler"); // A frame's draws, the ruler put in its place by a frame before it. @@ -857,7 +915,6 @@ namespace const size_t every = draws(); UNSCOPED_INFO("two hundred marks and a gap's cost what one does"); CHECK(every == one); - view->die(); } // A tint behind every line in sight and every gap costs what one does, @@ -880,7 +937,7 @@ namespace p.name = "view"; p.rect = LLRect(0, H, W, 0); p.default_text = text; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); // A frame's draws, laid out by a frame before it. const auto draws = [&]() { @@ -942,7 +999,6 @@ namespace const size_t every_line = draws(); UNSCOPED_INFO("underlines on every line cost what one line's do"); CHECK(every_line == one_line); - view->die(); } // The blocks the gutter draws its markers by are found again once the @@ -967,7 +1023,7 @@ namespace p.rect = LLRect(0, H, W, 0); p.syntax = "lsl"; p.show_fold_markers = true; - ALCodeEditor* editor = LLUICtrlFactory::create(p); + ALCodeEditor* editor = keep(LLUICtrlFactory::create(p)); editor->setFont(LLFontGL::getFontMonospace()); editor->setText(text); const S32 last = editor->document().lineCount() - 1; @@ -990,7 +1046,6 @@ namespace frame(); UNSCOPED_INFO("lexed to its end: the block gone"); CHECK(editor->foldRegions().empty()); - editor->die(); } // What is being composed is underlined under its own text on each row @@ -1016,7 +1071,7 @@ namespace // black. p.text_color = LLUIColor(LLColor4::white); p.bg_visible = false; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); view->setWordWrap(true); const auto frame = [&]() { @@ -1095,7 +1150,6 @@ namespace CHECK(changed_in(second, 0, second_from) == 0); UNSCOPED_INFO("nothing on the second row past where it ends"); CHECK(changed_in(second, second_to, W) == 0); - view->die(); } // The preview of the lines under the mouse on the map numbers them as @@ -1117,7 +1171,7 @@ namespace p.name = "editor"; p.rect = LLRect(0, H, W, 0); p.default_text = text; - ALCodeEditor* editor = LLUICtrlFactory::create(p); + ALCodeEditor* editor = keep(LLUICtrlFactory::create(p)); editor->setFont(LLFontGL::getFontMonospace()); // No gutter, so that only the preview says a line's number; read // only, so that no caret blinks between frames. @@ -1164,7 +1218,6 @@ namespace editor->setLineNumberBase(0); UNSCOPED_INFO("and from one again as before"); CHECK(changed(from_one, drawn()) == 0); - editor->die(); } // A gap put between the lines moves the map's rows below it down, each @@ -1193,7 +1246,7 @@ namespace p.name = "view"; p.rect = LLRect(0, H, W, 0); p.default_text = text; - ALTextView* made = LLUICtrlFactory::create(p); + ALTextView* made = keep(LLUICtrlFactory::create(p)); made->setFont(LLFontGL::getFontMonospace()); made->setScrollMap(true); made->setScrollMapWidth(60); @@ -1232,13 +1285,11 @@ namespace drawn_first->setLineAnnotations(gapped); const std::vector after = map_of(drawn_first); UNSCOPED_INFO("the gap moved the map"); - CHECK(after != before); + CHECK(frameDiff(after, before, 60, 3).pixels > 0); ALTextView* gapped_first = make(); gapped_first->setLineAnnotations(gapped); UNSCOPED_INFO("as a view with the gap from the start draws it"); - CHECK(after == map_of(gapped_first)); - drawn_first->die(); - gapped_first->die(); + CHECK(frameDiff(after, map_of(gapped_first), 60, 3) == FrameDiff()); } // The code editor's words beside the text -- an inlay's in its pill, a @@ -1256,7 +1307,7 @@ namespace p.name = "editor"; p.rect = LLRect(0, H, W, 0); p.syntax = "lsl"; - ALCodeEditor* editor = LLUICtrlFactory::create(p); + ALCodeEditor* editor = keep(LLUICtrlFactory::create(p)); editor->setFont(LLFontGL::getFontMonospace()); // Read-only, so that no caret blinks between frames. editor->setReadOnly(true); @@ -1306,7 +1357,6 @@ namespace const size_t every = draws(); UNSCOPED_INFO("hints, notes and tabs on every line cost what one line's do"); CHECK(every == one); - editor->die(); } // A selection that goes on past a line's end is drawn a little past the @@ -1327,7 +1377,7 @@ namespace // black. p.text_color = LLUIColor(LLColor4::white); p.bg_visible = false; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); view->setSelectionColor(LLUIColor(LLColor4(0.f, 0.f, 1.f, 1.f))); // The first line in from the edge, as a log's are. @@ -1359,7 +1409,6 @@ namespace CHECK(past > 0); UNSCOPED_INFO("a little, not as far again as the first line is in"); CHECK(past <= 10); - view->die(); } // The band a modal keymap has under the text is read and measured once @@ -1377,7 +1426,7 @@ namespace p.name = "view"; p.rect = LLRect(0, H, W, 0); p.default_text = "text"; - ALTextView* view = LLUICtrlFactory::create(p); + ALTextView* view = keep(LLUICtrlFactory::create(p)); view->setFont(LLFontGL::getFontMonospace()); S32 asked = 0; auto keymap = std::make_unique(); @@ -1396,11 +1445,16 @@ namespace frame(); UNSCOPED_INFO("not again while nothing moved"); CHECK(asked == once); - // What a font reload does: the faces swapped under the same font. + // What a font reload does: the faces swapped under the same font; + // put back however the case ends, for the views of the cases after. + struct Generation + { + const S32 was = LLFontGL::sResolutionGeneration; + ~Generation() { LLFontGL::sResolutionGeneration = was; } + } generation; ++LLFontGL::sResolutionGeneration; frame(); UNSCOPED_INFO("read again once the fonts were loaded again"); CHECK(asked > once); - view->die(); } } diff --git a/indra/llui/tests/altextview_test.cpp b/indra/llui/tests/altextview_test.cpp index 4a95e41065..27ae1f8c04 100644 --- a/indra/llui/tests/altextview_test.cpp +++ b/indra/llui/tests/altextview_test.cpp @@ -100,6 +100,14 @@ namespace } } + // Where a line's first row ends. + S32 firstRowEnd(S32 line) + { + const ALTextLayout::Line& laid = view->layout().line(line); + REQUIRE_FALSE(laid.rows.empty()); + return laid.rows[0].end; + } + // The local point of a column on a line, in the middle of its row. void pointOf(S32 line, S32 column, S32& x, S32& y) { @@ -652,7 +660,7 @@ namespace CHECK(v.caret() == ALTextPos(0, 4)); // A click on the label follows the link once let go of. std::string followed; - v.onLinkClicked([&followed](const ALTextView::Substitution& s) { followed = s.value.asString(); }); + boost::signals2::scoped_connection heard = v.onLinkClicked([&followed](const ALTextView::Substitution& s) { followed = s.value.asString(); }); S32 x, y; pointOf(0, 4, x, y); x += 4; @@ -681,9 +689,11 @@ namespace // An edit above slides it; one through it drops it. v.setReadOnly(false); v.document().insert(ALTextPos(0, 0), "1\n"); + REQUIRE(v.substitutions().size() == size_t(1)); UNSCOPED_INFO("slid down a line"); CHECK(v.substitutions().front().range.begin == ALTextPos(1, 4)); v.document().insert(ALTextPos(1, 0), "ab"); + REQUIRE(v.substitutions().size() == size_t(1)); UNSCOPED_INFO("slid along"); CHECK(v.substitutions().front().range == ALTextRange(ALTextPos(1, 6), ALTextPos(1, 34))); v.document().remove(ALTextRange(ALTextPos(1, 10), ALTextPos(1, 12))); @@ -728,7 +738,7 @@ namespace UNSCOPED_INFO("a click inside lands at a side"); CHECK((v.posAtLocal(x, y, true) == ALTextPos(0, 2) || v.posAtLocal(x, y, true) == ALTextPos(0, 5))); S32 clicked = 0; - v.onAtomClicked([&clicked](const ALTextView::Atom& a) { clicked = a.value.asInteger(); }); + boost::signals2::scoped_connection heard = v.onAtomClicked([&clicked](const ALTextView::Atom& a) { clicked = a.value.asInteger(); }); v.handleMouseDown(x, y, MASK_NONE); v.handleMouseUp(x, y, MASK_NONE); UNSCOPED_INFO("clicked"); @@ -889,6 +899,7 @@ namespace v.document().insert(ALTextPos(0, 0), "\n"); { INFO("slid down a line"); + REQUIRE(v.styles().size() == size_t(2)); CHECK(v.styles()[0].range.begin == ALTextPos(1, 0)); CHECK(v.styles()[1].range.begin == ALTextPos(2, 5)); } @@ -1100,8 +1111,12 @@ namespace // An edited text keeps Tab for itself; F6 goes into the views. v.setReadOnly(false); v.setCaret(ALTextPos(0, 0)); - UNSCOPED_INFO("Tab is a tab now"); - CHECK((!v.handleKeyHere(KEY_TAB, MASK_NONE) || !v.atomViewFocused())); + { + INFO("Tab is a tab now: soft, to the first stop, the keyboard kept"); + CHECK(v.handleKeyHere(KEY_TAB, MASK_NONE)); + CHECK_FALSE(v.atomViewFocused()); + CHECK(v.document().line(0) == " line " + ALTextView::atomPlaceholder() + " 1"); + } require_key(KEY_F6); UNSCOPED_INFO("F6 goes into the first view"); CHECK(second->hasFocus()); @@ -1292,8 +1307,10 @@ namespace REQUIRE(found->value.asInteger() == 2); REQUIRE(found->at.column == glyph.cluster); } + const size_t laid_as = static_cast(glyph.substitution - (1 << 30)); + REQUIRE(laid_as < v.atoms().size()); UNSCOPED_INFO("where the number it was laid out with is another's now"); - REQUIRE(v.atoms()[static_cast(glyph.substitution - (1 << 30))].value.asInteger() != 2); + REQUIRE(v.atoms()[laid_as].value.asInteger() != 2); } UNSCOPED_INFO("a box on the line"); CHECK(seen); @@ -2454,6 +2471,7 @@ namespace v.setScrollMap(true); v.setScrollMapWidth(80); ALTextRuler* ruler = v.findChild("ruler"); + REQUIRE(ruler); UNSCOPED_INFO("the press is taken"); CHECK(v.handleMouseDown(360, 100, MASK_NONE)); { @@ -3344,7 +3362,7 @@ namespace lines[5].gapStop = true; v.setLineAnnotations(lines); S32 moved = 0; - v.onCaretMoved([&moved]() { ++moved; }); + boost::signals2::scoped_connection heard = v.onCaretMoved([&moved]() { ++moved; }); v.setCaret(ALTextPos(1, 2)); moved = 0; @@ -3436,7 +3454,7 @@ namespace } ALTextView& v = make(many.c_str()); S32 told = 0; - v.onScrolled([&told]() { ++told; }); + boost::signals2::scoped_connection heard = v.onScrolled([&told]() { ++told; }); v.setScrollY(60); UNSCOPED_INFO("scrolled"); CHECK(told == 1); @@ -3485,7 +3503,7 @@ namespace v.setLineAnnotations(lines); v.setCaret(ALTextPos(4, 2)); U32 told = 0; - v.onTextChanged([&told]() { ++told; }); + boost::signals2::scoped_connection heard = v.onTextChanged([&told]() { ++told; }); // Lines one and two, one line now. v.replaceText(ALTextRange(ALTextPos(1, 0), ALTextPos(2, 3)), "ONE"); CHECK(v.text() == std::string("xzero\nONE\nthree\nfour")); @@ -3714,7 +3732,7 @@ namespace v.setCaret(ALTextPos(1, 2)); S32 told = 0; S32 seen = -2; - v.onCaretMoved([&]() { + boost::signals2::scoped_connection heard = v.onCaretMoved([&]() { ++told; seen = v.caretGap(); }); @@ -3769,7 +3787,7 @@ namespace // How many letters a row holds: a word too long for one breaks // where it overflows. v.setText(std::string(200, 'a')); - const S32 fits = v.layout().line(0).rows[0].end; + const S32 fits = firstRowEnd(0); UNSCOPED_INFO("a row holds a few letters"); CHECK(fits > 4); // Rows of 'a..a ', 'bb ' -- the word after it does not fit beside @@ -3806,7 +3824,7 @@ namespace ALTextView& v = make(""); v.setWordWrap(true); v.setText(std::string(200, 'a')); - const S32 fits = v.layout().line(0).rows[0].end; + const S32 fits = firstRowEnd(0); UNSCOPED_INFO("a row holds a few letters"); CHECK(fits > 6); // A word too long for a row, shown as it is: all of the first two @@ -3822,7 +3840,7 @@ namespace UNSCOPED_INFO("three rows"); CHECK(v.layout().rowCount(0) == 3); std::string followed; - v.onLinkClicked([&followed](const ALTextView::Substitution& s) { followed = s.value.asString(); }); + boost::signals2::scoped_connection heard = v.onLinkClicked([&followed](const ALTextView::Substitution& s) { followed = s.value.asString(); }); const auto click = [&](S32 column) { S32 x = 0, y = 0; pointOf(0, column, x, y); @@ -3861,7 +3879,7 @@ namespace CHECK(v.layout().line(0).rows[0].end == 5); } S32 clicked = 0; - v.onAtomClicked([&clicked](const ALTextView::Atom& a) { clicked = a.value.asInteger(); }); + boost::signals2::scoped_connection heard = v.onAtomClicked([&clicked](const ALTextView::Atom& a) { clicked = a.value.asInteger(); }); S32 x, y; pointOf(0, 2, x, y); x += picture.width / 2; @@ -4264,8 +4282,9 @@ namespace v.setScrollMap(true); v.setScrollMapWidth(80); ALTextRuler* ruler = v.findChild("ruler"); - S32 top = 0; - S32 bottom = 0; + REQUIRE(ruler); + S32 top = 0; + S32 bottom = 0; ll_test::TextViewProbe::mapWindow(*ruler, top, bottom); { INFO("the window at the map's top"); diff --git a/indra/llui/tests/alundostack_test.cpp b/indra/llui/tests/alundostack_test.cpp index fe5899102a..b02e72d6b4 100644 --- a/indra/llui/tests/alundostack_test.cpp +++ b/indra/llui/tests/alundostack_test.cpp @@ -54,6 +54,14 @@ namespace } } } + + // The step back taken and made the step forward, as an undo does. + static void undoOne(ALUndoStack& stack) + { + std::optional taken = stack.takeUndo(); + REQUIRE(taken); + stack.pushRedo(std::move(*taken)); + } }; // A change noted is a step back; taking it makes it a step forward @@ -128,12 +136,10 @@ namespace UNSCOPED_INFO("a frame later names nothing"); CHECK(stack.undoLabel() == std::string()); - std::optional back = stack.takeUndo(); - stack.pushRedo(*back); + undoOne(stack); UNSCOPED_INFO("the step forward keeps its name"); CHECK(stack.redoLabel() == std::string()); - back = stack.takeUndo(); - stack.pushRedo(*back); + undoOne(stack); UNSCOPED_INFO("and so does the next"); CHECK(stack.redoLabel() == std::string("Set the fill")); } @@ -161,8 +167,7 @@ namespace ALUndoStack stack; stack.note({ { "a" } }, "slider", 0.0, 1.0, join); stack.note({ { "b" } }, "other", 0.1, 1.0, join); - auto step = stack.takeUndo(); - stack.pushRedo(*step); + undoOne(stack); stack.note({ { "c" } }, "other", 0.2, 1.0, join); UNSCOPED_INFO("an edit after undo starts a new step"); REQUIRE(stack.inForce() == size_t(2)); @@ -258,7 +263,7 @@ namespace const auto back_to_start = [](const Step& step) { return step.mNames.size() >= 2 && step.mNames.back() == "start"; }; stack.note({ { "one" } }, "", 0.0, 1.0, join); stack.note({ { "two" } }, "", 5.0, 1.0, join); - stack.pushRedo(*stack.takeUndo()); + undoOne(stack); stack.note({ { "drag" } }, "slider", 10.0, 1.0, join, back_to_start); UNSCOPED_INFO("a drag is a step"); CHECK(stack.inForce() == size_t(2)); @@ -334,8 +339,9 @@ namespace CHECK_FALSE(stack.closeGroups()); stack.beginGroup(); stack.note({ { "6" } }, "", 40.0, 1.0, join); - stack.pushRedo(*stack.takeUndo()); + undoOne(stack); stack.note({ { "7" } }, "", 40.0, 1.0, join); + REQUIRE(stack.canUndo()); UNSCOPED_INFO("taken back while open: the next change a step of its own"); CHECK(stack.undone().back().mNames == std::vector{ "7" }); stack.clear(); @@ -405,10 +411,10 @@ namespace } stack.note({ { "d" } }, "", 20.0, 1.0, join); - stack.pushRedo(*stack.takeUndo()); + undoOne(stack); stack.note({ { "e" } }, "", 30.0, 1.0, join); - stack.pushRedo(*stack.takeUndo()); - stack.pushRedo(*stack.takeUndo()); + undoOne(stack); + undoOne(stack); std::vector forward = stack.takeForward(); { INFO("taken out"); diff --git a/indra/llui/tests/alviewtype_test.cpp b/indra/llui/tests/alviewtype_test.cpp index fe60143775..a8756c471c 100644 --- a/indra/llui/tests/alviewtype_test.cpp +++ b/indra/llui/tests/alviewtype_test.cpp @@ -134,7 +134,7 @@ namespace }; // Each class answers for itself and every base that is a kind, and for - // nothing else. A type with no kind is still answered, the slow way. + // nothing else; a class a test declares answers as the library's do. TEST_CASE_METHOD(alviewtype_data, "alviewtype: as<> finds a view's own class and each base kind but no other", "[alviewtype]") { if (!ui.ok()) @@ -207,7 +207,7 @@ namespace CHECK(inner->getParentByType() == f); UNSCOPED_INFO("the nearest floater view above it, through the floater"); CHECK(inner->getParentByType() == fv.get()); - UNSCOPED_INFO("no ancestor is a type with no kind"); + UNSCOPED_INFO("its ancestors are controls, but none a TestCtrl"); CHECK(inner->getParentByType() == nullptr); fv.reset(); @@ -361,6 +361,10 @@ namespace // Bound by on_check, a binding the item's own params do not name. TEST_CASE_METHOD(alviewtype_data, "alviewtype: a check item bound by on_check stays bound and shows the setting", "[alviewtype]") { + if (!ui.ok()) + { + SKIP("no UI: LLUI_TEST_APP_DIR does not point at the source tree"); + } LLControlGroup* config = LLUI::getInstance()->mSettingGroups["config"]; if (!config->controlExists("ViewTypeCheck")) { diff --git a/indra/llui/tests/alvimkeymap_test.cpp b/indra/llui/tests/alvimkeymap_test.cpp index 5be45a964c..21771ae3ee 100644 --- a/indra/llui/tests/alvimkeymap_test.cpp +++ b/indra/llui/tests/alvimkeymap_test.cpp @@ -571,14 +571,15 @@ namespace TEST_CASE_METHOD(alvimkeymap_data, "alvimkeymap: macros: q records keys as text; @ and @@ play them; qA adds", "[alvimkeymap]") { ALCodeEditor& e = make("one\ntwo\nthree\nfour\n"); - keys("qaA;jq"); + keys("qaA;j"); + UNSCOPED_INFO("recording says so while it runs"); + CHECK(vim->recording()); + keys("q"); CHECK_FALSE(vim->recording()); UNSCOPED_INFO("the register holds the keys as text"); CHECK(vim->registerText('a') == std::string("A;j")); UNSCOPED_INFO("the recording ran as it was typed"); CHECK(flat(e.text()) == std::string("one;|two|three|four|")); - UNSCOPED_INFO("recording says so while it runs"); - CHECK(true); keys("@a"); UNSCOPED_INFO("played once"); CHECK(flat(e.text()) == std::string("one;|two;|three|four|")); @@ -2003,6 +2004,7 @@ namespace keys(":di a"); { INFO("only those named"); + REQUIRE(listed.size() == size_t(2)); CHECK(listed[1].find("\"0") == std::string::npos); CHECK(listed[1].find("\"a") != std::string::npos); } @@ -2012,9 +2014,11 @@ namespace listed.pop_back(); keys("mb:marks"); + REQUIRE(listed.size() == size_t(3)); CHECK(listed[2] == std::string("mark line col file/text\n b 2 0 two three")); keys("G"); keys(":marks"); + REQUIRE(listed.size() == size_t(4)); UNSCOPED_INFO("a jump's"); CHECK(listed[3].find("\n ' 2 0 two three") != std::string::npos); @@ -3011,11 +3015,16 @@ namespace keys("q:"); UNSCOPED_INFO("the window holds the pick"); REQUIRE(static_cast(later)); + // The : history outlives the vim it was typed into: a line run + // would be the last on it. + const std::shared_ptr vim_shared = vim->sharedState(); e.setModalKeymap(nullptr); vim = nullptr; later("set nonumber", true); - UNSCOPED_INFO("vim gone: nothing"); + UNSCOPED_INFO("vim gone: nothing put back"); CHECK_FALSE(e.modalKeymap()); + UNSCOPED_INFO("and nothing run"); + CHECK(vim_shared->command == std::vector{ "set number" }); auto keymap = std::make_unique(); vim = keymap.get(); @@ -3025,12 +3034,13 @@ namespace keys(":set numberq:"); UNSCOPED_INFO("held again"); REQUIRE(static_cast(later)); + const std::shared_ptr tab_shared = vim->sharedState(); editor->die(); editor = nullptr; LLMortician::updateClass(); later("set nonumber", true); - UNSCOPED_INFO("the tab gone: nothing"); - CHECK(true); + UNSCOPED_INFO("the tab gone: nothing run"); + CHECK(tab_shared->command == std::vector{ "set number" }); } TEST_CASE_METHOD(alvimkeymap_data, "alvimkeymap: vim's Control motions ^H ^J ^N ^P ^M and ^L; Tab forward in the jumps", "[alvimkeymap]") @@ -6630,62 +6640,66 @@ namespace TEST_CASE_METHOD(alvimkeymap_data, "alvimkeymap: the : commands are taken by any abbreviation vim takes", "[alvimkeymap]") { + // Each run, and none of them unknown. + const auto run = [this](const char* line) { + ex(line); + INFO(":" << line << " said: " << vim->message()); + CHECK_FALSE(vim->messageIsError()); + }; ALCodeEditor& e = make("a b\nc\n"); - ex("joi"); + run("joi"); UNSCOPED_INFO(":joi joins"); CHECK(flat(e.text()) == std::string("a b c|")); make("a\nb\nc\n"); - ex("dele"); + run("dele"); UNSCOPED_INFO(":dele deletes"); CHECK(flat(editor->text()) == std::string("b|c|")); make("abc\n"); - ex("norma x"); + run("norma x"); UNSCOPED_INFO(":norma plays the keys"); CHECK(flat(editor->text()) == std::string("bc|")); make(" a\n"); - ex("lef"); + run("lef"); UNSCOPED_INFO(":lef to the left"); CHECK(flat(editor->text()) == std::string("a|")); make("a\n"); - ex("rig 5"); + run("rig 5"); UNSCOPED_INFO(":rig to the right"); CHECK(flat(editor->text()) == std::string(" a|")); make("a\nb\nc\n"); - ex("3mo 0"); + run("3mo 0"); UNSCOPED_INFO(":mo moves"); CHECK(flat(editor->text()) == std::string("c|a|b|")); make("a\nb\nc\n"); - ex("3cop 0"); + run("3cop 0"); UNSCOPED_INFO(":cop copies"); CHECK(flat(editor->text()) == std::string("c|a|b|c|")); make("a\nxb\nc\n"); - ex("gl/x/d"); + run("gl/x/d"); CHECK(flat(editor->text()) == std::string("a|c|")); make("a\nxb\nc\n"); - ex("vg/x/d"); + run("vg/x/d"); CHECK(flat(editor->text()) == std::string("xb|")); make("aa\n"); - ex("su/a/b/"); + run("su/a/b/"); CHECK(flat(editor->text()) == std::string("ba|")); make("a\nb\n"); - ex("ya"); - ex("pu"); + run("ya"); + run("pu"); UNSCOPED_INFO(":ya then :pu"); CHECK(flat(editor->text()) == std::string("a|a|b|")); make("a\n"); keys("x"); - ex("und"); + run("und"); UNSCOPED_INFO(":und undoes"); CHECK(flat(editor->text()) == std::string("a|")); - UNSCOPED_INFO("none of them unknown: " << vim->message()); - CHECK_FALSE(vim->messageIsError()); make("a\nb\n"); keys("gg"); - ex("2mar a"); + run("2mar a"); keys("'a"); UNSCOPED_INFO(":mar sets a mark"); CHECK(caretText() == std::string("1:0")); - ex("setlo ic"); + run("setlo ic"); UNSCOPED_INFO(":setlo sets"); CHECK(vim->shared().ignoreCase); } diff --git a/indra/llui/tests/alvimmappings_test.cpp b/indra/llui/tests/alvimmappings_test.cpp index f83c28843f..f1e0759805 100644 --- a/indra/llui/tests/alvimmappings_test.cpp +++ b/indra/llui/tests/alvimmappings_test.cpp @@ -103,9 +103,13 @@ namespace } UNSCOPED_INFO("shown back as :map lists them"); CHECK(spelt("a") == std::string("a")); - CHECK(maps.keysOf("").front().isChar); - CHECK(maps.keysOf("").front().ch == 'A'); - CHECK(maps.keysOf("").front().key == KEY_ESCAPE); + const std::vector shifted = maps.keysOf(""); + REQUIRE(shifted.size() == size_t(1)); + CHECK(shifted[0].isChar); + CHECK(shifted[0].ch == 'A'); + const std::vector escape = maps.keysOf(""); + REQUIRE(escape.size() == size_t(1)); + CHECK(escape[0].key == KEY_ESCAPE); } TEST_CASE_METHOD(alvimmappings_data, "alvimmappings: is the leader :let set when the mapping was made", "[alvimmappings]") @@ -162,8 +166,10 @@ namespace } UNSCOPED_INFO("Q in operator-pending mode too"); CHECK(maps.match(ALVimMappings::OPERATOR, chars("Q"), true).full != nullptr); + const std::vector control_l = maps.keysOf(""); + REQUIRE(control_l.size() == size_t(1)); UNSCOPED_INFO("the : line has "); - CHECK(maps.starts(ALVimMappings::COMMAND_LINE, maps.keysOf("").front())); + CHECK(maps.starts(ALVimMappings::COMMAND_LINE, control_l[0])); CHECK(run("map", "")); CHECK(listing.find("Q") != std::string::npos); CHECK(listing.find("gj") != std::string::npos); @@ -249,7 +255,9 @@ namespace { INFO(" kept"); CHECK(run("nnoremap", " g G")); - CHECK(maps.match(ALVimMappings::NORMAL, chars("g"), true).full->nowait); + const ALVimMappings::Match nowait = maps.match(ALVimMappings::NORMAL, chars("g"), true); + REQUIRE(nowait.full); + CHECK(nowait.full->nowait); } { INFO(" refuses one there"); @@ -264,6 +272,7 @@ namespace UNSCOPED_INFO("a bar ends it"); CHECK(run("nmap", "zb a\\|b|c")); const ALVimMappings::Match bar = maps.match(ALVimMappings::NORMAL, chars("zb"), true); + REQUIRE(bar.full); UNSCOPED_INFO("\\| is one"); CHECK(ALVimMappings::shown(bar.full->to) == std::string("ab")); } diff --git a/indra/llui/tests/alxuicatalog_test.cpp b/indra/llui/tests/alxuicatalog_test.cpp index 36cb81fb1a..74d1323d7c 100644 --- a/indra/llui/tests/alxuicatalog_test.cpp +++ b/indra/llui/tests/alxuicatalog_test.cpp @@ -138,8 +138,12 @@ namespace CHECK(a->layer("other", "en") != nullptr); CHECK(a->layer("other", "de") == nullptr); - CHECK((int)catalog.find("panel_b.xml")->kind == (int)ALXUICatalog::Kind::Panel); - CHECK((int)catalog.find("menu_c.xml")->kind == (int)ALXUICatalog::Kind::Menu); + const ALXUICatalog::Entry* panel = catalog.find("panel_b.xml"); + REQUIRE(panel != nullptr); + CHECK((int)panel->kind == (int)ALXUICatalog::Kind::Panel); + const ALXUICatalog::Entry* menu = catalog.find("menu_c.xml"); + REQUIRE(menu != nullptr); + CHECK((int)menu->kind == (int)ALXUICatalog::Kind::Menu); const ALXUICatalog::Entry* tmpl = catalog.find("widgets/button.xml"); UNSCOPED_INFO("template catalogued under its subdirectory"); REQUIRE(tmpl != nullptr); @@ -148,6 +152,7 @@ namespace const ALXUICatalog::Entry* broken = catalog.find("broken.xml"); UNSCOPED_INFO("a file that does not parse is still catalogued"); REQUIRE(broken != nullptr); + REQUIRE(broken->layers.size() == 1u); CHECK(broken->layers[0].doc == nullptr); UNSCOPED_INFO("and the reason"); CHECK_FALSE(broken->layers[0].error.empty()); @@ -162,7 +167,9 @@ namespace { ALXUICatalog catalog; catalog.scan(mSkins); - const ALXUICatalog::Entry& a = *catalog.find("floater_a.xml"); + const ALXUICatalog::Entry* found = catalog.find("floater_a.xml"); + REQUIRE(found != nullptr); + const ALXUICatalog::Entry& a = *found; std::vector layers = catalog.layersFor(a, "default", "en"); UNSCOPED_INFO("default/en: one file"); @@ -185,7 +192,9 @@ namespace UNSCOPED_INFO("default's language file"); CHECK(layers[1]->skin == std::string("default")); - layers = catalog.layersFor(*catalog.find("panel_b.xml"), "other", "de"); + const ALXUICatalog::Entry* b = catalog.find("panel_b.xml"); + REQUIRE(b != nullptr); + layers = catalog.layersFor(*b, "other", "de"); UNSCOPED_INFO("a file the skin lacks falls back to default"); REQUIRE(layers.size() == 1u); CHECK(layers[0]->skin == std::string("default")); @@ -242,7 +251,10 @@ namespace UNSCOPED_INFO("in the German layer"); CHECK(hits[0].layer->language == std::string("de")); - const ALXUICatalog::Layer* en = catalog.find("floater_a.xml")->layer("default", "en"); + const ALXUICatalog::Entry* floater = catalog.find("floater_a.xml"); + REQUIRE(floater != nullptr); + const ALXUICatalog::Layer* en = floater->layer("default", "en"); + REQUIRE(en != nullptr); pugi::xml_node second = ALXUICatalog::resolve(en->root(), ALXUISelection::fromString("body/ok#1")); UNSCOPED_INFO("the second button resolves"); REQUIRE(second); diff --git a/indra/llui/tests/alxuiedit_test.cpp b/indra/llui/tests/alxuiedit_test.cpp index 099a2f20b5..93ef626d4d 100644 --- a/indra/llui/tests/alxuiedit_test.cpp +++ b/indra/llui/tests/alxuiedit_test.cpp @@ -684,7 +684,6 @@ namespace CHECK(edit.text().find("top=\"40\"") != std::string::npos); } - // A move is a removal and an insertion; one undo is the whole move. { INFO("undo the redos"); CHECK(edit.undo()); diff --git a/indra/llui/tests/alxuilint_test.cpp b/indra/llui/tests/alxuilint_test.cpp index 76da274bf0..85efe235c1 100644 --- a/indra/llui/tests/alxuilint_test.cpp +++ b/indra/llui/tests/alxuilint_test.cpp @@ -332,6 +332,8 @@ namespace " \n" " \n" " ", /*decisive=*/false)); + UNSCOPED_INFO("callback not registered: " + run.describe()); + REQUIRE(run.count(ALXUILint::Rule::CallbackNotRegistered) == 1); UNSCOPED_INFO("advisory in a shell build"); CHECK((int)run.first(ALXUILint::Rule::CallbackNotRegistered)->severity == (int)ALXUILint::Severity::Note); diff --git a/indra/llui/tests/alxuischema_test.cpp b/indra/llui/tests/alxuischema_test.cpp index 542093e0cc..33cf79a66e 100644 --- a/indra/llui/tests/alxuischema_test.cpp +++ b/indra/llui/tests/alxuischema_test.cpp @@ -324,7 +324,9 @@ namespace UNSCOPED_INFO("a real is a type of its own"); CHECK(found); - const ALXUISchema::Attribute* alpha = find(*schema().tag("panel"), "bg_alpha_color.alpha"); + const ALXUISchema::Tag* panel = schema().tag("panel"); + REQUIRE(panel != nullptr); + const ALXUISchema::Attribute* alpha = find(*panel, "bg_alpha_color.alpha"); REQUIRE(alpha != nullptr); UNSCOPED_INFO("a colour component is a real"); CHECK((int)alpha->value == (int)ALParamType::REAL); @@ -374,8 +376,10 @@ namespace CHECK(find(*text, "font") != nullptr); UNSCOPED_INFO("and its parts"); CHECK(find(*text, "font.name") != nullptr); + const ALXUISchema::Tag* button = schema().tag("button"); + REQUIRE(button != nullptr); UNSCOPED_INFO("and an image"); - CHECK(find(*schema().tag("button"), "image_unselected") != nullptr); + CHECK(find(*button, "image_unselected") != nullptr); } // A registry knows that a button takes a label and cannot know what a @@ -577,15 +581,16 @@ namespace UNSCOPED_INFO("and nothing said about it"); CHECK_FALSE(name->deprecated); - // One sentence per tag, for the Library and for the XSD. + // One sentence per tag, for the Library and for the XSD, and it is + // the notes' own. A tag nobody has written one for falls back to what + // it did before there were sentences: nothing waits on prose. Every + // tag this binary registers has one, so the case above asks that of a + // name nobody wrote about. const ALXUISchema::Tag* button = schema().tag("button"); REQUIRE(button != nullptr); CHECK_FALSE(button->note.empty()); - - // A tag nobody has written one for falls back to what it did before - // there were sentences: nothing waits on prose. - UNSCOPED_INFO("and the notes do not have to be complete"); - CHECK(ALXUINotes::get().count() > 0); + UNSCOPED_INFO("the sentence is the one the notes hold"); + CHECK(button->note == ALXUINotes::get().note("button")); } // The kind a tag is looked for under, and whether a specimen of it can diff --git a/indra/llui/tests/alxuiservice_test.cpp b/indra/llui/tests/alxuiservice_test.cpp index 36ed602ad3..8a284bcca6 100644 --- a/indra/llui/tests/alxuiservice_test.cpp +++ b/indra/llui/tests/alxuiservice_test.cpp @@ -108,8 +108,11 @@ namespace CHECK(c.attribute == std::string("label")); c = ALXUIService::contextAt(doc, ALTextPos(5, 7)); // getChildList(); S32 seen = 0; for (auto it = children.rbegin(); it != children.rend(); ++it) @@ -173,16 +179,31 @@ namespace REQUIRE(same); } - CHECK(map.find(floater)->line == 1); - CHECK(map.find(floater)->tag == std::string("floater")); - CHECK(map.find(outer)->line == 3); - CHECK(map.find(inner1)->line == 4); + const ALXUISourceMap::Origin* from_floater = map.find(floater); + const ALXUISourceMap::Origin* from_outer = map.find(outer); + const ALXUISourceMap::Origin* from_inner1 = map.find(inner1); + const ALXUISourceMap::Origin* from_inner2 = map.find(inner2); + const ALXUISourceMap::Origin* from_leaf = map.find(leaf); + const ALXUISourceMap::Origin* from_other = map.find(other); + { + INFO("each of them came from the file"); + REQUIRE(from_floater); + REQUIRE(from_outer); + REQUIRE(from_inner1); + REQUIRE(from_inner2); + REQUIRE(from_leaf); + REQUIRE(from_other); + } + CHECK(from_floater->line == 1); + CHECK(from_floater->tag == std::string("floater")); + CHECK(from_outer->line == 3); + CHECK(from_inner1->line == 4); UNSCOPED_INFO("the second inner, not the first again"); - CHECK(map.find(inner2)->line == 5); + CHECK(from_inner2->line == 5); UNSCOPED_INFO("the leaf under the second inner"); - CHECK(map.find(leaf)->line == 7); + CHECK(from_leaf->line == 7); UNSCOPED_INFO("other, after a parameter element and a subtree"); - CHECK(map.find(other)->line == 10); + CHECK(from_other->line == 10); REQUIRE(dragHandle(floater) != nullptr); UNSCOPED_INFO("which came from no element"); diff --git a/indra/llui/tests/alxuitranslate_test.cpp b/indra/llui/tests/alxuitranslate_test.cpp index 0698f270a2..e2182922a8 100644 --- a/indra/llui/tests/alxuitranslate_test.cpp +++ b/indra/llui/tests/alxuitranslate_test.cpp @@ -32,6 +32,8 @@ #include +#include + namespace { struct alxuitranslate_data @@ -581,11 +583,7 @@ namespace // merge reads and anything else would be an override nobody asked for. const pugi::xml_node inner = overlay.resolve({ "inner" }); REQUIRE(inner); - S32 attributes = 0; - for (pugi::xml_attribute one : inner.attributes()) - { - ++attributes; - } + const S32 attributes = (S32)std::distance(inner.attributes_begin(), inner.attributes_end()); UNSCOPED_INFO("carrying only its name"); CHECK(attributes == 1); @@ -637,8 +635,11 @@ namespace CHECK((int)one->state == (int)ALXUITranslate::State::NotApplied); UNSCOPED_INFO("neither of them"); CHECK((int)two->state == (int)ALXUITranslate::State::NotApplied); - UNSCOPED_INFO("and both say the base has the name elsewhere"); - CHECK((int)one->miss == (int)ALXUITranslate::Miss::Moved); + { + INFO("and both say the base has the name elsewhere"); + CHECK((int)one->miss == (int)ALXUITranslate::Miss::Moved); + CHECK((int)two->miss == (int)ALXUITranslate::Miss::Moved); + } { INFO("so neither applies"); CHECK_FALSE(one->applies()); diff --git a/indra/llui/tests/alxuitreemodel_test.cpp b/indra/llui/tests/alxuitreemodel_test.cpp index dc574fcc94..a1da841823 100644 --- a/indra/llui/tests/alxuitreemodel_test.cpp +++ b/indra/llui/tests/alxuitreemodel_test.cpp @@ -283,8 +283,10 @@ namespace CHECK(hidden->isShown()); UNSCOPED_INFO("the authored state is remembered"); CHECK_FALSE(hidden->isAuthoredVisible()); + const ALXUISourceMap::Origin* hidden_origin = map.find(hidden->getView()); + REQUIRE(hidden_origin != nullptr); std::string authored; - map.find(hidden->getView())->node->getAttributeString("visible", authored); + hidden_origin->node->getAttributeString("visible", authored); UNSCOPED_INFO("and the document still says what it said"); CHECK(authored == std::string("false")); hidden->toggleShown(); @@ -303,6 +305,7 @@ namespace delete floater; LLXMLNodePtr root2; LLFloater* floater2 = build(fv.get(), root2); + REQUIRE(floater2 != nullptr); map.build(floater2, root2); model.build(floater2, map); ALXUITreeItem* outer2 = model.itemFor(ALXUISelection::fromString("outer")); @@ -483,6 +486,8 @@ namespace std::vector items { inner->getViewModelItem() }; UNSCOPED_INFO("and starting a drag says which"); CHECK(model.startDrag(items)); + UNSCOPED_INFO("to the starter, once"); + REQUIRE(picked.size() == 1u); UNSCOPED_INFO("by path"); CHECK(ALXUISelection::toString(picked.front()) == std::string("outer/inner")); @@ -588,10 +593,13 @@ namespace ll_test::FloaterViewScope floater_view(fv.get()); LLXMLNodePtr root; LLFloater* floater = build(fv.get(), root); + REQUIRE(floater != nullptr); ALXUISourceMap map; map.build(floater, root); // A label with a capital, which the words find all the same. - floater->findChild("inner", true)->setName("Inner"); + LLView* first_inner = floater->findChild("inner", true); + REQUIRE(first_inner != nullptr); + first_inner->setName("Inner"); ALXUITreeModel model; model.build(floater, map); ALXUITreeItem* top = model.rootItem(); diff --git a/indra/llui/tests/llcombobox_test.cpp b/indra/llui/tests/llcombobox_test.cpp index c2d2dd4ee2..f4bd82bad4 100644 --- a/indra/llui/tests/llcombobox_test.cpp +++ b/indra/llui/tests/llcombobox_test.cpp @@ -194,8 +194,28 @@ namespace CHECK(text->getTentative()); combo->setUnset(false); - UNSCOPED_INFO("and choosing it settles it"); + UNSCOPED_INFO("and saying it is set settles it"); CHECK_FALSE(text->getTentative()); + + // A choice made in the control settles it too, without its owner + // saying so: an item chosen, + combo->setUnset(true); + combo->selectFirstItem(); + { + INFO("and choosing it settles it"); + CHECK_FALSE(text->getTentative()); + CHECK_FALSE(combo->isUnset()); + } + + // or a value typed that the list does not have. + combo->setUnset(true); + text->setText(std::string("up")); + text->onCommit(); + { + INFO("and typing it settles it"); + CHECK(combo->getValue().asString() == std::string("up")); + CHECK_FALSE(combo->isUnset()); + } combo->die(); } } diff --git a/indra/llui/tests/llemojidictionary_test.cpp b/indra/llui/tests/llemojidictionary_test.cpp index f0c610923c..8a15710972 100644 --- a/indra/llui/tests/llemojidictionary_test.cpp +++ b/indra/llui/tests/llemojidictionary_test.cpp @@ -156,19 +156,26 @@ namespace } // findVariant: prefer the cleanest match — gender=-1 user shouldn't - // get a gendered variant when a gender-neutral one of the same tone - // exists. - TEST_CASE_METHOD(llemojidictionary_data, "llemojidictionary: findVariant with no gender asked for picks the gender-neutral variant", "[llemojidictionary]") + // get a gendered variant when one of the same tone that carries no + // gender exists. + TEST_CASE_METHOD(llemojidictionary_data, "llemojidictionary: findVariant with no gender asked for picks the variant that carries none", "[llemojidictionary]") { + // The astronaut's tone-3 variants all carry a gender, person (2) + // as much as man (0), and score alike. One that carries none goes + // after them all, so that the +1 for not asserting a gender we + // didn't ask for is what picks it, and not the order. LLEmojiDescriptor d = make_astronaut_descriptor(); + LLEmojiVariant clean; + utf8str_append_cp(clean.Character, (llwchar)0x1F9D1); + utf8str_append_cp(clean.Character, (llwchar)(0x1F3FA + 3)); + clean.Tone = 3; + clean.Gender = -1; + d.Variants.push_back(clean); + const LLEmojiVariant* v = LLEmojiDictionary::instance().findVariant(d, 3, -1); REQUIRE(v != nullptr); CHECK(v->Tone == U8(3)); - // Gender preference unset — best match should have Gender=2 - // (person, gender-neutral) with the +1 bonus for not asserting a - // gender we didn't ask for. (Both 'person' and bare 'person' - // are gender=2 in this fixture.) - CHECK(v->Gender == S8(2)); + CHECK(v->Gender == S8(-1)); } // findVariant: partial-axis match still beats nothing — user asks @@ -203,7 +210,8 @@ namespace CHECK(out[0].Tone == U8(5)); UNSCOPED_INFO("no Gender given: unset"); CHECK(out[0].Gender == S8(-1)); - CHECK(out[0].ShortCodes.size() == size_t(1)); + REQUIRE(out[0].ShortCodes.size() == size_t(1)); + CHECK(out[0].ShortCodes.front() == std::string(":thumbs_up_dark_skin_tone:")); } TEST_CASE_METHOD(llemojidictionary_data, "llemojidictionary: loadVariants with no Variants key returns none", "[llemojidictionary]") diff --git a/indra/llui/tests/lllayoutstack_test.cpp b/indra/llui/tests/lllayoutstack_test.cpp index dbe7f0e7ca..14ab89399f 100644 --- a/indra/llui/tests/lllayoutstack_test.cpp +++ b/indra/llui/tests/lllayoutstack_test.cpp @@ -432,6 +432,7 @@ namespace s->addPanel(grows); s->addPanel(dragged); s->updateLayout(); + REQUIRE(grows->getResizeBar() != nullptr); UNSCOPED_INFO("the pair can resize, so there is somewhere to drag"); CHECK(grows->getResizeBar()->getVisible()); s->die(); @@ -446,6 +447,8 @@ namespace s->addPanel(between); s->addPanel(dragged); s->updateLayout(); + REQUIRE(between->getResizeBar() != nullptr); + REQUIRE(grows->getResizeBar() != nullptr); UNSCOPED_INFO("a band that cannot give room has no bar of its own"); CHECK_FALSE(between->getResizeBar()->getVisible()); UNSCOPED_INFO("and it takes the bar off the panel behind it too"); diff --git a/indra/llui/tests/llstatbar_test.cpp b/indra/llui/tests/llstatbar_test.cpp index 689ed1d961..74da51414f 100644 --- a/indra/llui/tests/llstatbar_test.cpp +++ b/indra/llui/tests/llstatbar_test.cpp @@ -113,7 +113,8 @@ namespace TEST_CASE_METHOD(llstatbar_data, "llstatbar: calcTickValue over a tiny range about zero is finite and not negative", "[llstatbar]") { - // Both bounds tiny enough that their sum rounds to zero. + // Bounds a tiny distance either side of zero: normal floats still, + // not denormal ones, which start below about 1.2e-38. const F32 tick = LLStatBar::calcTickValue(-1e-30f, 1e-30f); CHECK(finite(tick)); CHECK(tick >= 0.f); @@ -184,13 +185,14 @@ namespace } // A range with nothing in it must not produce a spacing the tick walk would - // step forever on, and must stay finite. - TEST_CASE_METHOD(llstatbar_data, "llstatbar: an empty range scales to finite bounds and spacing", "[llstatbar]") + // step forever on, and must stay finite. No spacing at all is what the bar + // walks no ticks for. + TEST_CASE_METHOD(llstatbar_data, "llstatbar: an empty range scales to finite bounds and no tick spacing", "[llstatbar]") { const Scaled empty = scale(0.f, 0.f); CHECK(finite(empty.mMin)); CHECK(finite(empty.mMax)); - CHECK((empty.mTick == 0.f || finite(empty.mTick))); + CHECK(empty.mTick == 0.f); } // Both bounds on the same side of zero and equal: the degenerate case the diff --git a/indra/llui/tests/lltabcontainer_test.cpp b/indra/llui/tests/lltabcontainer_test.cpp index 39100b553e..f5508fe310 100644 --- a/indra/llui/tests/lltabcontainer_test.cpp +++ b/indra/llui/tests/lltabcontainer_test.cpp @@ -26,6 +26,7 @@ #include "../lltabcontainer.h" +#include "../llbadge.h" #include "../llbutton.h" #include "../llfocusmgr.h" #include "../lliconctrl.h" @@ -83,6 +84,24 @@ namespace return tabs->findChild((vertical ? "vtab_" : "htab_") + name, true); } + // The badge on a tab. The strip holds it rather than the button, so + // that it is drawn over everything, and with one tab there is one. + static const LLBadge* badgeIn(const LLView* view) + { + for (const LLView* child : *view->getChildList()) + { + if (const LLBadge* badge = child->as()) + { + return badge; + } + if (const LLBadge* badge = badgeIn(child)) + { + return badge; + } + } + return nullptr; + } + // A texture with a size and nothing else: what a UI image needs to // exist here, where no image provider makes any. struct SizedTexture : public LLTexture @@ -133,7 +152,7 @@ namespace // A count on the tab, and off it again. A tab with nothing to say looks // like one that never had anything. - TEST_CASE_METHOD(lltabcontainer_data, "lltabcontainer: setTabBadge makes a badge and keeps it once the count is cleared", "[lltabcontainer]") + TEST_CASE_METHOD(lltabcontainer_data, "lltabcontainer: setTabBadge makes a badge and keeps it hidden once the count is cleared", "[lltabcontainer]") { if (!ui.ok()) { @@ -150,10 +169,19 @@ namespace tabs->setTabBadge(counted, "12"); CHECK(button->hasBadge()); + const LLBadge* badge = badgeIn(tabs); + REQUIRE(badge != nullptr); + { + INFO("showing the count"); + CHECK(badge->getVisible()); + CHECK(badge->getLabel() == std::string("12")); + } tabs->setTabBadge(counted, std::string()); UNSCOPED_INFO("the badge stays made once it is made"); CHECK(button->hasBadge()); + UNSCOPED_INFO("and is hidden, as a tab with no count has none showing"); + CHECK_FALSE(badge->getVisible()); tabs->die(); } @@ -684,7 +712,9 @@ namespace tabs->addTabPanel(page("g", std::string())); LLPanel* last = page("h", std::string()); tabs->addTabPanel(last); - CHECK(tabButton(tabs, "f")->getRect().getWidth() == tabs->getMaxTabWidth()); + const LLButton* wide_button = tabButton(tabs, "f"); + REQUIRE(wide_button != nullptr); + CHECK(wide_button->getRect().getWidth() == tabs->getMaxTabWidth()); tabs->setTabVisibility(wide, false); UNSCOPED_INFO("seven tabs of sixty in four hundred: one more than fit"); @@ -711,7 +741,9 @@ namespace tabs->addTabPanel(first); tabs->addTabPanel(second); tabs->selectTabPanel(first); - tabButton(tabs, "first")->setFocus(true); + LLButton* first_button = tabButton(tabs, "first"); + REQUIRE(first_button != nullptr); + first_button->setFocus(true); CHECK_FALSE(tabs->handleKeyHere(KEY_RIGHT, MASK_CONTROL)); CHECK(tabs->getCurrentPanel() == first); @@ -735,8 +767,11 @@ namespace LLPanel* indented = page("indented", std::string()); tabs->addTabPanel(plain); tabs->addTabPanel(LLTabContainer::TabPanelParams().panel(indented).indent(10)); - const S32 pad = tabButton(tabs, "plain")->getLeftHPad(); - CHECK(tabButton(tabs, "indented")->getLeftHPad() == pad + 10); + const LLButton* plain_button = tabButton(tabs, "plain"); + const LLButton* indented_button = tabButton(tabs, "indented"); + REQUIRE(plain_button != nullptr); + REQUIRE(indented_button != nullptr); + CHECK(indented_button->getLeftHPad() == plain_button->getLeftHPad() + 10); tabs->die(); } @@ -829,12 +864,16 @@ namespace // Four tabs of sixty in the three hundred and ninety-eight between // the page's sides: each gets its sixty and a fourth of the rest. + // Each tab's button is found here, and the strip keeps it as long as + // it keeps the tab, so the reads of them below have it to read. const S32 room = 398; S32 total = 0; for (const char* name : { "a", "b", "c", "d" }) { - const S32 width = tabButton(tabs, name)->getRect().getWidth(); CAPTURE(name); + const LLButton* button = tabButton(tabs, name); + REQUIRE(button != nullptr); + const S32 width = button->getRect().getWidth(); REQUIRE(width > 60); total += width; } @@ -907,7 +946,10 @@ namespace S32 total = 0; for (const char* name : { "a", "b", "c", "d" }) { - total += tabButton(tabs, name)->getRect().getWidth(); + CAPTURE(name); + const LLButton* tab = tabButton(tabs, name); + REQUIRE(tab != nullptr); + total += tab->getRect().getWidth(); } CHECK(total == 398 - KEPT); CHECK(1 + total <= button->getRect().mLeft); diff --git a/indra/llui/tests/lltextparser_test.cpp b/indra/llui/tests/lltextparser_test.cpp index cb3242993f..f0b4bbedae 100644 --- a/indra/llui/tests/lltextparser_test.cpp +++ b/indra/llui/tests/lltextparser_test.cpp @@ -129,6 +129,7 @@ namespace entry->mPattern = "carol"; CHECK(pieces("carol and alice") == std::string("[carol:carol]| and alice|")); entry = parser.getHighlightById(id); + REQUIRE(entry != nullptr); entry->mCaseSensitive = true; CHECK(pieces("Carol and carol") == std::string("Carol and |[carol:carol]||")); parser.removeHighlight(id); diff --git a/indra/llui/tests/lluicolortable_test.cpp b/indra/llui/tests/lluicolortable_test.cpp index 3eba0c6dd1..489b02a7a8 100644 --- a/indra/llui/tests/lluicolortable_test.cpp +++ b/indra/llui/tests/lluicolortable_test.cpp @@ -118,8 +118,10 @@ namespace CHECK(table.getColor("T1Nope", LLColor4::yellow).get() == LLColor4::yellow); CHECK_FALSE(table.colorExists("T1Nope")); CHECK(table.getLoadedSheet().files().size() == 2u); + const ALColorSheet::Declaration* base = table.getLoadedSheet().declarationOf("T1Base"); + REQUIRE(base != nullptr); UNSCOPED_INFO("T1Base was last declared in the second file"); - CHECK(table.getLoadedSheet().declarationOf("T1Base")->file == 1); + CHECK(base->file == 1); } // A handle taken before a reload reads the colour after it. A name the @@ -192,7 +194,9 @@ namespace table.setColor("ColorPaletteEntry99", LLColor4(1, 1, 1, 1)); CHECK(a.get() == LLColor4(0.5f, 0.25f, 0.f, 1.f)); CHECK_FALSE(table.isDefault("T4A")); - CHECK(table.getLoadedColors().find("T4A")->second.get() == LLColor4(1, 0, 0, 1)); + const auto loaded = table.getLoadedColors().find("T4A"); + REQUIRE(loaded != table.getLoadedColors().end()); + CHECK(loaded->second.get() == LLColor4(1, 0, 0, 1)); const std::string text = written(); CHECK_THAT(text, Catch::Matchers::ContainsSubstring("name=\"T4A\"")); @@ -214,7 +218,9 @@ namespace table.load(documents, round_trip, "user.xml"); UNSCOPED_INFO("the written document reproduces it"); CHECK(a.get() == LLColor4(0.5f, 0.25f, 0.f, 1.f)); - CHECK(table.getUserColors().find("T4A")->second.get() == LLColor4(0.5f, 0.25f, 0.f, 1.f)); + const auto user = table.getUserColors().find("T4A"); + REQUIRE(user != table.getUserColors().end()); + CHECK(user->second.get() == LLColor4(0.5f, 0.25f, 0.f, 1.f)); } // What the skins' files have to say reaches the loaded sheet, and a diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index f41ae39ca3..458773a8eb 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -467,8 +467,8 @@ namespace "secondlife:///app/teleport/Ahern/50/50/50"); require_url("Ahern (50,50,50) multicase", url, - "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", - "secondlife:///app/teleport/Ahern/50/50/50/"); + "XXX SecondLife:///App/Teleport/Ahern/50/50/50/ XXX", + "SecondLife:///App/Teleport/Ahern/50/50/50/"); require_url("Ahern (50,50) [1]", url, "XXX secondlife:///app/teleport/Ahern/50/50/ XXX", @@ -638,8 +638,8 @@ namespace "secondlife:///app/region/Ahern/50/50/50"); require_url("Ahern (50,50,50) multicase", url, - "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", - "secondlife:///app/region/Ahern/50/50/50/"); + "XXX SecondLife:///App/Region/Ahern/50/50/50/ XXX", + "SecondLife:///App/Region/Ahern/50/50/50/"); require_url("Ahern (50,50) [1]", url, "XXX secondlife:///app/region/Ahern/50/50/ XXX",