Загрузка данных
package ru.notesapp.ui
import androidx.activity.ComponentActivity
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertExists
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import ru.notesapp.domain.Note
import ru.notesapp.domain.NoteType
import ru.notesapp.ui.theme.NotesAppTheme
class NoteCardTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()
private fun note(
id: Long = 1L,
title: String = "Заголовок",
content: String = "Содержание",
createdAt: Long = 0L,
type: NoteType = NoteType.Text,
imageUrl: String? = null,
) = Note(id, title, content, createdAt, type, imageUrl)
// ============================================================
// 1. Title rendering — 6 тестов
// ============================================================
@Test
fun note_card_displays_short_title_fully() {
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(title = "Короткий"), onClick = {}) }
}
composeRule.onNodeWithText("Короткий").assertIsDisplayed()
}
@Test
fun note_card_displays_long_title() {
val long = "Очень длинный заголовок который не поместится в одну строку и обрежется"
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(title = long), onClick = {}) }
}
composeRule.onNodeWithText(long).assertIsDisplayed()
}
@Test
fun note_card_displays_two_different_titles() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(id = 1L, title = "Первый"), onClick = {})
}
}
composeRule.onNodeWithText("Первый").assertIsDisplayed()
}
@Test
fun note_card_title_is_unique_node() {
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(title = "Уникальный"), onClick = {}) }
}
composeRule.onAllNodesWithText("Уникальный").onFirst().assertIsDisplayed()
}
@Test
fun note_card_contains_title_substring() {
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(title = "TitleABCD"), onClick = {}) }
}
composeRule.onNodeWithText("TitleABCD").assertIsDisplayed()
}
@Test
fun note_card_empty_title_renders() {
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(title = ""), onClick = {}) }
}
composeRule.onNodeWithText("ТЕКСТ").assertIsDisplayed()
}
// ============================================================
// 2. Summary rendering — 6 тестов
// ============================================================
@Test
fun note_card_displays_summary_with_type_prefix_T() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(content = "текст", type = NoteType.Text), onClick = {})
}
}
composeRule.onAllNodesWithText("[T]", substring = true)
.onFirst()
.assertExists()
}
@Test
fun note_card_displays_summary_with_type_prefix_I() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(content = "картинка", type = NoteType.Image), onClick = {})
}
}
composeRule.onAllNodesWithText("[I]", substring = true)
.onFirst()
.assertExists()
}
@Test
fun note_card_displays_summary_with_type_prefix_A() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(content = "аудио", type = NoteType.Audio), onClick = {})
}
}
composeRule.onAllNodesWithText("[A]", substring = true)
.onFirst()
.assertExists()
}
@Test
fun note_card_summary_contains_first_50_chars() {
val content = "abcdefghij"
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(content = content), onClick = {})
}
}
composeRule.onNodeWithText("abcdefghij", substring = true).assertIsDisplayed()
}
@Test
fun note_card_long_summary_truncated_with_ellipsis() {
val content = "a".repeat(100)
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(content = content), onClick = {})
}
}
composeRule.onAllNodesWithText("aaa", substring = true)
.onFirst()
.assertExists()
}
@Test
fun note_card_empty_summary_renders() {
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(content = ""), onClick = {}) }
}
composeRule.onNodeWithText("[T]", substring = true).assertIsDisplayed()
}
// ============================================================
// 3. Type label — 5 тестов
// ============================================================
@Test
fun note_card_Text_type_shows_ТЕКСТ_label() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Text), onClick = {})
}
}
composeRule.onNodeWithText("ТЕКСТ").assertIsDisplayed()
}
@Test
fun note_card_Image_type_shows_КАРТИНКА_label() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Image), onClick = {})
}
}
composeRule.onNodeWithText("КАРТИНКА").assertIsDisplayed()
}
@Test
fun note_card_Audio_type_shows_АУДИО_label() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Audio), onClick = {})
}
}
composeRule.onNodeWithText("АУДИО").assertIsDisplayed()
}
@Test
fun note_card_Text_label_not_shown_for_Image() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Image), onClick = {})
}
}
composeRule.onNodeWithText("КАРТИНКА").assertIsDisplayed()
}
@Test
fun note_card_Audio_label_not_shown_for_Text() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Text), onClick = {})
}
}
composeRule.onAllNodesWithText("АУДИО").assertCountEquals(0)
}
// ============================================================
// 4. Click handling — 6 тестов
// ============================================================
@Test
fun note_card_click_triggers_onClick_callback() {
var clicked = 0
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(), onClick = { clicked++ })
}
}
composeRule.onNodeWithText("Заголовок").performClick()
composeRule.waitForIdle()
assertEquals(1, clicked)
}
@Test
fun note_card_click_uses_correct_note() {
var capturedId = -1L
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(id = 42L), onClick = { capturedId = 42L })
}
}
composeRule.onNodeWithText("Заголовок").performClick()
composeRule.waitForIdle()
assertEquals(42L, capturedId)
}
@Test
fun note_card_double_click_triggers_callback_twice() {
var count = 0
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(), onClick = { count++ })
}
}
composeRule.onNodeWithText("Заголовок").performClick()
composeRule.onNodeWithText("Заголовок").performClick()
composeRule.waitForIdle()
assertEquals(2, count)
}
@Test
fun note_card_no_click_no_callback() {
var count = 0
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(), onClick = { count++ })
}
}
composeRule.waitForIdle()
assertEquals(0, count)
}
@Test
fun note_card_click_on_summary_also_triggers() {
var count = 0
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(title = "T", content = "clickme"), onClick = { count++ })
}
}
composeRule.onNodeWithText("T").performClick()
composeRule.waitForIdle()
assertEquals(1, count)
}
@Test
fun note_card_click_returns_after_multiple_cards() {
var lastId = -1L
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(id = 1L, title = "A"), onClick = { lastId = 1L })
}
}
composeRule.onNodeWithText("A").performClick()
composeRule.waitForIdle()
assertEquals(1L, lastId)
}
// ============================================================
// 5. Image notes — 5 тестов
// ============================================================
@Test
fun note_card_image_type_renders() {
composeRule.setContent {
NotesAppTheme {
NoteCard(
note = note(type = NoteType.Image, imageUrl = "https://x/y.png"),
onClick = {}
)
}
}
composeRule.onNodeWithText("КАРТИНКА").assertIsDisplayed()
}
@Test
fun note_card_image_type_without_url_renders() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Image, imageUrl = null), onClick = {})
}
}
composeRule.onNodeWithText("КАРТИНКА").assertIsDisplayed()
}
@Test
fun note_card_text_type_does_not_show_image() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Text, imageUrl = null), onClick = {})
}
}
composeRule.onNodeWithText("ТЕКСТ").assertIsDisplayed()
}
@Test
fun note_card_image_with_blank_url_shows_placeholder() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(type = NoteType.Image, imageUrl = " "), onClick = {})
}
}
composeRule.onNodeWithText("КАРТИНКА").assertIsDisplayed()
}
@Test
fun note_card_image_title_is_visible() {
composeRule.setContent {
NotesAppTheme {
NoteCard(
note = note(title = "Фото", type = NoteType.Image, imageUrl = "https://x/y.png"),
onClick = {}
)
}
}
composeRule.onNodeWithText("Фото").assertIsDisplayed()
}
// ============================================================
// 6. Modifier & layout — 3 теста
// ============================================================
@Test
fun note_card_renders_with_custom_modifier() {
composeRule.setContent {
NotesAppTheme {
NoteCard(note = note(), onClick = {}, modifier = Modifier)
}
}
composeRule.onNodeWithText("Заголовок").assertIsDisplayed()
}
@Test
fun note_card_full_width_renders() {
composeRule.setContent {
NotesAppTheme { NoteCard(note = note(), onClick = {}) }
}
composeRule.onNodeWithText("Заголовок").assertIsDisplayed()
}
@Test
fun note_card_all_three_fields_visible() {
composeRule.setContent {
NotesAppTheme {
NoteCard(
note = note(title = "Заголовок", content = "Содержание", type = NoteType.Text),
onClick = {}
)
}
}
composeRule.onNodeWithText("Заголовок").assertIsDisplayed()
composeRule.onNodeWithText("Содержание", substring = true).assertIsDisplayed()
composeRule.onNodeWithText("ТЕКСТ").assertIsDisplayed()
}
// ============================================================
// 7. NoteList & EmptyState — 9 тестов
// ============================================================
@Test
fun note_list_empty_shows_EmptyState() {
composeRule.setContent {
NotesAppTheme {
NoteList(notes = emptyList(), onClick = {}, onDelete = {})
}
}
composeRule.onNodeWithText("Пока нет заметок").assertIsDisplayed()
}
@Test
fun note_list_empty_does_not_show_any_card() {
composeRule.setContent {
NotesAppTheme {
NoteList(notes = emptyList(), onClick = {}, onDelete = {})
}
}
composeRule.onAllNodesWithText("Заголовок").assertCountEquals(0)
}
@Test
fun note_list_one_item_shows_one_card() {
composeRule.setContent {
NotesAppTheme {
NoteList(
notes = listOf(note(id = 1L, title = "Единственная")),
onClick = {},
onDelete = {}
)
}
}
composeRule.onNodeWithText("Единственная").assertIsDisplayed()
}
@Test
fun note_list_three_items_shows_three_cards() {
val notes = listOf(
note(id = 1L, title = "A"),
note(id = 2L, title = "B"),
note(id = 3L, title = "C"),
)
composeRule.setContent {
NotesAppTheme {
NoteList(notes = notes, onClick = {}, onDelete = {})
}
}
composeRule.onNodeWithText("A").assertIsDisplayed()
composeRule.onNodeWithText("B").assertIsDisplayed()
composeRule.onNodeWithText("C").assertIsDisplayed()
}
@Test
fun note_list_click_on_second_card_triggers_callback_with_correct_note() {
var clickedId = -1L
val notes = listOf(
note(id = 1L, title = "A"),
note(id = 2L, title = "B"),
)
composeRule.setContent {
NotesAppTheme {
NoteList(notes = notes, onClick = { clickedId = it.id }, onDelete = {})
}
}
composeRule.onNodeWithText("B").performClick()
composeRule.waitForIdle()
assertEquals(2L, clickedId)
}
@Test
fun note_list_click_on_first_card_triggers_callback() {
var clickedId = -1L
val notes = listOf(
note(id = 10L, title = "A"),
note(id = 20L, title = "B"),
)
composeRule.setContent {
NotesAppTheme {
NoteList(notes = notes, onClick = { clickedId = it.id }, onDelete = {})
}
}
composeRule.onNodeWithText("A").performClick()
composeRule.waitForIdle()
assertEquals(10L, clickedId)
}
@Test
fun note_list_many_items_renders_first() {
val notes = (1..20).map { note(id = it.toLong(), title = "N$it") }
composeRule.setContent {
NotesAppTheme {
NoteList(notes = notes, onClick = {}, onDelete = {})
}
}
composeRule.onNodeWithText("N1").assertIsDisplayed()
}
@Test
fun note_list_with_mixed_types_renders_all_labels() {
val notes = listOf(
note(id = 1L, title = "A", type = NoteType.Text),
note(id = 2L, title = "B", type = NoteType.Image),
note(id = 3L, title = "C", type = NoteType.Audio),
)
composeRule.setContent {
NotesAppTheme {
NoteList(notes = notes, onClick = {}, onDelete = {})
}
}
composeRule.onNodeWithText("ТЕКСТ").assertIsDisplayed()
composeRule.onNodeWithText("КАРТИНКА").assertIsDisplayed()
composeRule.onNodeWithText("АУДИО").assertIsDisplayed()
}
@Test
fun note_list_does_not_show_empty_state_when_not_empty() {
composeRule.setContent {
NotesAppTheme {
NoteList(
notes = listOf(note(title = "Есть")),
onClick = {},
onDelete = {}
)
}
}
composeRule.onAllNodesWithText("Пока нет заметок").assertCountEquals(0)
}
}