mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 03:14:40 -05:00
implement Source Filters
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.source.model
|
package eu.kanade.tachiyomi.source.model
|
||||||
|
|
||||||
sealed class Filter<T>(val name: String, var state: T) {
|
// The class is originally sealed, Tachidesk adds new subclasses for serialization
|
||||||
|
// sealed class Filter<T>(val name: String, var state: T) {
|
||||||
|
open class Filter<T>(val name: String, var state: T) {
|
||||||
open class Header(name: String) : Filter<Any>(name, 0)
|
open class Header(name: String) : Filter<Any>(name, 0)
|
||||||
open class Separator(name: String = "") : Filter<Any>(name, 0)
|
open class Separator(name: String = "") : Filter<Any>(name, 0)
|
||||||
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
|
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ object MangaAPI {
|
|||||||
get("{sourceId}/preferences", SourceController::getPreferences)
|
get("{sourceId}/preferences", SourceController::getPreferences)
|
||||||
post("{sourceId}/preferences", SourceController::setPreference)
|
post("{sourceId}/preferences", SourceController::setPreference)
|
||||||
|
|
||||||
get("{sourceId}/filters", SourceController::filters)
|
get("{sourceId}/filters", SourceController::getFilters)
|
||||||
|
patch("{sourceId}/filters", SourceController::setFilter)
|
||||||
|
|
||||||
get("{sourceId}/search/{searchTerm}/{pageNum}", SourceController::searchSingle)
|
get("{sourceId}/search/{searchTerm}/{pageNum}", SourceController::searchSingle)
|
||||||
// get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal)
|
// get("search/{searchTerm}/{pageNum}", SourceController::searchGlobal)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ package suwayomi.tachidesk.manga.controller
|
|||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList
|
import suwayomi.tachidesk.manga.impl.MangaList
|
||||||
import suwayomi.tachidesk.manga.impl.Search
|
import suwayomi.tachidesk.manga.impl.Search
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.FilterChange
|
||||||
import suwayomi.tachidesk.manga.impl.Source
|
import suwayomi.tachidesk.manga.impl.Source
|
||||||
import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange
|
import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
@@ -54,7 +55,7 @@ object SourceController {
|
|||||||
ctx.json(Source.getSourcePreferences(sourceId))
|
ctx.json(Source.getSourcePreferences(sourceId))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** fetch preferences of source with id `sourceId` */
|
/** set one preference of source with id `sourceId` */
|
||||||
fun setPreference(ctx: Context) {
|
fun setPreference(ctx: Context) {
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
|
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
|
||||||
@@ -62,11 +63,18 @@ object SourceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** fetch filters of source with id `sourceId` */
|
/** fetch filters of source with id `sourceId` */
|
||||||
fun filters(ctx: Context) {
|
fun getFilters(ctx: Context) {
|
||||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
val reset = ctx.queryParam("reset")?.toBoolean() ?: false
|
val reset = ctx.queryParam("reset")?.toBoolean() ?: false
|
||||||
|
ctx.json(Search.getFilterList(sourceId, reset))
|
||||||
|
}
|
||||||
|
|
||||||
ctx.json(Search.getInitialFilterList(sourceId, reset))
|
/** set one filter of source with id `sourceId` */
|
||||||
|
fun setFilter(ctx: Context) {
|
||||||
|
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||||
|
val filterChange = ctx.bodyAsClass(FilterChange::class.java)
|
||||||
|
|
||||||
|
ctx.json(Search.setFilter(sourceId, filterChange))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** single source search */
|
/** single source search */
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ package suwayomi.tachidesk.manga.impl
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import io.javalin.plugin.json.JsonMapper
|
||||||
|
import org.kodein.di.DI
|
||||||
|
import org.kodein.di.conf.global
|
||||||
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.manga.impl.MangaList.processEntries
|
import suwayomi.tachidesk.manga.impl.MangaList.processEntries
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||||
@@ -17,89 +22,102 @@ import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
|||||||
object Search {
|
object Search {
|
||||||
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
||||||
val source = getCatalogueSourceOrStub(sourceId)
|
val source = getCatalogueSourceOrStub(sourceId)
|
||||||
val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(sourceId)).awaitSingle()
|
val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(source)).awaitSingle()
|
||||||
return searchManga.processEntries(sourceId)
|
return searchManga.processEntries(sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val filterListCache = mutableMapOf<Long, FilterList>()
|
private val filterListCache = mutableMapOf<Long, FilterList>()
|
||||||
|
|
||||||
private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList {
|
private fun getFilterListOf(source: CatalogueSource, reset: Boolean = false): FilterList {
|
||||||
if (reset || !filterListCache.containsKey(sourceId)) {
|
if (reset || !filterListCache.containsKey(source.id)) {
|
||||||
filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList()
|
filterListCache[source.id] = source.getFilterList()
|
||||||
}
|
}
|
||||||
return filterListCache[sourceId]!!
|
return filterListCache[source.id]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInitialFilterList(sourceId: Long, reset: Boolean): List<FilterObject> {
|
fun getFilterList(sourceId: Long, reset: Boolean): List<FilterObject> {
|
||||||
return getFilterListOf(sourceId, reset).list.map {
|
val source = getCatalogueSourceOrStub(sourceId)
|
||||||
|
|
||||||
|
return getFilterListOf(source, reset).list.map {
|
||||||
FilterObject(
|
FilterObject(
|
||||||
when (it) {
|
when (it) {
|
||||||
is Filter.Header -> "Header"
|
is Filter.Header -> "Header"
|
||||||
is Filter.Separator -> "Separator"
|
is Filter.Separator -> "Separator"
|
||||||
|
is Filter.Select<*> -> "Select"
|
||||||
|
is Filter.Text -> "Text"
|
||||||
is Filter.CheckBox -> "CheckBox"
|
is Filter.CheckBox -> "CheckBox"
|
||||||
is Filter.TriState -> "TriState"
|
is Filter.TriState -> "TriState"
|
||||||
is Filter.Text -> "Text"
|
|
||||||
is Filter.Select<*> -> "Select"
|
|
||||||
is Filter.Group<*> -> "Group"
|
is Filter.Group<*> -> "Group"
|
||||||
is Filter.Sort -> "Sort"
|
is Filter.Sort -> "Sort"
|
||||||
|
else -> throw RuntimeException("sealed class Cannot have more Subtypes!")
|
||||||
},
|
},
|
||||||
// when (it) {
|
when (it) {
|
||||||
// is Filter.Select<*> -> it.getValuesType()
|
is Filter.Group<*> -> {
|
||||||
// else -> null
|
SerializableGroup(
|
||||||
// },
|
it.name,
|
||||||
it
|
it.state.map { item ->
|
||||||
|
when (item) {
|
||||||
|
is Filter.CheckBox -> FilterObject("CheckBox", item)
|
||||||
|
is Filter.TriState -> FilterObject("TriState", item)
|
||||||
|
is Filter.Text -> FilterObject("Text", item)
|
||||||
|
is Filter.Select<*> -> FilterObject("Select", item)
|
||||||
|
else -> throw RuntimeException("Illegal Group item type!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> it
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName
|
private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName
|
||||||
|
class SerializableGroup(name: String, state: List<FilterObject>) : Filter<List<FilterObject>>(name, state)
|
||||||
|
|
||||||
data class FilterObject(
|
data class FilterObject(
|
||||||
val type: String,
|
val type: String,
|
||||||
val filter: Filter<*>
|
val filter: Filter<*>,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun setFilter(sourceId: Long, change: FilterChange) {
|
||||||
|
val source = getCatalogueSourceOrStub(sourceId)
|
||||||
|
val filterList = getFilterListOf(source, false)
|
||||||
|
|
||||||
|
when (val filter = filterList[change.position]) {
|
||||||
|
is Filter.Header -> {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
is Filter.Separator -> {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
is Filter.Select<*> -> filter.state = change.state.toInt()
|
||||||
|
is Filter.Text -> filter.state = change.state
|
||||||
|
is Filter.CheckBox -> filter.state = change.state.toBooleanStrict()
|
||||||
|
is Filter.TriState -> filter.state = change.state.toInt()
|
||||||
|
is Filter.Group<*> -> {
|
||||||
|
val groupChange = jsonMapper.fromJsonString(change.state, FilterChange::class.java)
|
||||||
|
|
||||||
|
when (val groupFilter = filter.state[groupChange.position]) {
|
||||||
|
is Filter.CheckBox -> groupFilter.state = groupChange.state.toBooleanStrict()
|
||||||
|
is Filter.TriState -> groupFilter.state = groupChange.state.toInt()
|
||||||
|
is Filter.Text -> groupFilter.state = groupChange.state
|
||||||
|
is Filter.Select<*> -> groupFilter.state = groupChange.state.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Filter.Sort -> filter.state = jsonMapper.fromJsonString(change.state, Filter.Sort.Selection::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val jsonMapper by DI.global.instance<JsonMapper>()
|
||||||
|
|
||||||
|
data class FilterChange(
|
||||||
|
val position: Int,
|
||||||
|
val state: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun sourceGlobalSearch(searchTerm: String) {
|
fun sourceGlobalSearch(searchTerm: String) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: Exhentai had a filter serializer (now in SY) that we might be able to steal
|
|
||||||
*/
|
|
||||||
// private fun FilterList.toFilterWrapper(): List<FilterWrapper> {
|
|
||||||
// return mapNotNull { filter ->
|
|
||||||
// when (filter) {
|
|
||||||
// is Filter.Header -> FilterWrapper("Header",filter)
|
|
||||||
// is Filter.Separator -> FilterWrapper("Separator",filter)
|
|
||||||
// is Filter.CheckBox -> FilterWrapper("CheckBox",filter)
|
|
||||||
// is Filter.TriState -> FilterWrapper("TriState",filter)
|
|
||||||
// is Filter.Text -> FilterWrapper("Text",filter)
|
|
||||||
// is Filter.Select<*> -> FilterWrapper("Select",filter)
|
|
||||||
// is Filter.Group<*> -> {
|
|
||||||
// val group = GroupItem(filter)
|
|
||||||
// val subItems = filter.state.mapNotNull {
|
|
||||||
// when (it) {
|
|
||||||
// is Filter.CheckBox -> FilterWrapper("CheckBox",filter)
|
|
||||||
// is Filter.TriState -> FilterWrapper("TriState",filter)
|
|
||||||
// is Filter.Text -> FilterWrapper("Text",filter)
|
|
||||||
// is Filter.Select<*> -> FilterWrapper("Select",filter)
|
|
||||||
// else -> null
|
|
||||||
// } as? ISectionable<*, *>
|
|
||||||
// }
|
|
||||||
// subItems.forEach { it.header = group }
|
|
||||||
// group.subItems = subItems
|
|
||||||
// group
|
|
||||||
// }
|
|
||||||
// is Filter.Sort -> {
|
|
||||||
// val group = SortGroup(filter)
|
|
||||||
// val subItems = filter.values.map {
|
|
||||||
// SortItem(it, group)
|
|
||||||
// }
|
|
||||||
// group.subItems = subItems
|
|
||||||
// group
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class StubSource(override val id: Long) : CatalogueSource {
|
open class StubSource(override val id: Long) : CatalogueSource {
|
||||||
override val lang: String = "other"
|
override val lang: String = "other"
|
||||||
override val supportsLatest: Boolean = false
|
override val supportsLatest: Boolean = false
|
||||||
override val name: String
|
override val name: String
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ package suwayomi.tachidesk.server
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.App
|
import eu.kanade.tachiyomi.App
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||||
|
import io.javalin.plugin.json.JavalinJackson
|
||||||
|
import io.javalin.plugin.json.JsonMapper
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.bind
|
import org.kodein.di.bind
|
||||||
@@ -53,6 +55,7 @@ fun applicationSetup() {
|
|||||||
DI.global.addImport(
|
DI.global.addImport(
|
||||||
DI.Module("Server") {
|
DI.Module("Server") {
|
||||||
bind<ApplicationDirs>() with singleton { applicationDirs }
|
bind<ApplicationDirs>() with singleton { applicationDirs }
|
||||||
|
bind<JsonMapper>() with singleton { JavalinJackson() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ package suwayomi.tachidesk.server.util
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import io.javalin.plugin.json.JavalinJackson
|
import io.javalin.plugin.json.JsonMapper
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request.Builder
|
import okhttp3.Request.Builder
|
||||||
|
import org.kodein.di.DI
|
||||||
|
import org.kodein.di.conf.global
|
||||||
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.global.impl.AboutDataClass
|
import suwayomi.tachidesk.global.impl.AboutDataClass
|
||||||
import suwayomi.tachidesk.server.serverConfig
|
import suwayomi.tachidesk.server.serverConfig
|
||||||
import suwayomi.tachidesk.server.util.Browser.openInBrowser
|
import suwayomi.tachidesk.server.util.Browser.openInBrowser
|
||||||
@@ -30,6 +33,8 @@ object AppMutex {
|
|||||||
|
|
||||||
private val appIP = if (serverConfig.ip == "0.0.0.0") "127.0.0.1" else serverConfig.ip
|
private val appIP = if (serverConfig.ip == "0.0.0.0") "127.0.0.1" else serverConfig.ip
|
||||||
|
|
||||||
|
private val jsonMapper by DI.global.instance<JsonMapper>()
|
||||||
|
|
||||||
private fun checkAppMutex(): AppMutexState {
|
private fun checkAppMutex(): AppMutexState {
|
||||||
val client = OkHttpClient.Builder()
|
val client = OkHttpClient.Builder()
|
||||||
.connectTimeout(200, TimeUnit.MILLISECONDS)
|
.connectTimeout(200, TimeUnit.MILLISECONDS)
|
||||||
@@ -46,7 +51,7 @@ object AppMutex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
JavalinJackson().fromJsonString(response, AboutDataClass::class.java)
|
jsonMapper.fromJsonString(response, AboutDataClass::class.java)
|
||||||
AppMutexState.TachideskInstanceRunning
|
AppMutexState.TachideskInstanceRunning
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
AppMutexState.OtherApplicationRunning
|
AppMutexState.OtherApplicationRunning
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import suwayomi.tachidesk.manga.model.table.CategoryTable
|
|||||||
import suwayomi.tachidesk.test.ApplicationTest
|
import suwayomi.tachidesk.test.ApplicationTest
|
||||||
import suwayomi.tachidesk.test.clearTables
|
import suwayomi.tachidesk.test.clearTables
|
||||||
|
|
||||||
internal class CategoryControllerTest : ApplicationTest() {
|
class CategoryControllerTest : ApplicationTest() {
|
||||||
@Test
|
@Test
|
||||||
fun categoryReorder() {
|
fun categoryReorder() {
|
||||||
Category.createCategory("foo")
|
Category.createCategory("foo")
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package suwayomi.tachidesk.manga.impl
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import io.javalin.plugin.json.JavalinJackson
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.jupiter.api.AfterAll
|
||||||
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import rx.Observable
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.FilterChange
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.FilterObject
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.SerializableGroup
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.getFilterList
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.setFilter
|
||||||
|
import suwayomi.tachidesk.manga.impl.Search.sourceSearch
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
|
||||||
|
import suwayomi.tachidesk.manga.impl.util.source.StubSource
|
||||||
|
import suwayomi.tachidesk.test.ApplicationTest
|
||||||
|
import suwayomi.tachidesk.test.createSMangas
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
class SearchTest : ApplicationTest() {
|
||||||
|
class FakeSearchableSource(id: Long) : StubSource(id) {
|
||||||
|
var mangas: List<SManga> = emptyList()
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
return Observable.just(MangasPage(mangas, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sourceId = 1L
|
||||||
|
private val source = FakeSearchableSource(sourceId)
|
||||||
|
private val mangasCount = 10
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
fun setup() {
|
||||||
|
registerCatalogueSource(sourceId to source)
|
||||||
|
|
||||||
|
this.source.mangas = createSMangas(mangasCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun searchWorks() {
|
||||||
|
val searchResults = runBlocking {
|
||||||
|
sourceSearch(sourceId, "all the mangas", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(mangasCount, searchResults.mangaList.size, "should return all the mangas")
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
fun teardown() {
|
||||||
|
unregisterCatalogueSource(this.sourceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class FilterListTest : ApplicationTest() {
|
||||||
|
open class EmptyFilterListSource(id: Long) : StubSource(id) {
|
||||||
|
open var mFilterList = FilterList()
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList {
|
||||||
|
return mFilterList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `empty FilterList returns empty List`() {
|
||||||
|
val source = registerSource(EmptyFilterListSource::class)
|
||||||
|
source.mFilterList = FilterList()
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
0, filterList.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterListSource(id: Long) : EmptyFilterListSource(id) {
|
||||||
|
class SelectFilter(name: String, values: Array<String>) : Filter.Select<String>(name, values)
|
||||||
|
class TextFilter(name: String) : Filter.Text(name)
|
||||||
|
class TestCheckBox(name: String) : Filter.CheckBox(name, false)
|
||||||
|
class TriState(name: String, state: Int) : Filter.TriState(name, state)
|
||||||
|
class Group(name: String, state: List<TestCheckBox>) : Filter.Group<TestCheckBox>(name, state)
|
||||||
|
class Sort(name: String, values: Array<String>, state: Selection) : Filter.Sort(name, values, state)
|
||||||
|
|
||||||
|
override var mFilterList = FilterList(
|
||||||
|
Filter.Header("This is a header"),
|
||||||
|
Filter.Separator(),
|
||||||
|
SelectFilter("Select one of these:", arrayOf("this", "that", "none of them")),
|
||||||
|
TextFilter("text filter"),
|
||||||
|
TestCheckBox("check this or else!"),
|
||||||
|
TriState("wanna hook up?", Filter.TriState.STATE_IGNORE),
|
||||||
|
Group(
|
||||||
|
"my Todo",
|
||||||
|
listOf(
|
||||||
|
TestCheckBox("Write Tests"),
|
||||||
|
TestCheckBox("Write More Tests"),
|
||||||
|
TestCheckBox("Write Even More Tests"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Sort(
|
||||||
|
"Sort",
|
||||||
|
arrayOf("Alphabetic", "Date published", "Rating"),
|
||||||
|
Filter.Sort.Selection(2, false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun convertsEveryTypeCorrectly() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("Header", source.mFilterList[0]),
|
||||||
|
filterList[0]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("Separator", source.mFilterList[1]),
|
||||||
|
filterList[1]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("Select", source.mFilterList[2]),
|
||||||
|
filterList[2]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("Text", source.mFilterList[3]),
|
||||||
|
filterList[3]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("CheckBox", source.mFilterList[4]),
|
||||||
|
filterList[4]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("TriState", source.mFilterList[5]),
|
||||||
|
filterList[5]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
filterList[6],
|
||||||
|
FilterObject(
|
||||||
|
"Group",
|
||||||
|
SerializableGroup(
|
||||||
|
source.mFilterList[6].name,
|
||||||
|
listOf(
|
||||||
|
FilterObject("CheckBox", (source.mFilterList[6].state as List<Filter<*>>)[0]),
|
||||||
|
FilterObject("CheckBox", (source.mFilterList[6].state as List<Filter<*>>)[1]),
|
||||||
|
FilterObject("CheckBox", (source.mFilterList[6].state as List<Filter<*>>)[2]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
FilterObject("Sort", source.mFilterList[7]),
|
||||||
|
filterList[7]
|
||||||
|
)
|
||||||
|
|
||||||
|
// make sure that we can convert this to json
|
||||||
|
JavalinJackson().toJsonString(filterList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Header and Separator should not change`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(0, "change!")
|
||||||
|
)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(1, "change!")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[0].filter.state,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[1].filter.state,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Select changes are Int`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(2, "1")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[2].filter.state,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Text changes are String`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(3, "I'm a changed man!")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[3].filter.state,
|
||||||
|
"I'm a changed man!"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `CheckBox changes are Boolean`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(4, "true")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[4].filter.state,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TriState changes are Int`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(5, "1")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[5].filter.state,
|
||||||
|
Filter.TriState.STATE_INCLUDE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Group changes are Filters`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(6, """{"position":0,"state":"true"}""")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
(filterList[6].filter.state as List<FilterObject>)[0].filter.state,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Sort changes are Filter,Sort,Selection`() {
|
||||||
|
val source = registerSource(FilterListSource::class)
|
||||||
|
|
||||||
|
setFilter(
|
||||||
|
source.id,
|
||||||
|
FilterChange(7, """{"index":1,"ascending":"true"}""")
|
||||||
|
)
|
||||||
|
|
||||||
|
val filterList = getFilterList(source.id, false)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
filterList[7].filter.state,
|
||||||
|
Filter.Sort.Selection(1, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var sourceCount = 0L
|
||||||
|
|
||||||
|
private fun registerSource(sourceClass: KClass<*>): EmptyFilterListSource {
|
||||||
|
return synchronized(sourceCount) {
|
||||||
|
val source = sourceClass.primaryConstructor!!.call(sourceCount) as EmptyFilterListSource
|
||||||
|
registerCatalogueSource(sourceCount to source)
|
||||||
|
sourceCount++
|
||||||
|
source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
fun teardown() {
|
||||||
|
(0 until sourceCount).forEach { unregisterCatalogueSource(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package suwayomi.tachidesk
|
package suwayomi.tachidesk.test
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) Contributors to the Suwayomi project
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
@@ -9,7 +9,8 @@ package suwayomi.tachidesk
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.App
|
import eu.kanade.tachiyomi.App
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||||
import masstest.BASE_PATH
|
import io.javalin.plugin.json.JavalinJackson
|
||||||
|
import io.javalin.plugin.json.JsonMapper
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
@@ -59,6 +60,7 @@ open class ApplicationTest {
|
|||||||
DI.global.addImport(
|
DI.global.addImport(
|
||||||
DI.Module("Server") {
|
DI.Module("Server") {
|
||||||
bind<ApplicationDirs>() with singleton { applicationDirs }
|
bind<ApplicationDirs>() with singleton { applicationDirs }
|
||||||
|
bind<JsonMapper>() with singleton { JavalinJackson() }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ package suwayomi.tachidesk.test
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level
|
import ch.qos.logback.classic.Level
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.jetbrains.exposed.dao.id.IdTable
|
import org.jetbrains.exposed.dao.id.IdTable
|
||||||
import org.jetbrains.exposed.sql.batchInsert
|
import org.jetbrains.exposed.sql.batchInsert
|
||||||
@@ -41,6 +42,17 @@ fun createLibraryManga(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createSMangas(
|
||||||
|
count: Int
|
||||||
|
): List<SManga> {
|
||||||
|
return (0 until count).map {
|
||||||
|
SManga.create().apply {
|
||||||
|
title = "Manga $it"
|
||||||
|
url = "https://$title"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createChapters(
|
fun createChapters(
|
||||||
mangaId: Int,
|
mangaId: Int,
|
||||||
amount: Int,
|
amount: Int,
|
||||||
|
|||||||
Reference in New Issue
Block a user