Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ccc1371
chore: 네비게이션 컴포즈 의존성 추가 (NavHost 사용을 위한 설정)
chohs4164 Nov 8, 2025
43526f6
chore: 이미지 리소스 추가 (ic_confirm_check 등)
chohs4164 Nov 8, 2025
8a03dea
chore: MainActivity에 AppNavGraph 연결하여 화면 네비게이션 구성
chohs4164 Nov 8, 2025
9cc2761
feat: 타이머 화면 간 네비게이션을 위한 AppNavGraph 구현
chohs4164 Nov 8, 2025
cd59a79
feat: 타이머 리스트용 StudyItem 컴포저블 추가
chohs4164 Nov 8, 2025
45e2b90
feat: 타이머 데이터 전달 및 네비게이션이 포함된 TimerScreen 구현
chohs4164 Nov 8, 2025
9863bab
feat: 원형 타이머 애니메이션을 위한 TimerProgress 컴포저블 구현
chohs4164 Nov 8, 2025
8dddcfe
feat: 시·분·초 직접 입력 및 검증이 포함된 TimerSettingScreen 리뉴얼
chohs4164 Nov 8, 2025
5834611
feat: 스터디 아이템에 오른쪽 스와이프 편집/삭제 추가 (투명 카드)
chohs4164 Nov 8, 2025
222085c
chore(ui): 완료 버튼 영역 패딩 조정으로 레이아웃 안정화
chohs4164 Nov 8, 2025
b1f3c35
feat: TimerSettingScreen에 TimerViewModel 연결 및 데이터 반영 처리
chohs4164 Nov 8, 2025
6f8a9ff
fix: imerSettingScreen에서 추가한 타이머가 TimerScreen에 반영되지 않던 문제 수정 (ViewMod…
chohs4164 Nov 8, 2025
cba235e
feat: 하단 네비게이션 바 추가
chohs4164 Nov 8, 2025
2f6491d
fix: id 값으로 각 타이머의 SwipeableStudyItem를 구분하여 삭제하도록 수정
chohs4164 Nov 8, 2025
208a17f
style: 추가하기 버튼 ui 수정
chohs4164 Nov 8, 2025
ecc2ebe
feat: 타이머 진행 창 추가
chohs4164 Nov 8, 2025
6950ca9
feat: 타이머가 1시간 단위로 자연스럽게 회전하며 1시간 완수 시 10분간 축하 문구 표시되도록 구현
chohs4164 Nov 10, 2025
a66bd60
feat: 탭 바 기록 선택 시
chohs4164 Nov 11, 2025
84979e1
feat: 타이머 상세 기록 페이지 구현
chohs4164 Nov 12, 2025
dd3bfd5
style: 시작, 종료, 집중 시간 텍스트 위치 조정
chohs4164 Nov 12, 2025
627afa7
feat: RecordItemCard 클릭 시 RecordDetailScreen으로 데이터 전달 및 화면 전환 연결
chohs4164 Nov 12, 2025
307cbc2
style: 코드 전체에 ktlint 적용
MyKnow Nov 12, 2025
561f262
chore: 테스트 코드 작성을 위한 라이브러리 의존성 추가
MyKnow Nov 12, 2025
07e540a
chore: unit 테스트를 위한 Junit 의존성 추가
chohs4164 Nov 15, 2025
be515f6
chore: 중복된 Junit 의존성 정리
chohs4164 Nov 15, 2025
907aa48
test: 타이머 및 기록 기능 테스트 코드 추가
chohs4164 Nov 16, 2025
9b21ebd
fix: 테스트 대응을 위한 AppNavGraph 라우팅 보완
chohs4164 Nov 16, 2025
29cee2f
refactor: TimerScreen 상태 유지 위해 selectedTab을 ViewModel로 이전
chohs4164 Nov 16, 2025
e388d94
refactor: TimerScreen 탭 상태를 ViewModel과 연동하고 UI 로직 정리
chohs4164 Nov 16, 2025
56ac8bc
refactor: 기록 화면 및 타이머 실행 화면의 UI 렌더링 보완
chohs4164 Nov 16, 2025
7d84a3f
test: 기록 상세 화면 네비게이션 테스트 수정 및 navController 접근 방식 개선
chohs4164 Nov 16, 2025
056a893
refactor: record detail 데이터 모델 분리 및 구조화
chohs4164 Nov 16, 2025
7aac7ef
feat: TimerViewModel에 날짜별 상세 기록 조회 로직 추가
chohs4164 Nov 16, 2025
800e5d1
feat: RecordDetailScreen UI 개선 및 전체/개별 타이머 상세 조회 구분 적용
chohs4164 Nov 16, 2025
f1a3f6d
refactor: DailyFocus 구조 및 로직 정리
chohs4164 Nov 16, 2025
e6cde52
Merge branch 'feature/timer' of https://github.com/sprinter-wtc/Study…
chohs4164 Nov 16, 2025
86e5c96
chore: test에서 ktlint 함수 이름 규칙 비활성화
MyKnow Nov 17, 2025
1d91fcf
chore: ktlint 규칙 적용 수정
chohs4164 Nov 23, 2025
c42bd9c
Merge branch 'feature/timer' of https://github.com/sprinter-wtc/Study…
chohs4164 Nov 23, 2025
7e63039
restore: 특정 커밋의 파일 수동 복원
chohs4164 Nov 23, 2025
0c2e0a6
style: 와일드카드 import 제거
chohs4164 Nov 23, 2025
12a273b
restore: 특정 커밋의 파일 수동 복원
chohs4164 Nov 23, 2025
7b8aded
restore: 특정 커밋의 파일 수동 복원
chohs4164 Nov 23, 2025
fe94845
restore: 특정 커밋의 파일 수동 복원
chohs4164 Nov 23, 2025
7efdc3c
restore: 특정 커밋의 파일 수동 복원
chohs4164 Nov 23, 2025
fae4455
style: androidTest의 와일드카드 import 제거
chohs4164 Nov 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
[*.{kt,kts}]
ktlint_standard_annotation = disabled
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_function_naming_ignore_when_annotated_with = Composable

[app/src/test/java/**.kt]
ktlint_function_naming = disabled
ktlint_standard_function-naming = disabled

[app/src/androidTest/java/**.kt]
ktlint_function_naming = disabled
ktlint_standard_function-naming = disabled
8 changes: 7 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)

// 네비게이션 바를 위한 의존성 추가
implementation("androidx.navigation:navigation-compose:2.8.3")

// 테스트 케이스 작성을 위한 의존성 추가
testImplementation("junit:junit:4.13.2")

androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sprinter.studyspot

import androidx.activity.ComponentActivity
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import androidx.navigation.compose.rememberNavController
import com.sprinter.studyspot.timer.ui.RecordDetailScreen
import com.sprinter.studyspot.timer.viewmodel.TimerViewModel
import org.junit.Rule
import org.junit.Test

/**
* RecordDetailScreen 내부의 달력 UI 동작 테스트
* 요일 표시, 5주 그리드 생성, 날짜 정렬 여부 확인
* 공부한 날짜 강조 및 날짜 선택 기능 검증
**/
class RecordDetailScreenCalendarTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun calendar_dayClick_selectsStudyDay() {
composeRule.setContent {
RecordDetailScreen(
navController = rememberNavController(),
recordId = "1",
timerViewModel = TimerViewModel(),
)
}

// 4일은 studyDaySet = setOf(4, 6, 12, 13, 19)에 포함됨
composeRule.onNodeWithContentDescription("day-4").assertExists()

// 클릭
composeRule.onNodeWithContentDescription("day-4").performClick()

// 선택되면 색상이 FF8686 (선택 색)
composeRule
.onNodeWithContentDescription("day-4")
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.ContentDescription,
listOf("day-4"),
),
)
}

