diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/DialogHelper.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/DialogHelper.kt new file mode 100644 index 00000000..dabc3ae5 --- /dev/null +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/base/DialogHelper.kt @@ -0,0 +1,38 @@ +package nl.entreco.dartsscorecard.base + +import android.support.v7.app.AlertDialog +import nl.entreco.dartsscorecard.R +import nl.entreco.domain.model.players.Team +import javax.inject.Inject + +/** + * Created by entreco on 20/02/2018. + */ +class DialogHelper @Inject constructor(private val builder: AlertDialog.Builder) { + + fun revanche(previousIndex: Int, teams: Array, select: (Int) -> Unit) { + + if (onlyOneTeam(teams)) { + select(0) + } else if (moreThanOneTeam(teams)) { + var selectedIndex = previousIndex + builder + .setTitle(R.string.select_starting_team) + .setSingleChoiceItems(teams.map { it.toString() }.toTypedArray(), previousIndex, { _, which -> + selectedIndex = which + }) + .setPositiveButton(android.R.string.ok, { dialog, _ -> + select(selectedIndex) + dialog.dismiss() + }) + .setNegativeButton(R.string.cancel, { dialog, _ -> + dialog.dismiss() + }) + .create() + .show() + } + } + + private fun onlyOneTeam(teams: Array) = teams.size == 1 + private fun moreThanOneTeam(teams: Array) = teams.size > 1 +} diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaModel.kt index 9d3b541a..53f7ce65 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaModel.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/beta/BetaModel.kt @@ -19,6 +19,7 @@ class BetaModel(val feature: Feature) { val goal = ObservableField("$count / $total") val progress = ObservableFloat(((feature.votes.toFloat() / feature.required.toFloat()))) val image = ObservableField(feature.image) + val updates = ObservableField(feature.updates) private fun format(value: Int): String { return when{ @@ -26,4 +27,4 @@ class BetaModel(val feature: Feature) { else -> "${value / 1000}k" } } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/play/Play01Module.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/play/Play01Module.kt index 854323c1..67089f24 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/play/Play01Module.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/di/play/Play01Module.kt @@ -1,9 +1,19 @@ package nl.entreco.dartsscorecard.di.play +import android.content.Context +import android.support.v7.app.AlertDialog import dagger.Module +import dagger.Provides /** * Created by Entreco on 14/11/2017. */ @Module -class Play01Module \ No newline at end of file +class Play01Module { + + @Provides + @Play01Scope + fun provideAlertDialogBuilder(context: Context): AlertDialog.Builder { + return AlertDialog.Builder(context) + } +} diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01Animator.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01Animator.kt index 1e5bc23d..482a3e55 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01Animator.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01Animator.kt @@ -77,14 +77,7 @@ class Play01Animator(binding: ActivityPlay01Binding) { inputResume.animate().alpha(1 - slideOffset).translationX(slideOffset * -inputResume.width).setDuration(0).start() } - override fun onStateChanged(bottomSheet: View, newState: Int) { - when (newState) { - BottomSheetBehavior.STATE_COLLAPSED -> { - inputResume.setOnClickListener { expand() } - } - else -> inputResume.setOnClickListener(null) - } - } + override fun onStateChanged(bottomSheet: View, newState: Int) {} }) expand() @@ -143,4 +136,4 @@ class Play01Animator(binding: ActivityPlay01Binding) { } } } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01ViewModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01ViewModel.kt index 3e8d3861..b32db822 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01ViewModel.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/Play01ViewModel.kt @@ -2,13 +2,17 @@ package nl.entreco.dartsscorecard.play import android.databinding.ObservableBoolean import nl.entreco.dartsscorecard.base.BaseViewModel +import nl.entreco.dartsscorecard.base.DialogHelper import nl.entreco.dartsscorecard.play.score.GameLoadedNotifier import nl.entreco.dartsscorecard.play.score.TeamScoreListener import nl.entreco.dartsscorecard.play.score.UiCallback import nl.entreco.domain.Logger import nl.entreco.domain.model.* import nl.entreco.domain.model.players.Player +import nl.entreco.domain.model.players.Team import nl.entreco.domain.play.listeners.* +import nl.entreco.domain.play.revanche.RevancheRequest +import nl.entreco.domain.play.revanche.RevancheUsecase import nl.entreco.domain.play.start.MarkGameAsFinishedRequest import nl.entreco.domain.play.start.Play01Request import nl.entreco.domain.play.start.Play01Response @@ -22,12 +26,17 @@ import javax.inject.Inject /** * Created by Entreco on 11/11/2017. */ -class Play01ViewModel @Inject constructor(private val playGameUsecase: Play01Usecase, private val gameListeners: Play01Listeners, private val logger: Logger) : BaseViewModel(), UiCallback, InputListener { +class Play01ViewModel @Inject constructor(private val playGameUsecase: Play01Usecase, + private val revancheUsecase: RevancheUsecase, + private val gameListeners: Play01Listeners, + private val dialogHelper: DialogHelper, + private val logger: Logger) : BaseViewModel(), UiCallback, InputListener { val loading = ObservableBoolean(true) val finished = ObservableBoolean(false) private lateinit var game: Game private lateinit var request: Play01Request + private lateinit var teams: Array private lateinit var load: GameLoadedNotifier private lateinit var loaders: Array> @@ -38,14 +47,33 @@ class Play01ViewModel @Inject constructor(private val playGameUsecase: Play01Use this.playGameUsecase.loadGameAndStart(request, { response -> this.game = response.game - load.onLoaded(response.teams, game.scores, response.settings, this) - loaders.forEach { + this.teams = response.teams + this.load.onLoaded(response.teams, game.scores, response.settings, this) + this.loaders.forEach { it.onLoaded(response.teams, game.scores, response, null) } }, { err -> logger.e("err: $err") }) } + override fun onRevanche() { + dialogHelper.revanche(request.startIndex, teams) { startIndex -> + val nextTeam = (startIndex) % teams.size + revancheUsecase.recreateGameAndStart(RevancheRequest(request, teams, nextTeam), + { response -> + this.request = this.request.copy(gameId = response.game.id, startIndex = nextTeam) + this.game = response.game + this.teams = response.teams + this.load.onLoaded(response.teams, game.scores, response.settings, this) + this.loaders.forEach { + val playResponse = Play01Response(response.game, response.settings, response.teams, response.teamIds) + it.onLoaded(response.teams, game.scores, playResponse, null) + } + }, + { err -> logger.e("err: $err") }) + } + } + fun registerListeners(scoreListener: ScoreListener, statListener: StatListener, specialEventListener: SpecialEventListener<*>, vararg playerListeners: PlayerListener) { gameListeners.registerListeners(scoreListener, statListener, specialEventListener, *playerListeners) } @@ -116,4 +144,4 @@ class Play01ViewModel @Inject constructor(private val playGameUsecase: Play01Use private fun notifyListeners(next: Next, turn: Turn, by: Player, scores: Array) { gameListeners.onTurnSubmitted(next, turn, by, scores) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/input/InputViewModel.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/input/InputViewModel.kt index 245512cf..18a26f79 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/input/InputViewModel.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/input/InputViewModel.kt @@ -7,6 +7,7 @@ import android.databinding.ObservableInt import android.widget.TextView import nl.entreco.dartsscorecard.R import nl.entreco.dartsscorecard.base.BaseViewModel +import nl.entreco.dartsscorecard.play.Play01Animator import nl.entreco.domain.Analytics import nl.entreco.domain.Logger import nl.entreco.domain.model.* @@ -70,6 +71,13 @@ class InputViewModel @Inject constructor(private val analytics: Analytics, priva return true } + fun onResume(animator: Play01Animator, listener: InputListener) { + animator.expand() + if (resumeDescription.get() == R.string.game_shot_and_match) { + listener.onRevanche() + } + } + fun entered(score: Int) { val oldValue = scoredTxt.get() if (oldValue.length < 3) { @@ -204,4 +212,4 @@ class InputViewModel @Inject constructor(private val analytics: Analytics, priva } private fun gameIsFinished() = nextUp == null || nextUp?.state == State.MATCH -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/score/ScoreBindings.kt b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/score/ScoreBindings.kt index 8b6b8cab..031e6ccb 100644 --- a/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/score/ScoreBindings.kt +++ b/android/DartsScorecard/app/src/main/java/nl/entreco/dartsscorecard/play/score/ScoreBindings.kt @@ -3,6 +3,7 @@ package nl.entreco.dartsscorecard.play.score import android.databinding.BindingAdapter import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView +import android.util.Log import nl.entreco.domain.model.Score import nl.entreco.domain.model.players.Team import nl.entreco.domain.play.finish.GetFinishUsecase @@ -18,11 +19,17 @@ abstract class ScoreBindings { @BindingAdapter("adapter", "teams", "scores", "scoreSettings", "finishUsecase", "uiCallback", requireAll = true) fun addTeams(recyclerView: RecyclerView, adapter: ScoreAdapter, teams: ArrayList, scores: ArrayList, scoreSettings: ScoreSettings, finishUsecase: GetFinishUsecase, uiCallback: UiCallback?) { + if (teams.size != scores.size) { + throw IllegalStateException("state mismatch, scores.size != teams.size! -> was this game already started?") + } + recyclerView.layoutManager = LinearLayoutManager(recyclerView.context) recyclerView.itemAnimator = null recyclerView.adapter = adapter adapter.clear() + Log.w("TAG", "teams: $teams scores: $scores tsi:${scoreSettings.teamStartIndex}") + val listeners = mutableListOf() teams.forEachIndexed { index, team -> val vm = TeamScoreViewModel(team, scores[index], finishUsecase, starter = scoreSettings.teamStartIndex == index) @@ -39,4 +46,4 @@ abstract class ScoreBindings { recyclerView.smoothScrollToPosition(currentTeamIndex) } } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/main/res/layout/activity_launch.xml b/android/DartsScorecard/app/src/main/res/layout/activity_launch.xml index c56f4ac7..7d3e7b74 100644 --- a/android/DartsScorecard/app/src/main/res/layout/activity_launch.xml +++ b/android/DartsScorecard/app/src/main/res/layout/activity_launch.xml @@ -73,5 +73,14 @@ android:contentDescription="@null" android:src="@drawable/ic_entreco_black_bg" /> + + - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/layout/activity_play_01.xml b/android/DartsScorecard/app/src/main/res/layout/activity_play_01.xml index 763b6043..70ff14a6 100644 --- a/android/DartsScorecard/app/src/main/res/layout/activity_play_01.xml +++ b/android/DartsScorecard/app/src/main/res/layout/activity_play_01.xml @@ -69,7 +69,8 @@ android:id="@+id/includeInput" layout="@layout/play_01_input" app:listener="@{viewModel}" - app:viewModel="@{inputViewModel}" /> + app:viewModel="@{inputViewModel}" + app:animator="@{animator}"/> - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/layout/beta_view.xml b/android/DartsScorecard/app/src/main/res/layout/beta_view.xml index 6bfa6caa..1bc38c7a 100644 --- a/android/DartsScorecard/app/src/main/res/layout/beta_view.xml +++ b/android/DartsScorecard/app/src/main/res/layout/beta_view.xml @@ -67,7 +67,8 @@ android:id="@+id/progress" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignBottom="@id/preview"> + android:layout_alignBottom="@id/preview" + android:visibility="@{feature.votable ? View.VISIBLE: View.INVISIBLE}"> - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/layout/include_beta_detail.xml b/android/DartsScorecard/app/src/main/res/layout/include_beta_detail.xml index 6634e60d..57938e51 100644 --- a/android/DartsScorecard/app/src/main/res/layout/include_beta_detail.xml +++ b/android/DartsScorecard/app/src/main/res/layout/include_beta_detail.xml @@ -34,6 +34,7 @@ + + @@ -127,4 +137,4 @@ android:visibility="@{donateViewModel.loading ? View.VISIBLE : View.GONE}" /> - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/layout/include_resume.xml b/android/DartsScorecard/app/src/main/res/layout/include_resume.xml index 89fd6d1c..b9eb4d01 100644 --- a/android/DartsScorecard/app/src/main/res/layout/include_resume.xml +++ b/android/DartsScorecard/app/src/main/res/layout/include_resume.xml @@ -4,6 +4,7 @@ tools:showIn="@layout/play_01_input"> + @@ -14,33 +15,33 @@ android:layout_height="match_parent" android:orientation="horizontal"> - + - + - + - + - + - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/layout/play_01_input.xml b/android/DartsScorecard/app/src/main/res/layout/play_01_input.xml index 87a60d7a..a4d5fcce 100644 --- a/android/DartsScorecard/app/src/main/res/layout/play_01_input.xml +++ b/android/DartsScorecard/app/src/main/res/layout/play_01_input.xml @@ -17,6 +17,10 @@ name="viewModel" type="nl.entreco.dartsscorecard.play.input.InputViewModel" /> + + @@ -108,6 +112,8 @@ android:layout_alignParentStart="true" android:layout_alignTop="@id/input_arrow" android:background="@drawable/score_hint" + android:focusable="true" + android:onClick="@{() -> viewModel.onResume(animator, listener)}" android:translationX="-500dp"> - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/layout/play_01_main.xml b/android/DartsScorecard/app/src/main/res/layout/play_01_main.xml index d6df880a..3750f447 100644 --- a/android/DartsScorecard/app/src/main/res/layout/play_01_main.xml +++ b/android/DartsScorecard/app/src/main/res/layout/play_01_main.xml @@ -83,7 +83,7 @@ android:alpha="0" android:entries="@{viewModel.teamEntries}" android:onItemSelected="@{(adapter,i,index,l) -> viewModel.onTeamStat0Selected(adapter,index)}" - android:selectedItemPosition="@={viewModel.team0Index}" /> + android:selectedItemPosition="@={viewModel.team0Index}" /> - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/main/res/values/strings.xml b/android/DartsScorecard/app/src/main/res/values/strings.xml index 23f263d9..bcf36b56 100644 --- a/android/DartsScorecard/app/src/main/res/values/strings.xml +++ b/android/DartsScorecard/app/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ Start score Number of sets Number of legs + Cancel - + Continue @@ -55,7 +56,8 @@ to throw to throw first Game on - Game shot, and the match + Revanche? + Select Team to throw first How many darts did you use? diff --git a/android/DartsScorecard/app/src/main/res/values/styles_beta.xml b/android/DartsScorecard/app/src/main/res/values/styles_beta.xml index 39bb0cdf..a0537d41 100644 --- a/android/DartsScorecard/app/src/main/res/values/styles_beta.xml +++ b/android/DartsScorecard/app/src/main/res/values/styles_beta.xml @@ -57,4 +57,4 @@ - \ No newline at end of file + diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/base/DialogHelperTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/base/DialogHelperTest.kt new file mode 100644 index 00000000..e2a11536 --- /dev/null +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/base/DialogHelperTest.kt @@ -0,0 +1,79 @@ +package nl.entreco.dartsscorecard.base + +import android.support.v7.app.AlertDialog +import com.nhaarman.mockito_kotlin.* +import nl.entreco.domain.model.players.Team +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * Created by entreco on 20/02/2018. + */ +@RunWith(MockitoJUnitRunner::class) +class DialogHelperTest { + + @Mock private lateinit var mockDialog: AlertDialog + @Mock private lateinit var mockBuilder: AlertDialog.Builder + @Mock private lateinit var mockSelect: (Int) -> Unit + private lateinit var subject: DialogHelper + + private var givenTeams: MutableList = mutableListOf() + + @Before + fun setUp() { + whenever(mockBuilder.create()).thenReturn(mockDialog) + whenever(mockBuilder.setNegativeButton(any(), any())).thenReturn(mockBuilder) + whenever(mockBuilder.setPositiveButton(any(), any())).thenReturn(mockBuilder) + whenever(mockBuilder.setSingleChoiceItems(anyArray(), any(), any())).thenReturn(mockBuilder) + whenever(mockBuilder.setTitle(any())).thenReturn(mockBuilder) + subject = DialogHelper(mockBuilder) + } + + @Test + fun `it should show single choice dialog, when 'revanche' with more than 1 team`() { + givenTeams(2) + whenRevanche() + thenDialogIsShown() + } + + @Test + fun `it should directly select first team, when 'revanche' with 1 team`() { + givenTeams(1) + whenRevanche() + thenSelectedItemIs(0) + thenDialogisNotShown() + } + + @Test + fun `it should do nothing, when 'revanche' with 0 team`() { + givenTeams(0) + whenRevanche() + thenDialogisNotShown() + } + + private fun givenTeams(numberOfTeams: Int) { + (0 until numberOfTeams).forEach { + givenTeams.add(mock()) + } + } + + private fun whenRevanche() { + subject.revanche(0, givenTeams.toTypedArray(), mockSelect) + } + + private fun thenDialogIsShown() { + verify(mockDialog).show() + } + + private fun thenDialogisNotShown() { + verify(mockDialog, never()).show() + } + + private fun thenSelectedItemIs(expected: Int) { + verify(mockSelect).invoke(expected) + } + +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaActivityTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaActivityTest.kt index 113fff5a..35383a43 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaActivityTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaActivityTest.kt @@ -1,7 +1,9 @@ package nl.entreco.dartsscorecard.beta import android.content.Context +import android.content.IntentSender import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.spy import com.nhaarman.mockito_kotlin.verify import org.junit.Test @@ -14,7 +16,10 @@ import org.mockito.junit.MockitoJUnitRunner */ @RunWith(MockitoJUnitRunner::class) class BetaActivityTest { + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockBeta: BetaActivity + @Mock private lateinit var mockIntentSender: IntentSender val subject = spy(BetaActivity()) @@ -23,4 +28,10 @@ class BetaActivityTest { BetaActivity.launch(mockContext) verify(mockContext).startActivity(any()) } -} \ No newline at end of file + + @Test + fun `should start donate`() { + BetaActivity.donate(mockBeta, mockIntentSender) + verify(mockBeta).startIntentSenderForResult(eq(mockIntentSender), eq(180), any(), any(), any(), any()) + } +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaDiffCalculatorTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaDiffCalculatorTest.kt new file mode 100644 index 00000000..b83c49be --- /dev/null +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaDiffCalculatorTest.kt @@ -0,0 +1,51 @@ +package nl.entreco.dartsscorecard.beta + +import nl.entreco.domain.beta.Feature +import org.junit.Assert.* +import org.junit.Test + +/** + * Created by entreco on 19/02/2018. + */ +class BetaDiffCalculatorTest { + + private val feature1 = Feature("ref1", "title1", "desc1", "img1", "update",1,1) + private val feature2 = Feature("ref2", "title2", "desc2", "img2", "update",2,2) + private val feature3 = Feature("ref3", "title3", "desc3", "img3", "update",3,3) + private val feature4 = Feature("ref4", "title4", "desc4", "img4", "update",4,4) + + @Test + fun `areItemsTheSame true`() { + val subject = BetaDiffCalculator(listOf(feature1), listOf(feature1)) + assertTrue(subject.areItemsTheSame(0, 0)) + } + @Test + fun `areItemsTheSame false`() { + val subject = BetaDiffCalculator(listOf(feature1), listOf(feature3)) + assertFalse(subject.areItemsTheSame(0, 0)) + } + + @Test + fun getOldListSize() { + val subject = BetaDiffCalculator(listOf(feature1, feature2), listOf(feature3, feature4)) + assertEquals(2, subject.oldListSize) + } + + @Test + fun getNewListSize() { + val subject = BetaDiffCalculator(listOf(feature1, feature2), listOf(feature3, feature4)) + assertEquals(2, subject.newListSize) + } + + @Test + fun `areContentsTheSame true`() { + val subject = BetaDiffCalculator(listOf(feature1, feature2), listOf(feature1, feature4)) + assertTrue(subject.areContentsTheSame(0, 0)) + } + @Test + fun `areContentsTheSame false`() { + val subject = BetaDiffCalculator(listOf(feature1, feature2), listOf(feature1, feature4)) + assertFalse(subject.areContentsTheSame(1, 1)) + } + +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaModelTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaModelTest.kt index 43005b07..02ec00ff 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaModelTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaModelTest.kt @@ -8,11 +8,11 @@ import org.junit.Test * Created by entreco on 06/02/2018. */ class BetaModelTest{ - private val feature = Feature("reference","title", "description", "http://url.com", 10000, 500) + private val feature = Feature("reference","title", "description", "http://url.com", "update",10000, 500) private val subject = BetaModel(feature) @Test fun `it should set title`() { assertEquals("title", subject.title.get()) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaViewModelTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaViewModelTest.kt index 8f234fdd..7c439408 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaViewModelTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/beta/BetaViewModelTest.kt @@ -91,7 +91,7 @@ class BetaViewModelTest{ } private fun whenFetchingFeaturesSucceeds() { - expectedFeatureList = listOf(Feature("ref", "title", "desc", "img", 3, 1)) + expectedFeatureList = listOf(Feature("ref", "title", "desc", "img", "",3, 1)) subject.refresh(true) verify(mockSubscribeToFeaturesUsecase).subscribe(doneCaptor.capture(), any()) try { diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/di/play/Play01ModuleTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/di/play/Play01ModuleTest.kt index fdff7a87..3956d29f 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/di/play/Play01ModuleTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/di/play/Play01ModuleTest.kt @@ -1,14 +1,28 @@ package nl.entreco.dartsscorecard.di.play +import android.content.Context import org.junit.Assert.assertNotNull import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner /** * Created by Entreco on 02/01/2018. */ +@RunWith(MockitoJUnitRunner::class) class Play01ModuleTest { + + @Mock private lateinit var mockContext: Context + @Test fun `it should not be null`() { assertNotNull(Play01Module()) } -} \ No newline at end of file + + + @Test(expected = NullPointerException::class) + fun `it should provide AlertDialogBuilder`() { + Play01Module().provideAlertDialogBuilder(mockContext) + } +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelRevancheTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelRevancheTest.kt new file mode 100644 index 00000000..4051d168 --- /dev/null +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelRevancheTest.kt @@ -0,0 +1,71 @@ +package nl.entreco.dartsscorecard.play + +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.argumentCaptor +import com.nhaarman.mockito_kotlin.verify +import nl.entreco.dartsscorecard.base.DialogHelper +import nl.entreco.dartsscorecard.play.score.GameLoadedNotifier +import nl.entreco.domain.Logger +import nl.entreco.domain.model.Game +import nl.entreco.domain.model.players.Team +import nl.entreco.domain.play.revanche.RevancheResponse +import nl.entreco.domain.play.revanche.RevancheUsecase +import nl.entreco.domain.play.start.Play01Request +import nl.entreco.domain.play.start.Play01Response +import nl.entreco.domain.play.start.Play01Usecase +import nl.entreco.domain.settings.ScoreSettings +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * Created by entreco on 19/02/2018. + */ +@RunWith(MockitoJUnitRunner::class) +class Play01ViewModelRevancheTest { + + @Mock private lateinit var mockPlay01Usecamse: Play01Usecase + @Mock private lateinit var mockRevancheUsecase: RevancheUsecase + @Mock private lateinit var mockGameListeners: Play01Listeners + @Mock private lateinit var mockLoadNotifier: GameLoadedNotifier + @Mock private lateinit var mockDialogHelper: DialogHelper + @Mock private lateinit var mockLogger: Logger + @Mock private lateinit var mockGame: Game + @Mock private lateinit var mockScoreSettings: ScoreSettings + @Mock private lateinit var team1: Team + @Mock private lateinit var team2: Team + + + private val revancheCaptor = argumentCaptor<(RevancheResponse) -> Unit>() + + private lateinit var subject: Play01ViewModel + + private val doneCaptor = argumentCaptor<(Play01Response) -> Unit>() + private val selectCaptor = argumentCaptor<(Int) -> Unit>() + + @Test + fun `it should create new game when taking revanche`() { + givenFullyLoadedSubject() + whenTakingRevanche() + thenRevancheUsecaseIsExecuted() + } + + private fun givenFullyLoadedSubject() { + subject = Play01ViewModel(mockPlay01Usecamse, mockRevancheUsecase, mockGameListeners, mockDialogHelper, mockLogger) + subject.load(Play01Request(1, "1|2", 501, 1, 1, 1), mockLoadNotifier) + verify(mockPlay01Usecamse).loadGameAndStart(any(), doneCaptor.capture(), any()) + doneCaptor.lastValue.invoke(Play01Response(mockGame, mockScoreSettings, arrayOf(team1, team2), "1|2")) + } + + private fun whenTakingRevanche() { + subject.onRevanche() + verify(mockDialogHelper).revanche(any(), any(), selectCaptor.capture()) + selectCaptor.lastValue.invoke(0) + } + + private fun thenRevancheUsecaseIsExecuted() { + verify(mockRevancheUsecase).recreateGameAndStart(any(), revancheCaptor.capture(), any()) + revancheCaptor.lastValue.invoke(RevancheResponse(mockGame, mockScoreSettings, arrayOf(team1, team2), "1|2")) + } +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelTest.kt index aaf4dfe8..0c0462f5 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelTest.kt @@ -1,6 +1,7 @@ package nl.entreco.dartsscorecard.play import com.nhaarman.mockito_kotlin.* +import nl.entreco.dartsscorecard.base.DialogHelper import nl.entreco.dartsscorecard.play.score.GameLoadedNotifier import nl.entreco.dartsscorecard.play.score.TeamScoreListener import nl.entreco.domain.Logger @@ -12,6 +13,7 @@ import nl.entreco.domain.play.listeners.PlayerListener import nl.entreco.domain.play.listeners.ScoreListener import nl.entreco.domain.play.listeners.SpecialEventListener import nl.entreco.domain.play.listeners.StatListener +import nl.entreco.domain.play.revanche.RevancheUsecase import nl.entreco.domain.play.start.MarkGameAsFinishedRequest import nl.entreco.domain.play.start.Play01Request import nl.entreco.domain.play.start.Play01Response @@ -40,7 +42,9 @@ class Play01ViewModelTest { @Mock private lateinit var mockScore: Score @Mock private lateinit var mockRequest: Play01Request @Mock private lateinit var mockPlayGameUsecase: Play01Usecase + @Mock private lateinit var mockRevancheUsecase: RevancheUsecase @Mock private lateinit var mock01Listeners: Play01Listeners + @Mock private lateinit var mockDialogHelper: DialogHelper @Mock private lateinit var mockLogger: Logger @Mock private lateinit var mockCreatedNotifier: GameLoadedNotifier @Mock private lateinit var mockGameLoadedNotifier: GameLoadedNotifier @@ -195,12 +199,12 @@ class Play01ViewModelTest { game = Game(101, givenArbiter).start(0, givenTeams) req = Play01Request(gameId, teamIds, createGameRequest.startScore, createGameRequest.startIndex, createGameRequest.numLegs, createGameRequest.numSets) givenTeamScoreListeners = listOf(mockTeamScoreListener, mockTeamScoreListener) - subject = Play01ViewModel(mockPlayGameUsecase, mock01Listeners, mockLogger) + subject = Play01ViewModel(mockPlayGameUsecase, mockRevancheUsecase, mock01Listeners, mockDialogHelper, mockLogger) subject.load(req, mockCreatedNotifier, *loaders) } private fun givenFullyLoadedMockGame() { - subject = Play01ViewModel(mockPlayGameUsecase, mock01Listeners, mockLogger) + subject = Play01ViewModel(mockPlayGameUsecase, mockRevancheUsecase, mock01Listeners, mockDialogHelper, mockLogger) subject.load(mockRequest, mockCreatedNotifier) verify(mockPlayGameUsecase).loadGameAndStart(any(), doneCaptor.capture(), any()) doneCaptor.firstValue.invoke(Play01Response(mockGame, mockScoreSettings, givenTeams, teamIds)) @@ -321,4 +325,4 @@ class Play01ViewModelTest { private fun thenStatListenersAreNotified() { verify(mock01Listeners).onStatsUpdated(1,2) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelUndoTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelUndoTest.kt index 9749b25e..99272c60 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelUndoTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/Play01ViewModelUndoTest.kt @@ -1,9 +1,11 @@ package nl.entreco.dartsscorecard.play import com.nhaarman.mockito_kotlin.* +import nl.entreco.dartsscorecard.base.DialogHelper import nl.entreco.dartsscorecard.play.score.GameLoadedNotifier import nl.entreco.domain.Logger import nl.entreco.domain.model.Game +import nl.entreco.domain.play.revanche.RevancheUsecase import nl.entreco.domain.play.start.Play01Request import nl.entreco.domain.play.start.Play01Response import nl.entreco.domain.play.start.Play01Usecase @@ -29,7 +31,9 @@ class Play01ViewModelUndoTest{ @Mock private lateinit var mockLoaders: GameLoadedNotifier @Mock private lateinit var mockPlay01Usecase: Play01Usecase + @Mock private lateinit var mockRevancheUsecase: RevancheUsecase @Mock private lateinit var mockGameListeners: Play01Listeners + @Mock private lateinit var mockDialogHelper: DialogHelper @Mock private lateinit var mockLogger: Logger private lateinit var subject : Play01ViewModel @@ -64,7 +68,7 @@ class Play01ViewModelUndoTest{ whenever(mockGame.id).thenReturn(5) whenever(mockResponse.game).thenReturn(mockGame) - subject = Play01ViewModel(mockPlay01Usecase, mockGameListeners, mockLogger) + subject = Play01ViewModel(mockPlay01Usecase, mockRevancheUsecase, mockGameListeners, mockDialogHelper, mockLogger) subject.load(mockRequest, mockLoad, mockLoaders) verify(mockPlay01Usecase).loadGameAndStart(eq(mockRequest), load.capture(), any()) load.lastValue.invoke(mockResponse) @@ -101,4 +105,4 @@ class Play01ViewModelUndoTest{ private fun andLoadingIs(expected: Boolean) { assertEquals(expected, subject.loading.get()) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/input/InputViewModelTest.kt b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/input/InputViewModelTest.kt index bf7050b6..995e4ed8 100644 --- a/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/input/InputViewModelTest.kt +++ b/android/DartsScorecard/app/src/test/java/nl/entreco/dartsscorecard/play/input/InputViewModelTest.kt @@ -3,6 +3,7 @@ package nl.entreco.dartsscorecard.play.input import android.widget.TextView import com.nhaarman.mockito_kotlin.* import nl.entreco.dartsscorecard.R +import nl.entreco.dartsscorecard.play.Play01Animator import nl.entreco.domain.Analytics import nl.entreco.domain.Logger import nl.entreco.domain.model.* @@ -30,6 +31,7 @@ class InputViewModelTest { @Mock private lateinit var analytics: Analytics @InjectMocks private lateinit var subject: InputViewModel + @Mock private lateinit var mockAnimator: Play01Animator @Mock private lateinit var mockListener: InputListener @Mock private lateinit var mockInput: TextView @@ -315,6 +317,22 @@ class InputViewModelTest { verify(mockListener).onTurnSubmitted(any(), any()) } + @Test + fun `it should take revanche when game,shot and match`() { + givenResumeState(R.string.game_shot_and_match) + whenOnResumePressed() + thenExpandIsCalled() + andRevancheIsCalled() + } + + @Test + fun `it should NOT take revanche when NOT game,shot and match`() { + givenResumeState(R.string.game_on) + whenOnResumePressed() + thenExpandIsCalled() + andRevancheIsNotCalled() + } + private fun givenPlayer(playerName: String, pts: Int = 501, state: State = State.NORMAL) { givenPlayer = Player(playerName) givenRequiredScore = Score(pts, 0, 0) @@ -344,6 +362,10 @@ class InputViewModelTest { subject.toggle.set(singleMode) } + private fun givenResumeState(stateResource: Int) { + subject.resumeDescription.set(stateResource) + } + private fun whenPressingBack() { subject.back() } @@ -375,6 +397,10 @@ class InputViewModelTest { whenPressingSubmit(scored) } + private fun whenOnResumePressed() { + subject.onResume(mockAnimator, mockListener) + } + private fun thenScoredTxtIs(expected: String) { assertEquals(expected, subject.scoredTxt.get()) } @@ -386,10 +412,24 @@ class InputViewModelTest { private fun thenRequiredIs(required: Int) { assertEquals(required, subject.required.get().score) } + private fun thenFinalTurnIsStillEmpty() { assertNull(subject.finalTurn.get()) } + private fun thenFinalTurnIsSet() { assertNotNull(subject.finalTurn.get()) } + + private fun thenExpandIsCalled() { + verify(mockAnimator).expand() + } + + private fun andRevancheIsCalled() { + verify(mockListener).onRevanche() + } + + private fun andRevancheIsNotCalled() { + verify(mockListener, never()).onRevanche() + } } diff --git a/android/DartsScorecard/data/src/main/AndroidManifest.xml b/android/DartsScorecard/data/src/main/AndroidManifest.xml index 52d65d64..a052c5cb 100644 --- a/android/DartsScorecard/data/src/main/AndroidManifest.xml +++ b/android/DartsScorecard/data/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + + diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/analytics/FirebaseAnalytics.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/analytics/FirebaseAnalytics.kt index 4fbd6419..73c5cbb3 100644 --- a/android/DartsScorecard/data/src/main/java/nl/entreco/data/analytics/FirebaseAnalytics.kt +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/analytics/FirebaseAnalytics.kt @@ -1,6 +1,5 @@ package nl.entreco.data.analytics -import android.annotation.SuppressLint import android.content.Context import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics @@ -10,20 +9,20 @@ import nl.entreco.domain.beta.Donation /** * Created by Entreco on 15/11/2017. */ -@SuppressLint("MissingPermission") class FirebaseAnalytics(context: Context) : Analytics { private val fb by lazy { FirebaseAnalytics.getInstance(context) } override fun trackAchievement(achievementId: String) { - fb.logEvent(FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT, Bundle().apply { putString(FirebaseAnalytics.Param.ACHIEVEMENT_ID, achievementId) }) + fb.logEvent(FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT, Bundle().apply { + putString(FirebaseAnalytics.Param.ACHIEVEMENT_ID, achievementId) + }) } override fun trackViewDonations() { - fb.logEvent(FirebaseAnalytics.Event.VIEW_ITEM_LIST, Bundle() - .apply { - putString(FirebaseAnalytics.Param.ITEM_CATEGORY, "donations") - }) + fb.logEvent(FirebaseAnalytics.Event.VIEW_ITEM_LIST, Bundle().apply { + putString(FirebaseAnalytics.Param.ITEM_CATEGORY, "donations") + }) } override fun trackPurchase(donation: Donation) { @@ -44,4 +43,4 @@ class FirebaseAnalytics(context: Context) : Analytics { putString(FirebaseAnalytics.Param.ITEM_ID, productId) }) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/FeatureApiData.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/FeatureApiData.kt index 0beb0284..8e21b977 100644 --- a/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/FeatureApiData.kt +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/FeatureApiData.kt @@ -11,18 +11,21 @@ import com.google.firebase.firestore.PropertyName @IgnoreExtraProperties internal class FeatureApiData { - @PropertyName("description") - val description: String = "" + @PropertyName("description") + val description: String = "" - @PropertyName("image") - val image: String = "" + @PropertyName("image") + val image: String = "" - @PropertyName("title") - val title: String = "" + @PropertyName("title") + val title: String = "" - @PropertyName("goal") - val goal: Int = 0 + @PropertyName("progress") + val progress: String? = "" - @PropertyName("count") - val count: Int = 0 -} \ No newline at end of file + @PropertyName("goal") + val goal: Int = 0 + + @PropertyName("count") + val count: Int = 0 +} diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/RemoteFeatureRepository.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/RemoteFeatureRepository.kt index a3551c22..3be79403 100644 --- a/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/RemoteFeatureRepository.kt +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/api/beta/RemoteFeatureRepository.kt @@ -1,9 +1,6 @@ package nl.entreco.data.api.beta -import com.google.firebase.firestore.EventListener -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.FirebaseFirestoreException -import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.* import nl.entreco.domain.Logger import nl.entreco.domain.beta.Feature import nl.entreco.domain.repository.FeatureRepository @@ -26,11 +23,19 @@ class RemoteFeatureRepository(private val db: FirebaseFirestore, private val log features.clear() p0?.documents?.forEach { doc -> - val feature = doc.toObject(FeatureApiData::class.java) - features[doc.id] = Feature(doc.id, feature.title, feature.description, feature.image, feature.goal, feature.count) + convertToFeature(doc) }.also { onChange(ArrayList(features.values)) } } + private fun convertToFeature(doc: DocumentSnapshot) { + try { + val feature = doc.toObject(FeatureApiData::class.java) + features[doc.id] = Feature(doc.id, feature.title, feature.description, feature.image, feature.progress ?: "", feature.goal, feature.count) + } catch (oops: Exception) { + logger.e("Unable to convert snapshot to feature( $doc ) $oops") + } + } + override fun subscribe(onChange: (List) -> Unit): List { this.onChange = onChange return ArrayList(features.values) @@ -51,4 +56,4 @@ class RemoteFeatureRepository(private val db: FirebaseFirestore, private val log this.onChange = {} this.listener.remove() } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/DonationApiData.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/DonationApiData.kt new file mode 100644 index 00000000..2e36f908 --- /dev/null +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/DonationApiData.kt @@ -0,0 +1,24 @@ +package nl.entreco.data.billing + +import android.support.annotation.Keep +import com.google.firebase.firestore.IgnoreExtraProperties +import com.google.gson.annotations.SerializedName + +/** + * Created by entreco on 19/02/2018. + */ +@Keep +@IgnoreExtraProperties +internal data class DonationApiData( + @SerializedName("productId") + var productId: String, + + @SerializedName("price") + var price: String, + + @SerializedName("title") + var title: String, + + @SerializedName("description") + var description: String +) diff --git a/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/PlayStoreBillingRepository.kt b/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/PlayStoreBillingRepository.kt index 1748e98e..605191a9 100644 --- a/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/PlayStoreBillingRepository.kt +++ b/android/DartsScorecard/data/src/main/java/nl/entreco/data/billing/PlayStoreBillingRepository.kt @@ -5,10 +5,10 @@ import android.content.Context import android.content.Intent import android.support.annotation.UiThread import android.support.annotation.WorkerThread +import com.google.gson.GsonBuilder import nl.entreco.domain.beta.Donation import nl.entreco.domain.beta.donations.MakeDonationResponse import nl.entreco.domain.repository.BillingRepository -import org.json.JSONObject /** * Created by entreco on 08/02/2018. @@ -17,6 +17,7 @@ class PlayStoreBillingRepository(private val context: Context, private val servi private val BILLING_RESPONSE_RESULT_OK = 0 + private val gson by lazy { GsonBuilder().create() } private val apiVersion = 5 private val packageName = context.packageName @@ -38,18 +39,15 @@ class PlayStoreBillingRepository(private val context: Context, private val servi override fun fetchDonations(): List { val donations = FetchDonationsData() - val bundle = service.getService()?.run { - getSkuDetails(apiVersion, packageName, donations.type(), donations.skuBundle()) - } + val bundle = service.getService()?.getSkuDetails(apiVersion, packageName, donations.type(), donations.skuBundle()) return if (bundle?.getInt("RESPONSE_CODE") == BILLING_RESPONSE_RESULT_OK) { + bundle.getStringArrayList("DETAILS_LIST").mapNotNull { response -> - val json = JSONObject(response) - val productId = json.getString("productId") - val price = json.getString("price") - val votes = donations.getVotes(productId) + val donation = gson.fromJson(response, DonationApiData::class.java) + val votes = donations.getVotes(donation.productId) - Donation(json.getString("title"), json.getString("description"), productId, price, votes) + Donation(donation.title, donation.description, donation.productId, donation.price, votes) }.filter { donations.contains(it.sku) } } else { throw Throwable("Unable to retrieve donations, $bundle") @@ -77,4 +75,4 @@ class PlayStoreBillingRepository(private val context: Context, private val servi consumePurchase(apiVersion, packageName, token) }!! } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/data/src/test/java/nl/entreco/data/api/beta/FeatureApiDataTest.kt b/android/DartsScorecard/data/src/test/java/nl/entreco/data/api/beta/FeatureApiDataTest.kt index b17a5919..9f174291 100644 --- a/android/DartsScorecard/data/src/test/java/nl/entreco/data/api/beta/FeatureApiDataTest.kt +++ b/android/DartsScorecard/data/src/test/java/nl/entreco/data/api/beta/FeatureApiDataTest.kt @@ -7,7 +7,6 @@ import org.junit.Test * Created by entreco on 06/02/2018. */ class FeatureApiDataTest { - private val subject = FeatureApiData() @Test @@ -25,6 +24,11 @@ class FeatureApiDataTest { assertEquals("", subject.title) } + @Test + fun `progress is empty initially`() { + assertEquals("", subject.progress) + } + @Test fun `goal is zero initially`() { assertEquals(0, subject.goal) @@ -35,4 +39,4 @@ class FeatureApiDataTest { assertEquals(0, subject.count) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/data/src/test/java/nl/entreco/data/billing/BillingServiceConnectionTest.kt b/android/DartsScorecard/data/src/test/java/nl/entreco/data/billing/BillingServiceConnectionTest.kt new file mode 100644 index 00000000..664e125f --- /dev/null +++ b/android/DartsScorecard/data/src/test/java/nl/entreco/data/billing/BillingServiceConnectionTest.kt @@ -0,0 +1,63 @@ +package nl.entreco.data.billing + +import android.content.ComponentName +import android.os.IBinder +import com.nhaarman.mockito_kotlin.verify +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * Created by entreco on 20/02/2018. + */ +@RunWith(MockitoJUnitRunner::class) +class BillingServiceConnectionTest { + + @Mock private lateinit var mockComponentName : ComponentName + @Mock private lateinit var mockService : IBinder + @Mock private lateinit var mockCallback: (Boolean) -> Unit + private val subject = BillingServiceConnection() + + @Before + fun setUp() { + subject.setCallback(mockCallback) + } + + @Test + fun onServiceDisconnected() { + whenDisconnected() + verify(mockCallback).invoke(false) + } + + @Test + fun onServiceConnected() { + whenConnected() + verify(mockCallback).invoke(true) + } + + @Test + fun `it should get service`() { + whenConnected() + assertNotNull(subject.getService()) + } + + @Test + fun `it should clear service`() { + whenDisconnected() + assertNull(subject.getService()) + } + + + private fun whenConnected() { + subject.onServiceConnected(mockComponentName, mockService) + } + + private fun whenDisconnected() { + subject.onServiceDisconnected(mockComponentName) + } + +} diff --git a/android/DartsScorecard/data/src/test/java/nl/entreco/data/billing/PlayStoreBillingRepositoryTest.kt b/android/DartsScorecard/data/src/test/java/nl/entreco/data/billing/PlayStoreBillingRepositoryTest.kt new file mode 100644 index 00000000..56864563 --- /dev/null +++ b/android/DartsScorecard/data/src/test/java/nl/entreco/data/billing/PlayStoreBillingRepositoryTest.kt @@ -0,0 +1,188 @@ +package nl.entreco.data.billing + +import android.app.PendingIntent +import android.content.Context +import android.os.Bundle +import com.android.vending.billing.IInAppBillingService +import com.google.gson.GsonBuilder +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.eq +import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.whenever +import nl.entreco.domain.beta.Donation +import nl.entreco.domain.beta.donations.MakeDonationResponse +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * Created by entreco on 18/02/2018. + */ +@RunWith(MockitoJUnitRunner::class) +class PlayStoreBillingRepositoryTest { + + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockServiceConnection: BillingServiceConnection + @Mock private lateinit var mockInappBillingService: IInAppBillingService + @Mock private lateinit var mockPendingIntent: PendingIntent + @Mock private lateinit var mockBundle: Bundle + @Mock private lateinit var mockDoneCallback: (Boolean) -> Unit + + private lateinit var subject: PlayStoreBillingRepository + private lateinit var expectedResponse: MakeDonationResponse + private var expectedConsumtionResponse: Int = 0 + private var expectedDonation = emptyList() + + private val gson = GsonBuilder().create() + + @Test + fun `it should set callback when binding`() { + givenSubject() + whenBindingToBillingService() + thenCallbackIsSetOnService() + } + + @Test + fun `it should start service when binding`() { + givenSubject() + whenBindingToBillingService() + thenServiceIsStarted() + } + + @Test + fun `it should clear callback when unbinding`() { + givenSubject() + whenUnbindingToBillingService() + thenCallbackIsClearedOnService() + } + + @Test + fun `it should stop service when unbinding`() { + givenSubject() + whenUnbindingToBillingService() + thenServiceIsStopped() + } + + @Test + fun `it should report result when fetching donations`() { + givenSubject() + whenFetchingDonationsSucceeds() + thenDonationIsReturned() + } + + @Test(expected = Throwable::class) + fun `it should throw exception when fetching donations with invalid bundle`() { + givenSubject() + whenFetchingDonationsFails() + } + + @Test + fun `it should report result when making donations`() { + givenSubject() + whenDonationsSucceeds() + thenMakeResponseIsReturned() + } + + @Test(expected = Throwable::class) + fun `it should throw exception when donating with invalid bundle`() { + givenSubject() + whenDonatingFails() + } + + + @Test + fun `it should report result when consuming donations`() { + givenSubject() + whenConsumptionSucceeds() + thenConsumptionResponseIsReturned() + } + + @Test(expected = NullPointerException::class) + fun `it should throw exception when consuming with invalid bundle`() { + givenSubject() + whenConsumingFails() + } + + private fun givenSubject() { + subject = PlayStoreBillingRepository(mockContext, mockServiceConnection) + } + + private fun whenBindingToBillingService() { + subject.bind(mockDoneCallback) + } + + private fun whenUnbindingToBillingService() { + subject.unbind() + } + + private fun whenFetchingDonationsSucceeds() { + val product = gson.toJson(DonationApiData("10_feature_votes", "price", "title", "desc")) + + whenever(mockBundle.getStringArrayList("DETAILS_LIST")).thenReturn(arrayListOf(product)) + whenever(mockInappBillingService.getSkuDetails(any(), eq(null), any(), any())).thenReturn(mockBundle) + whenever(mockServiceConnection.getService()).thenReturn(mockInappBillingService) + + expectedDonation = subject.fetchDonations() + } + + private fun whenFetchingDonationsFails() { + whenever(mockServiceConnection.getService()).thenReturn(mockInappBillingService) + subject.fetchDonations() + } + + private fun whenDonationsSucceeds() { + whenever(mockBundle.getParcelable("BUY_INTENT")).thenReturn(mockPendingIntent) + whenever(mockInappBillingService.getBuyIntent(any(), eq(null), any(), any(), any())).thenReturn(mockBundle) + whenever(mockServiceConnection.getService()).thenReturn(mockInappBillingService) + expectedResponse = subject.donate(Donation("ti", "d", "s", "p", 10)) + } + + private fun whenDonatingFails() { + whenever(mockServiceConnection.getService()).thenReturn(mockInappBillingService) + subject.donate(Donation("ti", "d", "s", "p", 10)) + } + + private fun whenConsumptionSucceeds() { + whenever(mockInappBillingService.consumePurchase(any(), eq(null), eq("token"))).thenReturn(0) + whenever(mockServiceConnection.getService()).thenReturn(mockInappBillingService) + expectedConsumtionResponse = subject.consume("token") + } + + private fun whenConsumingFails() { + whenever(mockServiceConnection.getService()).thenReturn(null) + subject.consume("token") + } + + private fun thenCallbackIsSetOnService() { + verify(mockServiceConnection).setCallback(mockDoneCallback) + } + + private fun thenCallbackIsClearedOnService() { + verify(mockServiceConnection).setCallback(any()) + } + + private fun thenServiceIsStarted() { + verify(mockContext).bindService(any(), eq(mockServiceConnection), eq(Context.BIND_AUTO_CREATE)) + } + + private fun thenServiceIsStopped() { + verify(mockContext).unbindService(mockServiceConnection) + } + + private fun thenDonationIsReturned() { + assertNotNull(expectedDonation) + assertFalse(expectedDonation.isEmpty()) + } + + private fun thenMakeResponseIsReturned() { + assertNotNull(expectedResponse) + } + + private fun thenConsumptionResponseIsReturned() { + assertNotNull(expectedConsumtionResponse) + } + +} diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/beta/Feature.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/beta/Feature.kt index f05d2574..2099fdf8 100644 --- a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/beta/Feature.kt +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/beta/Feature.kt @@ -3,4 +3,4 @@ package nl.entreco.domain.beta /** * Created by entreco on 03/02/2018. */ -data class Feature(val ref: String, val title: String, val description: String, val image: String, val required: Int, val votes: Int) \ No newline at end of file +data class Feature(val ref: String, val title: String, val description: String, val image: String, val updates: String, val required: Int, val votes: Int) diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/Arbiter.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/Arbiter.kt index 0c8d6618..068e26eb 100644 --- a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/Arbiter.kt +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/Arbiter.kt @@ -26,7 +26,7 @@ class Arbiter(initial: Score) { this.turnHandler = TurnHandler(startIndex, teams) this.scores = Array(teams.size, { scoreSettings.score().copy() }) this.turnCounter = 0 - return turnHandler.start(scores[0]) + return turnHandler.start(scores[startIndex]) } fun handle(turn: Turn, next: Next): Next { @@ -134,4 +134,4 @@ class Arbiter(initial: Score) { fun getTurnCount(): Int { return turnCounter } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/listeners/InputListener.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/listeners/InputListener.kt index 54e3ebb7..e142e7d0 100644 --- a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/listeners/InputListener.kt +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/listeners/InputListener.kt @@ -10,4 +10,5 @@ interface InputListener { fun onUndo() fun onDartThrown(turn: Turn, by: Player) fun onTurnSubmitted(turn: Turn, by: Player) -} \ No newline at end of file + fun onRevanche() +} diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheRequest.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheRequest.kt new file mode 100644 index 00000000..414f389a --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheRequest.kt @@ -0,0 +1,9 @@ +package nl.entreco.domain.play.revanche + +import nl.entreco.domain.model.players.Team +import nl.entreco.domain.play.start.Play01Request + +/** + * Created by entreco on 19/02/2018. + */ +data class RevancheRequest(val originalRequest: Play01Request, val teams: Array, val newStartIndex: Int) diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheResponse.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheResponse.kt new file mode 100644 index 00000000..6b907784 --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheResponse.kt @@ -0,0 +1,10 @@ +package nl.entreco.domain.play.revanche + +import nl.entreco.domain.model.Game +import nl.entreco.domain.model.players.Team +import nl.entreco.domain.settings.ScoreSettings + +/** + * Created by entreco on 19/02/2018. + */ +data class RevancheResponse(val game: Game, val settings: ScoreSettings, val teams: Array, val teamIds: String) diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheUsecase.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheUsecase.kt new file mode 100644 index 00000000..d529faca --- /dev/null +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/revanche/RevancheUsecase.kt @@ -0,0 +1,31 @@ +package nl.entreco.domain.play.revanche + +import nl.entreco.domain.BaseUsecase +import nl.entreco.domain.common.executors.Background +import nl.entreco.domain.common.executors.Foreground +import nl.entreco.domain.repository.GameRepository +import nl.entreco.domain.settings.ScoreSettings +import javax.inject.Inject + +/** + * Created by entreco on 19/02/2018. + */ +class RevancheUsecase @Inject constructor( + private val gameRepository: GameRepository, bg: Background, fg: Foreground) : BaseUsecase(bg, fg) { + + fun recreateGameAndStart(request: RevancheRequest, done: (RevancheResponse) -> Unit, fail: (Throwable) -> Unit) { + onBackground({ + + val (_, teams, score, _, legs, sets) = request.originalRequest + val index = request.newStartIndex + val id = gameRepository.create(teams, score, index, legs, sets) + val game = gameRepository.fetchBy(id) + val scoreSettings = ScoreSettings(score, legs, sets, index) + + // + game.start(index, request.teams) + + onUi{ done(RevancheResponse(game, scoreSettings, request.teams, teams)) } + }, fail) + } +} diff --git a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/start/Play01Usecase.kt b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/start/Play01Usecase.kt index b49c6717..d6633d72 100644 --- a/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/start/Play01Usecase.kt +++ b/android/DartsScorecard/domain/src/main/java/nl/entreco/domain/play/start/Play01Usecase.kt @@ -29,7 +29,7 @@ class Play01Usecase @Inject constructor(private val retrieveGameUsecase: Retriev } fun undoLastTurn(req: UndoTurnRequest, done: (UndoTurnResponse) -> Unit, fail: (Throwable) -> Unit) { - undoTurnUsecase.exec(req, done , fail) + undoTurnUsecase.exec(req, done, fail) } fun markGameAsFinished(finishRequest: MarkGameAsFinishedRequest) { @@ -65,7 +65,7 @@ class Play01Usecase @Inject constructor(private val retrieveGameUsecase: Retriev gameResponse.game.start(playRequest.startIndex, teamResponse.teams) /** - * TODO: This 'prepares' the game for resuming. + * This 'prepares' the game for resuming. * It makes sure all Scores are correctly applied, * but also causes Play01ViewModel to have to ask Game * for properties related to Stats only @@ -73,10 +73,9 @@ class Play01Usecase @Inject constructor(private val retrieveGameUsecase: Retriev * CON -> Game is responsible for tracking Stats - related - stuff */ response.turns.forEach { gameResponse.game.handle(it.second) } - /** END OF TO DO */ val scoreSettings = ScoreSettings(playRequest.startScore, playRequest.numLegs, playRequest.numSets, playRequest.startIndex) done.invoke(Play01Response(gameResponse.game, scoreSettings, teamResponse.teams, playRequest.teamIds)) }, { err -> fail(err) }) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/beta/connect/SubscribeToFeaturesUsecaseTest.kt b/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/beta/connect/SubscribeToFeaturesUsecaseTest.kt index 4b12e781..caaa2803 100644 --- a/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/beta/connect/SubscribeToFeaturesUsecaseTest.kt +++ b/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/beta/connect/SubscribeToFeaturesUsecaseTest.kt @@ -118,6 +118,6 @@ class SubscribeToFeaturesUsecaseTest { } private fun f(title: String, required: Int, votes: Int): Feature { - return Feature("reference", title, "desc", "image", required, votes) + return Feature("reference", title, "desc", "image", "",required, votes) } -} \ No newline at end of file +} diff --git a/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/play/revanche/RevancheUsecaseTest.kt b/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/play/revanche/RevancheUsecaseTest.kt new file mode 100644 index 00000000..11343547 --- /dev/null +++ b/android/DartsScorecard/domain/src/test/java/nl/entreco/domain/play/revanche/RevancheUsecaseTest.kt @@ -0,0 +1,96 @@ +package nl.entreco.domain.play.revanche + +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.whenever +import nl.entreco.domain.common.executors.TestBackground +import nl.entreco.domain.common.executors.TestForeground +import nl.entreco.domain.model.Game +import nl.entreco.domain.model.players.Team +import nl.entreco.domain.play.start.Play01Request +import nl.entreco.domain.repository.GameRepository +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +/** + * Created by entreco on 19/02/2018. + */ +@RunWith(MockitoJUnitRunner::class) +class RevancheUsecaseTest { + + private val bg = TestBackground() + private val fg = TestForeground() + @Mock lateinit var mockDone: (RevancheResponse) -> Unit + @Mock lateinit var mockFail: (Throwable) -> Unit + @Mock lateinit var mockGame: Game + @Mock lateinit var mockGameRepository: GameRepository + private lateinit var subject: RevancheUsecase + + private val team1 = Team() + private val team2 = Team() + + @Test + fun `it should create new game`() { + givenSubject() + whenTakingRevanche() + thenNewGameIsCreated() + } + + @Test + fun `it should start newly created game`() { + givenSubject() + whenTakingRevanche() + thenNewGameIsStarted() + } + + @Test + fun `it should report failure 1`() { + givenSubject() + whenCreatingGameFails() + thenErrorIsReported() + } + + @Test + fun `it should report failure 2`() { + givenSubject() + whenFetchingGameByIdFails() + thenErrorIsReported() + } + + private fun givenSubject() { + subject = RevancheUsecase(mockGameRepository, bg, fg) + } + + private fun whenTakingRevanche() { + whenever(mockGameRepository.fetchBy(any())).thenReturn(mockGame) + subject.recreateGameAndStart(revanche(), mockDone, mockFail) + } + + private fun whenCreatingGameFails() { + whenever(mockGameRepository.create(any(), any(), any(), any(), any())).thenThrow(RuntimeException("Unable to create game")) + subject.recreateGameAndStart(revanche(), mockDone, mockFail) + } + + private fun whenFetchingGameByIdFails() { + whenever(mockGameRepository.fetchBy(any())).thenThrow(RuntimeException("Unable to create game")) + subject.recreateGameAndStart(revanche(), mockDone, mockFail) + } + + private fun revanche() = + RevancheRequest(Play01Request(0, "1|2", 101, 0, 2, 2), arrayOf(team1, team2), 1) + + private fun thenNewGameIsCreated() { + verify(mockDone).invoke(any()) + } + + private fun thenNewGameIsStarted() { + verify(mockGame).start(any(), any()) + } + + private fun thenErrorIsReported() { + verify(mockFail).invoke(any()) + } + +} diff --git a/store/screenshots/dsc_40.png b/store/screenshots/dsc_40.png new file mode 100644 index 00000000..1218fec1 Binary files /dev/null and b/store/screenshots/dsc_40.png differ diff --git a/store/screenshots/dsc_41.png b/store/screenshots/dsc_41.png new file mode 100644 index 00000000..372ce2d9 Binary files /dev/null and b/store/screenshots/dsc_41.png differ