0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-19 19:42:17 +02:00

Replace CRLF with LF

This commit is contained in:
CLIDragon 2024-07-15 17:11:59 +10:00 committed by David Allison
parent 1feb8b31b8
commit 2c7c0c5bda
6 changed files with 438 additions and 438 deletions

View File

@ -1,208 +1,208 @@
/* /*
* Copyright (c) 2023 * Copyright (c) 2023
* *
* This program is free software; you can redistribute it and/or modify it under * 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 * 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 * Foundation; either version 3 of the License, or (at your option) any later
* version. * version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY * 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 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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 * You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.ichi2.anki package com.ichi2.anki
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withResourceName import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.tests.InstrumentedTest import com.ichi2.anki.tests.InstrumentedTest
import com.ichi2.anki.tests.libanki.RetryRule import com.ichi2.anki.tests.libanki.RetryRule
import com.ichi2.anki.testutil.GrantStoragePermission.storagePermission import com.ichi2.anki.testutil.GrantStoragePermission.storagePermission
import com.ichi2.anki.testutil.ThreadUtils import com.ichi2.anki.testutil.ThreadUtils
import com.ichi2.anki.testutil.grantPermissions import com.ichi2.anki.testutil.grantPermissions
import com.ichi2.anki.testutil.notificationPermission import com.ichi2.anki.testutil.notificationPermission
import com.ichi2.libanki.Collection import com.ichi2.libanki.Collection
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.lang.AssertionError import java.lang.AssertionError
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ReviewerTest : InstrumentedTest() { class ReviewerTest : InstrumentedTest() {
// Launch IntroductionActivity instead of DeckPicker activity because in CI // Launch IntroductionActivity instead of DeckPicker activity because in CI
// builds, it seems to create IntroductionActivity after the DeckPicker, // builds, it seems to create IntroductionActivity after the DeckPicker,
// causing the DeckPicker activity to be destroyed. As a consequence, this // causing the DeckPicker activity to be destroyed. As a consequence, this
// will throw RootViewWithoutFocusException when Espresso tries to interact // will throw RootViewWithoutFocusException when Espresso tries to interact
// with an already destroyed activity. By launching IntroductionActivity, we // with an already destroyed activity. By launching IntroductionActivity, we
// ensure that IntroductionActivity is launched first and navigate to the // ensure that IntroductionActivity is launched first and navigate to the
// DeckPicker -> Reviewer activities // DeckPicker -> Reviewer activities
@get:Rule @get:Rule
val activityScenarioRule = ActivityScenarioRule(IntroductionActivity::class.java) val activityScenarioRule = ActivityScenarioRule(IntroductionActivity::class.java)
@get:Rule @get:Rule
val runtimePermissionRule = grantPermissions(storagePermission, notificationPermission) val runtimePermissionRule = grantPermissions(storagePermission, notificationPermission)
@get:Rule @get:Rule
val retry = RetryRule(10) val retry = RetryRule(10)
@Test @Test
fun testCustomSchedulerWithCustomData() { fun testCustomSchedulerWithCustomData() {
col.cardStateCustomizer = col.cardStateCustomizer =
""" """
states.good.normal.review.easeFactor = 3.0; states.good.normal.review.easeFactor = 3.0;
states.good.normal.review.scheduledDays = 123; states.good.normal.review.scheduledDays = 123;
customData.good.c += 1; customData.good.c += 1;
""" """
val note = addNoteUsingBasicModel("foo", "bar") val note = addNoteUsingBasicModel("foo", "bar")
val card = note.firstCard(col) val card = note.firstCard(col)
val deck = col.decks.get(note.notetype.did)!! val deck = col.decks.get(note.notetype.did)!!
card.moveToReviewQueue() card.moveToReviewQueue()
col.backend.updateCards( col.backend.updateCards(
listOf( listOf(
card.toBackendCard().toBuilder().setCustomData("""{"c":1}""").build() card.toBackendCard().toBuilder().setCustomData("""{"c":1}""").build()
), ),
true true
) )
closeGetStartedScreenIfExists() closeGetStartedScreenIfExists()
closeBackupCollectionDialogIfExists() closeBackupCollectionDialogIfExists()
reviewDeckWithName(deck.name) reviewDeckWithName(deck.name)
var cardFromDb = col.getCard(card.id).toBackendCard() var cardFromDb = col.getCard(card.id).toBackendCard()
assertThat(cardFromDb.easeFactor, equalTo(card.factor)) assertThat(cardFromDb.easeFactor, equalTo(card.factor))
assertThat(cardFromDb.interval, equalTo(card.ivl)) assertThat(cardFromDb.interval, equalTo(card.ivl))
assertThat(cardFromDb.customData, equalTo("""{"c":1}""")) assertThat(cardFromDb.customData, equalTo("""{"c":1}"""))
clickShowAnswerAndAnswerGood() clickShowAnswerAndAnswerGood()
fun runAssertion() { fun runAssertion() {
cardFromDb = col.getCard(card.id).toBackendCard() cardFromDb = col.getCard(card.id).toBackendCard()
assertThat(cardFromDb.easeFactor, equalTo(3000)) assertThat(cardFromDb.easeFactor, equalTo(3000))
assertThat(cardFromDb.interval, equalTo(123)) assertThat(cardFromDb.interval, equalTo(123))
assertThat(cardFromDb.customData, equalTo("""{"c":2}""")) assertThat(cardFromDb.customData, equalTo("""{"c":2}"""))
} }
try { try {
runAssertion() runAssertion()
} catch (e: Exception) { } catch (e: Exception) {
// Give separate threads a greater chance of doing the custom scheduling // Give separate threads a greater chance of doing the custom scheduling
// if the card scheduling values aren't updated immediately // if the card scheduling values aren't updated immediately
ThreadUtils.sleep(2000) ThreadUtils.sleep(2000)
runAssertion() runAssertion()
} }
} }
@Test @Test
fun testCustomSchedulerWithRuntimeError() { fun testCustomSchedulerWithRuntimeError() {
// Issue 15035 - runtime errors weren't handled // Issue 15035 - runtime errors weren't handled
col.cardStateCustomizer = "states.this_is_not_defined.normal.review = 12;" col.cardStateCustomizer = "states.this_is_not_defined.normal.review = 12;"
addNoteUsingBasicModel() addNoteUsingBasicModel()
closeGetStartedScreenIfExists() closeGetStartedScreenIfExists()
closeBackupCollectionDialogIfExists() closeBackupCollectionDialogIfExists()
reviewDeckWithName("Default") reviewDeckWithName("Default")
clickShowAnswer() clickShowAnswer()
ensureAnswerButtonsAreDisplayed() ensureAnswerButtonsAreDisplayed()
} }
private fun clickOnDeckWithName(deckName: String) { private fun clickOnDeckWithName(deckName: String) {
onView(withId(R.id.files)).checkWithTimeout(matches(hasDescendant(withText(deckName)))) onView(withId(R.id.files)).checkWithTimeout(matches(hasDescendant(withText(deckName))))
onView(withId(R.id.files)).perform( onView(withId(R.id.files)).perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>( RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText(deckName)), hasDescendant(withText(deckName)),
click() click()
) )
) )
} }
private fun clickOnStudyButtonIfExists() { private fun clickOnStudyButtonIfExists() {
onView(withId(R.id.studyoptions_start)) onView(withId(R.id.studyoptions_start))
.withFailureHandler { _, _ -> } .withFailureHandler { _, _ -> }
.perform(click()) .perform(click())
} }
private fun reviewDeckWithName(deckName: String) { private fun reviewDeckWithName(deckName: String) {
clickOnDeckWithName(deckName) clickOnDeckWithName(deckName)
// Adding cards directly to the database while in the Deck Picker screen // Adding cards directly to the database while in the Deck Picker screen
// will not update the page with correct card counts. Hence, clicking // will not update the page with correct card counts. Hence, clicking
// on the deck will bring us to the study options page where we need to // on the deck will bring us to the study options page where we need to
// click on the Study button. If we have added cards to the database // click on the Study button. If we have added cards to the database
// before the Deck Picker screen has fully loaded, then we skip clicking // before the Deck Picker screen has fully loaded, then we skip clicking
// the Study button // the Study button
clickOnStudyButtonIfExists() clickOnStudyButtonIfExists()
} }
private fun clickShowAnswerAndAnswerGood() { private fun clickShowAnswerAndAnswerGood() {
clickShowAnswer() clickShowAnswer()
ensureAnswerButtonsAreDisplayed() ensureAnswerButtonsAreDisplayed()
try { try {
// ...on the command line it has resource name "good_button"... // ...on the command line it has resource name "good_button"...
onView(withResourceName("good_button")).perform(click()) onView(withResourceName("good_button")).perform(click())
} catch (e: NoMatchingViewException) { } catch (e: NoMatchingViewException) {
// ...but in Android Studio it has resource name "flashcard_layout_ease3" !? // ...but in Android Studio it has resource name "flashcard_layout_ease3" !?
onView(withResourceName("flashcard_layout_ease3")).perform(click()) onView(withResourceName("flashcard_layout_ease3")).perform(click())
} }
} }
private fun clickShowAnswer() { private fun clickShowAnswer() {
try { try {
// ... on the command line, it has resource name "show_answer"... // ... on the command line, it has resource name "show_answer"...
onView(withResourceName("show_answer")).perform(click()) onView(withResourceName("show_answer")).perform(click())
} catch (e: NoMatchingViewException) { } catch (e: NoMatchingViewException) {
// ... but in Android Studio it has resource name "flashcard_layout_flip" !? // ... but in Android Studio it has resource name "flashcard_layout_flip" !?
onView(withResourceName("flashcard_layout_flip")).perform(click()) onView(withResourceName("flashcard_layout_flip")).perform(click())
} }
} }
private fun ensureAnswerButtonsAreDisplayed() { private fun ensureAnswerButtonsAreDisplayed() {
// We need to wait for the card to fully load to allow enough time for // We need to wait for the card to fully load to allow enough time for
// the messages to be passed in and out of the WebView when evaluating // the messages to be passed in and out of the WebView when evaluating
// the custom JS scheduler code. The ease buttons are hidden until the // the custom JS scheduler code. The ease buttons are hidden until the
// custom scheduler has finished running // custom scheduler has finished running
try { try {
// ...on the command line it has resource name "good_button"... // ...on the command line it has resource name "good_button"...
onView(withResourceName("good_button")).checkWithTimeout( onView(withResourceName("good_button")).checkWithTimeout(
matches(isDisplayed()), matches(isDisplayed()),
100 100
) )
} catch (e: AssertionError) { } catch (e: AssertionError) {
// ...but in Android Studio it has resource name "flashcard_layout_ease3" !? // ...but in Android Studio it has resource name "flashcard_layout_ease3" !?
onView(withResourceName("flashcard_layout_ease3")).checkWithTimeout( onView(withResourceName("flashcard_layout_ease3")).checkWithTimeout(
matches(isDisplayed()), matches(isDisplayed()),
100 100
) )
} }
} }
} }
private var Collection.cardStateCustomizer: String? private var Collection.cardStateCustomizer: String?
get() = config.get("cardStateCustomizer") get() = config.get("cardStateCustomizer")
set(value) { config.set("cardStateCustomizer", value) } set(value) { config.set("cardStateCustomizer", value) }
fun closeGetStartedScreenIfExists() { fun closeGetStartedScreenIfExists() {
onView(withId(R.id.get_started)).withFailureHandler { _, _ -> }.perform(click()) onView(withId(R.id.get_started)).withFailureHandler { _, _ -> }.perform(click())
} }
fun closeBackupCollectionDialogIfExists() { fun closeBackupCollectionDialogIfExists() {
onView(withText(R.string.button_backup_later)) onView(withText(R.string.button_backup_later))
.withFailureHandler { _, _ -> } .withFailureHandler { _, _ -> }
.perform(click()) .perform(click())
} }

View File

@ -1,99 +1,99 @@
/* /*
* Copyright (c) 2023 Abdo <abdo@abdnh.net> * Copyright (c) 2023 Abdo <abdo@abdnh.net>
* *
* This program is free software; you can redistribute it and/or modify it under * 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 * 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 * Foundation; either version 3 of the License, or (at your option) any later
* version. * version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY * 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 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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 * You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.ichi2.anki.pages package com.ichi2.anki.pages
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.webkit.WebView import android.webkit.WebView
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.ichi2.anki.R import com.ichi2.anki.R
import com.ichi2.anki.SingleFragmentActivity import com.ichi2.anki.SingleFragmentActivity
import com.ichi2.anki.dialogs.DiscardChangesDialog import com.ichi2.anki.dialogs.DiscardChangesDialog
import org.json.JSONObject import org.json.JSONObject
import timber.log.Timber import timber.log.Timber
class ImageOcclusion : PageFragment(R.layout.image_occlusion) { class ImageOcclusion : PageFragment(R.layout.image_occlusion) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
with(requireActivity()) { with(requireActivity()) {
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
DiscardChangesDialog.showDialog(this@with) { DiscardChangesDialog.showDialog(this@with) {
finish() finish()
} }
} }
} }
view.findViewById<MaterialToolbar>(R.id.toolbar).setOnMenuItemClickListener { view.findViewById<MaterialToolbar>(R.id.toolbar).setOnMenuItemClickListener {
if (it.itemId == R.id.action_save) { if (it.itemId == R.id.action_save) {
Timber.i("save item selected") Timber.i("save item selected")
webView.evaluateJavascript("anki.imageOcclusion.save()", null) webView.evaluateJavascript("anki.imageOcclusion.save()", null)
} }
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true
} }
} }
override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient { override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient {
return object : PageWebViewClient() { return object : PageWebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) { override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
val kind = requireArguments().getString(ARG_KEY_KIND) val kind = requireArguments().getString(ARG_KEY_KIND)
val noteOrNotetypeId = requireArguments().getLong(ARG_KEY_ID) val noteOrNotetypeId = requireArguments().getLong(ARG_KEY_ID)
val imagePath = requireArguments().getString(ARG_KEY_PATH) val imagePath = requireArguments().getString(ARG_KEY_PATH)
val options = JSONObject() val options = JSONObject()
options.put("kind", kind) options.put("kind", kind)
if (kind == "add") { if (kind == "add") {
options.put("imagePath", imagePath) options.put("imagePath", imagePath)
options.put("notetypeId", noteOrNotetypeId) options.put("notetypeId", noteOrNotetypeId)
} else { } else {
options.put("noteId", noteOrNotetypeId) options.put("noteId", noteOrNotetypeId)
} }
view?.evaluateJavascript("globalThis.anki.imageOcclusion.mode = $options") { view?.evaluateJavascript("globalThis.anki.imageOcclusion.mode = $options") {
super.onPageFinished(view, url) super.onPageFinished(view, url)
} }
} }
} }
} }
companion object { companion object {
private const val ARG_KEY_KIND = "kind" private const val ARG_KEY_KIND = "kind"
private const val ARG_KEY_ID = "id" private const val ARG_KEY_ID = "id"
private const val ARG_KEY_PATH = "imagePath" private const val ARG_KEY_PATH = "imagePath"
fun getIntent(context: Context, kind: String, noteOrNotetypeId: Long, imagePath: String?): Intent { fun getIntent(context: Context, kind: String, noteOrNotetypeId: Long, imagePath: String?): Intent {
val suffix = if (kind == "edit") { val suffix = if (kind == "edit") {
"/$noteOrNotetypeId" "/$noteOrNotetypeId"
} else { } else {
imagePath imagePath
} }
val arguments = bundleOf( val arguments = bundleOf(
ARG_KEY_KIND to kind, ARG_KEY_KIND to kind,
ARG_KEY_ID to noteOrNotetypeId, ARG_KEY_ID to noteOrNotetypeId,
ARG_KEY_PATH to imagePath, ARG_KEY_PATH to imagePath,
PATH_ARG_KEY to "image-occlusion$suffix" PATH_ARG_KEY to "image-occlusion$suffix"
) )
return SingleFragmentActivity.getIntent(context, ImageOcclusion::class, arguments) return SingleFragmentActivity.getIntent(context, ImageOcclusion::class, arguments)
} }
} }
} }

View File

@ -1,31 +1,31 @@
/* /*
* Copyright (c) 2022 Brayan Oliveira <brayandso.dev@gmail.com> * Copyright (c) 2022 Brayan Oliveira <brayandso.dev@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify it under * 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 * 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 * Foundation; either version 3 of the License, or (at your option) any later
* version. * version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY * 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 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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 * You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.ichi2.anki.preferences package com.ichi2.anki.preferences
import com.ichi2.anki.R import com.ichi2.anki.R
/** /**
* Fragment with preferences related to notifications * Fragment with preferences related to notifications
*/ */
class AccessibilitySettingsFragment : SettingsFragment() { class AccessibilitySettingsFragment : SettingsFragment() {
override val preferenceResource: Int override val preferenceResource: Int
get() = R.xml.preferences_accessibility get() = R.xml.preferences_accessibility
override val analyticsScreenNameConstant: String override val analyticsScreenNameConstant: String
get() = "prefs.accessibility" get() = "prefs.accessibility"
override fun initSubscreen() { override fun initSubscreen() {
} }
} }

