@@ -23,6 +23,7 @@ import org.wordpress.android.util.NetworkUtilsWrapper
2323import org.wordpress.android.viewmodel.ResourceProvider
2424import uniffi.wp_api.PostFormat
2525import uniffi.wp_api.PostStatus
26+ import java.util.Calendar
2627
2728@ExperimentalCoroutinesApi
2829@Suppress(" LargeClass" )
@@ -140,6 +141,18 @@ class PostRsSettingsViewModelTest :
140141 .isNotNull()
141142 }
142143
144+ @Test
145+ fun `retry while offline shows error` () {
146+ val viewModel = createViewModel()
147+
148+ viewModel.retry()
149+
150+ assertThat(viewModel.uiState.value.isLoading)
151+ .isFalse()
152+ assertThat(viewModel.uiState.value.error)
153+ .isNotNull()
154+ }
155+
143156 // endregion
144157
145158 // region Edit methods
@@ -361,6 +374,19 @@ class PostRsSettingsViewModelTest :
361374 .isEqualTo(DialogState .None )
362375 }
363376
377+ @Test
378+ fun `onDismissDialog clears author search state` () {
379+ val viewModel = createViewModel()
380+
381+ viewModel.onDismissDialog()
382+
383+ val state = viewModel.uiState.value
384+ assertThat(state.authorSearchQuery).isEmpty()
385+ assertThat(state.isSearchingAuthors).isFalse()
386+ assertThat(state.siteAuthors).isEmpty()
387+ assertThat(state.canLoadMoreAuthors).isFalse()
388+ }
389+
364390 // endregion
365391
366392 // region hasChanges
@@ -458,6 +484,21 @@ class PostRsSettingsViewModelTest :
458484 .isTrue()
459485 }
460486
487+ @Test
488+ fun `onSaveClicked while already saving is no-op` () {
489+ val viewModel = createViewModel()
490+ viewModel.onStatusSelected(PostStatus .Draft )
491+ viewModel.onSaveClicked()
492+ assertThat(viewModel.uiState.value.isSaving)
493+ .isTrue()
494+
495+ // Second save attempt should be no-op
496+ viewModel.onSaveClicked()
497+
498+ assertThat(viewModel.uiState.value.isSaving)
499+ .isTrue()
500+ }
501+
461502 // endregion
462503
463504 // region Back
@@ -581,6 +622,200 @@ class PostRsSettingsViewModelTest :
581622
582623 // endregion
583624
625+ // region Date and Time selection
626+
627+ @Test
628+ fun `onDateSelected updates editedDate` () {
629+ val viewModel = createViewModel()
630+
631+ viewModel.onDateSelected(2025 , 5 , 15 )
632+
633+ val date = viewModel.uiState.value.editedDate
634+ assertThat(date).isNotNull()
635+ val cal = Calendar .getInstance().apply {
636+ time = date!!
637+ }
638+ assertThat(cal[Calendar .YEAR ]).isEqualTo(2025 )
639+ assertThat(cal[Calendar .MONTH ]).isEqualTo(5 )
640+ assertThat(cal[Calendar .DAY_OF_MONTH ])
641+ .isEqualTo(15 )
642+ }
643+
644+ @Test
645+ fun `onDateSelected transitions to TimeDialog` () {
646+ val viewModel = createViewModel()
647+
648+ viewModel.onDateSelected(2025 , 5 , 15 )
649+
650+ assertThat(viewModel.uiState.value.dialogState)
651+ .isEqualTo(DialogState .TimeDialog )
652+ }
653+
654+ @Test
655+ fun `onTimeSelected updates editedDate` () {
656+ val viewModel = createViewModel()
657+
658+ viewModel.onTimeSelected(14 , 30 )
659+
660+ val date = viewModel.uiState.value.editedDate
661+ assertThat(date).isNotNull()
662+ val cal = Calendar .getInstance().apply {
663+ time = date!!
664+ }
665+ assertThat(cal[Calendar .HOUR_OF_DAY ])
666+ .isEqualTo(14 )
667+ assertThat(cal[Calendar .MINUTE ]).isEqualTo(30 )
668+ }
669+
670+ // endregion
671+
672+ // region Author selection
673+
674+ @Test
675+ fun `onAuthorSelected updates editedAuthor` () {
676+ val viewModel = createViewModel()
677+
678+ viewModel.onAuthorSelected(99L )
679+
680+ assertThat(
681+ viewModel.uiState.value.editedAuthor
682+ ).isEqualTo(99L )
683+ }
684+
685+ @Test
686+ fun `onAuthorSelected with same as original clears edit` () {
687+ val viewModel = createViewModel()
688+ // authorId defaults to 0
689+
690+ viewModel.onAuthorSelected(0L )
691+
692+ assertThat(
693+ viewModel.uiState.value.editedAuthor
694+ ).isNull()
695+ }
696+
697+ // endregion
698+
699+ // region Categories and Tags
700+
701+ @Test
702+ fun `onCategoriesClicked emits LaunchCategorySelection` () =
703+ test {
704+ val viewModel = createViewModel()
705+
706+ viewModel.events.test {
707+ viewModel.onCategoriesClicked()
708+
709+ val event = awaitItem()
710+ assertThat(event).isInstanceOf(
711+ PostRsSettingsEvent
712+ .LaunchCategorySelection ::class .java
713+ )
714+ cancelAndIgnoreRemainingEvents()
715+ }
716+ }
717+
718+ @Test
719+ fun `onCategoriesSelected with new ids updates edit` () {
720+ val viewModel = createViewModel()
721+
722+ viewModel.onCategoriesSelected(
723+ longArrayOf(1L , 2L )
724+ )
725+
726+ assertThat(
727+ viewModel.uiState.value.editedCategoryIds
728+ ).containsExactly(1L , 2L )
729+ }
730+
731+ @Test
732+ fun `onTagsSelected with new ids updates edit` () {
733+ val viewModel = createViewModel()
734+
735+ viewModel.onTagsSelected(longArrayOf(3L , 4L ))
736+
737+ assertThat(
738+ viewModel.uiState.value.editedTagIds
739+ ).containsExactly(3L , 4L )
740+ }
741+
742+ @Test
743+ fun `onCategoriesSelected with same ids clears edit` () {
744+ val viewModel = createViewModel()
745+ // original categoryIds is empty list
746+
747+ viewModel.onCategoriesSelected(longArrayOf())
748+
749+ assertThat(
750+ viewModel.uiState.value.editedCategoryIds
751+ ).isNull()
752+ }
753+
754+ // endregion
755+
756+ // region Featured image
757+
758+ @Test
759+ fun `onFeaturedImageSelected updates editedId` () {
760+ val viewModel = createViewModel()
761+
762+ viewModel.onFeaturedImageSelected(99L )
763+
764+ assertThat(
765+ viewModel.uiState.value.editedFeaturedImageId
766+ ).isEqualTo(99L )
767+ }
768+
769+ @Test
770+ fun `onFeaturedImageSelected with same id is no-op` () {
771+ val viewModel = createViewModel()
772+ // effectiveFeaturedImageId defaults to 0
773+
774+ viewModel.onFeaturedImageSelected(0L )
775+
776+ assertThat(
777+ viewModel.uiState.value.editedFeaturedImageId
778+ ).isNull()
779+ assertThat(
780+ viewModel.uiState.value.featuredImage
781+ ).isEqualTo(FieldState .Empty )
782+ }
783+
784+ @Test
785+ fun `onChooseFromWpMedia emits LaunchWpMediaPicker` () =
786+ test {
787+ val viewModel = createViewModel()
788+
789+ viewModel.events.test {
790+ viewModel.onChooseFromWpMedia()
791+
792+ val event = awaitItem()
793+ assertThat(event).isEqualTo(
794+ PostRsSettingsEvent .LaunchWpMediaPicker
795+ )
796+ cancelAndIgnoreRemainingEvents()
797+ }
798+ }
799+
800+ @Test
801+ fun `onChooseFromDevice emits LaunchDeviceMediaPicker` () =
802+ test {
803+ val viewModel = createViewModel()
804+
805+ viewModel.events.test {
806+ viewModel.onChooseFromDevice()
807+
808+ val event = awaitItem()
809+ assertThat(event).isEqualTo(
810+ PostRsSettingsEvent
811+ .LaunchDeviceMediaPicker
812+ )
813+ cancelAndIgnoreRemainingEvents()
814+ }
815+ }
816+
817+ // endregion
818+
584819 companion object {
585820 private const val TEST_POST_ID = 42L
586821 }
0 commit comments