mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Added several unit tests and improved testing infra.
This commit is contained in:
parent
25f015dfbb
commit
346a2cb4f9
@ -10,12 +10,18 @@ abstract class TrackedDisposable : Disposable {
|
||||
|
||||
init {
|
||||
disposableCount++
|
||||
|
||||
if (trackPrecise) {
|
||||
@Suppress("LeakingThis")
|
||||
disposables.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
final override fun dispose() {
|
||||
if (!disposed) {
|
||||
disposed = true
|
||||
disposableCount--
|
||||
disposables.remove(this)
|
||||
internalDispose()
|
||||
}
|
||||
}
|
||||
@ -25,16 +31,45 @@ abstract class TrackedDisposable : Disposable {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DISPOSABLE_PRINT_COUNT = 10
|
||||
|
||||
var disposables: MutableSet<Disposable> = mutableSetOf()
|
||||
var trackPrecise = false
|
||||
var disposableCount: Int = 0
|
||||
private set
|
||||
|
||||
fun checkNoLeaks(block: () -> Unit) {
|
||||
val count = disposableCount
|
||||
inline fun checkNoLeaks(trackPrecise: Boolean = false, block: () -> Unit) {
|
||||
val initialCount = disposableCount
|
||||
val initialTrackPrecise = this.trackPrecise
|
||||
val initialDisposables = disposables
|
||||
this.trackPrecise = trackPrecise
|
||||
disposables = mutableSetOf()
|
||||
|
||||
try {
|
||||
block()
|
||||
checkLeaks(disposableCount - initialCount)
|
||||
} finally {
|
||||
check(count == disposableCount) { "TrackedDisposables were leaked." }
|
||||
this.trackPrecise = initialTrackPrecise
|
||||
disposables = initialDisposables
|
||||
}
|
||||
}
|
||||
|
||||
fun checkLeaks(leakCount: Int) {
|
||||
buildString {
|
||||
append("$leakCount TrackedDisposables were leaked")
|
||||
|
||||
if (trackPrecise) {
|
||||
append(": ")
|
||||
disposables.take(DISPOSABLE_PRINT_COUNT).joinTo(this) {
|
||||
it::class.simpleName ?: "Anonymous"
|
||||
}
|
||||
|
||||
if (disposables.size > DISPOSABLE_PRINT_COUNT) {
|
||||
append(",..")
|
||||
}
|
||||
}
|
||||
|
||||
append(".")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(kotlin("test-common"))
|
||||
implementation(kotlin("test-annotations-common"))
|
||||
implementation(project(":test-utils"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,20 @@ class QuestNpc(
|
||||
override var areaId: Int,
|
||||
val data: Buffer,
|
||||
) : QuestEntity<NpcType> {
|
||||
constructor(
|
||||
type: NpcType,
|
||||
episode: Episode,
|
||||
areaId: Int,
|
||||
wave: Int,
|
||||
) : this(episode, areaId, Buffer.withSize(NPC_BYTE_SIZE)) {
|
||||
this.type = type
|
||||
// TODO: Set default data.
|
||||
// Set area_id after type, because you might want to overwrite the area_id that type has
|
||||
// determined.
|
||||
this.areaId = areaId
|
||||
// TODO: Set wave properties.
|
||||
}
|
||||
|
||||
var typeId: Short
|
||||
get() = data.getShort(0)
|
||||
set(value) {
|
||||
|
@ -1,11 +1,12 @@
|
||||
package world.phantasmal.lib.assembly
|
||||
|
||||
import world.phantasmal.core.Success
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class AssemblyTests {
|
||||
class AssemblyTests : LibTestSuite() {
|
||||
@Test
|
||||
fun assemble_basic_script() {
|
||||
val result = assemble("""
|
||||
|
@ -1,11 +1,12 @@
|
||||
package world.phantasmal.lib.assembly.dataFlowAnalysis
|
||||
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.toInstructions
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ControlFlowGraphTests {
|
||||
class ControlFlowGraphTests : LibTestSuite() {
|
||||
@Test
|
||||
fun single_instruction() {
|
||||
val im = toInstructions("""
|
||||
|
@ -1,13 +1,14 @@
|
||||
package world.phantasmal.lib.assembly.dataFlowAnalysis
|
||||
|
||||
import world.phantasmal.lib.assembly.*
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.toInstructions
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
private const val MAX_REGISTER_VALUES_SIZE: Long = 1L shl 32
|
||||
|
||||
class GetRegisterValueTests {
|
||||
class GetRegisterValueTests : LibTestSuite() {
|
||||
@Test
|
||||
fun when_no_instruction_sets_the_register_zero_is_returned() {
|
||||
val im = toInstructions("""
|
||||
|
@ -1,11 +1,12 @@
|
||||
package world.phantasmal.lib.assembly.dataFlowAnalysis
|
||||
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ValueSetTests {
|
||||
class ValueSetTests : LibTestSuite() {
|
||||
@Test
|
||||
fun empty_set_has_size_0() {
|
||||
val vs = ValueSet.empty()
|
||||
|
@ -1,11 +1,12 @@
|
||||
package world.phantasmal.lib.buffer
|
||||
|
||||
import world.phantasmal.lib.Endianness
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class BufferTests {
|
||||
class BufferTests : LibTestSuite() {
|
||||
@Test
|
||||
fun withCapacity() {
|
||||
withCapacity(Endianness.Little)
|
||||
|
@ -2,11 +2,12 @@ package world.phantasmal.lib.compression.prs
|
||||
|
||||
import world.phantasmal.lib.buffer.Buffer
|
||||
import world.phantasmal.lib.cursor.cursor
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class PrsCompressTests {
|
||||
class PrsCompressTests : LibTestSuite() {
|
||||
@Test
|
||||
fun edge_case_0_bytes() {
|
||||
val compressed = prsCompress(Buffer.withSize(0).cursor())
|
||||
|
@ -3,13 +3,13 @@ package world.phantasmal.lib.compression.prs
|
||||
import world.phantasmal.lib.buffer.Buffer
|
||||
import world.phantasmal.lib.cursor.Cursor
|
||||
import world.phantasmal.lib.cursor.cursor
|
||||
import world.phantasmal.lib.test.asyncTest
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class PrsDecompressTests {
|
||||
class PrsDecompressTests : LibTestSuite() {
|
||||
@Test
|
||||
fun edge_case_0_bytes() {
|
||||
testWithBuffer(Buffer.withSize(0))
|
||||
|
@ -1,6 +1,7 @@
|
||||
package world.phantasmal.lib.cursor
|
||||
|
||||
import world.phantasmal.lib.Endianness
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -8,7 +9,7 @@ import kotlin.test.assertEquals
|
||||
* Test suite for all [Cursor] implementations. There is a subclass of this suite for every [Cursor]
|
||||
* implementation.
|
||||
*/
|
||||
abstract class CursorTests {
|
||||
abstract class CursorTests : LibTestSuite() {
|
||||
abstract fun createCursor(
|
||||
bytes: ByteArray,
|
||||
endianness: Endianness,
|
||||
|
@ -2,7 +2,7 @@ package world.phantasmal.lib.cursor
|
||||
|
||||
import world.phantasmal.lib.Endianness
|
||||
import world.phantasmal.lib.buffer.Buffer
|
||||
import kotlin.math.abs
|
||||
import world.phantasmal.testUtils.assertCloseTo
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
@ -144,8 +144,8 @@ abstract class WritableCursorTests : CursorTests() {
|
||||
|
||||
// The read floats won't be exactly the same as the written floats in Kotlin JS, because
|
||||
// they're backed by numbers (64-bit floats).
|
||||
assertTrue(abs(1337.9001f - cursor.float()) < 0.001)
|
||||
assertTrue(abs(103.502f - cursor.float()) < 0.001)
|
||||
assertCloseTo(1337.9001f, cursor.float(), epsilon = 0.001f)
|
||||
assertCloseTo(103.502f, cursor.float(), epsilon = 0.001f)
|
||||
|
||||
assertEquals(8, cursor.position)
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package world.phantasmal.lib.fileFormats
|
||||
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import world.phantasmal.testUtils.assertCloseTo
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AreaCollisionGeometryTests : LibTestSuite() {
|
||||
@Test
|
||||
fun parse_forest_1() = asyncTest {
|
||||
val obj = parseAreaCollisionGeometry(readFile("/map_forest01c.rel"))
|
||||
|
||||
assertEquals(69, obj.meshes.size)
|
||||
assertEquals(11, obj.meshes[0].vertices.size)
|
||||
assertCloseTo(-589.5195f, obj.meshes[0].vertices[0].x)
|
||||
assertCloseTo(16.7166f, obj.meshes[0].vertices[0].y)
|
||||
assertCloseTo(-218.6852f, obj.meshes[0].vertices[0].z)
|
||||
assertEquals(12, obj.meshes[0].triangles.size)
|
||||
assertEquals(0b100000001, obj.meshes[0].triangles[0].flags)
|
||||
assertEquals(5, obj.meshes[0].triangles[0].index1)
|
||||
assertEquals(0, obj.meshes[0].triangles[0].index2)
|
||||
assertEquals(7, obj.meshes[0].triangles[0].index3)
|
||||
assertCloseTo(0.0137f, obj.meshes[0].triangles[0].normal.x)
|
||||
assertCloseTo(0.9994f, obj.meshes[0].triangles[0].normal.y)
|
||||
assertCloseTo(-0.0307f, obj.meshes[0].triangles[0].normal.z)
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package world.phantasmal.lib.fileFormats.ninja
|
||||
|
||||
import world.phantasmal.core.Success
|
||||
import world.phantasmal.lib.test.asyncTest
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NinjaTests {
|
||||
class NinjaTests : LibTestSuite() {
|
||||
@Test
|
||||
fun can_parse_rag_rappy_model() = asyncTest {
|
||||
val result = parseNj(readFile("/RagRappy.nj"))
|
||||
|
@ -1,11 +1,11 @@
|
||||
package world.phantasmal.lib.fileFormats.quest
|
||||
|
||||
import world.phantasmal.lib.test.asyncTest
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class BinTests {
|
||||
class BinTests : LibTestSuite() {
|
||||
@Test
|
||||
fun parse_quest_towards_the_future() = asyncTest {
|
||||
val bin = parseBin(readFile("/quest118_e_decompressed.bin"))
|
||||
|
@ -5,11 +5,12 @@ import world.phantasmal.lib.assembly.InstructionSegment
|
||||
import world.phantasmal.lib.assembly.OP_BB_MAP_DESIGNATE
|
||||
import world.phantasmal.lib.assembly.OP_SET_EPISODE
|
||||
import world.phantasmal.lib.buffer.Buffer
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ByteCodeTests {
|
||||
class ByteCodeTests : LibTestSuite() {
|
||||
@Test
|
||||
fun minimal() {
|
||||
val buffer = Buffer.fromByteArray(ubyteArrayOf(
|
||||
|
@ -1,11 +1,11 @@
|
||||
package world.phantasmal.lib.fileFormats.quest
|
||||
|
||||
import world.phantasmal.lib.test.asyncTest
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DatTests {
|
||||
class DatTests : LibTestSuite() {
|
||||
@Test
|
||||
fun parse_quest_towards_the_future() = asyncTest {
|
||||
val dat = parseDat(readFile("/quest118_e_decompressed.dat"))
|
||||
|
@ -1,12 +1,12 @@
|
||||
package world.phantasmal.lib.fileFormats.quest
|
||||
|
||||
import world.phantasmal.lib.test.asyncTest
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class QstTests {
|
||||
class QstTests : LibTestSuite() {
|
||||
@Test
|
||||
fun parse_a_GC_quest() = asyncTest {
|
||||
val cursor = readFile("/lost_heat_sword_gc.qst")
|
||||
|
@ -2,13 +2,13 @@ package world.phantasmal.lib.fileFormats.quest
|
||||
|
||||
import world.phantasmal.core.Success
|
||||
import world.phantasmal.lib.assembly.*
|
||||
import world.phantasmal.lib.test.asyncTest
|
||||
import world.phantasmal.lib.test.LibTestSuite
|
||||
import world.phantasmal.lib.test.readFile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class QuestTests {
|
||||
class QuestTests : LibTestSuite() {
|
||||
@Test
|
||||
fun parseBinDatToQuest_with_towards_the_future() = asyncTest {
|
||||
val result = parseBinDatToQuest(readFile("/quest118_e.bin"), readFile("/quest118_e.dat"))
|
||||
|
@ -0,0 +1,9 @@
|
||||
package world.phantasmal.lib.test
|
||||
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.testUtils.AbstractTestSuite
|
||||
import world.phantasmal.testUtils.TestContext
|
||||
|
||||
abstract class LibTestSuite : AbstractTestSuite<TestContext>() {
|
||||
override fun createContext(disposer: Disposer) = TestContext(disposer)
|
||||
}
|
@ -6,14 +6,6 @@ import world.phantasmal.lib.assembly.assemble
|
||||
import world.phantasmal.lib.cursor.Cursor
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Ensure you return the value of this function in your test function. On Kotlin/JS this function
|
||||
* actually returns a Promise. If this promise is not returned from the test function, the testing
|
||||
* framework won't wait for its completion. This is a workaround for issue
|
||||
* [https://youtrack.jetbrains.com/issue/KT-22228].
|
||||
*/
|
||||
expect fun asyncTest(block: suspend () -> Unit)
|
||||
|
||||
expect suspend fun readFile(path: String): Cursor
|
||||
|
||||
fun toInstructions(assembly: String): List<InstructionSegment> {
|
||||
|
BIN
lib/src/commonTest/resources/map_forest01c.rel
Normal file
BIN
lib/src/commonTest/resources/map_forest01c.rel
Normal file
Binary file not shown.
@ -1,15 +1,11 @@
|
||||
package world.phantasmal.lib.test
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.await
|
||||
import kotlinx.coroutines.promise
|
||||
import world.phantasmal.lib.Endianness
|
||||
import world.phantasmal.lib.cursor.ArrayBufferCursor
|
||||
import world.phantasmal.lib.cursor.Cursor
|
||||
|
||||
actual fun asyncTest(block: suspend () -> Unit): dynamic = GlobalScope.promise { block() }
|
||||
|
||||
actual suspend fun readFile(path: String): Cursor {
|
||||
return window.fetch(path)
|
||||
.then {
|
||||
|
@ -2,15 +2,10 @@
|
||||
|
||||
package world.phantasmal.lib.test
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import world.phantasmal.lib.buffer.Buffer
|
||||
import world.phantasmal.lib.cursor.Cursor
|
||||
import world.phantasmal.lib.cursor.cursor
|
||||
|
||||
actual fun asyncTest(block: suspend () -> Unit) {
|
||||
runBlocking { block() }
|
||||
}
|
||||
|
||||
actual suspend fun readFile(path: String): Cursor {
|
||||
val stream = {}::class.java.getResourceAsStream(path)
|
||||
?: error("""Couldn't load resource "$path".""")
|
||||
|
@ -1,6 +1,6 @@
|
||||
package world.phantasmal.observable
|
||||
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.observable.test.ObservableTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -10,7 +10,7 @@ typealias ObservableAndEmit = Pair<Observable<*>, () -> Unit>
|
||||
* Test suite for all [Observable] implementations. There is a subclass of this suite for every
|
||||
* [Observable] implementation.
|
||||
*/
|
||||
abstract class ObservableTests : TestSuite() {
|
||||
abstract class ObservableTests : ObservableTestSuite() {
|
||||
protected abstract fun create(): ObservableAndEmit
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,9 @@
|
||||
package world.phantasmal.observable.test
|
||||
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.testUtils.AbstractTestSuite
|
||||
import world.phantasmal.testUtils.TestContext
|
||||
|
||||
abstract class ObservableTestSuite : AbstractTestSuite<TestContext>() {
|
||||
override fun createContext(disposer: Disposer) = TestContext(disposer)
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package world.phantasmal.observable.value
|
||||
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.observable.test.ObservableTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class StaticValTests : TestSuite() {
|
||||
class StaticValTests : ObservableTestSuite() {
|
||||
@Test
|
||||
fun observing_StaticVal_should_never_create_leaks() = test {
|
||||
val static = StaticVal("test value")
|
||||
|
@ -1,9 +1,9 @@
|
||||
package world.phantasmal.observable.value
|
||||
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.observable.test.ObservableTestSuite
|
||||
import kotlin.test.*
|
||||
|
||||
class ValCreationTests : TestSuite() {
|
||||
class ValCreationTests : ObservableTestSuite() {
|
||||
@Test
|
||||
fun test_value() = test {
|
||||
assertEquals(7, value(7).value)
|
||||
|
@ -1,10 +1,10 @@
|
||||
package world.phantasmal.observable.value.list
|
||||
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.observable.test.ObservableTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class StaticListValTests : TestSuite() {
|
||||
class StaticListValTests : ObservableTestSuite() {
|
||||
@Test
|
||||
fun observing_StaticListVal_should_never_create_leaks() = test {
|
||||
val static = StaticListVal(listOf(1, 2, 3))
|
||||
|
@ -9,6 +9,8 @@ kotlin {
|
||||
browser {}
|
||||
}
|
||||
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
@ -24,5 +26,11 @@ kotlin {
|
||||
api(kotlin("test-js"))
|
||||
}
|
||||
}
|
||||
|
||||
named("jvmMain") {
|
||||
dependencies {
|
||||
api(kotlin("test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
|
||||
abstract class AbstractTestSuite<Ctx : TestContext> {
|
||||
fun test(testBlock: Ctx.() -> Unit) {
|
||||
TrackedDisposable.checkNoLeaks(trackPrecise = true) {
|
||||
val disposer = Disposer()
|
||||
|
||||
testBlock(createContext(disposer))
|
||||
|
||||
disposer.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun asyncTest(testBlock: suspend Ctx.() -> Unit) = world.phantasmal.testUtils.asyncTest {
|
||||
TrackedDisposable.checkNoLeaks(trackPrecise = true) {
|
||||
val disposer = Disposer()
|
||||
|
||||
testBlock(createContext(disposer))
|
||||
|
||||
disposer.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun createContext(disposer: Disposer): Ctx
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
fun assertCloseTo(expected: Double, actual: Double, epsilon: Double = 0.001) {
|
||||
assertTrue(abs(expected - actual) <= epsilon)
|
||||
}
|
||||
|
||||
fun assertCloseTo(expected: Float, actual: Float, epsilon: Float = 0.001f) {
|
||||
assertTrue(abs(expected - actual) <= epsilon)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
/**
|
||||
* Ensure you return the value of this function in your test function. On Kotlin/JS this function
|
||||
* actually returns a Promise. If this promise is not returned from the test function, the testing
|
||||
* framework won't wait for its completion. This is a workaround for issue
|
||||
* [https://youtrack.jetbrains.com/issue/KT-22228].
|
||||
*/
|
||||
expect fun asyncTest(block: suspend () -> Unit)
|
@ -0,0 +1,11 @@
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
|
||||
open class TestContext(val disposer: Disposer) {
|
||||
val scope: CoroutineScope = object : CoroutineScope {
|
||||
override val coroutineContext = Job()
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.core.disposable.TrackedDisposable
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
abstract class TestSuite {
|
||||
fun test(block: TestContext.() -> Unit) {
|
||||
val initialDisposableCount = TrackedDisposable.disposableCount
|
||||
val disposer = Disposer()
|
||||
|
||||
block(TestContext(disposer))
|
||||
|
||||
disposer.dispose()
|
||||
val leakCount = TrackedDisposable.disposableCount - initialDisposableCount
|
||||
assertEquals(0, leakCount, "TrackedDisposables were leaked")
|
||||
}
|
||||
|
||||
class TestContext(val disposer: Disposer) {
|
||||
val scope: CoroutineScope = object : CoroutineScope {
|
||||
override val coroutineContext = Job()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.promise
|
||||
|
||||
actual fun asyncTest(block: suspend () -> Unit): dynamic = GlobalScope.promise { block() }
|
@ -0,0 +1,9 @@
|
||||
@file:JvmName("AsyncTestJvm")
|
||||
|
||||
package world.phantasmal.testUtils
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
actual fun asyncTest(block: suspend () -> Unit) {
|
||||
runBlocking { block() }
|
||||
}
|
@ -13,8 +13,8 @@ import world.phantasmal.web.questEditor.controllers.QuestInfoController
|
||||
import world.phantasmal.web.questEditor.loading.AreaAssetLoader
|
||||
import world.phantasmal.web.questEditor.loading.EntityAssetLoader
|
||||
import world.phantasmal.web.questEditor.loading.QuestLoader
|
||||
import world.phantasmal.web.questEditor.rendering.QuestEditorMeshManager
|
||||
import world.phantasmal.web.questEditor.rendering.EntityManipulator
|
||||
import world.phantasmal.web.questEditor.rendering.QuestEditorMeshManager
|
||||
import world.phantasmal.web.questEditor.rendering.QuestRenderer
|
||||
import world.phantasmal.web.questEditor.stores.AreaStore
|
||||
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
||||
@ -63,7 +63,7 @@ class QuestEditor(
|
||||
// Main Widget
|
||||
return QuestEditorWidget(
|
||||
scope,
|
||||
QuestEditorToolbar(scope, toolbarController),
|
||||
{ s -> QuestEditorToolbar(s, toolbarController) },
|
||||
{ s -> QuestInfoWidget(s, questInfoController) },
|
||||
{ s -> NpcCountsWidget(s, npcCountsController) },
|
||||
{ s -> QuestEditorRendererWidget(s, canvas, renderer) }
|
||||
|
@ -39,17 +39,13 @@ class EntityManipulator(
|
||||
init {
|
||||
state = IdleState(questEditorStore, renderer, enabled)
|
||||
|
||||
observe(questEditorStore.selectedEntity, ::selectedEntityChanged)
|
||||
observe(questEditorStore.selectedEntity) { state.cancel() }
|
||||
|
||||
addDisposables(
|
||||
disposableListener(renderer.canvas, "pointerdown", ::onPointerDown)
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectedEntityChanged(entity: QuestEntityModel<*, *>?) {
|
||||
state.cancel()
|
||||
}
|
||||
|
||||
private fun onPointerDown(e: PointerEvent) {
|
||||
processPointerEvent(e)
|
||||
|
||||
|
@ -17,9 +17,12 @@ private class TestWidget(scope: CoroutineScope) : Widget(scope) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes ownership of the widgets created by the given createWidget functions.
|
||||
*/
|
||||
class QuestEditorWidget(
|
||||
scope: CoroutineScope,
|
||||
private val toolbar: Widget,
|
||||
private val createToolbar: (CoroutineScope) -> Widget,
|
||||
private val createQuestInfoWidget: (CoroutineScope) -> Widget,
|
||||
private val createNpcCountsWidget: (CoroutineScope) -> Widget,
|
||||
private val createQuestRendererWidget: (CoroutineScope) -> Widget,
|
||||
@ -28,7 +31,7 @@ class QuestEditorWidget(
|
||||
div {
|
||||
className = "pw-quest-editor-quest-editor"
|
||||
|
||||
addChild(toolbar)
|
||||
addChild(createToolbar(scope))
|
||||
addChild(DockWidget(
|
||||
scope,
|
||||
item = DockedRow(
|
||||
|
@ -31,6 +31,11 @@ class Viewer(
|
||||
val renderer = addDisposable(MeshRenderer(viewerStore, canvas, createEngine(canvas)))
|
||||
|
||||
// Main Widget
|
||||
return ViewerWidget(scope, ViewerToolbar(scope, viewerToolbarController), canvas, renderer)
|
||||
return ViewerWidget(
|
||||
scope,
|
||||
{ s -> ViewerToolbar(s, viewerToolbarController) },
|
||||
canvas,
|
||||
renderer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,12 @@ import world.phantasmal.web.core.widgets.RendererWidget
|
||||
import world.phantasmal.webui.dom.div
|
||||
import world.phantasmal.webui.widgets.Widget
|
||||
|
||||
/**
|
||||
* Takes ownership of the widget returned by [createToolbar].
|
||||
*/
|
||||
class ViewerWidget(
|
||||
scope: CoroutineScope,
|
||||
private val toolbar: Widget,
|
||||
private val createToolbar: (CoroutineScope) -> Widget,
|
||||
private val canvas: HTMLCanvasElement,
|
||||
private val renderer: Renderer,
|
||||
) : Widget(scope) {
|
||||
@ -18,7 +21,7 @@ class ViewerWidget(
|
||||
div {
|
||||
className = "pw-viewer-viewer"
|
||||
|
||||
addChild(toolbar)
|
||||
addChild(createToolbar(scope))
|
||||
div {
|
||||
className = "pw-viewer-viewer-container"
|
||||
|
||||
|
@ -1,41 +1,26 @@
|
||||
package world.phantasmal.web.application
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.cancel
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.core.disposable.disposable
|
||||
import world.phantasmal.core.disposable.use
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.web.core.loading.AssetLoader
|
||||
import world.phantasmal.web.core.PwTool
|
||||
import world.phantasmal.web.core.PwToolType
|
||||
import world.phantasmal.web.externals.babylon.Engine
|
||||
import world.phantasmal.web.test.TestApplicationUrl
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
|
||||
class ApplicationTests : TestSuite() {
|
||||
class ApplicationTests : WebTestSuite() {
|
||||
@Test
|
||||
fun initialization_and_shutdown_should_succeed_without_throwing() = test {
|
||||
(listOf(null) + PwTool.values().toList()).forEach { tool ->
|
||||
(listOf(null) + PwToolType.values().toList()).forEach { tool ->
|
||||
Disposer().use { disposer ->
|
||||
val httpClient = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
disposer.add(disposable { httpClient.cancel() })
|
||||
|
||||
val appUrl = TestApplicationUrl(if (tool == null) "" else "/${tool.slug}")
|
||||
|
||||
disposer.add(
|
||||
Application(
|
||||
scope,
|
||||
rootElement = document.body!!,
|
||||
assetLoader = AssetLoader(basePath = "", httpClient),
|
||||
assetLoader = components.assetLoader,
|
||||
applicationUrl = appUrl,
|
||||
createEngine = { Engine(it) }
|
||||
)
|
||||
|
@ -1,14 +1,15 @@
|
||||
package world.phantasmal.web.core.controllers
|
||||
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.web.core.PwTool
|
||||
import world.phantasmal.testUtils.TestContext
|
||||
import world.phantasmal.web.core.PwToolType
|
||||
import world.phantasmal.web.core.stores.UiStore
|
||||
import world.phantasmal.web.test.TestApplicationUrl
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
|
||||
class PathAwareTabControllerTests : TestSuite() {
|
||||
class PathAwareTabControllerTests : WebTestSuite() {
|
||||
@Test
|
||||
fun activeTab_is_initialized_correctly() = test {
|
||||
setup { ctrl, appUrl ->
|
||||
@ -23,7 +24,7 @@ class PathAwareTabControllerTests : TestSuite() {
|
||||
setup { ctrl, appUrl ->
|
||||
ctrl.setActiveTab(ctrl.tabs[2])
|
||||
|
||||
assertEquals("/${PwTool.HuntOptimizer.slug}/c", appUrl.url.value)
|
||||
assertEquals("/${PwToolType.HuntOptimizer.slug}/c", appUrl.url.value)
|
||||
assertEquals(1, appUrl.historyEntries)
|
||||
assertFalse(appUrl.canGoForward)
|
||||
}
|
||||
@ -32,7 +33,7 @@ class PathAwareTabControllerTests : TestSuite() {
|
||||
@Test
|
||||
fun activeTab_changes_when_applicationUrl_changes() = test {
|
||||
setup { ctrl, applicationUrl ->
|
||||
applicationUrl.pushUrl("/${PwTool.HuntOptimizer.slug}/c")
|
||||
applicationUrl.pushUrl("/${PwToolType.HuntOptimizer.slug}/c")
|
||||
|
||||
assertEquals("/c", ctrl.activeTab.value?.path)
|
||||
}
|
||||
@ -44,7 +45,7 @@ class PathAwareTabControllerTests : TestSuite() {
|
||||
val uiStore = disposer.add(UiStore(scope, appUrl))
|
||||
|
||||
disposer.add(
|
||||
PathAwareTabController(scope, uiStore, PwTool.HuntOptimizer, listOf(
|
||||
PathAwareTabController(uiStore, PwToolType.HuntOptimizer, listOf(
|
||||
PathAwareTab("A", "/a"),
|
||||
PathAwareTab("B", "/b"),
|
||||
PathAwareTab("C", "/c"),
|
||||
@ -55,11 +56,11 @@ class PathAwareTabControllerTests : TestSuite() {
|
||||
assertFalse(appUrl.canGoForward)
|
||||
assertEquals("/${uiStore.defaultTool.slug}", appUrl.url.value)
|
||||
|
||||
uiStore.setCurrentTool(PwTool.HuntOptimizer)
|
||||
uiStore.setCurrentTool(PwToolType.HuntOptimizer)
|
||||
|
||||
assertEquals(1, appUrl.historyEntries)
|
||||
assertFalse(appUrl.canGoForward)
|
||||
assertEquals("/${PwTool.HuntOptimizer.slug}", appUrl.url.value)
|
||||
assertEquals("/${PwToolType.HuntOptimizer.slug}", appUrl.url.value)
|
||||
|
||||
appUrl.back()
|
||||
|
||||
@ -69,12 +70,12 @@ class PathAwareTabControllerTests : TestSuite() {
|
||||
private fun TestContext.setup(
|
||||
block: (PathAwareTabController<PathAwareTab>, applicationUrl: TestApplicationUrl) -> Unit,
|
||||
) {
|
||||
val applicationUrl = TestApplicationUrl("/${PwTool.HuntOptimizer.slug}/b")
|
||||
val applicationUrl = TestApplicationUrl("/${PwToolType.HuntOptimizer.slug}/b")
|
||||
val uiStore = disposer.add(UiStore(scope, applicationUrl))
|
||||
uiStore.setCurrentTool(PwTool.HuntOptimizer)
|
||||
uiStore.setCurrentTool(PwToolType.HuntOptimizer)
|
||||
|
||||
val ctrl = disposer.add(
|
||||
PathAwareTabController(scope, uiStore, PwTool.HuntOptimizer, listOf(
|
||||
PathAwareTabController(uiStore, PwToolType.HuntOptimizer, listOf(
|
||||
PathAwareTab("A", "/a"),
|
||||
PathAwareTab("B", "/b"),
|
||||
PathAwareTab("C", "/c"),
|
||||
|
@ -1,20 +1,20 @@
|
||||
package world.phantasmal.web.core.store
|
||||
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.web.core.PwTool
|
||||
import world.phantasmal.web.core.PwToolType
|
||||
import world.phantasmal.web.core.stores.UiStore
|
||||
import world.phantasmal.web.test.TestApplicationUrl
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class UiStoreTests : TestSuite() {
|
||||
class UiStoreTests : WebTestSuite() {
|
||||
@Test
|
||||
fun applicationUrl_is_initialized_correctly() = test {
|
||||
val applicationUrl = TestApplicationUrl("/")
|
||||
val uiStore = disposer.add(UiStore(scope, applicationUrl))
|
||||
|
||||
assertEquals(PwTool.Viewer, uiStore.currentTool.value)
|
||||
assertEquals("/${PwTool.Viewer.slug}", applicationUrl.url.value)
|
||||
assertEquals(PwToolType.Viewer, uiStore.currentTool.value)
|
||||
assertEquals("/${PwToolType.Viewer.slug}", applicationUrl.url.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -22,7 +22,7 @@ class UiStoreTests : TestSuite() {
|
||||
val applicationUrl = TestApplicationUrl("/")
|
||||
val uiStore = disposer.add(UiStore(scope, applicationUrl))
|
||||
|
||||
PwTool.values().forEach { tool ->
|
||||
PwToolType.values().forEach { tool ->
|
||||
uiStore.setCurrentTool(tool)
|
||||
|
||||
assertEquals(tool, uiStore.currentTool.value)
|
||||
@ -35,13 +35,13 @@ class UiStoreTests : TestSuite() {
|
||||
val applicationUrl = TestApplicationUrl("/")
|
||||
val uiStore = disposer.add(UiStore(scope, applicationUrl))
|
||||
|
||||
assertEquals(PwTool.Viewer, uiStore.currentTool.value)
|
||||
assertEquals("/${PwTool.Viewer.slug}", applicationUrl.url.value)
|
||||
assertEquals(PwToolType.Viewer, uiStore.currentTool.value)
|
||||
assertEquals("/${PwToolType.Viewer.slug}", applicationUrl.url.value)
|
||||
|
||||
listOf("/models", "/textures", "/animations").forEach { prefix ->
|
||||
uiStore.setPathPrefix(prefix, replace = false)
|
||||
|
||||
assertEquals("/${PwTool.Viewer.slug}${prefix}", applicationUrl.url.value)
|
||||
assertEquals("/${PwToolType.Viewer.slug}${prefix}", applicationUrl.url.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ class UiStoreTests : TestSuite() {
|
||||
val applicationUrl = TestApplicationUrl("/")
|
||||
val uiStore = disposer.add(UiStore(scope, applicationUrl))
|
||||
|
||||
PwTool.values().forEach { tool ->
|
||||
PwToolType.values().forEach { tool ->
|
||||
listOf("/a", "/b", "/c").forEach { path ->
|
||||
applicationUrl.url.value = "/${tool.slug}$path"
|
||||
|
||||
@ -67,13 +67,13 @@ class UiStoreTests : TestSuite() {
|
||||
|
||||
assertEquals("/${uiStore.defaultTool.slug}", appUrl.url.value)
|
||||
|
||||
uiStore.setCurrentTool(PwTool.HuntOptimizer)
|
||||
uiStore.setCurrentTool(PwToolType.HuntOptimizer)
|
||||
|
||||
assertEquals("/${PwTool.HuntOptimizer.slug}", appUrl.url.value)
|
||||
assertEquals("/${PwToolType.HuntOptimizer.slug}", appUrl.url.value)
|
||||
|
||||
uiStore.setPathPrefix("/prefix", replace = true)
|
||||
|
||||
assertEquals("/${PwTool.HuntOptimizer.slug}/prefix", appUrl.url.value)
|
||||
assertEquals("/${PwToolType.HuntOptimizer.slug}/prefix", appUrl.url.value)
|
||||
|
||||
appUrl.back()
|
||||
|
||||
@ -81,6 +81,6 @@ class UiStoreTests : TestSuite() {
|
||||
|
||||
appUrl.forward()
|
||||
|
||||
assertEquals("/${PwTool.HuntOptimizer.slug}/prefix", appUrl.url.value)
|
||||
assertEquals("/${PwToolType.HuntOptimizer.slug}/prefix", appUrl.url.value)
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,18 @@
|
||||
package world.phantasmal.web.huntOptimizer
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import kotlinx.coroutines.cancel
|
||||
import world.phantasmal.core.disposable.disposable
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.web.core.loading.AssetLoader
|
||||
import world.phantasmal.web.core.PwTool
|
||||
import world.phantasmal.web.core.PwToolType
|
||||
import world.phantasmal.web.core.stores.UiStore
|
||||
import world.phantasmal.web.test.TestApplicationUrl
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
|
||||
class HuntOptimizerTests : TestSuite() {
|
||||
class HuntOptimizerTests : WebTestSuite() {
|
||||
@Test
|
||||
fun initialization_and_shutdown_should_succeed_without_throwing() = test {
|
||||
val httpClient = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
disposer.add(disposable { httpClient.cancel() })
|
||||
val uiStore =
|
||||
disposer.add(UiStore(scope, TestApplicationUrl("/${PwToolType.HuntOptimizer}")))
|
||||
|
||||
val uiStore = disposer.add(UiStore(scope, TestApplicationUrl("/${PwTool.HuntOptimizer}")))
|
||||
|
||||
disposer.add(
|
||||
HuntOptimizer(
|
||||
scope,
|
||||
AssetLoader(basePath = "", httpClient),
|
||||
uiStore
|
||||
)
|
||||
)
|
||||
val huntOptimizer = disposer.add(HuntOptimizer(components.assetLoader, uiStore))
|
||||
disposer.add(huntOptimizer.initialize(scope))
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,15 @@
|
||||
package world.phantasmal.web.questEditor
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import kotlinx.coroutines.cancel
|
||||
import world.phantasmal.core.disposable.disposable
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.web.core.loading.AssetLoader
|
||||
import world.phantasmal.web.externals.babylon.Engine
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
|
||||
class QuestEditorTests : TestSuite() {
|
||||
class QuestEditorTests : WebTestSuite() {
|
||||
@Test
|
||||
fun initialization_and_shutdown_should_succeed_without_throwing() = test {
|
||||
val httpClient = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
disposer.add(disposable { httpClient.cancel() })
|
||||
|
||||
disposer.add(
|
||||
QuestEditor(
|
||||
scope,
|
||||
AssetLoader(basePath = "", httpClient),
|
||||
createEngine = { Engine(it) }
|
||||
)
|
||||
val questEditor = disposer.add(
|
||||
QuestEditor(components.assetLoader, createEngine = { Engine(it) })
|
||||
)
|
||||
disposer.add(questEditor.initialize(scope))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
package world.phantasmal.web.questEditor.controllers
|
||||
|
||||
import world.phantasmal.lib.fileFormats.quest.Episode
|
||||
import world.phantasmal.lib.fileFormats.quest.NpcType
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import world.phantasmal.web.test.createQuestModel
|
||||
import world.phantasmal.web.test.createQuestNpcModel
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NpcCountsControllerTests : WebTestSuite() {
|
||||
@Test
|
||||
fun exposes_correct_model_before_and_after_a_quest_is_loaded() = test {
|
||||
val store = components.questEditorStore
|
||||
val ctrl = disposer.add(NpcCountsController(store))
|
||||
|
||||
assertTrue(ctrl.unavailable.value)
|
||||
|
||||
store.setCurrentQuest(createQuestModel(
|
||||
episode = Episode.I,
|
||||
npcs = listOf(
|
||||
createQuestNpcModel(NpcType.Scientist, Episode.I),
|
||||
createQuestNpcModel(NpcType.Nurse, Episode.I),
|
||||
createQuestNpcModel(NpcType.Nurse, Episode.I),
|
||||
createQuestNpcModel(NpcType.Principal, Episode.I),
|
||||
createQuestNpcModel(NpcType.Nurse, Episode.I),
|
||||
createQuestNpcModel(NpcType.Scientist, Episode.I),
|
||||
)
|
||||
))
|
||||
|
||||
assertFalse(ctrl.unavailable.value)
|
||||
assertEquals(3, ctrl.npcCounts.value.size)
|
||||
assertEquals("Principal", ctrl.npcCounts.value[0].name)
|
||||
assertEquals("1", ctrl.npcCounts.value[0].count)
|
||||
assertEquals("Scientist", ctrl.npcCounts.value[1].name)
|
||||
assertEquals("2", ctrl.npcCounts.value[1].count)
|
||||
assertEquals("Nurse", ctrl.npcCounts.value[2].name)
|
||||
assertEquals("3", ctrl.npcCounts.value[2].count)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package world.phantasmal.web.questEditor.controllers
|
||||
|
||||
import org.w3c.files.File
|
||||
import world.phantasmal.core.Failure
|
||||
import world.phantasmal.core.Severity
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class QuestEditorToolbarControllerTests : WebTestSuite() {
|
||||
@Test
|
||||
fun a_failure_is_exposed_when_openFiles_fails() = asyncTest {
|
||||
val ctrl = disposer.add(QuestEditorToolbarController(
|
||||
components.questLoader,
|
||||
components.areaStore,
|
||||
components.questEditorStore
|
||||
))
|
||||
|
||||
assertNull(ctrl.result.value)
|
||||
|
||||
ctrl.openFiles(listOf(File(arrayOf(), "unknown.extension")))
|
||||
|
||||
val result = ctrl.result.value
|
||||
|
||||
assertTrue(result is Failure)
|
||||
assertEquals(1, result.problems.size)
|
||||
assertEquals(Severity.Error, result.problems.first().severity)
|
||||
assertEquals(
|
||||
"Please select a .qst file or one .bin and one .dat file.",
|
||||
result.problems.first().uiMessage
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package world.phantasmal.web.questEditor.controllers
|
||||
|
||||
import world.phantasmal.lib.fileFormats.quest.Episode
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import world.phantasmal.web.test.createQuestModel
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class QuestInfoControllerTests : WebTestSuite() {
|
||||
@Test
|
||||
fun exposes_correct_model_before_and_after_a_quest_is_loaded() = test {
|
||||
val store = components.questEditorStore
|
||||
val ctrl = disposer.add(QuestInfoController(store))
|
||||
|
||||
assertTrue(ctrl.unavailable.value)
|
||||
assertTrue(ctrl.disabled.value)
|
||||
|
||||
store.setCurrentQuest(createQuestModel(
|
||||
id = 25,
|
||||
name = "A Quest",
|
||||
shortDescription = "A short description.",
|
||||
longDescription = "A long description.",
|
||||
episode = Episode.II
|
||||
))
|
||||
|
||||
assertFalse(ctrl.unavailable.value)
|
||||
assertFalse(ctrl.disabled.value)
|
||||
assertEquals("II", ctrl.episode.value)
|
||||
assertEquals(25, ctrl.id.value)
|
||||
assertEquals("A Quest", ctrl.name.value)
|
||||
assertEquals("A short description.", ctrl.shortDescription.value)
|
||||
assertEquals("A long description.", ctrl.longDescription.value)
|
||||
}
|
||||
}
|
100
web/src/test/kotlin/world/phantasmal/web/test/TestComponents.kt
Normal file
100
web/src/test/kotlin/world/phantasmal/web/test/TestComponents.kt
Normal file
@ -0,0 +1,100 @@
|
||||
package world.phantasmal.web.test
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import kotlinx.coroutines.cancel
|
||||
import world.phantasmal.core.disposable.Disposable
|
||||
import world.phantasmal.core.disposable.disposable
|
||||
import world.phantasmal.testUtils.TestContext
|
||||
import world.phantasmal.web.core.loading.AssetLoader
|
||||
import world.phantasmal.web.externals.babylon.Engine
|
||||
import world.phantasmal.web.externals.babylon.Scene
|
||||
import world.phantasmal.web.questEditor.loading.AreaAssetLoader
|
||||
import world.phantasmal.web.questEditor.loading.QuestLoader
|
||||
import world.phantasmal.web.questEditor.stores.AreaStore
|
||||
import world.phantasmal.web.questEditor.stores.QuestEditorStore
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Assigning a disposable to any of the properties in this class will add the assigned value to
|
||||
* [ctx]'s disposer.
|
||||
*/
|
||||
class TestComponents(private val ctx: TestContext) {
|
||||
var httpClient: HttpClient by default {
|
||||
HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}.also {
|
||||
ctx.disposer.add(disposable { it.cancel() })
|
||||
}
|
||||
}
|
||||
|
||||
// Babylon.js
|
||||
|
||||
var scene: Scene by default { Scene(Engine(null)) }
|
||||
|
||||
// Asset Loaders
|
||||
|
||||
var assetLoader: AssetLoader by default { AssetLoader(basePath = "", httpClient) }
|
||||
|
||||
var areaAssetLoader: AreaAssetLoader by default {
|
||||
AreaAssetLoader(ctx.scope, assetLoader, scene)
|
||||
}
|
||||
|
||||
var questLoader: QuestLoader by default { QuestLoader(ctx.scope, assetLoader) }
|
||||
|
||||
// Stores
|
||||
|
||||
var areaStore: AreaStore by default { AreaStore(ctx.scope, areaAssetLoader) }
|
||||
|
||||
var questEditorStore: QuestEditorStore by default {
|
||||
QuestEditorStore(ctx.scope, areaStore)
|
||||
}
|
||||
|
||||
private fun <T> default(defaultValue: () -> T) = LazyDefault {
|
||||
val value = defaultValue()
|
||||
|
||||
if (value is Disposable) {
|
||||
ctx.disposer.add(value)
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
private inner class LazyDefault<T>(private val defaultValue: () -> T) {
|
||||
private var initialized = false
|
||||
private var value: T? = null
|
||||
|
||||
operator fun getValue(thisRef: Any?, prop: KProperty<*>): T {
|
||||
if (!initialized) {
|
||||
val value = defaultValue()
|
||||
|
||||
if (value is Disposable) {
|
||||
ctx.disposer.add(value)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
initialized = true
|
||||
}
|
||||
|
||||
return value.unsafeCast<T>()
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: T) {
|
||||
require(initialized) {
|
||||
"Property ${prop.name} is already initialized."
|
||||
}
|
||||
|
||||
if (value is Disposable) {
|
||||
ctx.disposer.add(value)
|
||||
}
|
||||
|
||||
this.value = value
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
}
|
32
web/src/test/kotlin/world/phantasmal/web/test/TestModels.kt
Normal file
32
web/src/test/kotlin/world/phantasmal/web/test/TestModels.kt
Normal file
@ -0,0 +1,32 @@
|
||||
package world.phantasmal.web.test
|
||||
|
||||
import world.phantasmal.lib.fileFormats.quest.Episode
|
||||
import world.phantasmal.lib.fileFormats.quest.NpcType
|
||||
import world.phantasmal.lib.fileFormats.quest.QuestNpc
|
||||
import world.phantasmal.web.questEditor.models.QuestModel
|
||||
import world.phantasmal.web.questEditor.models.QuestNpcModel
|
||||
import world.phantasmal.web.questEditor.models.QuestObjectModel
|
||||
|
||||
fun createQuestModel(
|
||||
id: Int = 1,
|
||||
name: String = "Test",
|
||||
shortDescription: String = name,
|
||||
longDescription: String = name,
|
||||
episode: Episode = Episode.I,
|
||||
npcs: List<QuestNpcModel> = emptyList(),
|
||||
objects: List<QuestObjectModel> = emptyList(),
|
||||
): QuestModel =
|
||||
QuestModel(
|
||||
id,
|
||||
language = 1,
|
||||
name,
|
||||
shortDescription,
|
||||
longDescription,
|
||||
episode,
|
||||
emptyMap(),
|
||||
npcs.toMutableList(),
|
||||
objects.toMutableList(),
|
||||
) { _, _, _ -> null }
|
||||
|
||||
fun createQuestNpcModel(type: NpcType, episode: Episode): QuestNpcModel =
|
||||
QuestNpcModel(QuestNpc(type, episode, areaId = 0, wave = 0), wave = null)
|
@ -0,0 +1,9 @@
|
||||
package world.phantasmal.web.test
|
||||
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.testUtils.TestContext
|
||||
|
||||
open class WebTestContext(disposer: Disposer) : TestContext(disposer) {
|
||||
@Suppress("LeakingThis")
|
||||
val components = TestComponents(this)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package world.phantasmal.web.test
|
||||
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.testUtils.AbstractTestSuite
|
||||
|
||||
abstract class WebTestSuite : AbstractTestSuite<WebTestContext>() {
|
||||
override fun createContext(disposer: Disposer) = WebTestContext(disposer)
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package world.phantasmal.web.viewer
|
||||
|
||||
import world.phantasmal.web.externals.babylon.Engine
|
||||
import world.phantasmal.web.test.WebTestSuite
|
||||
import kotlin.test.Test
|
||||
|
||||
class ViewerTests : WebTestSuite() {
|
||||
@Test
|
||||
fun initialization_and_shutdown_should_succeed_without_throwing() = test {
|
||||
val viewer = disposer.add(
|
||||
Viewer(createEngine = { Engine(it) })
|
||||
)
|
||||
disposer.add(viewer.initialize(scope))
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package world.phantasmal.webui.test
|
||||
|
||||
import world.phantasmal.core.disposable.Disposer
|
||||
import world.phantasmal.testUtils.AbstractTestSuite
|
||||
import world.phantasmal.testUtils.TestContext
|
||||
|
||||
abstract class WebuiTestSuite : AbstractTestSuite<TestContext>() {
|
||||
override fun createContext(disposer: Disposer) = TestContext(disposer)
|
||||
}
|
@ -6,14 +6,13 @@ import world.phantasmal.observable.value.Val
|
||||
import world.phantasmal.observable.value.falseVal
|
||||
import world.phantasmal.observable.value.mutableVal
|
||||
import world.phantasmal.observable.value.trueVal
|
||||
import world.phantasmal.testUtils.TestSuite
|
||||
import world.phantasmal.webui.dom.div
|
||||
import world.phantasmal.webui.test.WebuiTestSuite
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
class WidgetTests : TestSuite() {
|
||||
class WidgetTests : WebuiTestSuite() {
|
||||
@Test
|
||||
fun ancestorHidden_and_selfOrAncestorHidden_should_update_when_hidden_changes() = test {
|
||||
val parentHidden = mutableVal(false)
|
||||
|
Loading…
Reference in New Issue
Block a user