View File

@ -1,55 +1,55 @@
/* /*
* Copyright (c) 2024 WPum <27683756+WPum@users.noreply.github.com> * Copyright (c) 2024 WPum <27683756+WPum@users.noreply.github.com>
* *
* This program is free software; you can redistribute it and/or modify it under * 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 * 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 * Foundation; either version 3 of the License, or (at your option) any later
* version. * version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY * 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 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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 * You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.ichi2.anki package com.ichi2.anki
import com.ichi2.anki.notifications.NotificationId import com.ichi2.anki.notifications.NotificationId
import com.ichi2.anki.worker.UniqueWorkNames import com.ichi2.anki.worker.UniqueWorkNames
import org.junit.Test import org.junit.Test
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KVisibility import kotlin.reflect.KVisibility
import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.declaredMemberProperties
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
class ConstantUniquenessTest { class ConstantUniquenessTest {
@Test @Test
fun testConstantUniqueness() { fun testConstantUniqueness() {
assertConstantUniqueness(NotificationId::class) assertConstantUniqueness(NotificationId::class)
assertConstantUniqueness(UniqueWorkNames::class) assertConstantUniqueness(UniqueWorkNames::class)
} }
companion object { companion object {
/** /**
* To check whether all PUBLIC CONST values in an object are unique. * To check whether all PUBLIC CONST values in an object are unique.
*/ */
fun <T : Any> assertConstantUniqueness(clazz: KClass<T>) { fun <T : Any> assertConstantUniqueness(clazz: KClass<T>) {
assertNotNull(clazz.objectInstance, "Can only check objects for uniqueness") assertNotNull(clazz.objectInstance, "Can only check objects for uniqueness")
val valueSet = HashSet<Any?>() val valueSet = HashSet<Any?>()
for (prop in clazz.declaredMemberProperties) { for (prop in clazz.declaredMemberProperties) {
if (!prop.isConst || prop.visibility != KVisibility.PUBLIC) { if (!prop.isConst || prop.visibility != KVisibility.PUBLIC) {
continue continue
} }
// use .call() since clazz represents an object // use .call() since clazz represents an object
val value = prop.call() val value = prop.call()
assertFalse(valueSet.contains(value), "Duplicate value ('$value') for constant in ${clazz.qualifiedName}") assertFalse(valueSet.contains(value), "Duplicate value ('$value') for constant in ${clazz.qualifiedName}")
valueSet.add(value) valueSet.add(value)
} }
} }
} }
} }

