// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/string_matching/acronym_matcher.h"
#include "base/containers/adapters.h"
#include "chromeos/ash/components/string_matching/tokenized_string.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash::string_matching {
namespace {
using acronym_matcher_constants::kIsFrontOfTokenCharScore;
using acronym_matcher_constants::kIsPrefixCharScore;
using acronym_matcher_constants::kNoMatchScore;
constexpr double kAbsError = 1e-5;
// Returns a string of |text| marked with the hits in |match| using block
// bracket. e.g. text= "Text", match.hits = [{0,1}], returns "[T]ext".
//
// TODO(crbug.com/1336160): Consider defining it as a |test_util| function as it
// has been used for several unit tests.
std::u16string MatchHit(const std::u16string& text,
const AcronymMatcher& match) {
std::u16string marked = text;
const AcronymMatcher::Hits& hits = match.hits();
for (const gfx::Range& hit : base::Reversed(hits)) {
marked.insert(hit.end(), 1, u']');
marked.insert(hit.start(), 1, u'[');
}
return marked;
}
} // namespace
class AcronymMatcherTest : public testing::Test {};
// Note on expected score calculations:
//
// When a query successfully matches to a text, each letter of the query
// contributes some amount towards a final total. The expected score in
// each test is then the sum over all of the contributions of the individual
// query letters. This is described in more detail in acronym_matcher.cc.
//
// When a query does not successfully match to a text, the overall expected
// score is `kNoMatchScore`.
TEST_F(AcronymMatcherTest, ConsecutiveTokensWithFirstTokenMatch) {
TokenizedString query(u"abc");
TokenizedString text(u"axx bxx cxx dxx exx");
AcronymMatcher am(query, text);
double expected_score = kIsPrefixCharScore + (kIsFrontOfTokenCharScore * 2);
EXPECT_NEAR(am.CalculateRelevance(), expected_score, kAbsError);
}
TEST_F(AcronymMatcherTest, ConsecutiveTokensWithNonFirstTokenMatch) {
TokenizedString query(u"bcd");
TokenizedString text(u"axx bxx cxx dxx exx");
AcronymMatcher am(query, text);
double expected_score = kIsFrontOfTokenCharScore * 3;
EXPECT_NEAR(am.CalculateRelevance(), expected_score, kAbsError);
}
TEST_F(AcronymMatcherTest, CaseInsensitive) {
TokenizedString query(u"bCd");
TokenizedString text(u"axx Bxx cxx Dxx exx");
AcronymMatcher am(query, text);
double expected_score = kIsFrontOfTokenCharScore * 3;
EXPECT_NEAR(am.CalculateRelevance(), expected_score, kAbsError);
}
// PrefixMatcher matches the chars of a given query as prefix of tokens in
// a given text. E.g, query "abc" is a prefix matching of both text "abc dxx"
// and "zxx abcx".
TEST_F(AcronymMatcherTest, PrefixMatchingNotAllowed) {
TokenizedString query(u"abc def");
TokenizedString text(u"abc def ghi");
AcronymMatcher am(query, text);
double expected_score = kNoMatchScore;
EXPECT_NEAR(am.CalculateRelevance(), expected_score, kAbsError);
}
TEST_F(AcronymMatcherTest, MixedAcronymAndPrefixMatchingNotAllowed) {
TokenizedString query(u"adefg");
TokenizedString text(u"abc def ghi");
AcronymMatcher am(query, text);
double expected_score = kNoMatchScore;
EXPECT_NEAR(am.CalculateRelevance(), expected_score, kAbsError);
}
TEST_F(AcronymMatcherTest, MatchHit) {
struct {
const std::u16string text;
const std::u16string query;
const std::u16string expect;
} kTestCases[] = {
{u"Crash of Crowns", u"coc", u"[C]rash [o]f [C]rowns"},
{u"Crash of Crowns", u"cra", u"Crash of Crowns"},
{u"abcxxx bxxx cxxx", u"abc", u"[a]bcxxx [b]xxx [c]xxx"},
{u"xxx abcxxx bxxx cxxx", u"abc", u"xxx [a]bcxxx [b]xxx [c]xxx"},
};
for (auto& test_case : kTestCases) {
const TokenizedString query(test_case.query);
const TokenizedString text(test_case.text);
AcronymMatcher am(query, text);
am.CalculateRelevance();
EXPECT_EQ(test_case.expect, MatchHit(test_case.text, am));
}
}
} // namespace ash::string_matching