@Test
fun monthNavigation_buttonsWork() {
composeRule.setContent {
RecordDetailScreen(
navController = rememberNavController(),
recordId = "1",
timerViewModel = TimerViewModel(),
)
}

// → 버튼 클릭 (다음 달)
composeRule.onNodeWithContentDescription("Next Month").performClick()

// ← 버튼 클릭 (이전 달)
composeRule.onNodeWithContentDescription("Prev Month").performClick()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sprinter.studyspot

import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.navigation.compose.rememberNavController
import com.sprinter.studyspot.timer.ui.RecordDetailScreen
import com.sprinter.studyspot.timer.viewmodel.TimerViewModel
import org.junit.Rule
import org.junit.Test

/**
* RecordDetailScreen의 전체 UI 구성 요소가 정상 렌더링되는지 확인
* 제목, 색상 배지, 달력 UI, 월 이동 버튼, 일별 기록 리스트 등의 UI 체크
*/
class RecordDetailScreenUITest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun recordDetailScreen_displaysTitleAndHeaders() {
// 🔸 테스트용 ViewModel (records 더미 데이터 포함)
val timerViewModel = TimerViewModel()

composeRule.setContent {
val navController = rememberNavController()

RecordDetailScreen(
navController = navController,
recordId = "1", // TimerViewModel의 records에서 id = "1" → "자바스크립트"
title = null, // title은 ViewModel 데이터 사용
color = null, // color도 ViewModel 데이터 사용
timerViewModel = timerViewModel,
)
}

// 🔹 제목이 잘 보이는지
composeRule.onNodeWithText("자바스크립트").assertExists()

// 🔹 헤더 텍스트가 잘 렌더링되는지
composeRule.onNodeWithText("시작").assertExists()
composeRule.onNodeWithText("종료").assertExists()
composeRule.onNodeWithText("집중 시간").assertExists()
}

@Test
fun backButton_isClickable() {
composeRule.setContent {
val navController = rememberNavController()
val timerViewModel = TimerViewModel()

RecordDetailScreen(
navController = navController,
recordId = "1",
title = null,
color = null,
timerViewModel = timerViewModel,
)
}

composeRule
.onNodeWithContentDescription("Back")
.assertExists()
.performClick()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.sprinter.studyspot

import androidx.activity.ComponentActivity
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.sprinter.studyspot.timer.ui.RecordDetailScreen
import com.sprinter.studyspot.timer.ui.component.RecordItemCard
import com.sprinter.studyspot.timer.viewmodel.TimerViewModel
import org.junit.Rule
import org.junit.Test

/**
* TimerScreen -> 기록 탭 -> 기록 상세 화면까지의 이동 흐름을 검증
* 뒤로 가기 버튼 클릭 시 '기록' 탭 상태가 유지되는지 확인
*/
class RecordFlowNavigationTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun clickingRecordItem_navigatesToDetailScreen() {
composeRule.setContent {
val navController = rememberNavController()
val timerViewModel = TimerViewModel()

NavHost(navController = navController, startDestination = "list") {
composable("list") {
RecordItemCard(
color = Color(0xFFFF8686),
title = "자바스크립트",
totalTime = "01:20",
dayCounts = mapOf(1 to 2, 2 to 3),
onClick = {
navController.navigate(
"recordDetail/1?title=자바스크립트&color=FFFF8686",
)
},
)
}

composable("recordDetail/{id}?title={title}&color={color}") { backStackEntry ->

val id = backStackEntry.arguments?.getString("id") ?: ""
val title = backStackEntry.arguments?.getString("title")
val colorHex = backStackEntry.arguments?.getString("color") ?: "FFFF8686"

// HEX string → Color 변환
val color =
try {
Color(colorHex.toLong(16))
} catch (_: Exception) {
Color.Gray
}

RecordDetailScreen(
navController = navController,
recordId = id,
title = title,
color = color,
timerViewModel = timerViewModel,
)
}
}
}

// 1) 목록에서 "자바스크립트" 카드 클릭
composeRule.onNodeWithText("자바스크립트").performClick()

// 2) DetailScreen 제목 표시 확인
composeRule.onNodeWithText("자바스크립트").assertExists()

// 3) 헤더 텍스트 확인 (정상 렌더링 확인)
composeRule.onNodeWithText("시작").assertExists()
composeRule.onNodeWithText("종료").assertExists()
composeRule.onNodeWithText("집중 시간").assertExists()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.sprinter.studyspot.timer.ui

import androidx.activity.ComponentActivity
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.sprinter.studyspot.timer.ui.component.RecordItemCard
import org.junit.Rule
import org.junit.Test

/**
* RecordItemCard 내부 데이터 로직 테스트
* 색상 표시, 타이틀, 총 공부 시간, 요일별 dayCounts 로직 검증
*/
class RecordItemCardTest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun recordItemCard_rendersCorrectly() {
composeRule.setContent {
RecordItemCard(
color = Color(0xFFFF8686),
title = "파이썬",
totalTime = "00:45",
dayCounts = mapOf(1 to 1, 3 to 2),
onClick = {},
)
}

composeRule.onNodeWithText("파이썬").assertExists()
composeRule.onNodeWithText("00:45").assertExists()
}

@Test
fun recordItemCard_click_works() {
var clicked = false

composeRule.setContent {
RecordItemCard(
color = Color(0xFFFF8686),
title = "파이썬",
totalTime = "00:45",
dayCounts = emptyMap(),
onClick = { clicked = true },
)
}

composeRule.onNodeWithText("파이썬").performClick()

assert(clicked)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.sprinter.studyspot

import androidx.activity.ComponentActivity
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.sprinter.studyspot.timer.ui.component.RecordItemCard
import org.junit.Rule
import org.junit.Test

/**
* RecordItemCard UI 컴포저블의 실제 화면 렌더링 테스트
* 클릭 동작, 레이아웃 배치, 색상 표시 등이 정상적으로 보이는지 검증
*/
class RecordItemCardUITest {
@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun recordItemCard_rendersCorrectly() {
composeRule.setContent {
RecordItemCard(
color = Color(0xFFFF8686),
title = "자바스크립트",
totalTime = "01:20",
dayCounts = mapOf(1 to 2, 3 to 1),
onClick = {},
)
}

// 타이틀
composeRule.onNodeWithText("자바스크립트").assertExists()

// 총 시간 표시
composeRule.onNodeWithText("01:20").assertExists()
}

@Test
fun recordItemCard_onClick_isTriggered() {
var clicked = false

composeRule.setContent {
RecordItemCard(
color = Color(0xFFFF8686),
title = "테스트 카드",
totalTime = "00:30",
dayCounts = mapOf(),
onClick = { clicked = true },
)
}

composeRule.onNodeWithText("테스트 카드").performClick()

assert(clicked)
}
}
Loading