View File

@ -1,30 +1,30 @@
/* /*
* Copyright (c) 2024 Brayan Oliveira <brayandso.dev@gmail.com> * Copyright (c) 2024 Brayan Oliveira <brayandso.dev@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify it under * 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 * 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 * Foundation; either version 3 of the License, or (at your option) any later
* version. * version.
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY * 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 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details. * 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 * You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.ichi2.anki.previewer package com.ichi2.anki.previewer
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class PreviewerViewModelTest { class PreviewerViewModelTest {
@Test @Test
fun `type answer fields are removed in questions`() { fun `type answer fields are removed in questions`() {
assertThat( assertThat(
PreviewerViewModel.typeAnsQuestionFilter("creu [[type:leu]]"), PreviewerViewModel.typeAnsQuestionFilter("creu [[type:leu]]"),
equalTo("creu ") equalTo("creu ")
) )
} }
} }

View File

@ -1,15 +1,15 @@
# We want control over the SDK used to build since we need newer JDKs # We want control over the SDK used to build since we need newer JDKs
# Use the latest LTS supported by temurin with version format xx.yy.zz-tem (ignore sub-version) # Use the latest LTS supported by temurin with version format xx.yy.zz-tem (ignore sub-version)
# You may find versions e.g. for JDK21 like so https://adoptium.net/temurin/archive/?version=21 # You may find versions e.g. for JDK21 like so https://adoptium.net/temurin/archive/?version=21
# You may verify that a JDK is installed by looking at the "sdk list java" output from, for example: # You may verify that a JDK is installed by looking at the "sdk list java" output from, for example:
# https://jitpack.io/com/github/ankidroid/Anki-Android/v2.18alpha7/build.log (just use a recent tag, # https://jitpack.io/com/github/ankidroid/Anki-Android/v2.18alpha7/build.log (just use a recent tag,
# if it has not built yet you will have to wait for it to build then on refresh the build log should exist) # if it has not built yet you will have to wait for it to build then on refresh the build log should exist)
before_install: before_install:
- sdk update - sdk update
- sdk list java - sdk list java
- sdk install java 21.0.2-tem - sdk install java 21.0.2-tem
- sdk use java 21.0.2-tem - sdk use java 21.0.2-tem
# We can do the absolute minimum to build the API module, no need to build AnkiDroid module # We can do the absolute minimum to build the API module, no need to build AnkiDroid module
install: install:
- ./gradlew :api:publishToMavenLocal - ./gradlew :api:publishToMavenLocal