SeekquarrySeekquarry - Yioop Repo - Seekquarry

/ tests / PhraseModelTest.php⤓
<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\tests;

use seekquarry\yioop\configs as C;
use seekquarry\yioop\models\PhraseModel;
use seekquarry\yioop\library\UnitTest;

/**
 * PhraseModelTest holds the unit tests for PhraseModel, covering how the
 * results of a query are reordered once their summaries are fetched.
 *
 * @author Chris Pollett
 */
class PhraseModelTest extends UnitTest
{
    /**
     * setUp makes a PhraseModel that opens no database connection
     */
    public function setUp()
    {
        $this->test_objects['MODEL'] = new PhraseModel(C\DB_NAME, false);
    }
    /**
     * tearDown drops the model made by setUp
     */
    public function tearDown()
    {
        $this->test_objects = [];
    }
    /**
     * exactMatchMultipliesTheRelevanceTestCase checks the case of a query
     * for anime, which also finds pages about animals, as the two words
     * have the same stem. A result whose title or summary holds the word
     * anime, in any case, has its relevance multiplied by
     * EXACT_MATCH_FACTOR; one holding only animal keeps its score.
     */
    public function exactMatchMultipliesTheRelevanceTestCase()
    {
        $fields = [PhraseModel::TITLE, PhraseModel::DESCRIPTION,
            PhraseModel::RELEVANCE, PhraseModel::SCORE];
        $pages = [
            array_combine($fields, ["Animal Liberation Front-Line",
                "Animal Liberation Front", 4.0, 11.0]),
            array_combine($fields, ["Watch Free Movies and TV Shows Online",
                "Adult <b>Anime</b>", 3.0, 10.0]),
        ];
        $this->test_objects['MODEL']->addExactMatchScore($pages, ["anime"]);
        $factor = PhraseModel::EXACT_MATCH_FACTOR;
        $this->assertEqual(array_column($pages, PhraseModel::SCORE),
            [7.0 + 3.0 * $factor, 11.0],
            "The exact match has its relevance multiplied");
    }
    /**
     * exactMatchIgnoresDiacriticsTestCase checks that a query word typed
     * without diacritics, jose, counts as an exact match for a title that
     * writes it with them, San José State University, as the index files
     * both forms under one term.
     */
    public function exactMatchIgnoresDiacriticsTestCase()
    {
        $fields = [PhraseModel::TITLE, PhraseModel::DESCRIPTION,
            PhraseModel::RELEVANCE, PhraseModel::SCORE];
        $pages = [array_combine($fields, ["San José State University",
            "", 4.0, 10.0])];
        $this->test_objects['MODEL']->addExactMatchScore($pages, ["jose"]);
        $factor = PhraseModel::EXACT_MATCH_FACTOR;
        $this->assertEqual($pages[0][PhraseModel::SCORE],
            6.0 + 4.0 * $factor, "jose matches José exactly");
    }
    /**
     * unspacedWordFoundAndLeftOutWordIgnoredTestCase checks that the word
     * 动漫, anime, is found inside a title written without spaces between
     * its words, and that a word left out of the query with a minus is not
     * looked for. Of the two words looked for, only one is in the title,
     * so half the relevance is multiplied.
     */
    public function unspacedWordFoundAndLeftOutWordIgnoredTestCase()
    {
        $fields = [PhraseModel::TITLE, PhraseModel::DESCRIPTION,
            PhraseModel::RELEVANCE, PhraseModel::SCORE];
        $pages = [
            array_combine($fields, ["cats only", "", 2.0, 3.0]),
            array_combine($fields, ["最新动漫新闻", "", 2.0, 3.0]),
        ];
        $this->test_objects['MODEL']->addExactMatchScore($pages,
            ["动漫", "新番", "-cats"]);
        $factor = PhraseModel::EXACT_MATCH_FACTOR;
        $this->assertEqual(array_column($pages, PhraseModel::SCORE),
            [3.0 + ($factor - 1) * 2.0 / 2, 3.0],
            "Only the Chinese title has its relevance multiplied");
    }
    /**
     * xResults gives ten results for the query x, with the titles,
     * descriptions and scores the search site yioop.com (Chris Pollett's
     * site) returned for them on 2026-10-13, in score order.
     *
     * @return array the results, as getSummariesByHash holds them
     */
    public function xResults()
    {
        $fields = [PhraseModel::URL, PhraseModel::TITLE,
            PhraseModel::DESCRIPTION, PhraseModel::SCORE];
        $rows = [
            ["https://www.dictionary.com/games/word-finder/" .
                "words-with-the-letter-x",
                "Words With the <b>Letter</b> <b>X</b> | Dictionary.com",
                "Words With the <b>Letter</b> <b>X</b> for Wordle, " .
                "Scrabble, Words with ... Featured Games", 38.049],
            ["https://www.foxnews.com/games/lil-snack-word-swap",
                "SWAP Mini Game | Fox News <b>x</b> Lil Snack Addictive " .
                "Mini Games | Fox News", "Swap the <b>letter</b> tiles to " .
                "solve each clue in this addictive <b>letter</b> swap " .
                "mini game!", 32.385],
            ["https://www.dictionary.com/games/word-finder/" .
                "words-that-start-with-x",
                "Words That Start with <b>X</b> | Dictionary.com",
                "That Start with <b>X</b> for Wordle, Scrabble, Words with " .
                "... Featured Games ... 1-<b>Letter</b> Words.", 31.395],
            ["https://www.dictionary.com/games/word-finder/" .
                "words-that-end-with-x",
                "Words That End with <b>X</b> | Dictionary.com",
                "Words That End with <b>X</b> for Wordle, Scrabble, Words " .
                "with ... Featured Games", 30.576],
            ["https://x.com/", "<b>X</b>. It’s what’s happening / <b>X</b>",
                "live commentary. <b>X</b>. It’s what’s happening ... 2026 " .
                "<b>X</b> Corp", 27.672],
            ["http://www.symphonyx.com/",
                "Symphony <b>X</b> Official Website",
                "Official Symphony <b>X</b> band website", 27.154],
            ["https://titlexwines.com/", "Title <b>X</b> | A Convergence of " .
                "Wine & Art – TITLE <b>X</b> Wines", "Title <b>X</b> unites " .
                "world-renowned artist Mister ... your access.", 27.077],
            ["https://www.lilnasx.com/", "LIL NAS <b>X</b>", "LIL NAS " .
                "<b>X</b>. Lil Nas <b>X</b> Store Listen Watch. Out", 26.605],
            ["https://directorxfilms.com/", "Home | Director <b>X</b>",
                "Home | Director <b>X</b>. Robyn Hood (Trailer). Global TV.",
                26.128],
            ["https://www.xjapan.com/", "<b>X</b> Japan Official Website",
                "<b>X</b> Japan is the most successful rock band ... outside " .
                "of Japan.", 26.040],
        ];
        return array_map(fn($row) => array_combine($fields, $row), $rows);
    }
    /**
     * diversifyMovesResultsLikeOnesAboveDownTestCase gives diversify the ten
     * results yioop.com gave for x, where three dictionary.com pages on
     * words with the letter x stand at places 1, 3 and 4 and x.com at
     * place 5. Each of the second and third dictionary.com pages shares
     * most of its words with the first, so they move below x.com. With
     * lambda set to 1, relevance alone counts and the order is kept.
     */
    public function diversifyMovesResultsLikeOnesAboveDownTestCase()
    {
        $pages = $this->xResults();
        $urls = array_column($pages, PhraseModel::URL);
        $this->test_objects['MODEL']->diversify($pages, ["x"], 30, 1);
        $this->assertEqual($urls, array_column($pages, PhraseModel::URL),
            "with lambda 1 the order is kept");
        $this->test_objects['MODEL']->diversify($pages, ["x"], 30, 0.7);
        $places = array_flip(array_column($pages, PhraseModel::URL));
        $this->assertTrue($places["https://x.com/"] <
            $places[$urls[2]] && $places["https://x.com/"] < $places[$urls[3]],
            "x.com moves above the second and third dictionary.com pages");
        $this->assertEqual($urls[0], $pages[0][PhraseModel::URL],
            "the top result stays first");
    }
}