mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Modularize database classes (#3192)
This commit is contained in:
parent
989a6bc820
commit
613714cdb4
94 changed files with 384 additions and 431 deletions
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* 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 Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.room.testing.MigrationTestHelper
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.IOException
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MeshtasticDatabaseTest {
|
||||
|
||||
companion object {
|
||||
private const val TEST_DB = "migration-test"
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val helper: MigrationTestHelper = MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
MeshtasticDatabase::class.java,
|
||||
)
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrateAll() {
|
||||
// Create earliest version of the database.
|
||||
helper.createDatabase(TEST_DB, 3).apply {
|
||||
close()
|
||||
}
|
||||
|
||||
// Open latest version of the database. Room validates the schema
|
||||
// once all migrations execute.
|
||||
Room.databaseBuilder(
|
||||
InstrumentationRegistry.getInstrumentation().targetContext,
|
||||
MeshtasticDatabase::class.java,
|
||||
TEST_DB
|
||||
).build().apply {
|
||||
openHelper.writableDatabase.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,333 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* 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 Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.dao.NodeInfoDao
|
||||
import com.geeksville.mesh.database.entity.MyNodeEntity
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.NodeSortOption
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.model.util.onlineTimeThreshold
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NodeInfoDaoTest {
|
||||
private lateinit var database: MeshtasticDatabase
|
||||
private lateinit var nodeInfoDao: NodeInfoDao
|
||||
|
||||
private val onlineThreshold = onlineTimeThreshold()
|
||||
private val offlineNodeLastHeard = onlineThreshold - 30
|
||||
private val onlineNodeLastHeard = onlineThreshold + 20
|
||||
|
||||
private val unknownNode =
|
||||
NodeEntity(
|
||||
num = 7,
|
||||
user =
|
||||
user {
|
||||
id = "!a1b2c3d4"
|
||||
longName = "Meshtastic c3d4"
|
||||
shortName = "c3d4"
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
},
|
||||
longName = "Meshtastic c3d4",
|
||||
shortName = null, // Dao filter for includeUnknown
|
||||
)
|
||||
|
||||
private val ourNode =
|
||||
NodeEntity(
|
||||
num = 8,
|
||||
user =
|
||||
user {
|
||||
id = "+16508765308".format(8)
|
||||
longName = "Kevin Mester"
|
||||
shortName = "KLO"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
},
|
||||
longName = "Kevin Mester",
|
||||
shortName = "KLO",
|
||||
latitude = 30.267153,
|
||||
longitude = -97.743057, // Austin
|
||||
hopsAway = 0,
|
||||
)
|
||||
|
||||
private val onlineNode =
|
||||
NodeEntity(
|
||||
num = 9,
|
||||
user =
|
||||
user {
|
||||
id = "!25060801"
|
||||
longName = "Meshtastic 0801"
|
||||
shortName = "0801"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0801",
|
||||
shortName = "0801",
|
||||
hopsAway = 0,
|
||||
lastHeard = onlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val offlineNode =
|
||||
NodeEntity(
|
||||
num = 10,
|
||||
user =
|
||||
user {
|
||||
id = "!25060802"
|
||||
longName = "Meshtastic 0802"
|
||||
shortName = "0802"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0802",
|
||||
shortName = "0802",
|
||||
hopsAway = 0,
|
||||
lastHeard = offlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val directNode =
|
||||
NodeEntity(
|
||||
num = 11,
|
||||
user =
|
||||
user {
|
||||
id = "!25060803"
|
||||
longName = "Meshtastic 0803"
|
||||
shortName = "0803"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0803",
|
||||
shortName = "0803",
|
||||
hopsAway = 0,
|
||||
lastHeard = onlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val relayedNode =
|
||||
NodeEntity(
|
||||
num = 12,
|
||||
user =
|
||||
user {
|
||||
id = "!25060804"
|
||||
longName = "Meshtastic 0804"
|
||||
shortName = "0804"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
},
|
||||
longName = "Meshtastic 0804",
|
||||
shortName = "0804",
|
||||
hopsAway = 3,
|
||||
lastHeard = onlineNodeLastHeard,
|
||||
)
|
||||
|
||||
private val myNodeInfo: MyNodeEntity =
|
||||
MyNodeEntity(
|
||||
myNodeNum = ourNode.num,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
couldUpdate = false,
|
||||
shouldUpdate = false,
|
||||
currentPacketId = 1L,
|
||||
messageTimeoutMsec = 5 * 60 * 1000,
|
||||
minAppVersion = 1,
|
||||
maxChannels = 8,
|
||||
hasWifi = false,
|
||||
)
|
||||
|
||||
private val testPositions =
|
||||
arrayOf(
|
||||
0.0 to 0.0,
|
||||
32.776665 to -96.796989, // Dallas
|
||||
32.960758 to -96.733521, // Richardson
|
||||
32.912901 to -96.781776, // North Dallas
|
||||
29.760427 to -95.369804, // Houston
|
||||
33.748997 to -84.387985, // Atlanta
|
||||
34.052235 to -118.243683, // Los Angeles
|
||||
40.712776 to -74.005974, // New York City
|
||||
41.878113 to -87.629799, // Chicago
|
||||
39.952583 to -75.165222, // Philadelphia
|
||||
)
|
||||
private val testNodes =
|
||||
listOf(ourNode, unknownNode, onlineNode, offlineNode, directNode, relayedNode) +
|
||||
testPositions.mapIndexed { index, pos ->
|
||||
NodeEntity(
|
||||
num = 1000 + index,
|
||||
user =
|
||||
user {
|
||||
id = "+165087653%02d".format(9 + index)
|
||||
longName = "Kevin Mester$index"
|
||||
shortName = "KM$index"
|
||||
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
|
||||
isLicensed = false
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { index.toByte() })
|
||||
},
|
||||
longName = "Kevin Mester$index",
|
||||
shortName = "KM$index",
|
||||
latitude = pos.first,
|
||||
longitude = pos.second,
|
||||
lastHeard = 9 + index,
|
||||
)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun createDb(): Unit = runBlocking {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
database = Room.inMemoryDatabaseBuilder(context, MeshtasticDatabase::class.java).build()
|
||||
nodeInfoDao = database.nodeInfoDao()
|
||||
|
||||
nodeInfoDao.apply {
|
||||
putAll(testNodes)
|
||||
setMyNodeInfo(myNodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of nodes based on [sort], [filter] and [includeUnknown] parameters. The list excludes [ourNode]
|
||||
* to ensure consistency in the results.
|
||||
*/
|
||||
private suspend fun getNodes(
|
||||
sort: NodeSortOption = NodeSortOption.LAST_HEARD,
|
||||
filter: String = "",
|
||||
includeUnknown: Boolean = true,
|
||||
onlyOnline: Boolean = false,
|
||||
onlyDirect: Boolean = false,
|
||||
) = nodeInfoDao
|
||||
.getNodes(
|
||||
sort = sort.sqlValue,
|
||||
filter = filter,
|
||||
includeUnknown = includeUnknown,
|
||||
hopsAwayMax = if (onlyDirect) 0 else -1,
|
||||
lastHeardMin = if (onlyOnline) onlineTimeThreshold() else -1,
|
||||
)
|
||||
.map { list -> list.map { it.toModel() } }
|
||||
.first()
|
||||
.filter { it.num != ourNode.num }
|
||||
|
||||
@Test // node list size
|
||||
fun testNodeListSize() = runBlocking {
|
||||
val nodes = nodeInfoDao.nodeDBbyNum().first()
|
||||
assertEquals(6 + testPositions.size, nodes.size)
|
||||
}
|
||||
|
||||
@Test // nodeDBbyNum() re-orders our node at the top of the list
|
||||
fun testOurNodeInfoIsFirst() = runBlocking {
|
||||
val nodes = nodeInfoDao.nodeDBbyNum().first()
|
||||
assertEquals(ourNode.num, nodes.values.first().node.num)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSortByLastHeard() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.LAST_HEARD)
|
||||
val sortedNodes = nodes.sortedByDescending { it.lastHeard }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSortByAlpha() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.ALPHABETICAL)
|
||||
val sortedNodes = nodes.sortedBy { it.user.longName.uppercase() }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSortByDistance() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.DISTANCE)
|
||||
fun NodeEntity.toNode() = Node(num = num, user = user, position = position)
|
||||
val sortedNodes =
|
||||
nodes.sortedWith( // nodes with invalid (null) positions at the end
|
||||
compareBy<Node> { it.validPosition == null }.thenBy { it.distance(ourNode.toNode()) },
|
||||
)
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSortByChannel() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.CHANNEL)
|
||||
val sortedNodes = nodes.sortedBy { it.channel }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSortByViaMqtt() = runBlocking {
|
||||
val nodes = getNodes(sort = NodeSortOption.VIA_MQTT)
|
||||
val sortedNodes = nodes.sortedBy { it.user.longName.contains("(MQTT)") }
|
||||
assertEquals(sortedNodes, nodes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIncludeUnknownIsFalse() = runBlocking {
|
||||
val nodes = getNodes(includeUnknown = false)
|
||||
val containsUnsetNode = nodes.any { it.isUnknownUser }
|
||||
assertFalse(containsUnsetNode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIncludeUnknownIsTrue() = runBlocking {
|
||||
val nodes = getNodes(includeUnknown = true)
|
||||
val containsUnsetNode = nodes.any { it.isUnknownUser }
|
||||
assertTrue(containsUnsetNode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOfflineNodesIncludedByDefault() = runBlocking {
|
||||
val nodes = getNodes()
|
||||
assertTrue(nodes.any { it.lastHeard < onlineTimeThreshold() })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyOnlineExcludesOffline() = runBlocking {
|
||||
val nodes = getNodes(onlyOnline = true)
|
||||
assertFalse(nodes.any { it.lastHeard < onlineTimeThreshold() })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelayedNodesIncludedByDefault() = runBlocking {
|
||||
val nodes = getNodes()
|
||||
assertTrue(nodes.any { it.hopsAway > 0 })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyDirectExcludesRelayed() = runBlocking {
|
||||
val nodes = getNodes(onlyDirect = true)
|
||||
assertFalse(nodes.any { it.hopsAway > 0 })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPkcMismatch() = runBlocking {
|
||||
val newNode =
|
||||
testNodes[1].copy(user = testNodes[1].user.copy { publicKey = ByteString.copyFrom(ByteArray(32) { 99 }) })
|
||||
nodeInfoDao.putAll(listOf(newNode))
|
||||
val nodes = getNodes()
|
||||
val containsMismatchNode = nodes.any { it.mismatchKey }
|
||||
assertTrue(containsMismatchNode)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* 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 Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.dao.NodeInfoDao
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.MyNodeEntity
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PacketDaoTest {
|
||||
private lateinit var database: MeshtasticDatabase
|
||||
private lateinit var nodeInfoDao: NodeInfoDao
|
||||
private lateinit var packetDao: PacketDao
|
||||
|
||||
private val myNodeInfo: MyNodeEntity =
|
||||
MyNodeEntity(
|
||||
myNodeNum = 42424242,
|
||||
model = null,
|
||||
firmwareVersion = null,
|
||||
couldUpdate = false,
|
||||
shouldUpdate = false,
|
||||
currentPacketId = 1L,
|
||||
messageTimeoutMsec = 5 * 60 * 1000,
|
||||
minAppVersion = 1,
|
||||
maxChannels = 8,
|
||||
hasWifi = false,
|
||||
)
|
||||
|
||||
private val myNodeNum: Int
|
||||
get() = myNodeInfo.myNodeNum
|
||||
|
||||
private val testContactKeys = listOf("0${DataPacket.ID_BROADCAST}", "1!test1234")
|
||||
|
||||
private fun generateTestPackets(myNodeNum: Int) = testContactKeys.flatMap { contactKey ->
|
||||
List(SAMPLE_SIZE) {
|
||||
Packet(
|
||||
uuid = 0L,
|
||||
myNodeNum = myNodeNum,
|
||||
port_num = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
contact_key = contactKey,
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = false,
|
||||
DataPacket(DataPacket.ID_BROADCAST, 0, "Message $it!"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun createDb(): Unit = runBlocking {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
database = Room.inMemoryDatabaseBuilder(context, MeshtasticDatabase::class.java).build()
|
||||
|
||||
nodeInfoDao = database.nodeInfoDao().apply { setMyNodeInfo(myNodeInfo) }
|
||||
|
||||
packetDao =
|
||||
database.packetDao().apply {
|
||||
generateTestPackets(42424243).forEach { insert(it) }
|
||||
generateTestPackets(myNodeNum).forEach { insert(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() {
|
||||
database.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_myNodeNum() = runBlocking {
|
||||
val myNodeInfo = nodeInfoDao.getMyNodeInfo().first()
|
||||
assertEquals(myNodeNum, myNodeInfo?.myNodeNum)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getAllPackets() = runBlocking {
|
||||
val packets = packetDao.getAllPackets(Portnums.PortNum.TEXT_MESSAGE_APP_VALUE).first()
|
||||
assertEquals(testContactKeys.size * SAMPLE_SIZE, packets.size)
|
||||
|
||||
val onlyMyNodeNum = packets.all { it.myNodeNum == myNodeNum }
|
||||
assertTrue(onlyMyNodeNum)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getContactKeys() = runBlocking {
|
||||
val contactKeys = packetDao.getContactKeys().first()
|
||||
assertEquals(testContactKeys.size, contactKeys.size)
|
||||
|
||||
val onlyMyNodeNum = contactKeys.values.all { it.myNodeNum == myNodeNum }
|
||||
assertTrue(onlyMyNodeNum)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getMessageCount() = runBlocking {
|
||||
testContactKeys.forEach { contactKey ->
|
||||
val messageCount = packetDao.getMessageCount(contactKey)
|
||||
assertEquals(SAMPLE_SIZE, messageCount)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getMessagesFrom() = runBlocking {
|
||||
testContactKeys.forEach { contactKey ->
|
||||
val messages = packetDao.getMessagesFrom(contactKey).first()
|
||||
assertEquals(SAMPLE_SIZE, messages.size)
|
||||
|
||||
val onlyFromContactKey = messages.all { it.packet.contact_key == contactKey }
|
||||
assertTrue(onlyFromContactKey)
|
||||
|
||||
val onlyMyNodeNum = messages.all { it.packet.myNodeNum == myNodeNum }
|
||||
assertTrue(onlyMyNodeNum)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getUnreadCount() = runBlocking {
|
||||
testContactKeys.forEach { contactKey ->
|
||||
val unreadCount = packetDao.getUnreadCount(contactKey)
|
||||
assertEquals(SAMPLE_SIZE, unreadCount)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_clearUnreadCount() = runBlocking {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
testContactKeys.forEach { contactKey ->
|
||||
packetDao.clearUnreadCount(contactKey, timestamp)
|
||||
val unreadCount = packetDao.getUnreadCount(contactKey)
|
||||
assertEquals(0, unreadCount)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_deleteContacts() = runBlocking {
|
||||
packetDao.deleteContacts(testContactKeys)
|
||||
|
||||
testContactKeys.forEach { contactKey ->
|
||||
val messages = packetDao.getMessagesFrom(contactKey).first()
|
||||
assertTrue(messages.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SAMPLE_SIZE = 10
|
||||
}
|
||||
}
|
||||
|
|
@ -21,12 +21,12 @@ import androidx.compose.ui.test.assertIsDisplayed
|
|||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.message.components.MessageItem
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue