Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS function to setTags and getTags #17795

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions AnkiDroid/src/main/assets/scripts/js-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ const jsApiList = {
ankiSttStart: "sttStart",
ankiSttStop: "sttStop",
ankiAddTagToNote: "addTagToNote",
ankiSetNoteTags: "setNoteTags",
ankiGetNoteTags: "getNoteTags",
};

class AnkiDroidJS {
Expand Down Expand Up @@ -119,12 +121,29 @@ class AnkiDroidJS {
Object.keys(jsApiList).forEach(method => {
if (method === "ankiAddTagToNote") {
AnkiDroidJS.prototype[method] = async function (noteId, tag) {
console.warn("ankiAddTagToNote is deprecated. Use ankiSetNoteTags instead.");
const endpoint = jsApiList[method];
const data = JSON.stringify({ noteId, tag });
return await this.handleRequest(endpoint, data);
};
return;
}
if (method === "ankiSetNoteTags") {
AnkiDroidJS.prototype[method] = async function (noteId, tags) {
const endpoint = jsApiList[method];
const data = JSON.stringify({ noteId, tags });
return await this.handleRequest(endpoint, data);
};
return;
}
if (method === "ankiGetNoteTags") {
AnkiDroidJS.prototype[method] = async function (noteId) {
const endpoint = jsApiList[method];
const data = JSON.stringify({ noteId });
return await this.handleRequest(endpoint, data);
};
return;
}
if (method === "ankiTtsSpeak") {
AnkiDroidJS.prototype[method] = async function (text, queueMode = 0) {
const endpoint = jsApiList[method];
Expand Down
27 changes: 27 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ import com.ichi2.libanki.Collection
import com.ichi2.libanki.Decks
import com.ichi2.libanki.SortOrder
import com.ichi2.utils.NetworkUtils
import com.ichi2.utils.stringIterable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
Expand Down Expand Up @@ -373,6 +375,31 @@ open class AnkiDroidJsAPI(
getColUnsafe.updateNote(note)
convertToByteArray(apiContract, true)
}

"setNoteTags" -> {
val jsonObject = JSONObject(apiParams)
val noteId = jsonObject.getLong("noteId")
val tags = jsonObject.getJSONArray("tags")
withCol {
val note =
getNote(noteId).apply {
setTagsFromStr(this@withCol, tags.stringIterable().joinToString(" "))
}
updateNote(note)
}
convertToByteArray(apiContract, true)
}

"getNoteTags" -> {
val jsonObject = JSONObject(apiParams)
val noteId = jsonObject.getLong("noteId")
val noteTags =
withCol {
getNote(noteId).tags
}
convertToByteArray(apiContract, JSONArray(noteTags).toString())
}

"sttSetLanguage" -> convertToByteArray(apiContract, speechRecognizer.setLanguage(apiParams))
"sttStart" -> {
val callback =
Expand Down
79 changes: 79 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/anki/AnkiDroidJsAPITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.ichi2.utils.BASIC_MODEL_NAME
import net.ankiweb.rsdroid.withoutUnicodeIsolation
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Ignore
import org.junit.Test
Expand Down Expand Up @@ -417,6 +418,68 @@ class AnkiDroidJsAPITest : RobolectricTest() {
assertEquals(CardType.New, cardAfterReset.type, "Card type after reset")
}

@Test
fun ankiGetNoteTagsTest() =
runTest {
val n =
addBasicNote("Front", "Back").update {
tags = mutableListOf("tag1", "tag2", "tag3")
}

val reviewer: Reviewer = startReviewer()
waitForAsyncTasksToComplete()

val jsapi = reviewer.jsApi

// test get tags for note
val expectedTags = n.tags
val response = getDataFromRequest("getNoteTags", jsapi, jsonObjectOf("noteId" to n.id))
val jsonResponse = JSONObject(response)
val actualTags = JSONArray(jsonResponse.getString("value"))

assertEquals(expectedTags.size, actualTags.length())
for (i in 0 until actualTags.length()) {
assertEquals(expectedTags[i], actualTags.getString(i))
}
}

@Test
fun ankiSetNoteTagsTest() =
runTest {
val n =
addBasicNote("Front", "Back").update {
tags = mutableListOf("tag1", "tag2", "tag3")
}

val reviewer: Reviewer = startReviewer()
waitForAsyncTasksToComplete()

val jsapi = reviewer.jsApi

// test set tags for note
val newTags =
JSONArray().apply {
put("tag4")
put("tag5")
put("tag6")
}

assertThat(
getDataFromRequest("setNoteTags", jsapi, jsonObjectOf("noteId" to n.id, "tags" to newTags)),
equalTo(formatApiResult(true)),
)
waitForAsyncTasksToComplete()

// Reload the note to ensure the tags are updated
val updatedNote = col.getNote(n.id)

// Verify the tags are updated
assertEquals(newTags.length(), updatedNote.tags.size)
for (i in 0 until newTags.length()) {
assertEquals(newTags.getString(i), updatedNote.tags[i])
}
}

companion object {
fun jsApiContract(data: String = ""): ByteArray =
JSONObject()
Expand Down Expand Up @@ -450,5 +513,21 @@ class AnkiDroidJsAPITest : RobolectricTest() {
jsAPI
.handleJsApiRequest(methodName, jsApiContract(apiData), false)
.decodeToString()

suspend fun getDataFromRequest(
methodName: String,
jsAPI: AnkiDroidJsAPI,
apiData: JSONObject,
): String =
jsAPI
.handleJsApiRequest(methodName, jsApiContract(apiData.toString()), false)
.decodeToString()
}
}

private fun jsonObjectOf(vararg pairs: Pair<String, Any>): JSONObject =
JSONObject().apply {
for ((key, value) in pairs) {
put(key, value)
}
}
7 changes: 7 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/testutils/TestClass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ interface TestClass {
col.decks.save(deckConfig)
}

/** Helper method to update a note */
fun Note.update(block: Note.() -> Unit): Note {
block(this)
col.updateNote(this)
return this
}

/** Helper method to all cards of a note */
fun Note.updateCards(update: Card.() -> Unit): Note {
cards().forEach { it.update(update) }
Expand Down
Loading