Skip to content

Commit

Permalink
충남대 Android_전영주 5주차 과제 Step0 (#11)
Browse files Browse the repository at this point in the history
* Initial commit

* feat: step0 (#17)

* 충남대Android_전영주_4주차 과제_Step1 (피드백 반영) (#37)

* feat: step0

* docs: 1단계 기능 목록 작성

* feat(ui): create BottomSheet layout

* feat: 마커 기능&바텀 시트 추가

* feat: 에러 화면 추가

* feat: 마지막 위치를 저장 및 앱 실행 시 포커스

* feat: 피드백 반영

* 충남대 Android_전영주 4주차 Step2 (#83)

* feat: step0

* docs: 1단계 기능 목록 작성

* feat(ui): create BottomSheet layout

* feat: 마커 기능&바텀 시트 추가

* feat: 에러 화면 추가

* feat: 마지막 위치를 저장 및 앱 실행 시 포커스

* feat: 피드백 반영

* feat: step2 UI테스트 코드 추가

* step0: 4주차 코드 가져오기

---------

Co-authored-by: MyStoryG <[email protected]>
  • Loading branch information
aengzu and MyStoryG authored Jul 22, 2024
1 parent 305453d commit da1570d
Show file tree
Hide file tree
Showing 50 changed files with 1,571 additions and 2 deletions.
13 changes: 13 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
Expand All @@ -19,6 +21,10 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

buildConfigField("String", "KAKAO_API_KEY", getApiKey("KAKAO_API_KEY"))
buildConfigField("String", "KAKAO_REST_API_KEY", getApiKey("KAKAO_REST_API_KEY"))

}

buildTypes {
Expand All @@ -39,10 +45,15 @@ android {
}

buildFeatures {
viewBinding = true
dataBinding = true
buildConfig = true
}
testOptions {
animationsDisabled = true
}
}
fun getApiKey(key: String): String = gradleLocalProperties(rootDir, providers).getProperty(key)

dependencies {

Expand All @@ -58,6 +69,8 @@ dependencies {
implementation("androidx.test:core-ktx:1.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3")
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.test.espresso:espresso-contrib:3.6.1")
implementation("androidx.test.ext:junit-ktx:1.2.1")
kapt("androidx.room:room-compiler:2.6.1")
implementation("com.google.dagger:hilt-android:2.48.1")
kapt("com.google.dagger:hilt-compiler:2.48.1")
Expand Down
95 changes: 95 additions & 0 deletions app/src/androidTest/java/campus/tech/kakao/map/MapActivityTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package campus.tech.kakao.map

import android.content.Context
import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import campus.tech.kakao.map.domain.model.PlaceVO
import campus.tech.kakao.map.presentation.MapActivity
import campus.tech.kakao.map.presentation.PlaceActivity
import org.hamcrest.CoreMatchers.not
import org.junit.Rule
import org.junit.Test

class MapActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(MapActivity::class.java)


@Test
fun 검색필드가_표시되는지_확인_테스트() {
onView(withId(R.id.searchBox)).check(matches(isDisplayed()))
}

@Test
fun 검색필드의_힌트텍스트_확인_테스트() {
onView(withId(R.id.searchTextView)).check(matches(withHint("검색어를 입력해 주세요")))
}

@Test
fun 맵뷰가_표시되는지_확인_테스트() {
onView(withId(R.id.mapView)).check(matches(isDisplayed()))
}

@Test
fun 검색필드를_누르면_액티비티_전환되는지_확인_테스트() {
Intents.init()
onView(withId(R.id.searchBox)).perform(click())
Intents.intended(hasComponent(PlaceActivity::class.java.name))
Intents.release()
}


@Test
fun 인텐트가_있을때_바텀시트가_표시되는지_확인() {
val place = PlaceVO("가짜 장소", "가짜 주소", "카페", 37.5665, 126.9780)
val intent =
Intent(ApplicationProvider.getApplicationContext(), MapActivity::class.java).apply {
putExtra("place", place)
}

launchActivity<MapActivity>(intent)
onView(withId(R.id.bottomSheet)).check(matches(isDisplayed()))
}

@Test
fun 바텀시트에_표시된_텍스트가_인텐트로_보낸것과_일치하는지_확인() {
val place = PlaceVO("가짜 장소", "가짜 주소", "카페", 37.5665, 126.9780)
val intent =
Intent(ApplicationProvider.getApplicationContext(), MapActivity::class.java).apply {
putExtra("place", place)
}
launchActivity<MapActivity>(intent)

onView(withId(R.id.nameTextaView)).check(matches(withText("가짜 장소")))
onView(withId(R.id.addressTextView)).check(matches(withText("가짜 주소")))
}

@Test
fun 인텐트와_마지막장소가_없다면_디폴트맵_표시하는지_확인_테스트() {

// 마지막 장소 초기화
val context = ApplicationProvider.getApplicationContext<Context>()
val sharedPreferences = context.getSharedPreferences("kakao_map", Context.MODE_PRIVATE)
sharedPreferences.edit().clear().apply()

launchActivity<MapActivity>()

onView(withId(R.id.mapView)).check(matches(isDisplayed()))
onView(withId(R.id.bottomSheet)).check(matches(not(isDisplayed())))


}


}
124 changes: 124 additions & 0 deletions app/src/androidTest/java/campus/tech/kakao/map/PlaceActivityTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package campus.tech.kakao.map


import android.content.pm.ActivityInfo
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.espresso.contrib.RecyclerViewActions
import campus.tech.kakao.map.presentation.MapActivity
import campus.tech.kakao.map.presentation.PlaceActivity
import campus.tech.kakao.map.presentation.PlaceAdapter
import campus.tech.kakao.map.presentation.SearchHistoryAdapter
import org.hamcrest.CoreMatchers.not
import org.junit.Rule
import org.junit.Test

class PlaceActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(PlaceActivity::class.java)

@Test
fun 검색박스에_텍스트_입력__검색_결과가_표시되는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
Thread.sleep(3000)
onView(withId(R.id.placeRecyclerView)).check(matches(isDisplayed()))
}

@Test
fun 검색_아이템_클릭__히스토리에__저장되는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
Thread.sleep(2000)
onView(withId(R.id.placeRecyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition<PlaceAdapter.PlaceViewHolder>(0, click())
)
Espresso.pressBack()
onView(withId(R.id.historyRecyclerView)).check(matches(hasDescendant(withText("N"))))
}

@Test
fun 검색_결과_클릭__지도_액티비티로_넘어가는지_테스트() {
Intents.init()
onView(withId(R.id.searchEditText)).perform(typeText("N"))
Thread.sleep(1000)
onView(withId(R.id.placeRecyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition<PlaceAdapter.PlaceViewHolder>(0, click())
)
Intents.intended(hasComponent(MapActivity::class.java.name))

Intents.release()
}

@Test
fun 검색_히스토리_항목_클릭__검색_결과_표시되는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("Z"))
Thread.sleep(3000)
onView(withId(R.id.placeRecyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition<PlaceAdapter.PlaceViewHolder>(0, click())
)
Espresso.pressBack()
onView(withId(R.id.historyRecyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition<SearchHistoryAdapter.HistoryViewHolder>(
0,
click()
)
)
Thread.sleep(3000)
onView(withId(R.id.placeRecyclerView)).check(matches(isDisplayed()))
}

@Test
fun 검색_히스토리에서_삭제__히스토리에서_제거되는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
Thread.sleep(1000)
onView(withId(R.id.historyRecyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition<SearchHistoryAdapter.HistoryViewHolder>(
0,
click()
)
)
Thread.sleep(1000)
onView(withId(R.id.historyRecyclerView)).check(matches(not(hasDescendant(withText("N")))))
}

@Test
fun 검색박스에서_텍스트_지우면_검색_결과가_사라지고__메시지_표시되는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
onView(withId(R.id.cancelButton)).perform(click())
Thread.sleep(2000)
onView(withId(R.id.emptyMessage)).check(matches(isDisplayed()))
onView(withId(R.id.placeRecyclerView)).check(matches(not(isDisplayed())))
}

@Test
fun 검색박스에서_x_버튼누르면_텍스트_지워지는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
onView(withId(R.id.cancelButton)).perform(click())
onView(withId(R.id.searchEditText)).check(matches(withText("")))
}

@Test
fun 기기_회전_후에_검색_히스토리가_유지되는지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
activityRule.scenario.onActivity {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
onView(withId(R.id.historyRecyclerView)).check(matches(hasDescendant(withText("N"))))
}

@Test
fun 검색_결과가_스크롤_가능한지_테스트() {
onView(withId(R.id.searchEditText)).perform(typeText("N"))
Thread.sleep(3000)
onView(withId(R.id.placeRecyclerView)).perform(
RecyclerViewActions.scrollToPosition<PlaceAdapter.PlaceViewHolder>(10)
)
onView(withId(R.id.placeRecyclerView)).check(matches(isDisplayed()))
}

}
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
android:supportsRtl="true"
android:theme="@style/Theme.Map"
tools:targetApi="31">
<activity
android:name=".presentation.ErrorActivity"
android:exported="false" />
<activity
android:name=".presentation.MapActivity"
android:exported="false" />
<activity
android:name=".presentation.PlaceActivity"
android:exported="true" />
<activity
android:name=".MainActivity"
android:exported="true">
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package campus.tech.kakao.map

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import campus.tech.kakao.map.presentation.MapActivity
import campus.tech.kakao.map.presentation.PlaceActivity


class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent = Intent(this, MapActivity::class.java)
startActivity(intent)
finish()
}
}
61 changes: 61 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/data/PreferenceHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package campus.tech.kakao.map.data

