Add text queries and whole-log chat search

TextQuery holds a find bar's text and options (match case, whole word,
regex) and matches plain text; HtmlText strips a log entry down to it.
ChatLogStore can now search a whole log rather than only the history
loaded into a chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 15:09:45 +00:00
parent 249430efad
commit a4057cd499
5 changed files with 255 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
package com.ts3client.chatlog;
import com.ts3client.text.TextQuery;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -40,4 +41,31 @@ class ChatLogStoreTest {
assertTrue(history.get(0).contains("ericek111"));
assertTrue(history.get(0).contains("hello"));
}
@Test
void searchReachesPastTheLoadedHistory() throws Exception {
ChatLogStore logs = new ChatLogStore(temp, "+Tyg2JtxE8vRNZp+JiUBnmBh0MY=");
logs.appendMessage(ChatLogScope.CHANNEL, false, 5, "uid", "alice", "the needle is here");
for (int i = 0; i < ChatLogStore.HISTORY_LIMIT; i++) {
logs.appendMessage(ChatLogScope.CHANNEL, false, 5, "uid", "alice", "filler " + i);
}
logs.appendServerEvent("a NEEDLE of the server");
assertTrue(logs.channelHtmlHistory().stream().noneMatch(e -> e.contains("needle")));
ChatLogStore.SearchResult channel = logs.search(ChatLogScope.CHANNEL, null,
new TextQuery("needle", false, false, false), 10);
assertEquals(1, channel.total());
assertTrue(channel.entries().get(0).contains("the needle is here"));
ChatLogStore.SearchResult server = logs.search(ChatLogScope.SERVER, null,
new TextQuery("needle", true, false, false), 10);
assertEquals(0, server.total());
ChatLogStore.SearchResult capped = logs.search(ChatLogScope.CHANNEL, null,
new TextQuery("filler", false, false, false), 10);
assertEquals(ChatLogStore.HISTORY_LIMIT, capped.total());
assertEquals(10, capped.entries().size());
assertTrue(capped.truncated());
assertTrue(capped.entries().get(9).contains("filler " + (ChatLogStore.HISTORY_LIMIT - 1)));
}
}

View File

@@ -0,0 +1,47 @@
package com.ts3client.text;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TextQueryTest {
@Test
void ignoresCaseUnlessAsked() {
assertTrue(new TextQuery("hello", false, false, false).matches("Say HELLO there"));
assertFalse(new TextQuery("hello", true, false, false).matches("Say HELLO there"));
assertTrue(new TextQuery("čau", false, false, false).matches("ČAU"));
}
@Test
void wholeWordsStopAtLettersAndDigitsOnly() {
TextQuery q = new TextQuery("cat", false, true, false);
assertTrue(q.matches("the cat sat"));
assertTrue(q.matches("(cat)"));
assertFalse(q.matches("concatenate"));
assertFalse(q.matches("cat2"));
assertTrue(new TextQuery("@all", false, true, false).matches("hey @all!"));
}
@Test
void literalUnlessRegex() {
assertFalse(new TextQuery("a.c", false, false, false).matches("abc"));
assertTrue(new TextQuery("a.c", false, false, true).matches("abc"));
}
@Test
void invalidRegexMatchesNothing() {
TextQuery q = new TextQuery("(", false, false, true);
assertTrue(q.isInvalid());
assertFalse(q.matches("("));
}
@Test
void plainTextOfHtml() {
assertEquals("<\"x\"> a & b", HtmlText.plain("<span class=\"t\">&lt;&quot;x&quot;&gt;</span> a &amp; b"));
assertEquals("line\nnext", HtmlText.plain("line<br>next"));
assertEquals("é", HtmlText.plain("&#233;"));
}
}