import android.content.Context
import android.content.SharedPreferences
import campus.tech.kakao.map.domain.model.PlaceVO
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken


object PreferenceHelper {
private const val PREF_NAME = "kakao_map"
private const val PREF_KEY_SEARCH_QUERY = "search_query"
private const val PREF_KEY_LAST_PLACE = "last_place"

fun defaultPrefs(context: Context): SharedPreferences =
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)

fun getSearchHistory(context: Context): List<String> {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val jsonSearchHistory = prefs.getString(PREF_KEY_SEARCH_QUERY, null)

return if (jsonSearchHistory != null) {
val type = object : TypeToken<List<String>>() {}.type
Gson().fromJson(jsonSearchHistory, type)
} else {
emptyList()
}
}

fun removeSearchQuery(context: Context, query: String) {
val searchHistory = getSearchHistory(context).toMutableList()
searchHistory.remove(query)
val jsonSearchHistory = Gson().toJson(searchHistory)
defaultPrefs(context).edit().putString(PREF_KEY_SEARCH_QUERY, jsonSearchHistory).apply()
}

fun saveSearchQuery(context: Context, query: String) {
val searchHistory = getSearchHistory(context).toMutableList()
searchHistory.remove(query)
searchHistory.add(0, query)
val jsonSearchHistory = Gson().toJson(searchHistory)

defaultPrefs(context).edit().putString(PREF_KEY_SEARCH_QUERY, jsonSearchHistory).apply()
}

fun saveLastPlace(context: Context, place: PlaceVO) {
val jsonLastPlace = Gson().toJson(place)
defaultPrefs(context).edit().putString(PREF_KEY_LAST_PLACE, jsonLastPlace).apply()
}

fun getLastPlace(context: Context): PlaceVO? {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val jsonLastPlace = prefs.getString(PREF_KEY_LAST_PLACE, null)

return if (jsonLastPlace != null) {
Gson().fromJson(jsonLastPlace, PlaceVO::class.java)
} else {
null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package campus.tech.kakao.map.data.model

import com.google.gson.annotations.SerializedName

data class DocumentEntity(
val id: String,
@SerializedName("place_name")
val placeName: String,
@SerializedName("category_name")
val categoryName: String,
@SerializedName("category_group_code")
val categoryGroupCode: String?,
@SerializedName("category_group_name")
val categoryGroupName: String,
val phone: String?,
@SerializedName("address_name")
val addressName: String,
@SerializedName("road_address_name")
val roadAddressName: String?,
val x: String,
val y: String,
@SerializedName("place_url")
val placeUrl: String,
val distance: String?
)
Loading

0 comments on commit da1570d

Please sign in to comment.