From 29ab75c8a3fd36e48cc2ab4877f5aef9d45ff8ce Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 3 Sep 2024 13:02:12 +0900 Subject: [PATCH 01/28] Always enable delete button for Variable name --- .../presentation/StepQuizCodeBlanksReducer.kt | 2 +- .../mapper/StepQuizCodeBlanksViewStateMapper.kt | 2 +- .../StepQuizCodeBlanksViewStateMapperTest.kt | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 8c7884ffb..beaf7eeff 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -385,7 +385,7 @@ class StepQuizCodeBlanksReducer( ) ) - activeCodeBlock.children.all { it.selectedSuggestion == null } -> + activeChildIndex == 0 || activeCodeBlock.children.all { it.selectedSuggestion == null } -> if (state.codeBlocks.size > 1) { removeActiveCodeBlockAndSetNextActive() } else { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index fc176599a..42610d602 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -41,7 +41,7 @@ object StepQuizCodeBlanksViewStateMapper { is CodeBlock.Variable -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> when { - activeChildIndex > 1 -> + activeChildIndex == 0 || activeChildIndex > 1 -> true activeCodeBlock.children[activeChildIndex].selectedSuggestion == null && diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt index bbaa7e896..045d04e9c 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt @@ -197,14 +197,14 @@ class StepQuizCodeBlanksViewStateMapperTest { CodeBlock.Variable( children = listOf( CodeBlockChild.SelectSuggestion( - isActive = true, + isActive = false, suggestions = suggestions, - selectedSuggestion = null + selectedSuggestion = suggestions[0] ), CodeBlockChild.SelectSuggestion( - isActive = false, + isActive = true, suggestions = suggestions, - selectedSuggestion = suggestions[0] + selectedSuggestion = null ) ) ) @@ -217,13 +217,13 @@ class StepQuizCodeBlanksViewStateMapperTest { children = listOf( StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 0, - isActive = true, - value = null + isActive = false, + value = suggestions[0].text ), StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 1, - isActive = false, - value = suggestions[0].text + isActive = true, + value = null ) ) ) From eb5fd98c91d1bffa51213bfc24ed21ab1652c6b0 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 3 Sep 2024 16:08:58 +0900 Subject: [PATCH 02/28] Update iOS code blanks files structure --- .../project.pbxproj | 82 +++++++++++++++--- .../StepQuizCodeBlanksActionButton.swift | 0 .../StepQuizCodeBlanksActionButtonsView.swift | 52 +++++++++++ ...izCodeBlanksCodeBlockChildBlankView.swift} | 8 +- ...uizCodeBlanksCodeBlockChildTextView.swift} | 10 +-- ...StepQuizCodeBlanksCodeBlockChildView.swift | 4 +- ...epQuizCodeBlanksPrintInstructionView.swift | 0 .../StepQuizCodeBlanksCodeBlocksView.swift | 73 ++++++++++++++++ ...uizCodeBlanksVariableInstructionView.swift | 0 .../Views/StepQuizCodeBlanksView.swift | 86 ++----------------- .../StepQuizCodeBlanksSuggestionsView.swift | 13 ++- 11 files changed, 224 insertions(+), 104 deletions(-) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{ => CodeBlocks/ActionButtons}/StepQuizCodeBlanksActionButton.swift (100%) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{StepQuizCodeBlanksBlankView.swift => CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildBlankView.swift} (77%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{StepQuizCodeBlanksOptionView.swift => CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildTextView.swift} (69%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{ => CodeBlocks/Children}/StepQuizCodeBlanksCodeBlockChildView.swift (74%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{ => CodeBlocks/Print}/StepQuizCodeBlanksPrintInstructionView.swift (100%) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{ => CodeBlocks/Variable}/StepQuizCodeBlanksVariableInstructionView.swift (100%) rename iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/{ => Suggestions}/StepQuizCodeBlanksSuggestionsView.swift (79%) diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 3f4bc93fb..176ee2f2a 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -149,6 +149,8 @@ 2C2FD622281920B1004E7AF6 /* SentryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD621281920B1004E7AF6 /* SentryInfo.swift */; }; 2C2FD62428192123004E7AF6 /* BundlePropertyListDeserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD62328192123004E7AF6 /* BundlePropertyListDeserializer.swift */; }; 2C306A0E29B4590C0068FF4F /* StageImplementFeatureViewStateKsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C306A0D29B4590C0068FF4F /* StageImplementFeatureViewStateKsExtensions.swift */; }; + 2C308B1F2C86E29400E85D14 /* StepQuizCodeBlanksCodeBlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C308B1E2C86E29400E85D14 /* StepQuizCodeBlanksCodeBlocksView.swift */; }; + 2C308B212C86E4C200E85D14 /* StepQuizCodeBlanksActionButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C308B202C86E4C200E85D14 /* StepQuizCodeBlanksActionButtonsView.swift */; }; 2C3100532AB194A200C09BFB /* StepQuizParsonsViewDataMapperCodeContentCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3100522AB194A200C09BFB /* StepQuizParsonsViewDataMapperCodeContentCache.swift */; }; 2C32374D2837F7190062CAF6 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C32374C2837F7190062CAF6 /* Images.swift */; }; 2C32375328380C340062CAF6 /* NavigationToolbarInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C32375228380C340062CAF6 /* NavigationToolbarInfoItem.swift */; }; @@ -521,8 +523,8 @@ 2CD48D8E28586B6F00CFCC4A /* StepQuizViewDataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD48D8D28586B6F00CFCC4A /* StepQuizViewDataMapper.swift */; }; 2CD4EDF92B79D51E0091F0B2 /* View+SafeAreaInset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD4EDF82B79D51E0091F0B2 /* View+SafeAreaInset.swift */; }; 2CD4EDFB2B79D74B0091F0B2 /* TransparentBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD4EDFA2B79D74B0091F0B2 /* TransparentBlurView.swift */; }; - 2CD67CA12C452B2400240C17 /* StepQuizCodeBlanksBlankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD67CA02C452B2400240C17 /* StepQuizCodeBlanksBlankView.swift */; }; - 2CD67CA32C452DED00240C17 /* StepQuizCodeBlanksOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD67CA22C452DED00240C17 /* StepQuizCodeBlanksOptionView.swift */; }; + 2CD67CA12C452B2400240C17 /* StepQuizCodeBlanksCodeBlockChildBlankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD67CA02C452B2400240C17 /* StepQuizCodeBlanksCodeBlockChildBlankView.swift */; }; + 2CD67CA32C452DED00240C17 /* StepQuizCodeBlanksCodeBlockChildTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD67CA22C452DED00240C17 /* StepQuizCodeBlanksCodeBlockChildTextView.swift */; }; 2CD7C2D32BFDDC5500DFD5BE /* TopicCompletedModalSpacebotAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD7C2D22BFDDC5500DFD5BE /* TopicCompletedModalSpacebotAvatarView.swift */; }; 2CDA9838294432C900ADE539 /* SkeletonCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDA9837294432C900ADE539 /* SkeletonCircleView.swift */; }; 2CDA98412944512D00ADE539 /* ProfileSkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDA98402944512D00ADE539 /* ProfileSkeletonView.swift */; }; @@ -946,6 +948,8 @@ 2C2FD621281920B1004E7AF6 /* SentryInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryInfo.swift; sourceTree = ""; }; 2C2FD62328192123004E7AF6 /* BundlePropertyListDeserializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundlePropertyListDeserializer.swift; sourceTree = ""; }; 2C306A0D29B4590C0068FF4F /* StageImplementFeatureViewStateKsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StageImplementFeatureViewStateKsExtensions.swift; sourceTree = ""; }; + 2C308B1E2C86E29400E85D14 /* StepQuizCodeBlanksCodeBlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksCodeBlocksView.swift; sourceTree = ""; }; + 2C308B202C86E4C200E85D14 /* StepQuizCodeBlanksActionButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksActionButtonsView.swift; sourceTree = ""; }; 2C3100522AB194A200C09BFB /* StepQuizParsonsViewDataMapperCodeContentCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizParsonsViewDataMapperCodeContentCache.swift; sourceTree = ""; }; 2C32374C2837F7190062CAF6 /* Images.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; }; 2C32375228380C340062CAF6 /* NavigationToolbarInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationToolbarInfoItem.swift; sourceTree = ""; }; @@ -1323,8 +1327,8 @@ 2CD48D8D28586B6F00CFCC4A /* StepQuizViewDataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizViewDataMapper.swift; sourceTree = ""; }; 2CD4EDF82B79D51E0091F0B2 /* View+SafeAreaInset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+SafeAreaInset.swift"; sourceTree = ""; }; 2CD4EDFA2B79D74B0091F0B2 /* TransparentBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransparentBlurView.swift; sourceTree = ""; }; - 2CD67CA02C452B2400240C17 /* StepQuizCodeBlanksBlankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksBlankView.swift; sourceTree = ""; }; - 2CD67CA22C452DED00240C17 /* StepQuizCodeBlanksOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksOptionView.swift; sourceTree = ""; }; + 2CD67CA02C452B2400240C17 /* StepQuizCodeBlanksCodeBlockChildBlankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksCodeBlockChildBlankView.swift; sourceTree = ""; }; + 2CD67CA22C452DED00240C17 /* StepQuizCodeBlanksCodeBlockChildTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksCodeBlockChildTextView.swift; sourceTree = ""; }; 2CD7C2D22BFDDC5500DFD5BE /* TopicCompletedModalSpacebotAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicCompletedModalSpacebotAvatarView.swift; sourceTree = ""; }; 2CDA9837294432C900ADE539 /* SkeletonCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkeletonCircleView.swift; sourceTree = ""; }; 2CDA98402944512D00ADE539 /* ProfileSkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSkeletonView.swift; sourceTree = ""; }; @@ -2146,6 +2150,51 @@ path = UIKit; sourceTree = ""; }; + 2C308B192C86E08F00E85D14 /* CodeBlocks */ = { + isa = PBXGroup; + children = ( + 2C308B1E2C86E29400E85D14 /* StepQuizCodeBlanksCodeBlocksView.swift */, + 2C308B1B2C86E17300E85D14 /* ActionButtons */, + 2CBEE4C42C86E955004486E8 /* Children */, + 2C308B1D2C86E20E00E85D14 /* Print */, + 2C308B1C2C86E20600E85D14 /* Variable */, + ); + path = CodeBlocks; + sourceTree = ""; + }; + 2C308B1A2C86E09D00E85D14 /* Suggestions */ = { + isa = PBXGroup; + children = ( + 2C677D012C4A3F860019AF03 /* StepQuizCodeBlanksSuggestionsView.swift */, + ); + path = Suggestions; + sourceTree = ""; + }; + 2C308B1B2C86E17300E85D14 /* ActionButtons */ = { + isa = PBXGroup; + children = ( + 2C008A262C5771350041D8BB /* StepQuizCodeBlanksActionButton.swift */, + 2C308B202C86E4C200E85D14 /* StepQuizCodeBlanksActionButtonsView.swift */, + ); + path = ActionButtons; + sourceTree = ""; + }; + 2C308B1C2C86E20600E85D14 /* Variable */ = { + isa = PBXGroup; + children = ( + 2C3B84E72C637AE100FE9D5C /* StepQuizCodeBlanksVariableInstructionView.swift */, + ); + path = Variable; + sourceTree = ""; + }; + 2C308B1D2C86E20E00E85D14 /* Print */ = { + isa = PBXGroup; + children = ( + 2CB3BC552C46171000F5354F /* StepQuizCodeBlanksPrintInstructionView.swift */, + ); + path = Print; + sourceTree = ""; + }; 2C323750283808300062CAF6 /* View */ = { isa = PBXGroup; children = ( @@ -3574,6 +3623,16 @@ path = SwiftUI; sourceTree = ""; }; + 2CBEE4C42C86E955004486E8 /* Children */ = { + isa = PBXGroup; + children = ( + 2CD67CA02C452B2400240C17 /* StepQuizCodeBlanksCodeBlockChildBlankView.swift */, + 2CD67CA22C452DED00240C17 /* StepQuizCodeBlanksCodeBlockChildTextView.swift */, + 2CE7AA1A2C7C4255000ABCD7 /* StepQuizCodeBlanksCodeBlockChildView.swift */, + ); + path = Children; + sourceTree = ""; + }; 2CBFB94828897D970044D1BA /* StepQuizCodeFullScreen */ = { isa = PBXGroup; children = ( @@ -3741,14 +3800,9 @@ 2CD67C9D2C451B0200240C17 /* Views */ = { isa = PBXGroup; children = ( - 2C008A262C5771350041D8BB /* StepQuizCodeBlanksActionButton.swift */, - 2CD67CA02C452B2400240C17 /* StepQuizCodeBlanksBlankView.swift */, - 2CE7AA1A2C7C4255000ABCD7 /* StepQuizCodeBlanksCodeBlockChildView.swift */, - 2CD67CA22C452DED00240C17 /* StepQuizCodeBlanksOptionView.swift */, - 2CB3BC552C46171000F5354F /* StepQuizCodeBlanksPrintInstructionView.swift */, - 2C677D012C4A3F860019AF03 /* StepQuizCodeBlanksSuggestionsView.swift */, - 2C3B84E72C637AE100FE9D5C /* StepQuizCodeBlanksVariableInstructionView.swift */, 2C84E70B2C47BAB6002EE787 /* StepQuizCodeBlanksView.swift */, + 2C308B192C86E08F00E85D14 /* CodeBlocks */, + 2C308B1A2C86E09D00E85D14 /* Suggestions */, ); path = Views; sourceTree = ""; @@ -5187,7 +5241,7 @@ E9A022AE291D0E3F004317DB /* TopicsRepetitionsCardView.swift in Sources */, 2C5CBBE32948F4B600113007 /* StepQuizSQLViewModel.swift in Sources */, E9F923F628A2633D00C065A7 /* WelcomeView.swift in Sources */, - 2CD67CA32C452DED00240C17 /* StepQuizCodeBlanksOptionView.swift in Sources */, + 2CD67CA32C452DED00240C17 /* StepQuizCodeBlanksCodeBlockChildTextView.swift in Sources */, E9BDB4052A7BE1E30069EF98 /* BadgeImageView.swift in Sources */, 2C7FE8A52B98261600F09615 /* PurchaseManager.swift in Sources */, 2C106D9928C1CE6E004FA584 /* SendEmailFeedbackController.swift in Sources */, @@ -5304,7 +5358,7 @@ 2CB9537E2AF2474100CA64BA /* StepQuizHintsFeatureStateKsExtensions.swift in Sources */, 2C963BCA2812D3550036DD53 /* ProfileSettingsView.swift in Sources */, 2C772E7D28ABB4E500A58758 /* AppleIDSocialAuthSDKProvider.swift in Sources */, - 2CD67CA12C452B2400240C17 /* StepQuizCodeBlanksBlankView.swift in Sources */, + 2CD67CA12C452B2400240C17 /* StepQuizCodeBlanksCodeBlockChildBlankView.swift in Sources */, 2C963BCC2812D9330036DD53 /* ProfileSettingsAssembly.swift in Sources */, E9470C6B29810AB7008ACF9A /* StepQuizOutputProtocol.swift in Sources */, 2C079687285CFFF500EE0487 /* StepQuizSortingAssembly.swift in Sources */, @@ -5358,6 +5412,7 @@ 2CF34F9D2C340DB60054477E /* CommentsSkeletonView.swift in Sources */, E9D537D02A71056100F21828 /* ProfileBadgesGridItemView.swift in Sources */, 2CB0ADEE2B04AD6D0089D557 /* ChallengeWidgetViewModel.swift in Sources */, + 2C308B212C86E4C200E85D14 /* StepQuizCodeBlanksActionButtonsView.swift in Sources */, 2CACBCC22B7A3E4E006D3AB2 /* UsersInterviewWidgetAssembly.swift in Sources */, E9CC6C0729893F2200D8D070 /* StepQuizInputProtocol.swift in Sources */, 2C96743728882A0C0091B6C9 /* StepQuizCodeDetailsView.swift in Sources */, @@ -5485,6 +5540,7 @@ E94BB0482A9DF9DD00736B7C /* StepQuizParsonsView.swift in Sources */, E99CCB0B287E945300898BBF /* HomeViewModel.swift in Sources */, 2C7CB6782ADFD0E8006F78DA /* StepQuizFillBlanksViewDataMapper.swift in Sources */, + 2C308B1F2C86E29400E85D14 /* StepQuizCodeBlanksCodeBlocksView.swift in Sources */, 2C0FA879292FD73400A37636 /* ProfileSettingsFeatureViewStateKsExtensions.swift in Sources */, 2C1061AA285C3C3300EBD614 /* StepQuizChoiceAssembly.swift in Sources */, 2CF72AA828477E0600E1C192 /* StepQuizTableRowView.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksActionButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksActionButton.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift new file mode 100644 index 000000000..36137dbad --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift @@ -0,0 +1,52 @@ +import SwiftUI + +struct StepQuizCodeBlanksActionButtonsView: View { + let isDeleteButtonEnabled: Bool + let isSpaceButtonHidden: Bool + + let onSpaceTap: () -> Void + let onDeleteTap: () -> Void + let onEnterTap: () -> Void + + var body: some View { + HStack(spacing: LayoutInsets.defaultInset) { + Spacer() + + if !isSpaceButtonHidden { + StepQuizCodeBlanksActionButton + .space(action: onSpaceTap) + } + + StepQuizCodeBlanksActionButton + .delete(action: onDeleteTap) + .disabled(!isDeleteButtonEnabled) + + StepQuizCodeBlanksActionButton + .enter(action: onEnterTap) + } + .padding(.horizontal) + } +} + +#if DEBUG +#Preview { + VStack { + StepQuizCodeBlanksActionButtonsView( + isDeleteButtonEnabled: false, + isSpaceButtonHidden: false, + onSpaceTap: {}, + onDeleteTap: {}, + onEnterTap: {} + ) + + StepQuizCodeBlanksActionButtonsView( + isDeleteButtonEnabled: true, + isSpaceButtonHidden: true, + onSpaceTap: {}, + onDeleteTap: {}, + onEnterTap: {} + ) + } + .padding() +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksBlankView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildBlankView.swift similarity index 77% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksBlankView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildBlankView.swift index e0418f71e..508a77bab 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksBlankView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildBlankView.swift @@ -1,6 +1,6 @@ import SwiftUI -struct StepQuizCodeBlanksBlankView: View { +struct StepQuizCodeBlanksCodeBlockChildBlankView: View { var width: CGFloat = 208 var height: CGFloat = 48 @@ -17,7 +17,7 @@ struct StepQuizCodeBlanksBlankView: View { } } -extension StepQuizCodeBlanksBlankView { +extension StepQuizCodeBlanksCodeBlockChildBlankView { init(style: Style, isActive: Bool) { let size = style.size self.init(width: size.width, height: size.height, isActive: isActive) @@ -41,8 +41,8 @@ extension StepQuizCodeBlanksBlankView { #if DEBUG #Preview { VStack { - StepQuizCodeBlanksBlankView(style: .small, isActive: true) - StepQuizCodeBlanksBlankView(style: .large, isActive: false) + StepQuizCodeBlanksCodeBlockChildBlankView(style: .small, isActive: true) + StepQuizCodeBlanksCodeBlockChildBlankView(style: .large, isActive: false) } } #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksOptionView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildTextView.swift similarity index 69% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksOptionView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildTextView.swift index 5d011735d..832fd731f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksOptionView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildTextView.swift @@ -1,6 +1,6 @@ import SwiftUI -extension StepQuizCodeBlanksOptionView { +extension StepQuizCodeBlanksCodeBlockChildTextView { enum Appearance { static let insets = LayoutInsets(horizontal: 12, vertical: LayoutInsets.smallInset) static let minWidth: CGFloat = 48 @@ -9,7 +9,7 @@ extension StepQuizCodeBlanksOptionView { } } -struct StepQuizCodeBlanksOptionView: View { +struct StepQuizCodeBlanksCodeBlockChildTextView: View { let text: String let isActive: Bool @@ -31,9 +31,9 @@ struct StepQuizCodeBlanksOptionView: View { #if DEBUG #Preview { VStack { - StepQuizCodeBlanksOptionView(text: "print", isActive: false) - StepQuizCodeBlanksOptionView(text: "There is a cat on the keyboard, it is true", isActive: true) - StepQuizCodeBlanksOptionView(text: "Typing messages out of the blue", isActive: true) + StepQuizCodeBlanksCodeBlockChildTextView(text: "print", isActive: false) + StepQuizCodeBlanksCodeBlockChildTextView(text: "There is a cat on the keyboard, it is true", isActive: true) + StepQuizCodeBlanksCodeBlockChildTextView(text: "Typing messages out of the blue", isActive: true) } } #endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksCodeBlockChildView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildView.swift similarity index 74% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksCodeBlockChildView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildView.swift index ef8b7d51f..6f45335c4 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksCodeBlockChildView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Children/StepQuizCodeBlanksCodeBlockChildView.swift @@ -18,9 +18,9 @@ struct StepQuizCodeBlanksCodeBlockChildView: View { child: StepQuizCodeBlanksViewStateCodeBlockChildItem ) -> some View { if let value = child.value { - StepQuizCodeBlanksOptionView(text: value, isActive: child.isActive) + StepQuizCodeBlanksCodeBlockChildTextView(text: value, isActive: child.isActive) } else { - StepQuizCodeBlanksBlankView(style: .small, isActive: child.isActive) + StepQuizCodeBlanksCodeBlockChildBlankView(style: .small, isActive: child.isActive) } } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksPrintInstructionView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Print/StepQuizCodeBlanksPrintInstructionView.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksPrintInstructionView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Print/StepQuizCodeBlanksPrintInstructionView.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift new file mode 100644 index 000000000..61fa3cf62 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift @@ -0,0 +1,73 @@ +import shared +import SwiftUI + +struct StepQuizCodeBlanksCodeBlocksView: View { + let state: StepQuizCodeBlanksViewStateContent + + let onCodeBlockTap: (StepQuizCodeBlanksViewStateCodeBlockItem) -> Void + let onCodeBlockChildTap: ( + StepQuizCodeBlanksViewStateCodeBlockItem, + StepQuizCodeBlanksViewStateCodeBlockChildItem + ) -> Void + + let onSpaceTap: () -> Void + let onDeleteTap: () -> Void + let onEnterTap: () -> Void + + var body: some View { + VStack(alignment: .leading, spacing: LayoutInsets.smallInset) { + ForEach(state.codeBlocks, id: \.id_) { codeBlock in + switch StepQuizCodeBlanksViewStateCodeBlockItemKs(codeBlock) { + case .blank(let blankItem): + StepQuizCodeBlanksCodeBlockChildBlankView( + style: .large, + isActive: blankItem.isActive + ) + .padding(.horizontal) + .onTapGesture { + onCodeBlockTap(codeBlock) + } + case .print(let printItem): + StepQuizCodeBlanksPrintInstructionView( + printItem: printItem, + onChildTap: { codeBlockChild in + onCodeBlockChildTap(codeBlock, codeBlockChild) + } + ) + .onTapGesture { + onCodeBlockTap(codeBlock) + } + case .variable(let variableItem): + StepQuizCodeBlanksVariableInstructionView( + variableItem: variableItem, + onChildTap: { codeBlockChild in + onCodeBlockChildTap(codeBlock, codeBlockChild) + } + ) + .onTapGesture { + onCodeBlockTap(codeBlock) + } + } + } + + if !state.isActionButtonsHidden { + StepQuizCodeBlanksActionButtonsView( + isDeleteButtonEnabled: state.isDeleteButtonEnabled, + isSpaceButtonHidden: state.isSpaceButtonHidden, + onSpaceTap: onSpaceTap, + onDeleteTap: onDeleteTap, + onEnterTap: onEnterTap + ) + } + } + .padding(.vertical, LayoutInsets.defaultInset) + .frame(maxWidth: .infinity, alignment: .leading) + .background(BackgroundView()) + } +} + +extension StepQuizCodeBlanksCodeBlocksView: Equatable { + static func == (lhs: StepQuizCodeBlanksCodeBlocksView, rhs: StepQuizCodeBlanksCodeBlocksView) -> Bool { + lhs.state.isEqual(rhs) + } +} diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksVariableInstructionView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Variable/StepQuizCodeBlanksVariableInstructionView.swift similarity index 100% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksVariableInstructionView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Variable/StepQuizCodeBlanksVariableInstructionView.swift diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift index 5e3e8ecfd..7d127807c 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift @@ -23,12 +23,15 @@ struct StepQuizCodeBlanksView: View { titleView Divider() - codeBlocksView( - codeBlocks: contentState.codeBlocks, - isDeleteButtonEnabled: contentState.isDeleteButtonEnabled, - isSpaceButtonHidden: contentState.isSpaceButtonHidden, - isActionButtonsHidden: contentState.isActionButtonsHidden + StepQuizCodeBlanksCodeBlocksView( + state: contentState, + onCodeBlockTap: viewModel.doCodeBlockMainAction(_:), + onCodeBlockChildTap: viewModel.doCodeBlockChildMainAction(codeBlock:codeBlockChild:), + onSpaceTap: viewModel.doSpaceAction, + onDeleteTap: viewModel.doDeleteAction, + onEnterTap: viewModel.doEnterAction ) + .equatable() Divider() StepQuizCodeBlanksSuggestionsView( @@ -36,6 +39,7 @@ struct StepQuizCodeBlanksView: View { isAnimationEffectActive: contentState.isSuggestionsHighlightEffectActive, onSuggestionTap: viewModel.doSuggestionMainAction(_:) ) + .equatable() Divider() } .padding(.horizontal, -LayoutInsets.defaultInset) @@ -52,78 +56,6 @@ struct StepQuizCodeBlanksView: View { .frame(maxWidth: .infinity, alignment: .leading) .background(BackgroundView()) } - - @MainActor - private func codeBlocksView( - codeBlocks: [StepQuizCodeBlanksViewStateCodeBlockItem], - isDeleteButtonEnabled: Bool, - isSpaceButtonHidden: Bool, - isActionButtonsHidden: Bool - ) -> some View { - VStack(alignment: .leading, spacing: LayoutInsets.smallInset) { - ForEach(codeBlocks, id: \.id_) { codeBlock in - switch StepQuizCodeBlanksViewStateCodeBlockItemKs(codeBlock) { - case .blank(let blankItem): - StepQuizCodeBlanksBlankView( - style: .large, - isActive: blankItem.isActive - ) - .padding(.horizontal) - .onTapGesture { - viewModel.doCodeBlockMainAction(codeBlock) - } - case .print(let printItem): - StepQuizCodeBlanksPrintInstructionView( - printItem: printItem, - onChildTap: { codeBlockChild in - viewModel.doCodeBlockChildMainAction( - codeBlock: codeBlock, - codeBlockChild: codeBlockChild - ) - } - ) - .onTapGesture { - viewModel.doCodeBlockMainAction(codeBlock) - } - case .variable(let variableItem): - StepQuizCodeBlanksVariableInstructionView( - variableItem: variableItem, - onChildTap: { codeBlockChild in - viewModel.doCodeBlockChildMainAction( - codeBlock: codeBlock, - codeBlockChild: codeBlockChild - ) - } - ) - .onTapGesture { - viewModel.doCodeBlockMainAction(codeBlock) - } - } - } - - if !isActionButtonsHidden { - HStack(spacing: LayoutInsets.defaultInset) { - Spacer() - - if !isSpaceButtonHidden { - StepQuizCodeBlanksActionButton - .space(action: viewModel.doSpaceAction) - } - - StepQuizCodeBlanksActionButton - .delete(action: viewModel.doDeleteAction) - .disabled(!isDeleteButtonEnabled) - - StepQuizCodeBlanksActionButton - .enter(action: viewModel.doEnterAction) - } - .padding(.horizontal) - } - } - .padding(.vertical, LayoutInsets.defaultInset) - .frame(maxWidth: .infinity, alignment: .leading) - .background(BackgroundView()) - } } extension StepQuizCodeBlanksView: Equatable { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksSuggestionsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/Suggestions/StepQuizCodeBlanksSuggestionsView.swift similarity index 79% rename from iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksSuggestionsView.swift rename to iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/Suggestions/StepQuizCodeBlanksSuggestionsView.swift index b14c313c5..090929573 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksSuggestionsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/Suggestions/StepQuizCodeBlanksSuggestionsView.swift @@ -21,7 +21,7 @@ struct StepQuizCodeBlanksSuggestionsView: View { onSuggestionTap(suggestion) }, label: { - StepQuizCodeBlanksOptionView( + StepQuizCodeBlanksCodeBlockChildTextView( text: suggestion.text, isActive: true ) @@ -31,7 +31,7 @@ struct StepQuizCodeBlanksSuggestionsView: View { ) .pulseEffect( shape: RoundedRectangle( - cornerRadius: StepQuizCodeBlanksOptionView.Appearance.cornerRadius + cornerRadius: StepQuizCodeBlanksCodeBlockChildTextView.Appearance.cornerRadius ), isActive: isAnimationEffectActive ) @@ -42,7 +42,7 @@ struct StepQuizCodeBlanksSuggestionsView: View { // Preserve height to avoid layout jumps if suggestions.isEmpty { - StepQuizCodeBlanksOptionView(text: "", isActive: false) + StepQuizCodeBlanksCodeBlockChildTextView(text: "", isActive: false) .hidden() } } @@ -50,6 +50,13 @@ struct StepQuizCodeBlanksSuggestionsView: View { } } +extension StepQuizCodeBlanksSuggestionsView: Equatable { + static func == (lhs: StepQuizCodeBlanksSuggestionsView, rhs: StepQuizCodeBlanksSuggestionsView) -> Bool { + lhs.isAnimationEffectActive == rhs.isAnimationEffectActive && + lhs.suggestions.map(\.text) == rhs.suggestions.map(\.text) + } +} + #if DEBUG #Preview { VStack { From ce227698cc9abf2f25d8aee8471b2786f817838c Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 3 Sep 2024 17:34:57 +0900 Subject: [PATCH 03/28] Add StepQuizCodeBlanksIfStatementView --- .../project.pbxproj | 12 ++++ .../StepQuizCodeBlanksIfStatementView.swift | 61 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 176ee2f2a..6a2f026f2 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -483,6 +483,7 @@ 2CBD1917291D392400F5FB0B /* UIView+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBD1916291D392400F5FB0B /* UIView+Animations.swift */; }; 2CBD1919291D399500F5FB0B /* UIKitBounceButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBD1918291D399500F5FB0B /* UIKitBounceButton.swift */; }; 2CBD191D291D3BF400F5FB0B /* UIKitRoundedRectangleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBD191C291D3BF400F5FB0B /* UIKitRoundedRectangleButton.swift */; }; + 2CBEE4C72C870059004486E8 /* StepQuizCodeBlanksIfStatementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBEE4C62C870059004486E8 /* StepQuizCodeBlanksIfStatementView.swift */; }; 2CBFB94A28897DBB0044D1BA /* StepQuizCodeFullScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFB94928897DBB0044D1BA /* StepQuizCodeFullScreenView.swift */; }; 2CBFB94C28897DD70044D1BA /* StepQuizCodeFullScreenAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBFB94B28897DD70044D1BA /* StepQuizCodeFullScreenAssembly.swift */; }; 2CC4AAF1280DB513002276A0 /* WebOAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC4AAF0280DB513002276A0 /* WebOAuthService.swift */; }; @@ -1287,6 +1288,7 @@ 2CBD1916291D392400F5FB0B /* UIView+Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Animations.swift"; sourceTree = ""; }; 2CBD1918291D399500F5FB0B /* UIKitBounceButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBounceButton.swift; sourceTree = ""; }; 2CBD191C291D3BF400F5FB0B /* UIKitRoundedRectangleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRoundedRectangleButton.swift; sourceTree = ""; }; + 2CBEE4C62C870059004486E8 /* StepQuizCodeBlanksIfStatementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksIfStatementView.swift; sourceTree = ""; }; 2CBFB94928897DBB0044D1BA /* StepQuizCodeFullScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenView.swift; sourceTree = ""; }; 2CBFB94B28897DD70044D1BA /* StepQuizCodeFullScreenAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenAssembly.swift; sourceTree = ""; }; 2CC4AAF0280DB513002276A0 /* WebOAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebOAuthService.swift; sourceTree = ""; }; @@ -2156,6 +2158,7 @@ 2C308B1E2C86E29400E85D14 /* StepQuizCodeBlanksCodeBlocksView.swift */, 2C308B1B2C86E17300E85D14 /* ActionButtons */, 2CBEE4C42C86E955004486E8 /* Children */, + 2CBEE4C52C87003A004486E8 /* Conditions */, 2C308B1D2C86E20E00E85D14 /* Print */, 2C308B1C2C86E20600E85D14 /* Variable */, ); @@ -3633,6 +3636,14 @@ path = Children; sourceTree = ""; }; + 2CBEE4C52C87003A004486E8 /* Conditions */ = { + isa = PBXGroup; + children = ( + 2CBEE4C62C870059004486E8 /* StepQuizCodeBlanksIfStatementView.swift */, + ); + path = Conditions; + sourceTree = ""; + }; 2CBFB94828897D970044D1BA /* StepQuizCodeFullScreen */ = { isa = PBXGroup; children = ( @@ -5111,6 +5122,7 @@ E9D2D675284E0B30000757AC /* StepQuizMatchingView.swift in Sources */, 2CBC97CD2A555AA20078E445 /* StageImplementProjectCompletedModalView.swift in Sources */, 2CC95C0E2A4EBB970036C73E /* ProjectLevelAvatarView.swift in Sources */, + 2CBEE4C72C870059004486E8 /* StepQuizCodeBlanksIfStatementView.swift in Sources */, 2C93C2D4292E5905004D1861 /* HyperskillSentryBreadcrumb+SentryBreadcrumb.swift in Sources */, 2C306A0E29B4590C0068FF4F /* StageImplementFeatureViewStateKsExtensions.swift in Sources */, 2CA7B892289329C600A789EF /* UIView+FindViewController.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift new file mode 100644 index 000000000..f8f8ec361 --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift @@ -0,0 +1,61 @@ +import shared +import SwiftUI + +struct StepQuizCodeBlanksIfStatementView: View { + let children: [StepQuizCodeBlanksViewStateCodeBlockChildItem] + + let onChildTap: (StepQuizCodeBlanksViewStateCodeBlockChildItem) -> Void + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .center, spacing: LayoutInsets.smallInset) { + Text("if") + .font(StepQuizCodeBlanksAppearance.blankFont) + .foregroundColor(StepQuizCodeBlanksAppearance.blankTextColor) + + ForEach(children, id: \.id) { child in + StepQuizCodeBlanksCodeBlockChildView(child: child, action: onChildTap) + } + + Text(":") + .font(StepQuizCodeBlanksAppearance.blankFont) + .foregroundColor(StepQuizCodeBlanksAppearance.blankTextColor) + } + .padding(.horizontal, LayoutInsets.defaultInset) + .padding(.vertical, LayoutInsets.smallInset) + .background(Color(ColorPalette.violet400Alpha7)) + .cornerRadius(StepQuizCodeBlanksAppearance.cornerRadius) + .padding(.horizontal) + } + .scrollBounceBehaviorBasedOnSize(axes: .horizontal) + } +} + +#if DEBUG +#Preview { + VStack { + StepQuizCodeBlanksIfStatementView( + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) + ], + onChildTap: { _ in } + ) + + StepQuizCodeBlanksIfStatementView( + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: "x") + ], + onChildTap: { _ in } + ) + + StepQuizCodeBlanksIfStatementView( + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "x"), + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: true, value: nil) + ], + onChildTap: { _ in } + ) + } + .padding() +} +#endif From 6be2ddf991d1b1e96b0166d40abe35ac8f565764 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 4 Sep 2024 12:46:36 +0900 Subject: [PATCH 04/28] Add if statement suggestion --- .../app/step_quiz_code_blanks/domain/model/Suggestion.kt | 7 +++++++ .../presentation/StepQuizCodeBlanksReducer.kt | 2 +- .../step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt index 219daf9b4..0d95eaa5d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt @@ -19,6 +19,13 @@ sealed class Suggestion { "Variable(text='$text')" } + data object IfStatement : Suggestion() { + override val text: String = "if" + + override val analyticRepresentation: String = + "IfStatement(text='$text')" + } + data class ConstantString( override val text: String ) : Suggestion() { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index beaf7eeff..fae28cda9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -560,7 +560,7 @@ class StepQuizCodeBlanksReducer( CodeBlock.Blank( isActive = isActive, suggestions = if (isVariableSuggestionAvailable) { - listOf(Suggestion.Print, Suggestion.Variable) + listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement) } else { listOf(Suggestion.Print) } diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt index d0160f37b..df1ed4ef5 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt @@ -25,7 +25,7 @@ class StepQuizCodeBlanksReducerTest { private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) @Test - fun `Initialize should return Content state with active Blank and Print and Variable suggestions`() { + fun `Initialize should return Content state with active Blank and Print and Variable and If suggestions`() { val step = Step.stub( id = 1, block = Block.stub(options = Block.Options(codeBlanksVariables = listOf("a", "b"))) @@ -39,7 +39,7 @@ class StepQuizCodeBlanksReducerTest { codeBlocks = listOf( CodeBlock.Blank( isActive = true, - suggestions = listOf(Suggestion.Print, Suggestion.Variable) + suggestions = listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement) ) ) ) From 584ba59dfd9284baadf9c7bd54fd4e8f7b0a99b0 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 4 Sep 2024 13:30:21 +0900 Subject: [PATCH 05/28] Display if statement code block --- .../StepQuizCodeBlanksIfStatementView.swift | 33 ++++++++++------- .../StepQuizCodeBlanksCodeBlocksView.swift | 10 ++++++ .../domain/model/CodeBlock.kt | 21 +++++++++++ .../presentation/StepQuizCodeBlanksReducer.kt | 35 +++++++++++++++---- .../StepQuizCodeBlanksViewStateMapper.kt | 14 ++++++-- .../view/model/StepQuizCodeBlanksViewState.kt | 5 +++ 6 files changed, 97 insertions(+), 21 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift index f8f8ec361..077536086 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift @@ -2,7 +2,7 @@ import shared import SwiftUI struct StepQuizCodeBlanksIfStatementView: View { - let children: [StepQuizCodeBlanksViewStateCodeBlockChildItem] + let ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement let onChildTap: (StepQuizCodeBlanksViewStateCodeBlockChildItem) -> Void @@ -13,7 +13,7 @@ struct StepQuizCodeBlanksIfStatementView: View { .font(StepQuizCodeBlanksAppearance.blankFont) .foregroundColor(StepQuizCodeBlanksAppearance.blankTextColor) - ForEach(children, id: \.id) { child in + ForEach(ifStatementItem.children, id: \.id) { child in StepQuizCodeBlanksCodeBlockChildView(child: child, action: onChildTap) } @@ -35,24 +35,33 @@ struct StepQuizCodeBlanksIfStatementView: View { #Preview { VStack { StepQuizCodeBlanksIfStatementView( - children: [ - StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) - ], + ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement( + id: 0, + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) + ] + ), onChildTap: { _ in } ) StepQuizCodeBlanksIfStatementView( - children: [ - StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: "x") - ], + ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement( + id: 0, + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: "x") + ] + ), onChildTap: { _ in } ) StepQuizCodeBlanksIfStatementView( - children: [ - StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "x"), - StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: true, value: nil) - ], + ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement( + id: 0, + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "x"), + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: true, value: nil) + ] + ), onChildTap: { _ in } ) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift index 61fa3cf62..864907bcf 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift @@ -47,6 +47,16 @@ struct StepQuizCodeBlanksCodeBlocksView: View { .onTapGesture { onCodeBlockTap(codeBlock) } + case .ifStatement(let ifStatementItem): + StepQuizCodeBlanksIfStatementView( + ifStatementItem: ifStatementItem, + onChildTap: { codeBlockChild in + onCodeBlockChildTap(codeBlock, codeBlockChild) + } + ) + .onTapGesture { + onCodeBlockTap(codeBlock) + } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index 8faf3aced..907b0b655 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -80,6 +80,27 @@ sealed class CodeBlock { override fun toString(): String = "Variable(children=$children)" } + + internal data class IfStatement( + override val children: List + ) : CodeBlock() { + override val isActive: Boolean = false + + override val suggestions: List = emptyList() + + override val analyticRepresentation: String + get() = "IfStatement(children=$children)" + + override fun toReplyString(): String = + buildString { + append("if ") + append(joinChildrenToReplyString(children)) + append(":") + } + + override fun toString(): String = + "IfStatement(children=$children)" + } } internal fun CodeBlock.Companion.joinChildrenToReplyString(children: List): String = diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index fae28cda9..31de73385 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -105,20 +105,39 @@ class StepQuizCodeBlanksReducer( ) ) ) + Suggestion.IfStatement -> + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = state.codeBlanksVariablesSuggestions + + state.codeBlanksStringsSuggestions, + selectedSuggestion = null + ) + ) + ) else -> activeCodeBlock } - is CodeBlock.Print -> { + is CodeBlock.Print, + is CodeBlock.IfStatement -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> - activeCodeBlock.copy( - children = activeCodeBlock.children.mutate { + val activeChild = activeCodeBlock.children[activeChildIndex] as CodeBlockChild.SelectSuggestion + val newChildren = activeCodeBlock.children + .mutate { set( activeChildIndex, - activeCodeBlock.children[activeChildIndex].copy( + activeChild.copy( selectedSuggestion = message.suggestion as? Suggestion.ConstantString ) ) } - ) + .cast>() + + when (activeCodeBlock) { + is CodeBlock.Print -> activeCodeBlock.copy(children = newChildren) + is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) + else -> activeCodeBlock + } } ?: activeCodeBlock } is CodeBlock.Variable -> { @@ -393,6 +412,7 @@ class StepQuizCodeBlanksReducer( } } } + is CodeBlock.IfStatement -> TODO() } } @@ -519,8 +539,9 @@ class StepQuizCodeBlanksReducer( private fun setCodeBlockIsActive(codeBlock: CodeBlock, isActive: Boolean): CodeBlock = when (codeBlock) { is CodeBlock.Blank -> codeBlock.copy(isActive = isActive) + is CodeBlock.Print, is CodeBlock.Variable, - is CodeBlock.Print -> { + is CodeBlock.IfStatement -> { if (isActive) { if (codeBlock.activeChild() != null) { codeBlock @@ -536,6 +557,7 @@ class StepQuizCodeBlanksReducer( when (codeBlock) { is CodeBlock.Print -> codeBlock.copy(children = newChildren) is CodeBlock.Variable -> codeBlock.copy(children = newChildren) + is CodeBlock.IfStatement -> codeBlock.copy(children = newChildren) else -> codeBlock } } @@ -547,6 +569,7 @@ class StepQuizCodeBlanksReducer( when (codeBlock) { is CodeBlock.Print -> codeBlock.copy(children = newChildren) is CodeBlock.Variable -> codeBlock.copy(children = newChildren) + is CodeBlock.IfStatement -> codeBlock.copy(children = newChildren) else -> codeBlock } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index 42610d602..9416672f2 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -23,7 +23,8 @@ object StepQuizCodeBlanksViewStateMapper { when (activeCodeBlock) { is CodeBlock.Blank -> activeCodeBlock.suggestions is CodeBlock.Print, - is CodeBlock.Variable -> + is CodeBlock.Variable, + is CodeBlock.IfStatement -> (activeCodeBlock.activeChild() as? CodeBlockChild.SelectSuggestion)?.let { if (it.selectedSuggestion == null) { it.suggestions @@ -37,7 +38,8 @@ object StepQuizCodeBlanksViewStateMapper { val isDeleteButtonEnabled = when (activeCodeBlock) { is CodeBlock.Blank -> codeBlocks.size > 1 - is CodeBlock.Print -> true + is CodeBlock.Print, + is CodeBlock.IfStatement -> true is CodeBlock.Variable -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> when { @@ -57,7 +59,8 @@ object StepQuizCodeBlanksViewStateMapper { val isSpaceButtonHidden = if (state.codeBlanksOperationsSuggestions.isNotEmpty()) { when (activeCodeBlock) { - is CodeBlock.Print -> { + is CodeBlock.Print, + is CodeBlock.IfStatement -> { val activeChild = activeCodeBlock.activeChild() as? CodeBlockChild.SelectSuggestion activeChild?.selectedSuggestion == null } @@ -101,6 +104,11 @@ object StepQuizCodeBlanksViewStateMapper { id = index, children = codeBlock.children.mapIndexed(::mapCodeBlockChild) ) + is CodeBlock.IfStatement -> + StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( + id = index, + children = codeBlock.children.mapIndexed(::mapCodeBlockChild) + ) } private fun mapCodeBlockChild( diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt index 5221faeee..7698f8684 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt @@ -47,6 +47,11 @@ sealed interface StepQuizCodeBlanksViewState { val values: List get() = children.drop(1) } + + data class IfStatement( + override val id: Int, + override val children: List + ) : CodeBlockItem } data class CodeBlockChildItem( From 0b40dd433e3ff13857685a4e3cff3617ac1d8c8c Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 4 Sep 2024 17:44:57 +0900 Subject: [PATCH 06/28] Add indentLevel --- .../StepQuizCodeBlanksIfStatementView.swift | 3 + ...epQuizCodeBlanksPrintInstructionView.swift | 5 ++ .../StepQuizCodeBlanksCodeBlocksView.swift | 69 ++++++++++--------- ...uizCodeBlanksVariableInstructionView.swift | 3 + .../Views/StepQuizCodeBlanksView.swift | 7 +- .../domain/model/CodeBlock.kt | 6 ++ .../presentation/StepQuizCodeBlanksReducer.kt | 63 +++++++++++++++-- .../StepQuizCodeBlanksViewStateMapper.kt | 9 ++- .../view/model/StepQuizCodeBlanksViewState.kt | 6 ++ 9 files changed, 132 insertions(+), 39 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift index 077536086..3def9b2cc 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksIfStatementView.swift @@ -37,6 +37,7 @@ struct StepQuizCodeBlanksIfStatementView: View { StepQuizCodeBlanksIfStatementView( ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) ] @@ -47,6 +48,7 @@ struct StepQuizCodeBlanksIfStatementView: View { StepQuizCodeBlanksIfStatementView( ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: "x") ] @@ -57,6 +59,7 @@ struct StepQuizCodeBlanksIfStatementView: View { StepQuizCodeBlanksIfStatementView( ifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemIfStatement( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "x"), StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: true, value: nil) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Print/StepQuizCodeBlanksPrintInstructionView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Print/StepQuizCodeBlanksPrintInstructionView.swift index ab0cb80e3..c634638d9 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Print/StepQuizCodeBlanksPrintInstructionView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Print/StepQuizCodeBlanksPrintInstructionView.swift @@ -37,6 +37,7 @@ struct StepQuizCodeBlanksPrintInstructionView: View { StepQuizCodeBlanksPrintInstructionView( printItem: StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [.init(id: 0, isActive: false, value: "")] ), onChildTap: { _ in } @@ -44,6 +45,7 @@ struct StepQuizCodeBlanksPrintInstructionView: View { StepQuizCodeBlanksPrintInstructionView( printItem: StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [.init(id: 0, isActive: true, value: "")] ), onChildTap: { _ in } @@ -51,6 +53,7 @@ struct StepQuizCodeBlanksPrintInstructionView: View { StepQuizCodeBlanksPrintInstructionView( printItem: StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [.init(id: 0, isActive: true, value: "There is a cat on the keyboard, it is true")] ), onChildTap: { _ in } @@ -58,6 +61,7 @@ struct StepQuizCodeBlanksPrintInstructionView: View { StepQuizCodeBlanksPrintInstructionView( printItem: StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [.init(id: 0, isActive: false, value: "There is a cat on the keyboard, it is true")] ), onChildTap: { _ in } @@ -66,6 +70,7 @@ struct StepQuizCodeBlanksPrintInstructionView: View { StepQuizCodeBlanksPrintInstructionView( printItem: StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [ .init(id: 0, isActive: false, value: "x"), .init(id: 1, isActive: true, value: "") diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift index 864907bcf..71c17ae2e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift @@ -17,39 +17,12 @@ struct StepQuizCodeBlanksCodeBlocksView: View { var body: some View { VStack(alignment: .leading, spacing: LayoutInsets.smallInset) { ForEach(state.codeBlocks, id: \.id_) { codeBlock in - switch StepQuizCodeBlanksViewStateCodeBlockItemKs(codeBlock) { - case .blank(let blankItem): - StepQuizCodeBlanksCodeBlockChildBlankView( - style: .large, - isActive: blankItem.isActive - ) - .padding(.horizontal) - .onTapGesture { - onCodeBlockTap(codeBlock) - } - case .print(let printItem): - StepQuizCodeBlanksPrintInstructionView( - printItem: printItem, - onChildTap: { codeBlockChild in - onCodeBlockChildTap(codeBlock, codeBlockChild) - } - ) - .onTapGesture { - onCodeBlockTap(codeBlock) - } - case .variable(let variableItem): - StepQuizCodeBlanksVariableInstructionView( - variableItem: variableItem, - onChildTap: { codeBlockChild in - onCodeBlockChildTap(codeBlock, codeBlockChild) - } - ) - .onTapGesture { - onCodeBlockTap(codeBlock) - } - case .ifStatement(let ifStatementItem): - StepQuizCodeBlanksIfStatementView( - ifStatementItem: ifStatementItem, + HStack(spacing: 0) { + Spacer() + .frame(width: LayoutInsets.defaultInset * CGFloat(codeBlock.indentLevel)) + + buildCodeBlockView( + codeBlock: codeBlock, onChildTap: { codeBlockChild in onCodeBlockChildTap(codeBlock, codeBlockChild) } @@ -74,6 +47,36 @@ struct StepQuizCodeBlanksCodeBlocksView: View { .frame(maxWidth: .infinity, alignment: .leading) .background(BackgroundView()) } + + @ViewBuilder + private func buildCodeBlockView( + codeBlock: StepQuizCodeBlanksViewStateCodeBlockItem, + onChildTap: @escaping (StepQuizCodeBlanksViewStateCodeBlockChildItem) -> Void + ) -> some View { + switch StepQuizCodeBlanksViewStateCodeBlockItemKs(codeBlock) { + case .blank(let blankItem): + StepQuizCodeBlanksCodeBlockChildBlankView( + style: .large, + isActive: blankItem.isActive + ) + .padding(.horizontal) + case .print(let printItem): + StepQuizCodeBlanksPrintInstructionView( + printItem: printItem, + onChildTap: onChildTap + ) + case .variable(let variableItem): + StepQuizCodeBlanksVariableInstructionView( + variableItem: variableItem, + onChildTap: onChildTap + ) + case .ifStatement(let ifStatementItem): + StepQuizCodeBlanksIfStatementView( + ifStatementItem: ifStatementItem, + onChildTap: onChildTap + ) + } + } } extension StepQuizCodeBlanksCodeBlocksView: Equatable { diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Variable/StepQuizCodeBlanksVariableInstructionView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Variable/StepQuizCodeBlanksVariableInstructionView.swift index c595c99db..7d1217b7f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Variable/StepQuizCodeBlanksVariableInstructionView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Variable/StepQuizCodeBlanksVariableInstructionView.swift @@ -38,6 +38,7 @@ struct StepQuizCodeBlanksVariableInstructionView: View { StepQuizCodeBlanksVariableInstructionView( variableItem: StepQuizCodeBlanksViewStateCodeBlockItemVariable( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil), StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: false, value: nil) @@ -49,6 +50,7 @@ struct StepQuizCodeBlanksVariableInstructionView: View { StepQuizCodeBlanksVariableInstructionView( variableItem: StepQuizCodeBlanksViewStateCodeBlockItemVariable( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "fruit_a"), StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: true, value: nil) @@ -60,6 +62,7 @@ struct StepQuizCodeBlanksVariableInstructionView: View { StepQuizCodeBlanksVariableInstructionView( variableItem: StepQuizCodeBlanksViewStateCodeBlockItemVariable( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "fruit_a"), StepQuizCodeBlanksViewStateCodeBlockChildItem( diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift index 7d127807c..78dc6bec7 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift @@ -70,7 +70,9 @@ extension StepQuizCodeBlanksView: Equatable { StepQuizCodeBlanksView( viewStateKs: .content( StepQuizCodeBlanksViewStateContent( - codeBlocks: [StepQuizCodeBlanksViewStateCodeBlockItemBlank(id: 0, isActive: true)], + codeBlocks: [ + StepQuizCodeBlanksViewStateCodeBlockItemBlank(id: 0, indentLevel: 0, isActive: true) + ], suggestions: [Suggestion.Print()], isDeleteButtonEnabled: true, isSpaceButtonHidden: true, @@ -93,6 +95,7 @@ extension StepQuizCodeBlanksView: Equatable { codeBlocks: [ StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) ] @@ -123,6 +126,7 @@ extension StepQuizCodeBlanksView: Equatable { codeBlocks: [ StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 0, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem( id: 0, @@ -133,6 +137,7 @@ extension StepQuizCodeBlanksView: Equatable { ), StepQuizCodeBlanksViewStateCodeBlockItemPrint( id: 1, + indentLevel: 0, children: [ StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) ] diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index 907b0b655..137678d41 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -7,6 +7,8 @@ sealed class CodeBlock { internal abstract val isActive: Boolean + internal abstract val indentLevel: Int + internal abstract val suggestions: List internal abstract val children: List @@ -23,6 +25,7 @@ sealed class CodeBlock { internal data class Blank( override val isActive: Boolean, + override val indentLevel: Int, override val suggestions: List ) : CodeBlock() { override val children: List = emptyList() @@ -34,6 +37,7 @@ sealed class CodeBlock { } internal data class Print( + override val indentLevel: Int, override val children: List ) : CodeBlock() { override val isActive: Boolean = false @@ -55,6 +59,7 @@ sealed class CodeBlock { } internal data class Variable( + override val indentLevel: Int, override val children: List ) : CodeBlock() { val name: CodeBlockChild.SelectSuggestion? @@ -82,6 +87,7 @@ sealed class CodeBlock { } internal data class IfStatement( + override val indentLevel: Int, override val children: List ) : CodeBlock() { override val isActive: Boolean = false diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 31de73385..5a6fe1b0f 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -81,6 +81,7 @@ class StepQuizCodeBlanksReducer( is CodeBlock.Blank -> when (message.suggestion) { Suggestion.Print -> CodeBlock.Print( + indentLevel = activeCodeBlock.indentLevel, children = listOf( CodeBlockChild.SelectSuggestion( isActive = true, @@ -92,6 +93,7 @@ class StepQuizCodeBlanksReducer( ) Suggestion.Variable -> CodeBlock.Variable( + indentLevel = activeCodeBlock.indentLevel, children = listOf( CodeBlockChild.SelectSuggestion( isActive = true, @@ -107,6 +109,7 @@ class StepQuizCodeBlanksReducer( ) Suggestion.IfStatement -> CodeBlock.IfStatement( + indentLevel = activeCodeBlock.indentLevel, children = listOf( CodeBlockChild.SelectSuggestion( isActive = true, @@ -247,7 +250,8 @@ class StepQuizCodeBlanksReducer( val newChildren = when (targetCodeBlock) { is CodeBlock.Print, - is CodeBlock.Variable -> { + is CodeBlock.Variable, + is CodeBlock.IfStatement -> { targetCodeBlock.children.mapIndexed { index, child -> require(child is CodeBlockChild.SelectSuggestion) if (index == message.codeBlockChildItem.id) { @@ -272,6 +276,7 @@ class StepQuizCodeBlanksReducer( when (targetCodeBlock) { is CodeBlock.Print -> targetCodeBlock.copy(children = newChildren) is CodeBlock.Variable -> targetCodeBlock.copy(children = newChildren) + is CodeBlock.IfStatement -> targetCodeBlock.copy(children = newChildren) else -> targetCodeBlock } ) @@ -303,6 +308,7 @@ class StepQuizCodeBlanksReducer( if (activeCodeBlockIndex == null) { return state to actions } + val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex] val newCodeBlocks = state.codeBlocks.mutate { val removeActiveCodeBlockAndSetNextActive = { @@ -322,12 +328,13 @@ class StepQuizCodeBlanksReducer( activeCodeBlockIndex, createBlankCodeBlock( isActive = true, + indentLevel = activeCodeBlock.indentLevel, isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable ) ) } - when (val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex]) { + when (activeCodeBlock) { is CodeBlock.Blank -> { if (state.codeBlocks.size > 1) { removeActiveCodeBlockAndSetNextActive() @@ -412,7 +419,39 @@ class StepQuizCodeBlanksReducer( } } } - is CodeBlock.IfStatement -> TODO() + is CodeBlock.IfStatement -> { + val activeChildIndex = activeCodeBlock.activeChildIndex() ?: return@mutate + val activeChild = activeCodeBlock.children[activeChildIndex] + + when { + activeChild.selectedSuggestion != null -> + set( + activeCodeBlockIndex, + activeCodeBlock.copy( + children = activeCodeBlock.children.mutate { + set( + activeChildIndex, + activeChild.copy(selectedSuggestion = null) + ) + } + ) + ) + + activeChildIndex > 0 -> + set( + activeCodeBlockIndex, + activeCodeBlock.copy( + children = activeCodeBlock.children.mutate { + set( + activeChildIndex - 1, + this[activeChildIndex - 1].copy(isActive = true) + ) + removeAt(activeChildIndex) + } + ) + ) + } + } } } @@ -438,6 +477,12 @@ class StepQuizCodeBlanksReducer( ) return if (activeCodeBlockIndex != null) { + val indentLevel = + when (val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex]) { + is CodeBlock.IfStatement -> activeCodeBlock.indentLevel + 1 + else -> activeCodeBlock.indentLevel + } + val newCodeBlocks = state.codeBlocks.mutate { set( activeCodeBlockIndex, @@ -447,6 +492,7 @@ class StepQuizCodeBlanksReducer( activeCodeBlockIndex + 1, createBlankCodeBlock( isActive = true, + indentLevel = indentLevel, isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable ) ) @@ -482,7 +528,8 @@ class StepQuizCodeBlanksReducer( val newChildren = when (activeCodeBlock) { is CodeBlock.Print, - is CodeBlock.Variable -> { + is CodeBlock.Variable, + is CodeBlock.IfStatement -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> val activeChild = activeCodeBlock.children[activeChildIndex] as CodeBlockChild.SelectSuggestion @@ -527,6 +574,7 @@ class StepQuizCodeBlanksReducer( when (activeCodeBlock) { is CodeBlock.Print -> activeCodeBlock.copy(children = newChildren) is CodeBlock.Variable -> activeCodeBlock.copy(children = newChildren) + is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) else -> activeCodeBlock } ) @@ -578,10 +626,12 @@ class StepQuizCodeBlanksReducer( private fun createBlankCodeBlock( isActive: Boolean, + indentLevel: Int, isVariableSuggestionAvailable: Boolean ): CodeBlock.Blank = CodeBlock.Blank( isActive = isActive, + indentLevel = indentLevel, suggestions = if (isVariableSuggestionAvailable) { listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement) } else { @@ -593,6 +643,7 @@ class StepQuizCodeBlanksReducer( if (step.id == 47580L) { listOf( CodeBlock.Variable( + indentLevel = 0, children = listOf( CodeBlockChild.SelectSuggestion( isActive = false, @@ -607,6 +658,7 @@ class StepQuizCodeBlanksReducer( ) ), CodeBlock.Variable( + indentLevel = 0, children = listOf( CodeBlockChild.SelectSuggestion( isActive = false, @@ -621,6 +673,7 @@ class StepQuizCodeBlanksReducer( ) ), CodeBlock.Variable( + indentLevel = 0, children = listOf( CodeBlockChild.SelectSuggestion( isActive = false, @@ -636,6 +689,7 @@ class StepQuizCodeBlanksReducer( ), createBlankCodeBlock( isActive = true, + indentLevel = 0, isVariableSuggestionAvailable = StepQuizCodeBlanksFeature.isVariableSuggestionsAvailable(step) ) ) @@ -643,6 +697,7 @@ class StepQuizCodeBlanksReducer( listOf( createBlankCodeBlock( isActive = true, + indentLevel = 0, isVariableSuggestionAvailable = StepQuizCodeBlanksFeature.isVariableSuggestionsAvailable(step) ) ) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index 9416672f2..0de8dc380 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -93,20 +93,27 @@ object StepQuizCodeBlanksViewStateMapper { ): StepQuizCodeBlanksViewState.CodeBlockItem = when (codeBlock) { is CodeBlock.Blank -> - StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = index, isActive = codeBlock.isActive) + StepQuizCodeBlanksViewState.CodeBlockItem.Blank( + id = index, + indentLevel = codeBlock.indentLevel, + isActive = codeBlock.isActive + ) is CodeBlock.Print -> StepQuizCodeBlanksViewState.CodeBlockItem.Print( id = index, + indentLevel = codeBlock.indentLevel, children = codeBlock.children.mapIndexed(::mapCodeBlockChild) ) is CodeBlock.Variable -> StepQuizCodeBlanksViewState.CodeBlockItem.Variable( id = index, + indentLevel = codeBlock.indentLevel, children = codeBlock.children.mapIndexed(::mapCodeBlockChild) ) is CodeBlock.IfStatement -> StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( id = index, + indentLevel = codeBlock.indentLevel, children = codeBlock.children.mapIndexed(::mapCodeBlockChild) ) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt index 7698f8684..e1ceff687 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt @@ -23,10 +23,13 @@ sealed interface StepQuizCodeBlanksViewState { sealed interface CodeBlockItem { val id: Int + val indentLevel: Int + val children: List data class Blank( override val id: Int, + override val indentLevel: Int, val isActive: Boolean ) : CodeBlockItem { override val children: List = emptyList() @@ -34,11 +37,13 @@ sealed interface StepQuizCodeBlanksViewState { data class Print( override val id: Int, + override val indentLevel: Int, override val children: List ) : CodeBlockItem data class Variable( override val id: Int, + override val indentLevel: Int, override val children: List ) : CodeBlockItem { val name: CodeBlockChildItem? @@ -50,6 +55,7 @@ sealed interface StepQuizCodeBlanksViewState { data class IfStatement( override val id: Int, + override val indentLevel: Int, override val children: List ) : CodeBlockItem } From 5e3bc14e361c8829ff8e87c84b0bfe0a3097f8f9 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 5 Sep 2024 13:25:44 +0900 Subject: [PATCH 07/28] Add decrease indent level button --- .../StepQuizCodeBlanksOutputProtocol.swift | 1 + .../StepQuizCodeBlanksViewModel.swift | 6 ++++++ .../StepQuizCodeBlanksActionButton.swift | 15 +++++++++++++++ .../StepQuizCodeBlanksActionButtonsView.swift | 15 +++++++++++++-- .../StepQuizCodeBlanksCodeBlocksView.swift | 5 ++++- .../Views/StepQuizCodeBlanksView.swift | 6 +++++- .../mapper/StepQuizCodeBlanksViewStateMapper.kt | 13 ++++++++++++- .../view/model/StepQuizCodeBlanksViewState.kt | 1 + 8 files changed, 57 insertions(+), 5 deletions(-) diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksOutputProtocol.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksOutputProtocol.swift index 12cd5561a..363b034df 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksOutputProtocol.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksOutputProtocol.swift @@ -11,4 +11,5 @@ protocol StepQuizCodeBlanksOutputProtocol: AnyObject { func handleStepQuizCodeBlanksDidTapDelete() func handleStepQuizCodeBlanksDidTapEnter() func handleStepQuizCodeBlanksDidTapSpace() + func handleStepQuizCodeBlanksDidTapDecreaseIndentLevel() } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksViewModel.swift index 860a13547..379445416 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/StepQuizCodeBlanksViewModel.swift @@ -48,4 +48,10 @@ final class StepQuizCodeBlanksViewModel { impactFeedbackGenerator.triggerFeedback() moduleOutput?.handleStepQuizCodeBlanksDidTapSpace() } + + @MainActor + func doDecreaseIndentLevelAction() { + impactFeedbackGenerator.triggerFeedback() + moduleOutput?.handleStepQuizCodeBlanksDidTapDecreaseIndentLevel() + } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift index 524220ab5..00744ba2e 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButton.swift @@ -62,6 +62,19 @@ extension StepQuizCodeBlanksActionButton { action: action ) } + + static func decreaseIndentLevel(action: @escaping () -> Void) -> StepQuizCodeBlanksActionButton { + StepQuizCodeBlanksActionButton( + appearance: .init( + padding: LayoutInsets( + horizontal: LayoutInsets.smallInset, + vertical: 5.5 + ) + ), + imageSystemName: "arrow.left.to.line", + action: action + ) + } } #if DEBUG @@ -71,12 +84,14 @@ extension StepQuizCodeBlanksActionButton { StepQuizCodeBlanksActionButton.delete(action: {}) StepQuizCodeBlanksActionButton.enter(action: {}) StepQuizCodeBlanksActionButton.space(action: {}) + StepQuizCodeBlanksActionButton.decreaseIndentLevel(action: {}) } HStack { StepQuizCodeBlanksActionButton.delete(action: {}) StepQuizCodeBlanksActionButton.enter(action: {}) StepQuizCodeBlanksActionButton.space(action: {}) + StepQuizCodeBlanksActionButton.decreaseIndentLevel(action: {}) } .disabled(true) } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift index 36137dbad..f6a041701 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/ActionButtons/StepQuizCodeBlanksActionButtonsView.swift @@ -3,15 +3,22 @@ import SwiftUI struct StepQuizCodeBlanksActionButtonsView: View { let isDeleteButtonEnabled: Bool let isSpaceButtonHidden: Bool + let isDecreaseIndentLevelButtonHidden: Bool let onSpaceTap: () -> Void let onDeleteTap: () -> Void let onEnterTap: () -> Void + let onDecreaseIndentLevelTap: () -> Void var body: some View { HStack(spacing: LayoutInsets.defaultInset) { Spacer() + if !isDecreaseIndentLevelButtonHidden { + StepQuizCodeBlanksActionButton + .decreaseIndentLevel(action: onDecreaseIndentLevelTap) + } + if !isSpaceButtonHidden { StepQuizCodeBlanksActionButton .space(action: onSpaceTap) @@ -34,17 +41,21 @@ struct StepQuizCodeBlanksActionButtonsView: View { StepQuizCodeBlanksActionButtonsView( isDeleteButtonEnabled: false, isSpaceButtonHidden: false, + isDecreaseIndentLevelButtonHidden: false, onSpaceTap: {}, onDeleteTap: {}, - onEnterTap: {} + onEnterTap: {}, + onDecreaseIndentLevelTap: {} ) StepQuizCodeBlanksActionButtonsView( isDeleteButtonEnabled: true, isSpaceButtonHidden: true, + isDecreaseIndentLevelButtonHidden: true, onSpaceTap: {}, onDeleteTap: {}, - onEnterTap: {} + onEnterTap: {}, + onDecreaseIndentLevelTap: {} ) } .padding() diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift index 71c17ae2e..79f8d394f 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift @@ -13,6 +13,7 @@ struct StepQuizCodeBlanksCodeBlocksView: View { let onSpaceTap: () -> Void let onDeleteTap: () -> Void let onEnterTap: () -> Void + let onDecreaseIndentLevelTap: () -> Void var body: some View { VStack(alignment: .leading, spacing: LayoutInsets.smallInset) { @@ -37,9 +38,11 @@ struct StepQuizCodeBlanksCodeBlocksView: View { StepQuizCodeBlanksActionButtonsView( isDeleteButtonEnabled: state.isDeleteButtonEnabled, isSpaceButtonHidden: state.isSpaceButtonHidden, + isDecreaseIndentLevelButtonHidden: state.isDecreaseIndentLevelButtonHidden, onSpaceTap: onSpaceTap, onDeleteTap: onDeleteTap, - onEnterTap: onEnterTap + onEnterTap: onEnterTap, + onDecreaseIndentLevelTap: onDecreaseIndentLevelTap ) } } diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift index 78dc6bec7..804758741 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/StepQuizCodeBlanksView.swift @@ -29,7 +29,8 @@ struct StepQuizCodeBlanksView: View { onCodeBlockChildTap: viewModel.doCodeBlockChildMainAction(codeBlock:codeBlockChild:), onSpaceTap: viewModel.doSpaceAction, onDeleteTap: viewModel.doDeleteAction, - onEnterTap: viewModel.doEnterAction + onEnterTap: viewModel.doEnterAction, + onDecreaseIndentLevelTap: viewModel.doDecreaseIndentLevelAction ) .equatable() Divider() @@ -76,6 +77,7 @@ extension StepQuizCodeBlanksView: Equatable { suggestions: [Suggestion.Print()], isDeleteButtonEnabled: true, isSpaceButtonHidden: true, + isDecreaseIndentLevelButtonHidden: true, onboardingState: StepQuizCodeBlanksFeatureOnboardingStateUnavailable() ) ), @@ -107,6 +109,7 @@ extension StepQuizCodeBlanksView: Equatable { ], isDeleteButtonEnabled: false, isSpaceButtonHidden: true, + isDecreaseIndentLevelButtonHidden: true, onboardingState: StepQuizCodeBlanksFeatureOnboardingStateUnavailable() ) ), @@ -149,6 +152,7 @@ extension StepQuizCodeBlanksView: Equatable { ], isDeleteButtonEnabled: false, isSpaceButtonHidden: true, + isDecreaseIndentLevelButtonHidden: true, onboardingState: StepQuizCodeBlanksFeatureOnboardingStateUnavailable() ) ), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index 0de8dc380..9f6747fd4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -17,7 +17,9 @@ object StepQuizCodeBlanksViewStateMapper { state: StepQuizCodeBlanksFeature.State.Content ): StepQuizCodeBlanksViewState.Content { val codeBlocks = state.codeBlocks.mapIndexed(::mapCodeBlock) - val activeCodeBlock = state.activeCodeBlockIndex()?.let { state.codeBlocks[it] } + + val activeCodeBlockIndex = state.activeCodeBlockIndex() + val activeCodeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } val suggestions = when (activeCodeBlock) { @@ -78,11 +80,20 @@ object StepQuizCodeBlanksViewStateMapper { true } + val isDecreaseIndentLevelButtonHidden = + when { + activeCodeBlock == null -> true + activeCodeBlock.indentLevel < 1 -> true + state.codeBlocks.getOrNull(activeCodeBlockIndex - 1) is CodeBlock.IfStatement -> true + else -> false + } + return StepQuizCodeBlanksViewState.Content( codeBlocks = codeBlocks, suggestions = suggestions, isDeleteButtonEnabled = isDeleteButtonEnabled, isSpaceButtonHidden = isSpaceButtonHidden, + isDecreaseIndentLevelButtonHidden = isDecreaseIndentLevelButtonHidden, onboardingState = state.onboardingState ) } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt index e1ceff687..41750f675 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt @@ -11,6 +11,7 @@ sealed interface StepQuizCodeBlanksViewState { val suggestions: List, val isDeleteButtonEnabled: Boolean, val isSpaceButtonHidden: Boolean, + val isDecreaseIndentLevelButtonHidden: Boolean, internal val onboardingState: OnboardingState = OnboardingState.Unavailable ) : StepQuizCodeBlanksViewState { val isActionButtonsHidden: Boolean From 149e1a7773481280ce9438a3acc1231efac53b3f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 5 Sep 2024 14:40:20 +0900 Subject: [PATCH 08/28] Handle decrease indent level button clicked --- .../Modules/StepQuiz/StepQuizViewModel.swift | 8 +++ .../hyperskill/HyperskillAnalyticTarget.kt | 1 + ...reaseIndentLevelHyperskillAnalyticEvent.kt | 41 ++++++++++++ .../presentation/StepQuizCodeBlanksFeature.kt | 1 + .../presentation/StepQuizCodeBlanksReducer.kt | 66 +++++++++++++++---- 5 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/analytic/StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent.kt diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift index 908b44506..a862a41c2 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuiz/StepQuizViewModel.swift @@ -285,6 +285,14 @@ extension StepQuizViewModel: StepQuizCodeBlanksOutputProtocol { ) ) } + + func handleStepQuizCodeBlanksDidTapDecreaseIndentLevel() { + onNewMessage( + StepQuizFeatureMessageStepQuizCodeBlanksMessage( + message: StepQuizCodeBlanksFeatureMessageDecreaseIndentLevelButtonClicked() + ) + ) + } } // MARK: - StepQuizViewModel: StepQuizProblemOnboardingModalViewControllerDelegate - diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt index da52ad1af..4da644af9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticTarget.kt @@ -21,6 +21,7 @@ enum class HyperskillAnalyticTarget(val targetName: String) { DELETE("delete"), ENTER("enter"), SPACE("space"), + DECREASE_INDENT_LEVEL("decrease_indent_level"), DONE("done"), YES("yes"), NO("no"), diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/analytic/StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/analytic/StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent.kt new file mode 100644 index 000000000..9875f2f6c --- /dev/null +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/analytic/StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent.kt @@ -0,0 +1,41 @@ +package org.hyperskill.app.step_quiz_code_blanks.domain.analytic + +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute +import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import ru.nobird.app.core.model.mapOfNotNull + +/** + * Represents click on the "Decrease indent level" button in the code block analytic event. + * + * JSON payload: + * ``` + * { + * "route": "/learn/step/1", + * "action": "click", + * "part": "code_blanks", + * "target": "decrease_indent_level", + * "context": + * { + * "code_block": "Print(isActive=true, suggestions=[ConstantString(text=suggestion)], selectedSuggestion=null)" + * } + * } + * ``` + * + * @see HyperskillAnalyticEvent + */ +class StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent( + route: HyperskillAnalyticRoute, + codeBlock: CodeBlock? +) : HyperskillAnalyticEvent( + route = route, + action = HyperskillAnalyticAction.CLICK, + part = HyperskillAnalyticPart.CODE_BLANKS, + target = HyperskillAnalyticTarget.DECREASE_INDENT_LEVEL, + context = mapOfNotNull( + StepQuizCodeBlanksAnalyticParams.PARAM_CODE_BLOCK to codeBlock?.analyticRepresentation + ) +) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt index 0e1c8f402..0fd51a880 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt @@ -58,6 +58,7 @@ object StepQuizCodeBlanksFeature { data object DeleteButtonClicked : Message data object EnterButtonClicked : Message data object SpaceButtonClicked : Message + data object DecreaseIndentLevelButtonClicked : Message } internal sealed interface InternalMessage : Message { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 5a6fe1b0f..f5d731a62 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -5,6 +5,7 @@ import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent @@ -36,6 +37,7 @@ class StepQuizCodeBlanksReducer( Message.DeleteButtonClicked -> handleDeleteButtonClicked(state) Message.EnterButtonClicked -> handleEnterButtonClicked(state) Message.SpaceButtonClicked -> handleSpaceButtonClicked(state) + Message.DecreaseIndentLevelButtonClicked -> handleDecreaseIndentLevelButtonClicked(state) } ?: (state to emptySet()) private fun initialize( @@ -60,21 +62,21 @@ class StepQuizCodeBlanksReducer( } val activeCodeBlockIndex = state.activeCodeBlockIndex() + val activeCodeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } val actions = setOf( InternalAction.LogAnalyticEvent( StepQuizCodeBlanksClickedSuggestionHyperskillAnalyticEvent( route = stepRoute.analyticRoute, - codeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] }, + codeBlock = activeCodeBlock, suggestion = message.suggestion ) ) ) - if (activeCodeBlockIndex == null) { + if (activeCodeBlock == null) { return state to actions } - val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex] val newCodeBlock = when (activeCodeBlock) { @@ -295,20 +297,20 @@ class StepQuizCodeBlanksReducer( } val activeCodeBlockIndex = state.activeCodeBlockIndex() + val activeCodeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } val actions = setOf( InternalAction.LogAnalyticEvent( StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent( route = stepRoute.analyticRoute, - codeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } + codeBlock = activeCodeBlock ) ) ) - if (activeCodeBlockIndex == null) { + if (activeCodeBlock == null) { return state to actions } - val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex] val newCodeBlocks = state.codeBlocks.mutate { val removeActiveCodeBlockAndSetNextActive = { @@ -466,19 +468,20 @@ class StepQuizCodeBlanksReducer( } val activeCodeBlockIndex = state.activeCodeBlockIndex() + val activeCodeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } val actions = setOf( InternalAction.LogAnalyticEvent( StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent( route = stepRoute.analyticRoute, - codeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } + codeBlock = activeCodeBlock ) ) ) - return if (activeCodeBlockIndex != null) { + return if (activeCodeBlock != null) { val indentLevel = - when (val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex]) { + when (activeCodeBlock) { is CodeBlock.IfStatement -> activeCodeBlock.indentLevel + 1 else -> activeCodeBlock.indentLevel } @@ -511,20 +514,20 @@ class StepQuizCodeBlanksReducer( } val activeCodeBlockIndex = state.activeCodeBlockIndex() + val activeCodeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } val actions = setOf( InternalAction.LogAnalyticEvent( StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent( route = stepRoute.analyticRoute, - codeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } + codeBlock = activeCodeBlock ) ) ) - if (activeCodeBlockIndex == null) { + if (activeCodeBlock == null) { return state to actions } - val activeCodeBlock = state.codeBlocks[activeCodeBlockIndex] val newChildren = when (activeCodeBlock) { is CodeBlock.Print, @@ -584,6 +587,45 @@ class StepQuizCodeBlanksReducer( return state.copy(codeBlocks = newCodeBlocks) to actions } + private fun handleDecreaseIndentLevelButtonClicked( + state: State + ): StepQuizCodeBlanksReducerResult? { + if (state !is State.Content) { + return null + } + + val activeCodeBlockIndex = state.activeCodeBlockIndex() + val activeCodeBlock = activeCodeBlockIndex?.let { state.codeBlocks[it] } + + val actions = setOf( + InternalAction.LogAnalyticEvent( + StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent( + route = stepRoute.analyticRoute, + codeBlock = activeCodeBlock + ) + ) + ) + + if (activeCodeBlock == null || activeCodeBlock.indentLevel < 1) { + return state to actions + } + val newIndentLevel = activeCodeBlock.indentLevel - 1 + + return state.copy( + codeBlocks = state.codeBlocks.mutate { + set( + activeCodeBlockIndex, + when (activeCodeBlock) { + is CodeBlock.Blank -> activeCodeBlock.copy(indentLevel = newIndentLevel) + is CodeBlock.Print -> activeCodeBlock.copy(indentLevel = newIndentLevel) + is CodeBlock.Variable -> activeCodeBlock.copy(indentLevel = newIndentLevel) + is CodeBlock.IfStatement -> activeCodeBlock.copy(indentLevel = newIndentLevel) + } + ) + } + ) to actions + } + private fun setCodeBlockIsActive(codeBlock: CodeBlock, isActive: Boolean): CodeBlock = when (codeBlock) { is CodeBlock.Blank -> codeBlock.copy(isActive = isActive) From 551a81839a3c8621a9f969fa63528e2b9cd4f828 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 5 Sep 2024 15:24:40 +0900 Subject: [PATCH 09/28] Update delete logic --- .../step_quiz_code_blanks/domain/model/CodeBlock.kt | 6 ++++++ .../presentation/StepQuizCodeBlanksReducer.kt | 12 +++++++++++- .../view/mapper/StepQuizCodeBlanksViewStateMapper.kt | 9 ++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index 137678d41..fda22b583 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -23,6 +23,12 @@ sealed class CodeBlock { internal fun activeChildIndex(): Int? = children.indexOfFirstOrNull { it.isActive } + internal fun areAllChildrenUnselected(): Boolean = + children.all { it is CodeBlockChild.SelectSuggestion && it.selectedSuggestion == null } + + internal fun hasAnySelectedChild(): Boolean = + children.any { it is CodeBlockChild.SelectSuggestion && it.selectedSuggestion != null } + internal data class Blank( override val isActive: Boolean, override val indentLevel: Int, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index f5d731a62..1b64a390d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -413,7 +413,7 @@ class StepQuizCodeBlanksReducer( ) ) - activeChildIndex == 0 || activeCodeBlock.children.all { it.selectedSuggestion == null } -> + activeChildIndex == 0 || activeCodeBlock.areAllChildrenUnselected() -> if (state.codeBlocks.size > 1) { removeActiveCodeBlockAndSetNextActive() } else { @@ -425,6 +425,8 @@ class StepQuizCodeBlanksReducer( val activeChildIndex = activeCodeBlock.activeChildIndex() ?: return@mutate val activeChild = activeCodeBlock.children[activeChildIndex] + val nextCodeBlock = state.codeBlocks.getOrNull(activeCodeBlockIndex + 1) + when { activeChild.selectedSuggestion != null -> set( @@ -452,6 +454,14 @@ class StepQuizCodeBlanksReducer( } ) ) + + (activeChildIndex == 0 || activeCodeBlock.areAllChildrenUnselected()) && + (nextCodeBlock?.let { it.indentLevel == activeCodeBlock.indentLevel } ?: true) -> + if (state.codeBlocks.size > 1) { + removeActiveCodeBlockAndSetNextActive() + } else { + replaceActiveCodeWithBlank() + } } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index 9f6747fd4..c6c36006a 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -40,8 +40,7 @@ object StepQuizCodeBlanksViewStateMapper { val isDeleteButtonEnabled = when (activeCodeBlock) { is CodeBlock.Blank -> codeBlocks.size > 1 - is CodeBlock.Print, - is CodeBlock.IfStatement -> true + is CodeBlock.Print -> true is CodeBlock.Variable -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> when { @@ -49,13 +48,17 @@ object StepQuizCodeBlanksViewStateMapper { true activeCodeBlock.children[activeChildIndex].selectedSuggestion == null && - activeCodeBlock.children.any { it.selectedSuggestion != null } -> + activeCodeBlock.hasAnySelectedChild() -> false else -> true } } ?: false } + is CodeBlock.IfStatement -> + codeBlocks.getOrNull(activeCodeBlockIndex + 1)?.let { + it.indentLevel == activeCodeBlock.indentLevel + } ?: true null -> false } From 1b1b22347d255c04179e57678be622673ecdb6b3 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 5 Sep 2024 15:34:52 +0900 Subject: [PATCH 10/28] Fix tests --- config/detekt/baseline.xml | 3 ++ .../domain/model/CodeBlock.kt | 8 ++--- .../view/model/StepQuizCodeBlanksViewState.kt | 8 ++--- .../StepQuizCodeBlanksViewStateMapperTest.kt | 36 ++++++++++++------- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 86ff7e88f..247e82132 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -55,6 +55,7 @@ ImplicitDefaultLocale:TimeIntervalUtil.kt$TimeIntervalUtil$String.format("%02d:00 \u2014 %02d:00", i, i + 1) InvalidPackageDeclaration:HandleActions.kt$package org.hyperskill.app.core.view LambdaParameterInRestartableEffect:OnComposableShownFirstTime.kt$block + LargeClass:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer : StateReducer LargeClass:StepQuizReducer.kt$StepQuizReducer : StateReducer LongMethod:AppReducer.kt$AppReducer$private fun handleFetchAppStartupConfigSuccess( state: State, message: Message.FetchAppStartupConfigSuccess ): ReducerResult LongMethod:ChallengeCard.kt$@Composable fun ChallengeCard( viewState: ChallengeWidgetViewState, onNewMessage: (Message) -> Unit ) @@ -67,6 +68,7 @@ LongMethod:ProblemOfDayCardFormDelegate.kt$ProblemOfDayCardFormDelegate$fun render( dateFormatter: SharedDateFormatter, binding: LayoutProblemOfTheDayCardBinding, state: HomeFeature.ProblemOfDayState, areProblemsLimited: Boolean ) LongMethod:ProfileBadges.kt$@Composable fun ProfileBadges( viewState: BadgesViewState, windowWidthSizeClass: WindowWidthSizeClass, onBadgeClick: (BadgeKind) -> Unit, onExpandButtonClick: (ProfileFeature.Message.BadgesVisibilityButton) -> Unit, modifier: Modifier = Modifier ) LongMethod:ProfileSettingsDialogFragment.kt$ProfileSettingsDialogFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) + LongMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun createInitialCodeBlocks(step: Step): List<CodeBlock> LongMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleDeleteButtonClicked( state: State ): StepQuizCodeBlanksReducerResult? LongMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleSpaceButtonClicked( state: State ): StepQuizCodeBlanksReducerResult? LongMethod:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleSuggestionClicked( state: State, message: Message.SuggestionClicked ): StepQuizCodeBlanksReducerResult? @@ -248,6 +250,7 @@ ReturnCount:SearchReducer.kt$SearchReducer$private fun handleSearchResultsItemClickedMessage( state: State, message: Message.SearchResultsItemClicked ): SearchReducerResult? ReturnCount:SharedDateFormatter.kt$SharedDateFormatter$fun formatTimeDistance(millis: Long): String ReturnCount:StateExtentions.kt$internal fun ChallengeWidgetFeature.State.Content.setCurrentChallengeIntervalProgressAsCompleted(): Challenge? + ReturnCount:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleDecreaseIndentLevelButtonClicked( state: State ): StepQuizCodeBlanksReducerResult? ReturnCount:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleDeleteButtonClicked( state: State ): StepQuizCodeBlanksReducerResult? ReturnCount:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleSpaceButtonClicked( state: State ): StepQuizCodeBlanksReducerResult? ReturnCount:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun handleSuggestionClicked( state: State, message: Message.SuggestionClicked ): StepQuizCodeBlanksReducerResult? diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index fda22b583..c0c459f50 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -31,7 +31,7 @@ sealed class CodeBlock { internal data class Blank( override val isActive: Boolean, - override val indentLevel: Int, + override val indentLevel: Int = 0, override val suggestions: List ) : CodeBlock() { override val children: List = emptyList() @@ -43,7 +43,7 @@ sealed class CodeBlock { } internal data class Print( - override val indentLevel: Int, + override val indentLevel: Int = 0, override val children: List ) : CodeBlock() { override val isActive: Boolean = false @@ -65,7 +65,7 @@ sealed class CodeBlock { } internal data class Variable( - override val indentLevel: Int, + override val indentLevel: Int = 0, override val children: List ) : CodeBlock() { val name: CodeBlockChild.SelectSuggestion? @@ -93,7 +93,7 @@ sealed class CodeBlock { } internal data class IfStatement( - override val indentLevel: Int, + override val indentLevel: Int = 0, override val children: List ) : CodeBlock() { override val isActive: Boolean = false diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt index 41750f675..0cddcf03b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt @@ -30,7 +30,7 @@ sealed interface StepQuizCodeBlanksViewState { data class Blank( override val id: Int, - override val indentLevel: Int, + override val indentLevel: Int = 0, val isActive: Boolean ) : CodeBlockItem { override val children: List = emptyList() @@ -38,13 +38,13 @@ sealed interface StepQuizCodeBlanksViewState { data class Print( override val id: Int, - override val indentLevel: Int, + override val indentLevel: Int = 0, override val children: List ) : CodeBlockItem data class Variable( override val id: Int, - override val indentLevel: Int, + override val indentLevel: Int = 0, override val children: List ) : CodeBlockItem { val name: CodeBlockChildItem? @@ -56,7 +56,7 @@ sealed interface StepQuizCodeBlanksViewState { data class IfStatement( override val id: Int, - override val indentLevel: Int, + override val indentLevel: Int = 0, override val children: List ) : CodeBlockItem } diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt index 045d04e9c..226547d07 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt @@ -31,7 +31,8 @@ class StepQuizCodeBlanksViewStateMapperTest { codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), suggestions = listOf(Suggestion.Print), isDeleteButtonEnabled = false, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -73,7 +74,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = suggestions, isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -117,7 +119,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = listOf(Suggestion.Print), isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -178,7 +181,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = printSuggestions, isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -230,7 +234,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = suggestions, isDeleteButtonEnabled = false, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -282,7 +287,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = suggestions, isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -334,7 +340,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = emptyList(), isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -352,7 +359,8 @@ class StepQuizCodeBlanksViewStateMapperTest { codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), suggestions = suggestions, isDeleteButtonEnabled = false, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -394,7 +402,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = suggestions, isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -436,7 +445,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = emptyList(), isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -488,7 +498,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = suggestions, isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) @@ -540,7 +551,8 @@ class StepQuizCodeBlanksViewStateMapperTest { ), suggestions = emptyList(), isDeleteButtonEnabled = true, - isSpaceButtonHidden = true + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) From f4eaa8142ff3145a9bedb10b3068dd98af264c92 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 5 Sep 2024 16:05:23 +0900 Subject: [PATCH 11/28] Fix isDeleteButtonEnabled for IfStatement --- config/detekt/baseline.xml | 1 + .../StepQuizCodeBlanksViewStateMapper.kt | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 247e82132..22e2b1941 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -206,6 +206,7 @@ ModifierWithoutDefault:BadgeImage.kt$modifier NestedBlockDepth:AuthSocialWebViewClient.kt$AuthSocialWebViewClient$override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? ): Boolean NestedBlockDepth:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$private fun setCodeBlockIsActive(codeBlock: CodeBlock, isActive: Boolean): CodeBlock + NestedBlockDepth:StepQuizCodeBlanksViewStateMapper.kt$StepQuizCodeBlanksViewStateMapper$private fun mapContentState( state: StepQuizCodeBlanksFeature.State.Content ): StepQuizCodeBlanksViewState.Content PreviewPublic:BadgeCard.kt$BadgeCardPreview PreviewPublic:BadgeCard.kt$LastLevelBadgeCardPreview PreviewPublic:BadgeGrid.kt$PhoneBadgeGridPreview diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index c6c36006a..899f8f17d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -55,10 +55,21 @@ object StepQuizCodeBlanksViewStateMapper { } } ?: false } - is CodeBlock.IfStatement -> - codeBlocks.getOrNull(activeCodeBlockIndex + 1)?.let { - it.indentLevel == activeCodeBlock.indentLevel - } ?: true + is CodeBlock.IfStatement -> { + activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> + when { + activeChildIndex > 0 -> + true + + activeCodeBlock.children[activeChildIndex].selectedSuggestion != null -> + true + + else -> + codeBlocks.getOrNull(activeCodeBlockIndex + 1) + ?.let { it.indentLevel == activeCodeBlock.indentLevel } ?: true + } + } ?: false + } null -> false } From a6465ae90577086314c2fd244648f3c9cc8e7473 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 6 Sep 2024 14:18:41 +0900 Subject: [PATCH 12/28] Add suggestions --- .../domain/model/Suggestion.kt | 14 +++ .../presentation/StepQuizCodeBlanksReducer.kt | 89 +++++++++++++++---- ...seStatementsSuggestionsAvailabilityTest.kt | 61 +++++++++++++ 3 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt index 0d95eaa5d..9ff56fa42 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/Suggestion.kt @@ -26,6 +26,20 @@ sealed class Suggestion { "IfStatement(text='$text')" } + data object ElifStatement : Suggestion() { + override val text: String = "elif" + + override val analyticRepresentation: String = + "ElifStatement(text='$text')" + } + + data object ElseStatement : Suggestion() { + override val text: String = "else" + + override val analyticRepresentation: String = + "ElseStatement(text='$text')" + } + data class ConstantString( override val text: String ) : Suggestion() { diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 1b64a390d..eef2d1a08 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -21,6 +21,7 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.State import ru.nobird.app.core.model.cast import ru.nobird.app.core.model.mutate +import ru.nobird.app.core.model.slice import ru.nobird.app.presentation.redux.reducer.StateReducer private typealias StepQuizCodeBlanksReducerResult = Pair> @@ -325,13 +326,18 @@ class StepQuizCodeBlanksReducer( } removeAt(activeCodeBlockIndex) } - val replaceActiveCodeWithBlank = { + val replaceActiveCodeBlockWithBlank = { set( activeCodeBlockIndex, createBlankCodeBlock( isActive = true, indentLevel = activeCodeBlock.indentLevel, - isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable + suggestions = getSuggestionsForBlankCodeBlock( + index = activeCodeBlockIndex, + indentLevel = activeCodeBlock.indentLevel, + codeBlocks = this, + isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable + ) ) ) } @@ -378,7 +384,7 @@ class StepQuizCodeBlanksReducer( removeActiveCodeBlockAndSetNextActive() else -> - replaceActiveCodeWithBlank() + replaceActiveCodeBlockWithBlank() } } is CodeBlock.Variable -> { @@ -417,7 +423,7 @@ class StepQuizCodeBlanksReducer( if (state.codeBlocks.size > 1) { removeActiveCodeBlockAndSetNextActive() } else { - replaceActiveCodeWithBlank() + replaceActiveCodeBlockWithBlank() } } } @@ -460,7 +466,7 @@ class StepQuizCodeBlanksReducer( if (state.codeBlocks.size > 1) { removeActiveCodeBlockAndSetNextActive() } else { - replaceActiveCodeWithBlank() + replaceActiveCodeBlockWithBlank() } } } @@ -501,15 +507,23 @@ class StepQuizCodeBlanksReducer( activeCodeBlockIndex, setCodeBlockIsActive(codeBlock = state.codeBlocks[activeCodeBlockIndex], isActive = false) ) + + val insertIndex = activeCodeBlockIndex + 1 add( - activeCodeBlockIndex + 1, + insertIndex, createBlankCodeBlock( isActive = true, indentLevel = indentLevel, - isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable + suggestions = getSuggestionsForBlankCodeBlock( + index = insertIndex, + indentLevel = indentLevel, + codeBlocks = this, + isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable + ) ) ) } + state.copy(codeBlocks = newCodeBlocks) to actions } else { state to actions @@ -626,7 +640,15 @@ class StepQuizCodeBlanksReducer( set( activeCodeBlockIndex, when (activeCodeBlock) { - is CodeBlock.Blank -> activeCodeBlock.copy(indentLevel = newIndentLevel) + is CodeBlock.Blank -> activeCodeBlock.copy( + indentLevel = newIndentLevel, + suggestions = getSuggestionsForBlankCodeBlock( + index = activeCodeBlockIndex, + indentLevel = newIndentLevel, + codeBlocks = this, + isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable + ) + ) is CodeBlock.Print -> activeCodeBlock.copy(indentLevel = newIndentLevel) is CodeBlock.Variable -> activeCodeBlock.copy(indentLevel = newIndentLevel) is CodeBlock.IfStatement -> activeCodeBlock.copy(indentLevel = newIndentLevel) @@ -679,17 +701,50 @@ class StepQuizCodeBlanksReducer( private fun createBlankCodeBlock( isActive: Boolean, indentLevel: Int, - isVariableSuggestionAvailable: Boolean + suggestions: List ): CodeBlock.Blank = CodeBlock.Blank( isActive = isActive, indentLevel = indentLevel, - suggestions = if (isVariableSuggestionAvailable) { + suggestions = suggestions + ) + + private fun getSuggestionsForBlankCodeBlock( + index: Int = -1, + indentLevel: Int = 0, + codeBlocks: List = emptyList(), + isVariableSuggestionAvailable: Boolean + ): List = + when { + areElifAndElseStatementsSuggestionsAvailable(index, indentLevel, codeBlocks) -> + listOf(Suggestion.Print, Suggestion.Variable, Suggestion.ElifStatement, Suggestion.ElseStatement) + + isVariableSuggestionAvailable -> listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement) - } else { + + else -> listOf(Suggestion.Print) - } - ) + } + + internal fun areElifAndElseStatementsSuggestionsAvailable( + index: Int, + indentLevel: Int, + codeBlocks: List + ): Boolean { + if (index < 2 || codeBlocks.isEmpty()) { + return false + } + + val previousCodeBlock = codeBlocks + .slice(to = index) + .reversed() + .firstOrNull { it.indentLevel == indentLevel } + + return when (previousCodeBlock) { + is CodeBlock.IfStatement -> true + else -> false + } + } private fun createInitialCodeBlocks(step: Step): List = if (step.id == 47580L) { @@ -742,7 +797,9 @@ class StepQuizCodeBlanksReducer( createBlankCodeBlock( isActive = true, indentLevel = 0, - isVariableSuggestionAvailable = StepQuizCodeBlanksFeature.isVariableSuggestionsAvailable(step) + suggestions = getSuggestionsForBlankCodeBlock( + isVariableSuggestionAvailable = StepQuizCodeBlanksFeature.isVariableSuggestionsAvailable(step) + ) ) ) } else { @@ -750,7 +807,9 @@ class StepQuizCodeBlanksReducer( createBlankCodeBlock( isActive = true, indentLevel = 0, - isVariableSuggestionAvailable = StepQuizCodeBlanksFeature.isVariableSuggestionsAvailable(step) + suggestions = getSuggestionsForBlankCodeBlock( + isVariableSuggestionAvailable = StepQuizCodeBlanksFeature.isVariableSuggestionsAvailable(step) + ) ) ) } diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt new file mode 100644 index 000000000..12b1e9dfc --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt @@ -0,0 +1,61 @@ +package org.hyperskill.step_quiz_code_blanks + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer + +class StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `Should return false if index is less than 2`() { + val result = + reducer.areElifAndElseStatementsSuggestionsAvailable(index = 1, indentLevel = 0, codeBlocks = emptyList()) + assertFalse(result) + } + + @Test + fun `Should return false if codeBlocks is empty`() { + val result = + reducer.areElifAndElseStatementsSuggestionsAvailable(index = 2, indentLevel = 0, codeBlocks = emptyList()) + assertFalse(result) + } + + @Test + fun `Should return false if no previous code block at same indent level`() { + val codeBlocks = listOf( + CodeBlock.IfStatement(indentLevel = 0, children = emptyList()), + CodeBlock.Print(indentLevel = 1, children = emptyList()) + ) + val result = + reducer.areElifAndElseStatementsSuggestionsAvailable(index = 2, indentLevel = 1, codeBlocks = codeBlocks) + assertFalse(result) + } + + @Test + fun `Should return true if previous code block is IfStatement at same indent level`() { + val codeBlocks = listOf( + CodeBlock.IfStatement(indentLevel = 0, children = emptyList()), + CodeBlock.Print(indentLevel = 1, children = emptyList()) + ) + val result = + reducer.areElifAndElseStatementsSuggestionsAvailable(index = 2, indentLevel = 0, codeBlocks = codeBlocks) + assertTrue(result) + } + + @Test + fun `Should return true if previous code block is IfStatement at same indent level nested`() { + val codeBlocks = listOf( + CodeBlock.IfStatement(indentLevel = 0, children = emptyList()), + CodeBlock.Print(indentLevel = 1, children = emptyList()), + CodeBlock.IfStatement(indentLevel = 1, children = emptyList()), + CodeBlock.Print(indentLevel = 2, children = emptyList()) + ) + val result = + reducer.areElifAndElseStatementsSuggestionsAvailable(index = 4, indentLevel = 1, codeBlocks = codeBlocks) + assertTrue(result) + } +} \ No newline at end of file From 68930b1e5ad8c80fdf43201d895be472aab4ef7f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 6 Sep 2024 15:30:03 +0900 Subject: [PATCH 13/28] Render new elif and else statements --- .../project.pbxproj | 8 ++ .../StepQuizCodeBlanksElifStatementView.swift | 73 +++++++++++++++++++ .../StepQuizCodeBlanksElseStatementView.swift | 45 ++++++++++++ .../StepQuizCodeBlanksCodeBlocksView.swift | 9 +++ .../domain/model/CodeBlock.kt | 53 ++++++++++++-- .../view/model/StepQuizCodeBlanksViewState.kt | 14 ++++ ...seStatementsSuggestionsAvailabilityTest.kt | 13 ++++ 7 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElifStatementView.swift create mode 100644 iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElseStatementView.swift diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj index 3d14111a9..0fd2617ca 100644 --- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj +++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj @@ -584,6 +584,8 @@ 2CEEE03328916A3D00282849 /* ProblemOfDayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEEE03228916A3D00282849 /* ProblemOfDayViewModel.swift */; }; 2CEEE03528916A6800282849 /* ProblemOfDayOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEEE03428916A6800282849 /* ProblemOfDayOutputProtocol.swift */; }; 2CEEE03728917F1100282849 /* TimeIntervalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEEE03628917F1100282849 /* TimeIntervalExtensions.swift */; }; + 2CEFEBE22C8AD43F0069567E /* StepQuizCodeBlanksElifStatementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEFEBE12C8AD43F0069567E /* StepQuizCodeBlanksElifStatementView.swift */; }; + 2CEFEBE42C8AD5280069567E /* StepQuizCodeBlanksElseStatementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEFEBE32C8AD5280069567E /* StepQuizCodeBlanksElseStatementView.swift */; }; 2CF0B4E629F9CEAF009C2A2D /* StudyPlanSectionActivitiesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF0B4E529F9CEAF009C2A2D /* StudyPlanSectionActivitiesList.swift */; }; 2CF2DA3A27EC5B2D0055426D /* Assembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF2DA3927EC5B2D0055426D /* Assembly.swift */; }; 2CF34F912C2E8EAE0054477E /* CommentsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF34F902C2E8EAE0054477E /* CommentsContentView.swift */; }; @@ -1390,6 +1392,8 @@ 2CEEE03228916A3D00282849 /* ProblemOfDayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemOfDayViewModel.swift; sourceTree = ""; }; 2CEEE03428916A6800282849 /* ProblemOfDayOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemOfDayOutputProtocol.swift; sourceTree = ""; }; 2CEEE03628917F1100282849 /* TimeIntervalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeIntervalExtensions.swift; sourceTree = ""; }; + 2CEFEBE12C8AD43F0069567E /* StepQuizCodeBlanksElifStatementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksElifStatementView.swift; sourceTree = ""; }; + 2CEFEBE32C8AD5280069567E /* StepQuizCodeBlanksElseStatementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeBlanksElseStatementView.swift; sourceTree = ""; }; 2CF0B4E529F9CEAF009C2A2D /* StudyPlanSectionActivitiesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyPlanSectionActivitiesList.swift; sourceTree = ""; }; 2CF2DA3927EC5B2D0055426D /* Assembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assembly.swift; sourceTree = ""; }; 2CF34F902C2E8EAE0054477E /* CommentsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsContentView.swift; sourceTree = ""; }; @@ -3639,6 +3643,8 @@ 2CBEE4C52C87003A004486E8 /* Conditions */ = { isa = PBXGroup; children = ( + 2CEFEBE12C8AD43F0069567E /* StepQuizCodeBlanksElifStatementView.swift */, + 2CEFEBE32C8AD5280069567E /* StepQuizCodeBlanksElseStatementView.swift */, 2CBEE4C62C870059004486E8 /* StepQuizCodeBlanksIfStatementView.swift */, ); path = Conditions; @@ -5235,6 +5241,7 @@ 2C0DB90728644F2C001EA35E /* CodeEditorView.swift in Sources */, 2CDA98452944590800ADE539 /* ProfileStatisticsView.swift in Sources */, 2C8DD40E2AFB907000FD5359 /* ShareStreakAction.swift in Sources */, + 2CEFEBE22C8AD43F0069567E /* StepQuizCodeBlanksElifStatementView.swift in Sources */, 2C7CB6822ADFDB45006F78DA /* UIFont+SizeOfString.swift in Sources */, 2CC4AAF1280DB513002276A0 /* WebOAuthService.swift in Sources */, 2CF2DA3A27EC5B2D0055426D /* Assembly.swift in Sources */, @@ -5583,6 +5590,7 @@ 2C93AF2529B34FE6004639E0 /* StepQuizPyCharmAssembly.swift in Sources */, 2CDA98412944512D00ADE539 /* ProfileSkeletonView.swift in Sources */, 2CEDE70729965B4D0032D399 /* RestartApplicationLocalNotification.swift in Sources */, + 2CEFEBE42C8AD5280069567E /* StepQuizCodeBlanksElseStatementView.swift in Sources */, 2CACBCBC2B7A12F1006D3AB2 /* UsersInterviewWidgetView.swift in Sources */, 2CDA98432944524D00ADE539 /* HomeSkeletonView.swift in Sources */, 2C9D493D29F07015000599AB /* StudyPlanSectionErrorView.swift in Sources */, diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElifStatementView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElifStatementView.swift new file mode 100644 index 000000000..8f4c66aec --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElifStatementView.swift @@ -0,0 +1,73 @@ +import shared +import SwiftUI + +struct StepQuizCodeBlanksElifStatementView: View { + let elifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElifStatement + + let onChildTap: (StepQuizCodeBlanksViewStateCodeBlockChildItem) -> Void + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .center, spacing: LayoutInsets.smallInset) { + Text("elif") + .font(StepQuizCodeBlanksAppearance.blankFont) + .foregroundColor(StepQuizCodeBlanksAppearance.blankTextColor) + + ForEach(elifStatementItem.children, id: \.id) { child in + StepQuizCodeBlanksCodeBlockChildView(child: child, action: onChildTap) + } + + Text(":") + .font(StepQuizCodeBlanksAppearance.blankFont) + .foregroundColor(StepQuizCodeBlanksAppearance.blankTextColor) + } + .padding(.horizontal, LayoutInsets.defaultInset) + .padding(.vertical, LayoutInsets.smallInset) + .background(Color(ColorPalette.violet400Alpha7)) + .cornerRadius(StepQuizCodeBlanksAppearance.cornerRadius) + .padding(.horizontal) + } + .scrollBounceBehaviorBasedOnSize(axes: .horizontal) + } +} + +#if DEBUG +#Preview { + VStack { + StepQuizCodeBlanksElifStatementView( + elifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElifStatement( + id: 0, + indentLevel: 0, + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: nil) + ] + ), + onChildTap: { _ in } + ) + + StepQuizCodeBlanksElifStatementView( + elifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElifStatement( + id: 0, + indentLevel: 0, + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: true, value: "x") + ] + ), + onChildTap: { _ in } + ) + + StepQuizCodeBlanksElifStatementView( + elifStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElifStatement( + id: 0, + indentLevel: 0, + children: [ + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 0, isActive: false, value: "x"), + StepQuizCodeBlanksViewStateCodeBlockChildItem(id: 1, isActive: true, value: nil) + ] + ), + onChildTap: { _ in } + ) + } + .padding() +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElseStatementView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElseStatementView.swift new file mode 100644 index 000000000..91a75e90e --- /dev/null +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/Conditions/StepQuizCodeBlanksElseStatementView.swift @@ -0,0 +1,45 @@ +import shared +import SwiftUI + +struct StepQuizCodeBlanksElseStatementView: View { + let elseStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElseStatement + + var body: some View { + Text("else:") + .font(StepQuizCodeBlanksAppearance.blankFont) + .foregroundColor(StepQuizCodeBlanksAppearance.blankTextColor) + .padding(.horizontal, LayoutInsets.defaultInset) + .padding(.vertical, LayoutInsets.smallInset) + .frame(minHeight: StepQuizCodeBlanksCodeBlockChildTextView.Appearance.minHeight) + .background(Color(ColorPalette.violet400Alpha7)) + .addBorder( + color: elseStatementItem.isActive ? StepQuizCodeBlanksAppearance.activeBorderColor : .clear, + width: elseStatementItem.isActive ? 1 : 0, + cornerRadius: StepQuizCodeBlanksAppearance.cornerRadius + ) + .padding(.horizontal) + .animation(.default, value: elseStatementItem.isActive) + } +} + +#if DEBUG +#Preview { + StepQuizCodeBlanksElseStatementView( + elseStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElseStatement( + id: 0, + indentLevel: 0, + isActive: true + ) + ) +} + +#Preview { + StepQuizCodeBlanksElseStatementView( + elseStatementItem: StepQuizCodeBlanksViewStateCodeBlockItemElseStatement( + id: 0, + indentLevel: 0, + isActive: false + ) + ) +} +#endif diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift index 79f8d394f..9b5e4c634 100644 --- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift +++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizCodeBlanks/Views/CodeBlocks/StepQuizCodeBlanksCodeBlocksView.swift @@ -78,6 +78,15 @@ struct StepQuizCodeBlanksCodeBlocksView: View { ifStatementItem: ifStatementItem, onChildTap: onChildTap ) + case .elifStatement(let elifStatementItem): + StepQuizCodeBlanksElifStatementView( + elifStatementItem: elifStatementItem, + onChildTap: onChildTap + ) + case .elseStatement(let elseStatementItem): + StepQuizCodeBlanksElseStatementView( + elseStatementItem: elseStatementItem + ) } } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index c0c459f50..ea964f221 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -37,7 +37,7 @@ sealed class CodeBlock { override val children: List = emptyList() override val analyticRepresentation: String - get() = "Blank(isActive=$isActive, suggestions=$suggestions)" + get() = "Blank(isActive=$isActive, indentLevel=$indentLevel, suggestions=$suggestions)" override fun toReplyString(): String = "" } @@ -51,7 +51,7 @@ sealed class CodeBlock { override val suggestions: List = emptyList() override val analyticRepresentation: String = - "Print(children=$children)" + "Print(indentLevel=$indentLevel, children=$children)" override fun toReplyString(): String = buildString { @@ -61,7 +61,7 @@ sealed class CodeBlock { } override fun toString(): String = - "Print(children=$children)" + "Print(indentLevel=$indentLevel, children=$children)" } internal data class Variable( @@ -79,7 +79,7 @@ sealed class CodeBlock { override val suggestions: List = emptyList() override val analyticRepresentation: String - get() = "Variable(children=$children)" + get() = "Variable(indentLevel=$indentLevel, children=$children)" override fun toReplyString(): String = buildString { @@ -89,7 +89,7 @@ sealed class CodeBlock { } override fun toString(): String = - "Variable(children=$children)" + "Variable(indentLevel=$indentLevel, children=$children)" } internal data class IfStatement( @@ -101,7 +101,7 @@ sealed class CodeBlock { override val suggestions: List = emptyList() override val analyticRepresentation: String - get() = "IfStatement(children=$children)" + get() = "IfStatement(indentLevel=$indentLevel, children=$children)" override fun toReplyString(): String = buildString { @@ -111,7 +111,46 @@ sealed class CodeBlock { } override fun toString(): String = - "IfStatement(children=$children)" + "IfStatement(indentLevel=$indentLevel, children=$children)" + } + + internal data class ElifStatement( + override val indentLevel: Int = 0, + override val children: List + ) : CodeBlock() { + override val isActive: Boolean = false + + override val suggestions: List = emptyList() + + override val analyticRepresentation: String + get() = "ElifStatement(indentLevel=$indentLevel, children=$children)" + + override fun toReplyString(): String = + buildString { + append("elif ") + append(joinChildrenToReplyString(children)) + append(":") + } + + override fun toString(): String = + "ElifStatement(indentLevel=$indentLevel, children=$children)" + } + + internal data class ElseStatement( + override val isActive: Boolean, + override val indentLevel: Int = 0 + ) : CodeBlock() { + override val suggestions: List = emptyList() + + override val children: List = emptyList() + + override val analyticRepresentation: String + get() = "ElseStatement(isActive=$isActive, indentLevel=$indentLevel)" + + override fun toReplyString(): String = "else:" + + override fun toString(): String = + "ElseStatement(isActive=$isActive, indentLevel=$indentLevel)" } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt index 0cddcf03b..2bf809df5 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/model/StepQuizCodeBlanksViewState.kt @@ -59,6 +59,20 @@ sealed interface StepQuizCodeBlanksViewState { override val indentLevel: Int = 0, override val children: List ) : CodeBlockItem + + data class ElifStatement( + override val id: Int, + override val indentLevel: Int = 0, + override val children: List + ) : CodeBlockItem + + data class ElseStatement( + override val id: Int, + override val indentLevel: Int = 0, + val isActive: Boolean + ) : CodeBlockItem { + override val children: List = emptyList() + } } data class CodeBlockChildItem( diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt index 12b1e9dfc..7b38d627e 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt @@ -58,4 +58,17 @@ class StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest { reducer.areElifAndElseStatementsSuggestionsAvailable(index = 4, indentLevel = 1, codeBlocks = codeBlocks) assertTrue(result) } + + @Test + fun `Should return true if previous code block is ElifStatement at same indent level`() { + val codeBlocks = listOf( + CodeBlock.IfStatement(indentLevel = 0, children = emptyList()), + CodeBlock.Print(indentLevel = 1, children = emptyList()), + CodeBlock.ElifStatement(indentLevel = 0, children = emptyList()), + CodeBlock.Print(indentLevel = 1, children = emptyList()) + ) + val result = + reducer.areElifAndElseStatementsSuggestionsAvailable(index = 4, indentLevel = 0, codeBlocks = codeBlocks) + assertTrue(result) + } } \ No newline at end of file From f81844541fb8cb3ec01c917d7259a46c1821b718 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Fri, 6 Sep 2024 19:08:03 +0900 Subject: [PATCH 14/28] Handle new conditions logic --- config/detekt/baseline.xml | 1 + .../domain/model/CodeBlock.kt | 6 + .../presentation/StepQuizCodeBlanksFeature.kt | 3 + .../presentation/StepQuizCodeBlanksReducer.kt | 164 +++++++++++++----- .../StepQuizCodeBlanksViewStateMapper.kt | 49 +++++- 5 files changed, 174 insertions(+), 49 deletions(-) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 22e2b1941..acbb0f7a5 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -158,6 +158,7 @@ MagicNumber:StepDelegate.kt$StepDelegate$25 MagicNumber:StepDelegate.kt$StepDelegate$5 MagicNumber:StepDelegate.kt$StepDelegate$50 + MagicNumber:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$2 MagicNumber:StepQuizCodeBlanksReducer.kt$StepQuizCodeBlanksReducer$47580L MagicNumber:StudyPlanActivityAdapterDelegate.kt$StudyPlanActivityAdapterDelegate.ViewHolder$100f MagicNumber:SubscriptionSyncLoading.kt$0.5f diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index ea964f221..72dc16e25 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -26,9 +26,15 @@ sealed class CodeBlock { internal fun areAllChildrenUnselected(): Boolean = children.all { it is CodeBlockChild.SelectSuggestion && it.selectedSuggestion == null } + internal fun areAllChildrenSelected(): Boolean = + children.all { it is CodeBlockChild.SelectSuggestion && it.selectedSuggestion != null } + internal fun hasAnySelectedChild(): Boolean = children.any { it is CodeBlockChild.SelectSuggestion && it.selectedSuggestion != null } + internal fun hasAnyUnselectedChild(): Boolean = + children.any { it is CodeBlockChild.SelectSuggestion && it.selectedSuggestion == null } + internal data class Blank( override val isActive: Boolean, override val indentLevel: Int = 0, diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt index 0fd51a880..0dce1f428 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt @@ -35,6 +35,9 @@ object StepQuizCodeBlanksFeature { internal val codeBlanksVariablesSuggestions: List = step.codeBlanksVariablesSuggestions() + internal val codeBlanksVariablesAndStringsSuggestions: List = + codeBlanksVariablesSuggestions + codeBlanksStringsSuggestions + internal val codeBlanksOperationsSuggestions: List = step.codeBlanksOperationsSuggestions() } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index eef2d1a08..5aa7e743b 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -88,8 +88,7 @@ class StepQuizCodeBlanksReducer( children = listOf( CodeBlockChild.SelectSuggestion( isActive = true, - suggestions = state.codeBlanksVariablesSuggestions + - state.codeBlanksStringsSuggestions, + suggestions = state.codeBlanksVariablesAndStringsSuggestions, selectedSuggestion = null ) ) @@ -116,16 +115,33 @@ class StepQuizCodeBlanksReducer( children = listOf( CodeBlockChild.SelectSuggestion( isActive = true, - suggestions = state.codeBlanksVariablesSuggestions + - state.codeBlanksStringsSuggestions, + suggestions = state.codeBlanksVariablesAndStringsSuggestions, selectedSuggestion = null ) ) ) - else -> activeCodeBlock + Suggestion.ElifStatement -> + CodeBlock.ElifStatement( + indentLevel = activeCodeBlock.indentLevel, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = state.codeBlanksVariablesAndStringsSuggestions, + selectedSuggestion = null + ) + ) + ) + Suggestion.ElseStatement -> + CodeBlock.ElseStatement( + isActive = false, + indentLevel = activeCodeBlock.indentLevel + ) + is Suggestion.ConstantString -> activeCodeBlock } + is CodeBlock.Print, - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> val activeChild = activeCodeBlock.children[activeChildIndex] as CodeBlockChild.SelectSuggestion val newChildren = activeCodeBlock.children @@ -142,10 +158,12 @@ class StepQuizCodeBlanksReducer( when (activeCodeBlock) { is CodeBlock.Print -> activeCodeBlock.copy(children = newChildren) is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) + is CodeBlock.ElifStatement -> activeCodeBlock.copy(children = newChildren) else -> activeCodeBlock } } ?: activeCodeBlock } + is CodeBlock.Variable -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> activeCodeBlock.copy( @@ -172,13 +190,35 @@ class StepQuizCodeBlanksReducer( ) } ?: activeCodeBlock } + + is CodeBlock.ElseStatement -> activeCodeBlock } - val newCodeBlocks = state.codeBlocks.mutate { set(activeCodeBlockIndex, newCodeBlock) } + val newCodeBlocks = state.codeBlocks.mutate { + set(activeCodeBlockIndex, newCodeBlock) + + if (newCodeBlock is CodeBlock.ElseStatement) { + val blankInsertIndex = activeCodeBlockIndex + 1 + val blankIndentLevel = newCodeBlock.indentLevel + 1 + add( + blankInsertIndex, + createBlankCodeBlock( + isActive = true, + indentLevel = blankIndentLevel, + suggestions = getSuggestionsForBlankCodeBlock( + index = blankInsertIndex, + indentLevel = blankIndentLevel, + codeBlocks = this, + isVariableSuggestionAvailable = state.isVariableSuggestionsAvailable + ) + ) + ) + } + } val isFulfilledOnboardingPrintCodeBlock = state.onboardingState is OnboardingState.HighlightSuggestions && - activeCodeBlock is CodeBlock.Print && activeCodeBlock.children.any { it.selectedSuggestion == null } && - newCodeBlock is CodeBlock.Print && newCodeBlock.children.all { it.selectedSuggestion != null } + activeCodeBlock is CodeBlock.Print && activeCodeBlock.hasAnyUnselectedChild() && + newCodeBlock is CodeBlock.Print && newCodeBlock.areAllChildrenSelected() val (onboardingState, onboardingActions) = if (isFulfilledOnboardingPrintCodeBlock) { OnboardingState.HighlightCallToActionButton to @@ -254,7 +294,8 @@ class StepQuizCodeBlanksReducer( val newChildren = when (targetCodeBlock) { is CodeBlock.Print, is CodeBlock.Variable, - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { targetCodeBlock.children.mapIndexed { index, child -> require(child is CodeBlockChild.SelectSuggestion) if (index == message.codeBlockChildItem.id) { @@ -264,7 +305,9 @@ class StepQuizCodeBlanksReducer( } } } - else -> null + null, + is CodeBlock.Blank, + is CodeBlock.ElseStatement -> null } val newCodeBlocks = state.codeBlocks.mutate { @@ -280,7 +323,9 @@ class StepQuizCodeBlanksReducer( is CodeBlock.Print -> targetCodeBlock.copy(children = newChildren) is CodeBlock.Variable -> targetCodeBlock.copy(children = newChildren) is CodeBlock.IfStatement -> targetCodeBlock.copy(children = newChildren) - else -> targetCodeBlock + is CodeBlock.ElifStatement -> targetCodeBlock.copy(children = newChildren) + is CodeBlock.Blank, + is CodeBlock.ElseStatement -> targetCodeBlock } ) } @@ -342,6 +387,11 @@ class StepQuizCodeBlanksReducer( ) } + val isNextCodeBlockHasSameIndentLevelOrTrue = state.codeBlocks + .getOrNull(activeCodeBlockIndex + 1) + ?.let { it.indentLevel == activeCodeBlock.indentLevel } + ?: true + when (activeCodeBlock) { is CodeBlock.Blank -> { if (state.codeBlocks.size > 1) { @@ -427,47 +477,67 @@ class StepQuizCodeBlanksReducer( } } } - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { val activeChildIndex = activeCodeBlock.activeChildIndex() ?: return@mutate - val activeChild = activeCodeBlock.children[activeChildIndex] - - val nextCodeBlock = state.codeBlocks.getOrNull(activeCodeBlockIndex + 1) + val activeChild = activeCodeBlock.children[activeChildIndex] as CodeBlockChild.SelectSuggestion when { - activeChild.selectedSuggestion != null -> + activeChild.selectedSuggestion != null -> { + val newChildren = activeCodeBlock.children.mutate { + set( + activeChildIndex, + activeChild.copy(selectedSuggestion = null) + ) + }.cast>() set( activeCodeBlockIndex, - activeCodeBlock.copy( - children = activeCodeBlock.children.mutate { - set( - activeChildIndex, - activeChild.copy(selectedSuggestion = null) - ) - } - ) + when (activeCodeBlock) { + is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) + is CodeBlock.ElifStatement -> activeCodeBlock.copy(children = newChildren) + else -> activeCodeBlock + } ) + } - activeChildIndex > 0 -> + activeChildIndex > 0 -> { + val newChildren = activeCodeBlock.children.mutate { + val previousChildIndex = activeChildIndex - 1 + val previousChild = this[previousChildIndex] as CodeBlockChild.SelectSuggestion + set( + previousChildIndex, + previousChild.copy(isActive = true) + ) + + removeAt(activeChildIndex) + }.cast>() set( activeCodeBlockIndex, - activeCodeBlock.copy( - children = activeCodeBlock.children.mutate { - set( - activeChildIndex - 1, - this[activeChildIndex - 1].copy(isActive = true) - ) - removeAt(activeChildIndex) - } - ) + when (activeCodeBlock) { + is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) + is CodeBlock.ElifStatement -> activeCodeBlock.copy(children = newChildren) + else -> activeCodeBlock + } ) + } (activeChildIndex == 0 || activeCodeBlock.areAllChildrenUnselected()) && - (nextCodeBlock?.let { it.indentLevel == activeCodeBlock.indentLevel } ?: true) -> + isNextCodeBlockHasSameIndentLevelOrTrue -> { if (state.codeBlocks.size > 1) { removeActiveCodeBlockAndSetNextActive() } else { replaceActiveCodeBlockWithBlank() } + } + } + } + is CodeBlock.ElseStatement -> { + if (isNextCodeBlockHasSameIndentLevelOrTrue) { + if (state.codeBlocks.size > 1) { + removeActiveCodeBlockAndSetNextActive() + } else { + replaceActiveCodeBlockWithBlank() + } } } } @@ -498,7 +568,9 @@ class StepQuizCodeBlanksReducer( return if (activeCodeBlock != null) { val indentLevel = when (activeCodeBlock) { - is CodeBlock.IfStatement -> activeCodeBlock.indentLevel + 1 + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement, + is CodeBlock.ElseStatement -> activeCodeBlock.indentLevel + 1 else -> activeCodeBlock.indentLevel } @@ -556,7 +628,8 @@ class StepQuizCodeBlanksReducer( val newChildren = when (activeCodeBlock) { is CodeBlock.Print, is CodeBlock.Variable, - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> val activeChild = activeCodeBlock.children[activeChildIndex] as CodeBlockChild.SelectSuggestion @@ -591,7 +664,8 @@ class StepQuizCodeBlanksReducer( .cast>() } } - else -> null + is CodeBlock.Blank, + is CodeBlock.ElseStatement -> null } val newCodeBlocks = state.codeBlocks.mutate { @@ -602,6 +676,7 @@ class StepQuizCodeBlanksReducer( is CodeBlock.Print -> activeCodeBlock.copy(children = newChildren) is CodeBlock.Variable -> activeCodeBlock.copy(children = newChildren) is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) + is CodeBlock.ElifStatement -> activeCodeBlock.copy(children = newChildren) else -> activeCodeBlock } ) @@ -652,6 +727,8 @@ class StepQuizCodeBlanksReducer( is CodeBlock.Print -> activeCodeBlock.copy(indentLevel = newIndentLevel) is CodeBlock.Variable -> activeCodeBlock.copy(indentLevel = newIndentLevel) is CodeBlock.IfStatement -> activeCodeBlock.copy(indentLevel = newIndentLevel) + is CodeBlock.ElifStatement -> activeCodeBlock.copy(indentLevel = newIndentLevel) + is CodeBlock.ElseStatement -> activeCodeBlock.copy(indentLevel = newIndentLevel) } ) } @@ -661,9 +738,11 @@ class StepQuizCodeBlanksReducer( private fun setCodeBlockIsActive(codeBlock: CodeBlock, isActive: Boolean): CodeBlock = when (codeBlock) { is CodeBlock.Blank -> codeBlock.copy(isActive = isActive) + is CodeBlock.ElseStatement -> codeBlock.copy(isActive = isActive) is CodeBlock.Print, is CodeBlock.Variable, - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { if (isActive) { if (codeBlock.activeChild() != null) { codeBlock @@ -680,6 +759,7 @@ class StepQuizCodeBlanksReducer( is CodeBlock.Print -> codeBlock.copy(children = newChildren) is CodeBlock.Variable -> codeBlock.copy(children = newChildren) is CodeBlock.IfStatement -> codeBlock.copy(children = newChildren) + is CodeBlock.ElifStatement -> codeBlock.copy(children = newChildren) else -> codeBlock } } @@ -692,6 +772,7 @@ class StepQuizCodeBlanksReducer( is CodeBlock.Print -> codeBlock.copy(children = newChildren) is CodeBlock.Variable -> codeBlock.copy(children = newChildren) is CodeBlock.IfStatement -> codeBlock.copy(children = newChildren) + is CodeBlock.ElifStatement -> codeBlock.copy(children = newChildren) else -> codeBlock } } @@ -741,7 +822,8 @@ class StepQuizCodeBlanksReducer( .firstOrNull { it.indentLevel == indentLevel } return when (previousCodeBlock) { - is CodeBlock.IfStatement -> true + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> true else -> false } } diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt index 899f8f17d..fb7f1d6b9 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/view/mapper/StepQuizCodeBlanksViewStateMapper.kt @@ -23,10 +23,13 @@ object StepQuizCodeBlanksViewStateMapper { val suggestions = when (activeCodeBlock) { - is CodeBlock.Blank -> activeCodeBlock.suggestions + is CodeBlock.Blank -> + activeCodeBlock.suggestions + is CodeBlock.Print, is CodeBlock.Variable, - is CodeBlock.IfStatement -> + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> (activeCodeBlock.activeChild() as? CodeBlockChild.SelectSuggestion)?.let { if (it.selectedSuggestion == null) { it.suggestions @@ -34,7 +37,9 @@ object StepQuizCodeBlanksViewStateMapper { emptyList() } } - null -> emptyList() + + null, + is CodeBlock.ElseStatement -> emptyList() } ?: emptyList() val isDeleteButtonEnabled = @@ -55,13 +60,16 @@ object StepQuizCodeBlanksViewStateMapper { } } ?: false } - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { activeCodeBlock.activeChildIndex()?.let { activeChildIndex -> + val activeChild = activeCodeBlock.children[activeChildIndex] as CodeBlockChild.SelectSuggestion + when { activeChildIndex > 0 -> true - activeCodeBlock.children[activeChildIndex].selectedSuggestion != null -> + activeChild.selectedSuggestion != null -> true else -> @@ -70,13 +78,17 @@ object StepQuizCodeBlanksViewStateMapper { } } ?: false } + is CodeBlock.ElseStatement -> + codeBlocks.getOrNull(activeCodeBlockIndex + 1) + ?.let { it.indentLevel == activeCodeBlock.indentLevel } ?: true null -> false } val isSpaceButtonHidden = if (state.codeBlanksOperationsSuggestions.isNotEmpty()) { when (activeCodeBlock) { is CodeBlock.Print, - is CodeBlock.IfStatement -> { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement -> { val activeChild = activeCodeBlock.activeChild() as? CodeBlockChild.SelectSuggestion activeChild?.selectedSuggestion == null } @@ -88,17 +100,26 @@ object StepQuizCodeBlanksViewStateMapper { true } } - else -> true + null, + is CodeBlock.Blank, + is CodeBlock.ElseStatement -> true } } else { true } + val isPreviousCodeBlockCondition = + when (activeCodeBlockIndex?.let { state.codeBlocks.getOrNull(it - 1) }) { + is CodeBlock.IfStatement, + is CodeBlock.ElifStatement, + is CodeBlock.ElseStatement -> true + else -> false + } val isDecreaseIndentLevelButtonHidden = when { activeCodeBlock == null -> true activeCodeBlock.indentLevel < 1 -> true - state.codeBlocks.getOrNull(activeCodeBlockIndex - 1) is CodeBlock.IfStatement -> true + isPreviousCodeBlockCondition -> true else -> false } @@ -141,6 +162,18 @@ object StepQuizCodeBlanksViewStateMapper { indentLevel = codeBlock.indentLevel, children = codeBlock.children.mapIndexed(::mapCodeBlockChild) ) + is CodeBlock.ElifStatement -> + StepQuizCodeBlanksViewState.CodeBlockItem.ElifStatement( + id = index, + indentLevel = codeBlock.indentLevel, + children = codeBlock.children.mapIndexed(::mapCodeBlockChild) + ) + is CodeBlock.ElseStatement -> + StepQuizCodeBlanksViewState.CodeBlockItem.ElseStatement( + id = index, + indentLevel = codeBlock.indentLevel, + isActive = codeBlock.isActive + ) } private fun mapCodeBlockChild( From bb151d5e29d60bea430fffeef63013f9266f18b2 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 9 Sep 2024 16:52:38 +0900 Subject: [PATCH 15/28] Handle indentation level when building reply --- .../domain/model/CodeBlock.kt | 6 + .../StepQuizCodeBlanksStateExtensionsTest.kt | 310 +++++++++++++++--- 2 files changed, 263 insertions(+), 53 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index c0c459f50..10ddd9da6 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -55,6 +55,7 @@ sealed class CodeBlock { override fun toReplyString(): String = buildString { + append(buildIndentString(indentLevel)) append("print(") append(joinChildrenToReplyString(children)) append(")") @@ -83,6 +84,7 @@ sealed class CodeBlock { override fun toReplyString(): String = buildString { + append(buildIndentString(indentLevel)) append(name?.toReplyString() ?: "") append(" = ") append(joinChildrenToReplyString(values)) @@ -105,6 +107,7 @@ sealed class CodeBlock { override fun toReplyString(): String = buildString { + append(buildIndentString(indentLevel)) append("if ") append(joinChildrenToReplyString(children)) append(":") @@ -115,6 +118,9 @@ sealed class CodeBlock { } } +internal fun CodeBlock.Companion.buildIndentString(indentLevel: Int): String = + "\t".repeat(indentLevel) + internal fun CodeBlock.Companion.joinChildrenToReplyString(children: List): String = buildString { children.forEachIndexed { index, child -> diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt index 60fc9f235..4517b15c9 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt @@ -18,6 +18,11 @@ import org.hyperskill.app.submissions.domain.model.Reply import org.hyperskill.step.domain.model.stub class StepQuizCodeBlanksStateExtensionsTest { + companion object { + private const val REPLY_CODE_LANGUAGE = "python3" + private const val REPLY_CODE_PREFIX = "# solved with code blanks\n" + } + @Test fun `activeCodeBlockIndex should return null if no active code block`() { val state = stubContentState( @@ -70,24 +75,14 @@ class StepQuizCodeBlanksStateExtensionsTest { ), CodeBlock.Blank(isActive = true, suggestions = emptyList()) ) - val step = Step.stub(id = 1).copy( - block = Block.stub( - options = Block.Options( - codeTemplates = mapOf("python3" to "# put your python code here") - ) - ) - ) - val state = stubContentState( - step = step, - codeBlocks = codeBlocks - ) + val state = stubContentState(codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { - append("# solved with code blanks\n") + append(REPLY_CODE_PREFIX) append("print(\"test\")\n") }, - language = "python3" + language = REPLY_CODE_LANGUAGE ) assertEquals(expectedReply, state.createReply()) @@ -120,24 +115,14 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ), ) - val step = Step.stub(id = 1).copy( - block = Block.stub( - options = Block.Options( - codeTemplates = mapOf("python3" to "# put your python code here") - ) - ) - ) - val state = stubContentState( - step = step, - codeBlocks = codeBlocks - ) + val state = stubContentState(codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { - append("# solved with code blanks\n") + append(REPLY_CODE_PREFIX) append("a = 1\nprint(a)") }, - language = "python3" + language = REPLY_CODE_LANGUAGE ) assertEquals(expectedReply, state.createReply()) @@ -248,27 +233,17 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ) ) - val step = Step.stub(id = 1).copy( - block = Block.stub( - options = Block.Options( - codeTemplates = mapOf("python3" to "# put your python code here") - ) - ) - ) - val state = stubContentState( - step = step, - codeBlocks = codeBlocks - ) + val state = stubContentState(codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { - append("# solved with code blanks\n") + append(REPLY_CODE_PREFIX) append("x = 1000\n") append("r = 5\n") append("y = 10\n") append("print(x * (1 + r / 100) ** y)") }, - language = "python3" + language = REPLY_CODE_LANGUAGE ) assertEquals(expectedReply, state.createReply()) @@ -393,28 +368,251 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ) ) - val step = Step.stub(id = 1).copy( - block = Block.stub( - options = Block.Options( - codeTemplates = mapOf("python3" to "# put your python code here") - ) - ) - ) - val state = stubContentState( - step = step, - codeBlocks = codeBlocks - ) + val state = stubContentState(codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { - append("# solved with code blanks\n") + append(REPLY_CODE_PREFIX) append("x = 1000\n") append("r = 5\n") append("y = 10\n") append("a = x * (1 + r / 100) ** y\n") append("print(a)") }, - language = "python3" + language = REPLY_CODE_LANGUAGE + ) + + assertEquals(expectedReply, state.createReply()) + } + + @Test + fun `createReply should return correct Reply with single IfStatement`() { + val codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("a") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("33") + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("b") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("200") + ) + ) + ), + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("b") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString(">") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("a") + ) + ) + ), + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("\"b is greater than a\"") + ) + ) + ) + ) + val state = stubContentState(codeBlocks = codeBlocks) + + val expectedReply = Reply.code( + code = buildString { + append(REPLY_CODE_PREFIX) + append("a = 33\n") + append("b = 200\n") + append("if b > a:\n") + append("\tprint(\"b is greater than a\")") + }, + language = REPLY_CODE_LANGUAGE + ) + + assertEquals(expectedReply, state.createReply()) + } + + @Test + fun `createReply should return correct Reply with multiple IfStatement and indentation level of 2`() { + val codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("x") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("10") + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("y") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("5") + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("z") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("15") + ) + ) + ), + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("x") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString(">") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("y") + ) + ) + ), + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("\"x is greater than y\"") + ) + ) + ), + CodeBlock.IfStatement( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("z") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString(">") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("x") + ) + ) + ), + CodeBlock.Print( + indentLevel = 2, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("\"z is greater than x\"") + ) + ) + ), + CodeBlock.IfStatement( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("z") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("<") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("x") + ) + ) + ), + CodeBlock.Print( + indentLevel = 2, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("\"z is less than x\"") + ) + ) + ) + ) + val state = stubContentState(codeBlocks = codeBlocks) + + val expectedReply = Reply.code( + code = buildString { + append(REPLY_CODE_PREFIX) + append("x = 10\n") + append("y = 5\n") + append("z = 15\n") + append("if x > y:\n") + append("\tprint(\"x is greater than y\")\n") + append("\tif z > x:\n") + append("\t\tprint(\"z is greater than x\")\n") + append("\tif z < x:\n") + append("\t\tprint(\"z is less than x\")") + }, + language = REPLY_CODE_LANGUAGE ) assertEquals(expectedReply, state.createReply()) @@ -451,7 +649,13 @@ class StepQuizCodeBlanksStateExtensionsTest { } private fun stubContentState( - step: Step = Step.stub(id = 1), + step: Step = Step.stub(id = 1).copy( + block = Block.stub( + options = Block.Options( + codeTemplates = mapOf(REPLY_CODE_LANGUAGE to "# put your python code here") + ) + ) + ), codeBlocks: List ): StepQuizCodeBlanksFeature.State.Content = StepQuizCodeBlanksFeature.State.Content( From 5652c743bedfb5b3ab5bf63b87b32b931ddafe2e Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 9 Sep 2024 17:43:47 +0900 Subject: [PATCH 16/28] Test DecreaseIndentLevelButtonClicked --- .../StepQuizCodeBlanksReducerTest.kt | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt index df1ed4ef5..a24b3a5ac 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt @@ -8,6 +8,7 @@ import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent @@ -1242,6 +1243,80 @@ class StepQuizCodeBlanksReducerTest { assertContainsSpaceButtonClickedAnalyticEvent(actions) } + @Test + fun `DecreaseIndentLevelButtonClicked should not update state if no active code block`() { + val initialState = stubContentState( + codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList())) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + assertEquals(initialState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + @Test + fun `DecreaseIndentLevelButtonClicked should not decrease indent level below 1`() { + val initialState = stubContentState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + assertEquals(initialState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + @Test + fun `DecreaseIndentLevelButtonClicked should decrease indent level by 1`() { + val initialState = stubContentState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList())) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + @Test + fun `DecreaseIndentLevelButtonClicked should decrease indent level for active code block only`() { + val initialState = stubContentState( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, indentLevel = 2, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) + ) + ) + + assertEquals(expectedState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + @Test fun `Onboarding should be unavailable`() { val initialState = StepQuizCodeBlanksFeature.State.Idle @@ -1336,6 +1411,15 @@ class StepQuizCodeBlanksReducerTest { } } + private fun assertContainsDecreaseIndentLevelAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent + } + } + } + private fun stubContentState( step: Step = Step.stub(id = 1), codeBlocks: List, From 8bd92162c9f52cf63065a37067f0ee75c538cd69 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 10 Sep 2024 18:40:00 +0900 Subject: [PATCH 17/28] Add view state tests --- .../StepQuizCodeBlanksViewStateMapperTest.kt | 902 ++++++++++++++++-- 1 file changed, 802 insertions(+), 100 deletions(-) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt index 226547d07..c6ec3a274 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt @@ -4,6 +4,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.Block import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild @@ -22,24 +23,6 @@ class StepQuizCodeBlanksViewStateMapperTest { assertEquals(StepQuizCodeBlanksViewState.Idle, viewState) } - @Test - fun `Content with print suggestion and disabled delete button when active code block is Blank`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), - suggestions = listOf(Suggestion.Print), - isDeleteButtonEnabled = false, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - @Test fun `Content with suggestions and enabled delete button when active code block is Print`() { val suggestions = listOf( @@ -191,23 +174,32 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with active Variable and disabled delete button`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") + fun `Delete button should be disabled when active code block is Blank and single`() { + val state = stubState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), + suggestions = listOf(Suggestion.Print), + isDeleteButtonEnabled = false, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + @Test + fun `Delete button should be enabled when active code block is Print and single`() { val state = stubState( codeBlocks = listOf( - CodeBlock.Variable( + CodeBlock.Print( children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ), CodeBlockChild.SelectSuggestion( isActive = true, - suggestions = suggestions, + suggestions = listOf(Suggestion.Print), selectedSuggestion = null ) ) @@ -216,24 +208,19 @@ class StepQuizCodeBlanksViewStateMapperTest { ) val expectedViewState = StepQuizCodeBlanksViewState.Content( codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( + StepQuizCodeBlanksViewState.CodeBlockItem.Print( id = 0, children = listOf( StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 0, - isActive = false, - value = suggestions[0].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, isActive = true, value = null ) ) ) ), - suggestions = suggestions, - isDeleteButtonEnabled = false, + suggestions = listOf(Suggestion.Print), + isDeleteButtonEnabled = true, isSpaceButtonHidden = true, isDecreaseIndentLevelButtonHidden = true ) @@ -244,7 +231,7 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with active not filled Variable and enabled delete button`() { + fun `Delete button should be enabled when Variable active name is unselected`() { val suggestions = listOf( Suggestion.ConstantString("1"), Suggestion.ConstantString("2") @@ -297,7 +284,7 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with active filled Variable and enabled delete button`() { + fun `Delete button should be enabled when Variable active name is selected`() { val suggestions = listOf( Suggestion.ConstantString("1"), Suggestion.ConstantString("2") @@ -314,7 +301,7 @@ class StepQuizCodeBlanksViewStateMapperTest { CodeBlockChild.SelectSuggestion( isActive = false, suggestions = suggestions, - selectedSuggestion = suggestions[1] + selectedSuggestion = null ) ) ) @@ -333,7 +320,7 @@ class StepQuizCodeBlanksViewStateMapperTest { StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 1, isActive = false, - value = suggestions[1].text + value = null ) ) ) @@ -350,15 +337,59 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with suggestions when active code block is Blank`() { - val suggestions = listOf(Suggestion.Print, Suggestion.Variable) + fun `Delete button should be enabled when Variable active value child index is greater than one`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = suggestions)) + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[1] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) ) val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.Variable( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = false, + value = suggestions[0].text + ), + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 1, + isActive = false, + value = suggestions[1].text + ), + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 2, + isActive = true, + value = null + ) + ) + ) + ), suggestions = suggestions, - isDeleteButtonEnabled = false, + isDeleteButtonEnabled = true, isSpaceButtonHidden = true, isDecreaseIndentLevelButtonHidden = true ) @@ -369,15 +400,20 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with suggestions when active code block is Print and no selected suggestion`() { + fun `Delete button should be disabled when Variable name is selected and active value is unselected`() { val suggestions = listOf( Suggestion.ConstantString("1"), Suggestion.ConstantString("2") ) val state = stubState( codeBlocks = listOf( - CodeBlock.Print( + CodeBlock.Variable( children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), CodeBlockChild.SelectSuggestion( isActive = true, suggestions = suggestions, @@ -389,11 +425,16 @@ class StepQuizCodeBlanksViewStateMapperTest { ) val expectedViewState = StepQuizCodeBlanksViewState.Content( codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Print( + StepQuizCodeBlanksViewState.CodeBlockItem.Variable( id = 0, children = listOf( StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 0, + isActive = false, + value = suggestions[0].text + ), + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 1, isActive = true, value = null ) @@ -401,7 +442,7 @@ class StepQuizCodeBlanksViewStateMapperTest { ) ), suggestions = suggestions, - isDeleteButtonEnabled = true, + isDeleteButtonEnabled = false, isSpaceButtonHidden = true, isDecreaseIndentLevelButtonHidden = true ) @@ -412,19 +453,24 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with no suggestions when active code block is Print and has selected suggestion`() { + fun `Delete button should be enabled when Variable name is selected and active value is selected`() { val suggestions = listOf( Suggestion.ConstantString("1"), Suggestion.ConstantString("2") ) val state = stubState( codeBlocks = listOf( - CodeBlock.Print( + CodeBlock.Variable( children = listOf( CodeBlockChild.SelectSuggestion( - isActive = true, + isActive = false, suggestions = suggestions, selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[1] ) ) ) @@ -432,20 +478,25 @@ class StepQuizCodeBlanksViewStateMapperTest { ) val expectedViewState = StepQuizCodeBlanksViewState.Content( codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Print( + StepQuizCodeBlanksViewState.CodeBlockItem.Variable( id = 0, children = listOf( StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 0, - isActive = true, + isActive = false, value = suggestions[0].text + ), + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 1, + isActive = true, + value = suggestions[1].text ) ) ) ), suggestions = emptyList(), isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, + isSpaceButtonHidden = false, isDecreaseIndentLevelButtonHidden = true ) @@ -455,7 +506,7 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with suggestions when active code block is Variable and active child has no selected suggestion`() { + fun `Delete button should be enabled when Variable name is unselected and active value is unselected`() { val suggestions = listOf( Suggestion.ConstantString("1"), Suggestion.ConstantString("2") @@ -465,12 +516,12 @@ class StepQuizCodeBlanksViewStateMapperTest { CodeBlock.Variable( children = listOf( CodeBlockChild.SelectSuggestion( - isActive = true, + isActive = false, suggestions = suggestions, selectedSuggestion = null ), CodeBlockChild.SelectSuggestion( - isActive = false, + isActive = true, suggestions = suggestions, selectedSuggestion = null ) @@ -485,12 +536,12 @@ class StepQuizCodeBlanksViewStateMapperTest { children = listOf( StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 0, - isActive = true, + isActive = false, value = null ), StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 1, - isActive = false, + isActive = true, value = null ) ) @@ -508,7 +559,7 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Content with no suggestions when active code block is Variable and active child has selected suggestion`() { + fun `Delete button should be enabled when Variable name is unselected and active value is selected`() { val suggestions = listOf( Suggestion.ConstantString("1"), Suggestion.ConstantString("2") @@ -518,14 +569,14 @@ class StepQuizCodeBlanksViewStateMapperTest { CodeBlock.Variable( children = listOf( CodeBlockChild.SelectSuggestion( - isActive = true, + isActive = false, suggestions = suggestions, - selectedSuggestion = suggestions[0] + selectedSuggestion = null ), CodeBlockChild.SelectSuggestion( - isActive = false, + isActive = true, suggestions = suggestions, - selectedSuggestion = suggestions[1] + selectedSuggestion = suggestions[0] ) ) ) @@ -538,20 +589,20 @@ class StepQuizCodeBlanksViewStateMapperTest { children = listOf( StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 0, - isActive = true, - value = suggestions[0].text + isActive = false, + value = null ), StepQuizCodeBlanksViewState.CodeBlockChildItem( id = 1, - isActive = false, - value = suggestions[1].text + isActive = true, + value = suggestions[0].text ) ) ) ), suggestions = emptyList(), isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, + isSpaceButtonHidden = false, isDecreaseIndentLevelButtonHidden = true ) @@ -561,48 +612,699 @@ class StepQuizCodeBlanksViewStateMapperTest { } @Test - fun `Action buttons hidden when onboarding is available`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), - onboardingState = OnboardingState.HighlightSuggestions + fun `Delete button should be enabled when IfStatement active child index greater than zero`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") ) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isActionButtonsHidden) - } - - @Test - fun `Action buttons not hidden when onboarding is unavailable`() { val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), - onboardingState = OnboardingState.Unavailable + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = false, + value = suggestions[0].text + ), + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 1, + isActive = true, + value = null + ) + ) + ) + ), + suggestions = suggestions, + isDeleteButtonEnabled = true, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true ) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertFalse(viewState.isActionButtonsHidden) + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) } @Test - fun `Suggestions highlight effect is active when onboardingState is HighlightSuggestions`() { + fun `Delete button should be enabled when IfStatement child is selected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), - onboardingState = OnboardingState.HighlightSuggestions + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ) + ) + ) + ) ) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSuggestionsHighlightEffectActive) - } - - private fun stubState( + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = suggestions[0].text + ) + ) + ) + ), + suggestions = emptyList(), + isDeleteButtonEnabled = true, + isSpaceButtonHidden = false, + isDecreaseIndentLevelButtonHidden = true + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + /* ktlint-disable */ + @Test + fun `Delete button should be enabled when IfStatement child is unselected and next code block on same indent level`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = stubState( + codeBlocks = listOf( + CodeBlock.IfStatement( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ), + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( + id = 0, + indentLevel = 1, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ) + ) + ), + StepQuizCodeBlanksViewState.CodeBlockItem.Print( + id = 1, + indentLevel = 1, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ) + ) + ) + ), + suggestions = suggestions, + isDeleteButtonEnabled = true, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = false + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + /* ktlint-disable */ + @Test + fun `Delete button should be disabled when IfStatement child is unselected and next code block on different indent level`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = stubState( + codeBlocks = listOf( + CodeBlock.IfStatement( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ), + CodeBlock.Print( + indentLevel = 2, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( + id = 0, + indentLevel = 1, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ) + ) + ), + StepQuizCodeBlanksViewState.CodeBlockItem.Print( + id = 1, + indentLevel = 2, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ) + ) + ) + ), + suggestions = suggestions, + isDeleteButtonEnabled = false, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = false + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + @Test + fun `Content with suggestions when active code block is Blank`() { + val suggestions = listOf(Suggestion.Print, Suggestion.Variable) + val state = stubState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = suggestions)) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), + suggestions = suggestions, + isDeleteButtonEnabled = false, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + @Test + fun `Content without suggestions when code block active child has selected suggestion`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ) + ) + val codeBlocks = listOf( + CodeBlock.Print(children = children), + CodeBlock.Variable(children = children), + CodeBlock.IfStatement(children = children) + ) + + codeBlocks.forEach { codeBlock -> + val state = stubState(codeBlocks = listOf(codeBlock)) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.suggestions.isEmpty()) + } + } + + @Test + fun `Content with suggestions when code block active child is unselected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + val codeBlocks = listOf( + CodeBlock.Print(children = children), + CodeBlock.Variable(children = children), + CodeBlock.IfStatement(children = children) + ) + + codeBlocks.forEach { codeBlock -> + val state = stubState(codeBlocks = listOf(codeBlock)) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertEquals(suggestions, viewState.suggestions) + } + } + + @Test + fun `Content with suggestions when active code block is Variable and active child has no selected suggestion`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = stubState( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.Variable( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ), + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 1, + isActive = false, + value = null + ) + ) + ) + ), + suggestions = suggestions, + isDeleteButtonEnabled = true, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + @Test + fun `isSpaceButtonHidden should be true when codeBlanksOperationsSuggestions is empty`() { + val state = stubState( + step = Step.stub(id = 0), + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when no active code block`() { + val state = stubState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active Print code block has no active child`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active Print code block child has no selected suggestion`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be false when active Print code block child has selected suggestion`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isSpaceButtonHidden) + } + + /* ktlint-disable */ + @Test + fun `isSpaceButtonHidden should be true when active Variable code block's first child has no selected suggestion`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active Variable code block's second child has no selected suggestion`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("x") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be false when active IfStatement code block child has selected suggestion`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("if") + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active IfStatement code block child has no selected suggestion`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be true when no active code block`() { + val state = stubState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be true when active code block's indent level is less than 1`() { + val state = stubState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be false when active code block's indent level is 1 or more`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be true when previous code block is IfStatement`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.IfStatement(indentLevel = 1, children = emptyList()), + CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be false when previous code block is not IfStatement`() { + val state = stubState( + codeBlocks = listOf( + CodeBlock.Print(indentLevel = 1, children = emptyList()), + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `Action buttons hidden when onboarding is available`() { + val state = stubState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), + onboardingState = OnboardingState.HighlightSuggestions + ) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isActionButtonsHidden) + } + + @Test + fun `Action buttons not hidden when onboarding is unavailable`() { + val state = stubState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), + onboardingState = OnboardingState.Unavailable + ) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isActionButtonsHidden) + } + + @Test + fun `Suggestions highlight effect is active when onboardingState is HighlightSuggestions`() { + val state = stubState( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), + onboardingState = OnboardingState.HighlightSuggestions + ) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSuggestionsHighlightEffectActive) + } + + private fun stubState( + step: Step = Step.stub( + id = 0, + block = Block.stub( + options = Block.Options( + codeBlanksOperations = listOf("+") + ) + ) + ), codeBlocks: List, onboardingState: OnboardingState = OnboardingState.Unavailable ): StepQuizCodeBlanksFeature.State.Content = StepQuizCodeBlanksFeature.State.Content( - step = Step.stub(id = 0), + step = step, codeBlocks = codeBlocks, onboardingState = onboardingState ) -} \ No newline at end of file +} + +// internal fun Step.codeBlanksOperationsSuggestions(): List = +// block.options.codeBlanksOperations.orEmpty().map(Suggestion::ConstantString) \ No newline at end of file From 627b5dd81917c321c55f01d7fadf760d4a245a9d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 10:49:42 +0900 Subject: [PATCH 18/28] Fix tests --- .../StepQuizCodeBlanksReducerTest.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt index a24b3a5ac..d392906d4 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt @@ -1285,7 +1285,13 @@ class StepQuizCodeBlanksReducerTest { ) val expectedState = initialState.copy( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + indentLevel = 0, + suggestions = listOf(Suggestion.Print) + ) + ) ) assertEquals(expectedState, state) @@ -1309,7 +1315,7 @@ class StepQuizCodeBlanksReducerTest { val expectedState = initialState.copy( codeBlocks = listOf( CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) + CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = listOf(Suggestion.Print)) ) ) From 32f36ad503430f0e18e7718aa7eb734d84bb2159 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 12:52:59 +0900 Subject: [PATCH 19/28] Refactor tests --- .../presentation/StepQuizCodeBlanksFeature.kt | 2 + .../StepQuizCodeBlanksReducerTest.kt | 1433 ----------------- .../StepQuizCodeBlanksViewStateMapperTest.kt | 1310 --------------- .../StepQuizCodeBlanksCreateReplyTest.kt} | 107 +- .../StepQuizCodeBlanksFeatureStateStub.kt | 18 + ...eBlanksReducerCodeBlockChildClickedTest.kt | 164 ++ ...izCodeBlanksReducerCodeBlockClickedTest.kt | 129 ++ ...cerDecreaseIndentLevelButtonClickedTest.kt | 97 ++ ...odeBlanksReducerDeleteButtonClickedTest.kt | 470 ++++++ ...CodeBlanksReducerEnterButtonClickedTest.kt | 114 ++ ...StepQuizCodeBlanksReducerInitializeTest.kt | 59 + ...StepQuizCodeBlanksReducerOnboardingTest.kt | 67 + ...CodeBlanksReducerSpaceButtonClickedTest.kt | 198 +++ ...zCodeBlanksReducerSuggestionClickedTest.kt | 263 +++ .../StepQuizCodeBlanksStateExtensionsTest.kt | 85 + ...erIsDecreaseIndentLevelButtonHiddenTest.kt | 104 ++ ...iewStateMapperIsDeleteButtonEnabledTest.kt | 403 +++++ ...sViewStateMapperIsSpaceButtonHiddenTest.kt | 239 +++ ...zCodeBlanksViewStateMapperSequencesTest.kt | 170 ++ ...odeBlanksViewStateMapperSuggestionsTest.kt | 114 ++ .../view/StepQuizCodeBlanksViewStateTest.kt | 39 + 21 files changed, 2749 insertions(+), 2836 deletions(-) delete mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt delete mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt rename shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/{StepQuizCodeBlanksStateExtensionsTest.kt => presentation/StepQuizCodeBlanksCreateReplyTest.kt} (86%) create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeatureStateStub.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensionsTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDecreaseIndentLevelButtonHiddenTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDeleteButtonEnabledTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsSpaceButtonHiddenTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSequencesTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSuggestionsTest.kt create mode 100644 shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt index 0fd51a880..8cc1fe4e3 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeature.kt @@ -29,6 +29,8 @@ object StepQuizCodeBlanksFeature { val codeBlocks: List, val onboardingState: OnboardingState = OnboardingState.Unavailable ) : State { + companion object; + internal val codeBlanksStringsSuggestions: List = step.codeBlanksStringsSuggestions() diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt deleted file mode 100644 index a24b3a5ac..000000000 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksReducerTest.kt +++ /dev/null @@ -1,1433 +0,0 @@ -package org.hyperskill.step_quiz_code_blanks - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import org.hyperskill.app.step.domain.model.Block -import org.hyperskill.app.step.domain.model.Step -import org.hyperskill.app.step.domain.model.StepRoute -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSuggestionHyperskillAnalyticEvent -import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock -import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild -import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion -import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature -import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState -import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer -import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState -import org.hyperskill.step.domain.model.stub - -class StepQuizCodeBlanksReducerTest { - private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) - - @Test - fun `Initialize should return Content state with active Blank and Print and Variable and If suggestions`() { - val step = Step.stub( - id = 1, - block = Block.stub(options = Block.Options(codeBlanksVariables = listOf("a", "b"))) - ) - - val message = StepQuizCodeBlanksFeature.InternalMessage.Initialize(step) - val (state, actions) = reducer.reduce(StepQuizCodeBlanksFeature.State.Idle, message) - - val expectedState = StepQuizCodeBlanksFeature.State.Content( - step = step, - codeBlocks = listOf( - CodeBlock.Blank( - isActive = true, - suggestions = listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement) - ) - ) - ) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertEquals(expectedState.codeBlocks, state.codeBlocks) - assertTrue(actions.isEmpty()) - } - - @Test - fun `Initialize should return Content state with active Blank and Print suggestion`() { - val step = Step.stub(id = 1) - - val message = StepQuizCodeBlanksFeature.InternalMessage.Initialize(step) - val (state, actions) = reducer.reduce(StepQuizCodeBlanksFeature.State.Idle, message) - - val expectedState = StepQuizCodeBlanksFeature.State.Content( - step = step, - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) - ) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertEquals(expectedState.codeBlocks, state.codeBlocks) - assertTrue(actions.isEmpty()) - } - - @Test - fun `SuggestionClicked should not update state if no active code block`() { - val initialState = - stubContentState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Print) - val (state, actions) = reducer.reduce(initialState, message) - - assertEquals(initialState, state) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `SuggestionClicked should not update state if suggestion does not exist`() { - val initialState = - stubContentState(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.ConstantString("test")) - val (state, actions) = reducer.reduce(initialState, message) - - assertEquals(initialState, state) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `SuggestionClicked should not update state if state is not Content`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Print) - val (state, actions) = reducer.reduce(initialState, message) - - assertEquals(initialState, state) - assertTrue(actions.isEmpty()) - } - - @Test - fun `SuggestionClicked should update active Blank code block to Print if suggestion exists`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank( - isActive = true, - suggestions = listOf(Suggestion.Print) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Print) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = initialState.codeBlanksStringsSuggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `SuggestionClicked should update active Blank code block to Variable if suggestion exists`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank( - isActive = true, - suggestions = listOf(Suggestion.Print, Suggestion.Variable) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Variable) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = initialState.codeBlanksVariablesSuggestions, - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = initialState.codeBlanksStringsSuggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `SuggestionClicked should update Print code block with selected suggestion`() { - val suggestion = Suggestion.ConstantString("suggestion") - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) - val (state, actions) = reducer.reduce(initialState, message) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertEquals(suggestion, (state.codeBlocks[0] as CodeBlock.Print).children[0].selectedSuggestion) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `SuggestionClicked should update Variable code block with selected suggestion for name`() { - val suggestion = Suggestion.ConstantString("suggestion") - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(suggestion), - selectedSuggestion = suggestion - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertEquals(expectedState.codeBlocks, state.codeBlocks) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `SuggestionClicked should update Variable code block with selected suggestion for value`() { - val suggestion = Suggestion.ConstantString("suggestion") - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(suggestion), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(suggestion), - selectedSuggestion = suggestion - ) - ) - ) - ) - ) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertEquals(expectedState.codeBlocks, state.codeBlocks) - assertContainsSuggestionClickedAnalyticEvent(actions) - } - - @Test - fun `CodeBlockClicked should not update state if state is not Content`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true) - ) - val (state, actions) = reducer.reduce(initialState, message) - - assertEquals(initialState, state) - assertTrue(actions.isEmpty()) - } - - @Test - fun `CodeBlockClicked should update active Print code block`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, suggestions = emptyList(), selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = false) - ) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Blank(isActive = true, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, suggestions = emptyList(), selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent - } - } - } - - @Test - fun `CodeBlockClicked should update active Variable code block`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, suggestions = emptyList(), selectedSuggestion = null - ) - ) - ), - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, suggestions = emptyList(), selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, suggestions = emptyList(), selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = false) - ) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, suggestions = emptyList(), selectedSuggestion = null - ) - ) - ), - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, suggestions = emptyList(), selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, suggestions = emptyList(), selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent - } - } - } - - @Test - fun `CodeBlockChildClicked should not update state if state is not Content`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Variable(id = 0, children = emptyList()), - codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) - ) - val (state, actions) = reducer.reduce(initialState, message) - - assertEquals(initialState, state) - assertTrue(actions.isEmpty()) - } - - @Test - fun `CodeBlockChildClicked should not update state if target code block is not found`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Variable(id = 1, children = emptyList()), - codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) - ) - val (state, actions) = reducer.reduce(initialState, message) - - assertEquals(initialState, state) - assertContainsCodeBlockChildClickedAnalyticEvent(actions) - } - - @Test - fun `CodeBlockChildClicked should update state to activate the clicked Variable child`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Variable(id = 0, children = emptyList()), - codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) - ) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsCodeBlockChildClickedAnalyticEvent(actions) - } - - @Test - fun `CodeBlockChildClicked should update state to activate the clicked Print child`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( - codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Print(id = 0, children = emptyList()), - codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) - ) - val (state, actions) = reducer.reduce(initialState, message) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsCodeBlockChildClickedAnalyticEvent(actions) - } - - @Test - fun `DeleteButtonClicked should not update state if state is not Content`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - assertEquals(initialState, state) - assertTrue(actions.isEmpty()) - } - - @Test - fun `DeleteButtonClicked should log analytic event and not update state if no active code block`() { - val initialState = - stubContentState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - assertEquals(initialState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - - @Test - fun `DeleteButtonClicked should not update state if active code block is Blank and single`() { - val initialState = - stubContentState(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - assertEquals(initialState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - - @Test - fun `DeleteButtonClicked should clear suggestion if active Print code block has selected suggestion`() { - val suggestion = Suggestion.ConstantString("suggestion") - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = suggestion - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - - @Test - fun `DeleteButtonClicked should set next code block as active if no code block before deleted`() { - val initialStates = listOf( - stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ), - CodeBlock.Blank(isActive = false, suggestions = emptyList()) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = true, suggestions = emptyList()), - CodeBlock.Blank(isActive = false, suggestions = emptyList()) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = true, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ), - CodeBlock.Blank(isActive = false, suggestions = emptyList()) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = true, suggestions = emptyList()), - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ), - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - ) - val expectedStates = listOf( - initialStates[0].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), - initialStates[1].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), - initialStates[2].copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - initialStates[3].copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - initialStates[4].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), - initialStates[5].copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - initialStates[6].copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - ) - - initialStates.zip(expectedStates).forEach { (initialState, expectedState) -> - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - assertEquals(expectedState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - } - - @Test - fun `DeleteButtonClicked should set previous code block as active if has code block before deleted`() { - val initialStates = listOf( - stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ), - CodeBlock.Blank(isActive = true, suggestions = emptyList()) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(Suggestion.ConstantString("suggestion")), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, suggestions = emptyList()) - ) - ) - ) - val expectedStates = listOf( - initialStates[0].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), - initialStates[1].copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ), - initialStates[2].copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(Suggestion.ConstantString("suggestion")), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ), - initialStates[0].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), - ) - - initialStates.zip(expectedStates).forEach { (initialState, expectedState) -> - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - assertEquals(expectedState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - } - - @Test - fun `DeleteButtonClicked should not update state if no active code block`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - assertEquals(initialState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - - @Test - fun `DeleteButtonClicked should replace single Print code block with Blank`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) - ) - - assertEquals(expectedState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - - @Test - fun `DeleteButtonClicked should replace single Variable code block with Blank`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) - ) - - assertEquals(expectedState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - - @Test - fun `EnterButtonClicked should not update state if state is not Content`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) - - assertEquals(initialState, state) - assertTrue(actions.isEmpty()) - } - - @Test - fun `EnterButtonClicked should log analytic event and not update state if no active code block`() { - val initialState = - stubContentState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) - - assertEquals(initialState, state) - assertContainsEnterButtonClickedAnalyticEvent(actions) - } - - @Test - fun `EnterButtonClicked should log analytic event and add new active Blank block if active code block exists`() { - val initialState = - stubContentState(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print)) - ) - ) - - assertEquals(expectedState, state) - assertContainsEnterButtonClickedAnalyticEvent(actions) - } - - @Test - fun `EnterButtonClicked should add new active Blank block after active code block`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = true, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print)), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsEnterButtonClickedAnalyticEvent(actions) - } - - @Test - fun `SpaceButtonClicked should not update state if state is not Content`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) - - assertEquals(initialState, state) - assertTrue(actions.isEmpty()) - } - - @Test - fun `SpaceButtonClicked should not update state if active Print block has no active child`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) - - assertEquals(initialState, state) - assertContainsSpaceButtonClickedAnalyticEvent(actions) - } - - @Test - fun `SpaceButtonClicked should add a new child to active Print code block`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(Suggestion.ConstantString("suggestion")), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(Suggestion.ConstantString("suggestion")), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsSpaceButtonClickedAnalyticEvent(actions) - } - - @Test - fun `SpaceButtonClicked should add a new child to active Variable code block`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(Suggestion.ConstantString("x")), - selectedSuggestion = Suggestion.ConstantString("x") - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(Suggestion.ConstantString("suggestion")), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(Suggestion.ConstantString("x")), - selectedSuggestion = Suggestion.ConstantString("x") - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(Suggestion.ConstantString("suggestion")), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsSpaceButtonClickedAnalyticEvent(actions) - } - - @Test - fun `SpaceButtonClicked should add a new child with operations suggestions after closing parentheses`() { - val initialState = stubContentState( - step = Step.stub( - id = 1, - block = Block.stub( - options = Block.Options(codeBlanksOperations = listOf("*", "+")) - ) - ), - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(Suggestion.ConstantString(")")), - selectedSuggestion = Suggestion.ConstantString(")") - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = listOf(Suggestion.ConstantString(")")), - selectedSuggestion = Suggestion.ConstantString(")") - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = initialState.codeBlanksOperationsSuggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsSpaceButtonClickedAnalyticEvent(actions) - } - - @Test - fun `DecreaseIndentLevelButtonClicked should not update state if no active code block`() { - val initialState = stubContentState( - codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList())) - ) - - val (state, actions) = reducer.reduce( - initialState, - StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked - ) - - assertEquals(initialState, state) - assertContainsDecreaseIndentLevelAnalyticEvent(actions) - } - - @Test - fun `DecreaseIndentLevelButtonClicked should not decrease indent level below 1`() { - val initialState = stubContentState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) - ) - - val (state, actions) = reducer.reduce( - initialState, - StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked - ) - - assertEquals(initialState, state) - assertContainsDecreaseIndentLevelAnalyticEvent(actions) - } - - @Test - fun `DecreaseIndentLevelButtonClicked should decrease indent level by 1`() { - val initialState = stubContentState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList())) - ) - - val (state, actions) = reducer.reduce( - initialState, - StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked - ) - - val expectedState = initialState.copy( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) - ) - - assertEquals(expectedState, state) - assertContainsDecreaseIndentLevelAnalyticEvent(actions) - } - - @Test - fun `DecreaseIndentLevelButtonClicked should decrease indent level for active code block only`() { - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, indentLevel = 2, suggestions = emptyList()) - ) - ) - - val (state, actions) = reducer.reduce( - initialState, - StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked - ) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) - ) - ) - - assertEquals(expectedState, state) - assertContainsDecreaseIndentLevelAnalyticEvent(actions) - } - - @Test - fun `Onboarding should be unavailable`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val (state, _) = reducer.reduce( - initialState, - StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 1)) - ) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertTrue(state.onboardingState is OnboardingState.Unavailable) - } - - @Test - fun `Onboarding should be available`() { - val initialState = StepQuizCodeBlanksFeature.State.Idle - val (state, _) = reducer.reduce( - initialState, - StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 47329)) - ) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertTrue(state.onboardingState is OnboardingState.HighlightSuggestions) - } - - @Test - fun `Onboarding SuggestionClicked should update onboardingState to HighlightCallToActionButton`() { - val suggestion = Suggestion.ConstantString("suggestion") - val initialState = stubContentState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ), - onboardingState = OnboardingState.HighlightSuggestions - ) - - val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) - val (state, _) = reducer.reduce(initialState, message) - - assertTrue(state is StepQuizCodeBlanksFeature.State.Content) - assertEquals(OnboardingState.HighlightCallToActionButton, state.onboardingState) - } - - private fun assertContainsSuggestionClickedAnalyticEvent(actions: Set) { - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedSuggestionHyperskillAnalyticEvent - } - } - } - - private fun assertContainsCodeBlockChildClickedAnalyticEvent(actions: Set) { - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent - } - } - } - - private fun assertContainsDeleteButtonClickedAnalyticEvent(actions: Set) { - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent - } - } - } - - private fun assertContainsEnterButtonClickedAnalyticEvent(actions: Set) { - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent - } - } - } - - private fun assertContainsSpaceButtonClickedAnalyticEvent(actions: Set) { - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent - } - } - } - - private fun assertContainsDecreaseIndentLevelAnalyticEvent(actions: Set) { - assertTrue { - actions.any { - it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && - it.analyticEvent is StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent - } - } - } - - private fun stubContentState( - step: Step = Step.stub(id = 1), - codeBlocks: List, - onboardingState: OnboardingState = OnboardingState.Unavailable - ): StepQuizCodeBlanksFeature.State.Content = - StepQuizCodeBlanksFeature.State.Content( - step = step, - codeBlocks = codeBlocks, - onboardingState = onboardingState - ) -} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt deleted file mode 100644 index c6ec3a274..000000000 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksViewStateMapperTest.kt +++ /dev/null @@ -1,1310 +0,0 @@ -package org.hyperskill.step_quiz_code_blanks - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue -import org.hyperskill.app.step.domain.model.Block -import org.hyperskill.app.step.domain.model.Step -import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock -import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild -import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion -import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature -import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState -import org.hyperskill.app.step_quiz_code_blanks.view.mapper.StepQuizCodeBlanksViewStateMapper -import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState -import org.hyperskill.step.domain.model.stub - -class StepQuizCodeBlanksViewStateMapperTest { - @Test - fun `map should return Idle view state for Idle state`() { - val state = StepQuizCodeBlanksFeature.State.Idle - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - assertEquals(StepQuizCodeBlanksViewState.Idle, viewState) - } - - @Test - fun `Content with suggestions and enabled delete button when active code block is Print`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Content with sequence of filled Print and active Blank`() { - val printSuggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = printSuggestions, - selectedSuggestion = printSuggestions[0] - ) - ) - ), - CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print)) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = printSuggestions[0].text - ) - ) - ), - StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 1, isActive = true) - ), - suggestions = listOf(Suggestion.Print), - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Content with sequence of filled Print and active not filled Print`() { - val printSuggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = printSuggestions, - selectedSuggestion = printSuggestions[0] - ) - ) - ), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = printSuggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = printSuggestions[0].text - ) - ) - ), - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 1, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = printSuggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be disabled when active code block is Blank and single`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), - suggestions = listOf(Suggestion.Print), - isDeleteButtonEnabled = false, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when active code block is Print and single`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(Suggestion.Print), - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = listOf(Suggestion.Print), - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when Variable active name is unselected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = false, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when Variable active name is selected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = suggestions[0].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = false, - value = null - ) - ) - ) - ), - suggestions = emptyList(), - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when Variable active value child index is greater than one`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = suggestions[1] - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = suggestions[0].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = false, - value = suggestions[1].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 2, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be disabled when Variable name is selected and active value is unselected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = suggestions[0].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = false, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when Variable name is selected and active value is selected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = suggestions[1] - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = suggestions[0].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = true, - value = suggestions[1].text - ) - ) - ) - ), - suggestions = emptyList(), - isDeleteButtonEnabled = true, - isSpaceButtonHidden = false, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when Variable name is unselected and active value is unselected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = null - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when Variable name is unselected and active value is selected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = null - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = true, - value = suggestions[0].text - ) - ) - ) - ), - suggestions = emptyList(), - isDeleteButtonEnabled = true, - isSpaceButtonHidden = false, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when IfStatement active child index greater than zero`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = false, - value = suggestions[0].text - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Delete button should be enabled when IfStatement child is selected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = suggestions[0].text - ) - ) - ) - ), - suggestions = emptyList(), - isDeleteButtonEnabled = true, - isSpaceButtonHidden = false, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - /* ktlint-disable */ - @Test - fun `Delete button should be enabled when IfStatement child is unselected and next code block on same indent level`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement( - indentLevel = 1, - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ), - CodeBlock.Print( - indentLevel = 1, - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( - id = 0, - indentLevel = 1, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ), - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 1, - indentLevel = 1, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = false - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - /* ktlint-disable */ - @Test - fun `Delete button should be disabled when IfStatement child is unselected and next code block on different indent level`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement( - indentLevel = 1, - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ), - CodeBlock.Print( - indentLevel = 2, - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement( - id = 0, - indentLevel = 1, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ), - StepQuizCodeBlanksViewState.CodeBlockItem.Print( - id = 1, - indentLevel = 2, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = false, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = false - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Content with suggestions when active code block is Blank`() { - val suggestions = listOf(Suggestion.Print, Suggestion.Variable) - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = suggestions)) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf(StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true)), - suggestions = suggestions, - isDeleteButtonEnabled = false, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `Content without suggestions when code block active child has selected suggestion`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = suggestions[0] - ) - ) - val codeBlocks = listOf( - CodeBlock.Print(children = children), - CodeBlock.Variable(children = children), - CodeBlock.IfStatement(children = children) - ) - - codeBlocks.forEach { codeBlock -> - val state = stubState(codeBlocks = listOf(codeBlock)) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.suggestions.isEmpty()) - } - } - - @Test - fun `Content with suggestions when code block active child is unselected`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - val codeBlocks = listOf( - CodeBlock.Print(children = children), - CodeBlock.Variable(children = children), - CodeBlock.IfStatement(children = children) - ) - - codeBlocks.forEach { codeBlock -> - val state = stubState(codeBlocks = listOf(codeBlock)) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertEquals(suggestions, viewState.suggestions) - } - } - - @Test - fun `Content with suggestions when active code block is Variable and active child has no selected suggestion`() { - val suggestions = listOf( - Suggestion.ConstantString("1"), - Suggestion.ConstantString("2") - ) - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = suggestions, - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = suggestions, - selectedSuggestion = null - ) - ) - ) - ) - ) - val expectedViewState = StepQuizCodeBlanksViewState.Content( - codeBlocks = listOf( - StepQuizCodeBlanksViewState.CodeBlockItem.Variable( - id = 0, - children = listOf( - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 0, - isActive = true, - value = null - ), - StepQuizCodeBlanksViewState.CodeBlockChildItem( - id = 1, - isActive = false, - value = null - ) - ) - ) - ), - suggestions = suggestions, - isDeleteButtonEnabled = true, - isSpaceButtonHidden = true, - isDecreaseIndentLevelButtonHidden = true - ) - - val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertEquals(expectedViewState, actualViewState) - } - - @Test - fun `isSpaceButtonHidden should be true when codeBlanksOperationsSuggestions is empty`() { - val state = stubState( - step = Step.stub(id = 0), - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be true when no active code block`() { - val state = stubState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be true when active Print code block has no active child`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be true when active Print code block child has no selected suggestion`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be false when active Print code block child has selected suggestion`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("suggestion") - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertFalse(viewState.isSpaceButtonHidden) - } - - /* ktlint-disable */ - @Test - fun `isSpaceButtonHidden should be true when active Variable code block's first child has no selected suggestion`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ), - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be true when active Variable code block's second child has no selected suggestion`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Variable( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("x") - ), - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be false when active IfStatement code block child has selected suggestion`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = Suggestion.ConstantString("if") - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertFalse(viewState.isSpaceButtonHidden) - } - - @Test - fun `isSpaceButtonHidden should be true when active IfStatement code block child has no selected suggestion`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSpaceButtonHidden) - } - - @Test - fun `isDecreaseIndentLevelButtonHidden should be true when no active code block`() { - val state = stubState(codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList()))) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isDecreaseIndentLevelButtonHidden) - } - - @Test - fun `isDecreaseIndentLevelButtonHidden should be true when active code block's indent level is less than 1`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isDecreaseIndentLevelButtonHidden) - } - - @Test - fun `isDecreaseIndentLevelButtonHidden should be false when active code block's indent level is 1 or more`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print( - indentLevel = 1, - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertFalse(viewState.isDecreaseIndentLevelButtonHidden) - } - - @Test - fun `isDecreaseIndentLevelButtonHidden should be true when previous code block is IfStatement`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.IfStatement(indentLevel = 1, children = emptyList()), - CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isDecreaseIndentLevelButtonHidden) - } - - @Test - fun `isDecreaseIndentLevelButtonHidden should be false when previous code block is not IfStatement`() { - val state = stubState( - codeBlocks = listOf( - CodeBlock.Print(indentLevel = 1, children = emptyList()), - CodeBlock.Print( - indentLevel = 1, - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertFalse(viewState.isDecreaseIndentLevelButtonHidden) - } - - @Test - fun `Action buttons hidden when onboarding is available`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), - onboardingState = OnboardingState.HighlightSuggestions - ) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isActionButtonsHidden) - } - - @Test - fun `Action buttons not hidden when onboarding is unavailable`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), - onboardingState = OnboardingState.Unavailable - ) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertFalse(viewState.isActionButtonsHidden) - } - - @Test - fun `Suggestions highlight effect is active when onboardingState is HighlightSuggestions`() { - val state = stubState( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())), - onboardingState = OnboardingState.HighlightSuggestions - ) - val viewState = StepQuizCodeBlanksViewStateMapper.map(state) - - assertTrue(viewState is StepQuizCodeBlanksViewState.Content) - assertTrue(viewState.isSuggestionsHighlightEffectActive) - } - - private fun stubState( - step: Step = Step.stub( - id = 0, - block = Block.stub( - options = Block.Options( - codeBlanksOperations = listOf("+") - ) - ) - ), - codeBlocks: List, - onboardingState: OnboardingState = OnboardingState.Unavailable - ): StepQuizCodeBlanksFeature.State.Content = - StepQuizCodeBlanksFeature.State.Content( - step = step, - codeBlocks = codeBlocks, - onboardingState = onboardingState - ) -} - -// internal fun Step.codeBlanksOperationsSuggestions(): List = -// block.options.codeBlanksOperations.orEmpty().map(Suggestion::ConstantString) \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksCreateReplyTest.kt similarity index 86% rename from shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt rename to shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksCreateReplyTest.kt index 4517b15c9..a4e9aa148 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksStateExtensionsTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksCreateReplyTest.kt @@ -1,65 +1,31 @@ -package org.hyperskill.step_quiz_code_blanks +package org.hyperskill.step_quiz_code_blanks.presentation import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue import org.hyperskill.app.step.domain.model.Block import org.hyperskill.app.step.domain.model.Step import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature -import org.hyperskill.app.step_quiz_code_blanks.presentation.activeCodeBlockIndex import org.hyperskill.app.step_quiz_code_blanks.presentation.createReply -import org.hyperskill.app.step_quiz_code_blanks.presentation.isVariableSuggestionsAvailable import org.hyperskill.app.submissions.domain.model.Reply import org.hyperskill.step.domain.model.stub -class StepQuizCodeBlanksStateExtensionsTest { +class StepQuizCodeBlanksCreateReplyTest { companion object { private const val REPLY_CODE_LANGUAGE = "python3" private const val REPLY_CODE_PREFIX = "# solved with code blanks\n" } - @Test - fun `activeCodeBlockIndex should return null if no active code block`() { - val state = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = false, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) - ) - ) - assertNull(state.activeCodeBlockIndex()) - } - - @Test - fun `activeCodeBlockIndex should return index of the active code block`() { - val state = stubContentState( - codeBlocks = listOf( - CodeBlock.Blank(isActive = false, suggestions = emptyList()), - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = emptyList(), - selectedSuggestion = null - ) - ) - ) + private val step = Step.stub( + id = 1, + block = Block.stub( + options = Block.Options( + codeTemplates = mapOf(REPLY_CODE_LANGUAGE to "# put your python code here") ) ) - assertEquals(1, state.activeCodeBlockIndex()) - } + ) @Test fun `createReply should return Reply with code from code blocks and language from step options`() { @@ -75,7 +41,7 @@ class StepQuizCodeBlanksStateExtensionsTest { ), CodeBlock.Blank(isActive = true, suggestions = emptyList()) ) - val state = stubContentState(codeBlocks = codeBlocks) + val state = StepQuizCodeBlanksFeature.State.Content.stub(step = step, codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { @@ -115,7 +81,7 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ), ) - val state = stubContentState(codeBlocks = codeBlocks) + val state = StepQuizCodeBlanksFeature.State.Content.stub(step = step, codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { @@ -233,7 +199,7 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ) ) - val state = stubContentState(codeBlocks = codeBlocks) + val state = StepQuizCodeBlanksFeature.State.Content.stub(step = step, codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { @@ -368,7 +334,7 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ) ) - val state = stubContentState(codeBlocks = codeBlocks) + val state = StepQuizCodeBlanksFeature.State.Content.stub(step = step, codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { @@ -446,7 +412,7 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ) ) - val state = stubContentState(codeBlocks = codeBlocks) + val state = StepQuizCodeBlanksFeature.State.Content.stub(step = step, codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { @@ -597,7 +563,7 @@ class StepQuizCodeBlanksStateExtensionsTest { ) ) ) - val state = stubContentState(codeBlocks = codeBlocks) + val state = StepQuizCodeBlanksFeature.State.Content.stub(step = step, codeBlocks = codeBlocks) val expectedReply = Reply.code( code = buildString { @@ -617,49 +583,4 @@ class StepQuizCodeBlanksStateExtensionsTest { assertEquals(expectedReply, state.createReply()) } - - @Test - fun `isVariableSuggestionsAvailable should return true if variable suggestions are available`() { - val step = Step.stub( - id = 1, - block = Block.stub(options = Block.Options(codeBlanksVariables = listOf("a", "b"))) - ) - val state = stubContentState( - step = step, - codeBlocks = emptyList() - ) - - assertTrue(state.isVariableSuggestionsAvailable) - } - - @Test - fun `isVariableSuggestionsAvailable should return false if variable suggestions are not available`() { - listOf(null, emptyList()).forEach { codeBlanksVariables -> - val step = Step.stub( - id = 1, - block = Block.stub(options = Block.Options(codeBlanksVariables = codeBlanksVariables)) - ) - val state = stubContentState( - step = step, - codeBlocks = emptyList() - ) - - assertFalse(state.isVariableSuggestionsAvailable) - } - } - - private fun stubContentState( - step: Step = Step.stub(id = 1).copy( - block = Block.stub( - options = Block.Options( - codeTemplates = mapOf(REPLY_CODE_LANGUAGE to "# put your python code here") - ) - ) - ), - codeBlocks: List - ): StepQuizCodeBlanksFeature.State.Content = - StepQuizCodeBlanksFeature.State.Content( - step = step, - codeBlocks = codeBlocks - ) } \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeatureStateStub.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeatureStateStub.kt new file mode 100644 index 000000000..20feff2f7 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksFeatureStateStub.kt @@ -0,0 +1,18 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState +import org.hyperskill.step.domain.model.stub + +fun StepQuizCodeBlanksFeature.State.Content.Companion.stub( + step: Step = Step.stub(id = 1), + codeBlocks: List = emptyList(), + onboardingState: OnboardingState = OnboardingState.Unavailable +): StepQuizCodeBlanksFeature.State.Content = + StepQuizCodeBlanksFeature.State.Content( + step = step, + codeBlocks = codeBlocks, + onboardingState = onboardingState + ) \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt new file mode 100644 index 000000000..feea44ebf --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt @@ -0,0 +1,164 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState + +class StepQuizCodeBlanksReducerCodeBlockChildClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `CodeBlockChildClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Variable(id = 0, children = emptyList()), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + + @Test + fun `CodeBlockChildClicked should not update state if target code block is not found`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Variable(id = 1, children = emptyList()), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockChildClicked should update state to activate the clicked Variable child`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Variable(id = 0, children = emptyList()), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockChildClicked should update state to activate the clicked Print child`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Print(id = 0, children = emptyList()), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + + private fun assertContainsCodeBlockChildClickedAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedCodeBlockChildHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt new file mode 100644 index 000000000..7a88b7b36 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt @@ -0,0 +1,129 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState + +class StepQuizCodeBlanksReducerCodeBlockClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `CodeBlockClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + + @Test + fun `CodeBlockClicked should update active Print code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = false) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank(isActive = true, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockClicked should update active Variable code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, suggestions = emptyList(), selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = false) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockClickedAnalyticEvent(actions) + } + + private fun assertContainsCodeBlockClickedAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedCodeBlockHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt new file mode 100644 index 000000000..63c0f39d7 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt @@ -0,0 +1,97 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer + +class StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `DecreaseIndentLevelButtonClicked should not update state if no active code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = false, suggestions = emptyList())) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + assertEquals(initialState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + @Test + fun `DecreaseIndentLevelButtonClicked should not decrease indent level below 1`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + assertEquals(initialState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + @Test + fun `DecreaseIndentLevelButtonClicked should decrease indent level by 1`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList())) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + @Test + fun `DecreaseIndentLevelButtonClicked should decrease indent level for active code block only`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, indentLevel = 2, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) + ) + ) + + assertEquals(expectedState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + + private fun assertContainsDecreaseIndentLevelAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt new file mode 100644 index 000000000..2ab0d37fe --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt @@ -0,0 +1,470 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer + +class StepQuizCodeBlanksReducerDeleteButtonClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `DeleteButtonClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + + @Test + fun `DeleteButtonClicked should not update state if no active code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + assertEquals(initialState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should not update state if active code block is Blank and single`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = emptyList() + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + assertEquals(initialState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should clear suggestion if active Print code block has selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = suggestion + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should set next code block as active if no code block before deleted`() { + val initialStates = listOf( + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Blank(isActive = false, suggestions = emptyList()) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = true, suggestions = emptyList()), + CodeBlock.Blank(isActive = false, suggestions = emptyList()) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = true, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Blank(isActive = false, suggestions = emptyList()) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = true, suggestions = emptyList()), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + ) + val expectedStates = listOf( + initialStates[0].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), + initialStates[1].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), + initialStates[2].copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + initialStates[3].copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + initialStates[4].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), + initialStates[5].copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + initialStates[6].copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + ) + + initialStates.zip(expectedStates).forEach { (initialState, expectedState) -> + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + } + + @Test + fun `DeleteButtonClicked should set previous code block as active if has code block before deleted`() { + val initialStates = listOf( + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Blank(isActive = true, suggestions = emptyList()) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(Suggestion.ConstantString("suggestion")), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, suggestions = emptyList()) + ) + ) + ) + val expectedStates = listOf( + initialStates[0].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), + initialStates[1].copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ), + initialStates[2].copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(Suggestion.ConstantString("suggestion")), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ), + initialStates[0].copy(codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList()))), + ) + + initialStates.zip(expectedStates).forEach { (initialState, expectedState) -> + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + } + + @Test + fun `DeleteButtonClicked should replace single Print code block with Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should replace single Variable code block with Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + private fun assertContainsDeleteButtonClickedAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedDeleteHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt new file mode 100644 index 000000000..1f52b1479 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt @@ -0,0 +1,114 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer + +class StepQuizCodeBlanksReducerEnterButtonClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `EnterButtonClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + + @Test + fun `EnterButtonClicked should log analytic event and not update state if no active code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = false, + suggestions = emptyList() + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) + + assertEquals(initialState, state) + assertContainsEnterButtonClickedAnalyticEvent(actions) + } + + @Test + fun `EnterButtonClicked should log analytic event and add new active Blank block if active code block exists`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = emptyList() + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print)) + ) + ) + + assertEquals(expectedState, state) + assertContainsEnterButtonClickedAnalyticEvent(actions) + } + + @Test + fun `EnterButtonClicked should add new active Blank block after active code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = true, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print)), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsEnterButtonClickedAnalyticEvent(actions) + } + + private fun assertContainsEnterButtonClickedAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedEnterHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt new file mode 100644 index 000000000..ef8ad0c50 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerInitializeTest.kt @@ -0,0 +1,59 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.Block +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer +import org.hyperskill.step.domain.model.stub + +class StepQuizCodeBlanksReducerInitializeTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `Initialize should return Content state with active Blank and Print and Variable and If suggestions`() { + val step = Step.stub( + id = 1, + block = Block.stub(options = Block.Options(codeBlanksVariables = listOf("a", "b"))) + ) + + val message = StepQuizCodeBlanksFeature.InternalMessage.Initialize(step) + val (state, actions) = reducer.reduce(StepQuizCodeBlanksFeature.State.Idle, message) + + val expectedState = StepQuizCodeBlanksFeature.State.Content( + step = step, + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = listOf(Suggestion.Print, Suggestion.Variable, Suggestion.IfStatement) + ) + ) + ) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(expectedState.codeBlocks, state.codeBlocks) + assertTrue(actions.isEmpty()) + } + + @Test + fun `Initialize should return Content state with active Blank and Print suggestion`() { + val step = Step.stub(id = 1) + + val message = StepQuizCodeBlanksFeature.InternalMessage.Initialize(step) + val (state, actions) = reducer.reduce(StepQuizCodeBlanksFeature.State.Idle, message) + + val expectedState = StepQuizCodeBlanksFeature.State.Content( + step = step, + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(expectedState.codeBlocks, state.codeBlocks) + assertTrue(actions.isEmpty()) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt new file mode 100644 index 000000000..e6f4e5daf --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerOnboardingTest.kt @@ -0,0 +1,67 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer +import org.hyperskill.step.domain.model.stub + +class StepQuizCodeBlanksReducerOnboardingTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `Onboarding should be unavailable`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val (state, _) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 1)) + ) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertTrue(state.onboardingState is OnboardingState.Unavailable) + } + + @Test + fun `Onboarding should be available`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val (state, _) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.InternalMessage.Initialize(Step.stub(id = 47329)) + ) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertTrue(state.onboardingState is OnboardingState.HighlightSuggestions) + } + + @Test + fun `Onboarding SuggestionClicked should update onboardingState to HighlightCallToActionButton`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ), + onboardingState = OnboardingState.HighlightSuggestions + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, _) = reducer.reduce(initialState, message) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(OnboardingState.HighlightCallToActionButton, state.onboardingState) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt new file mode 100644 index 000000000..b1295d945 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt @@ -0,0 +1,198 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.Block +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer +import org.hyperskill.step.domain.model.stub + +class StepQuizCodeBlanksReducerSpaceButtonClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `SpaceButtonClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + + @Test + fun `SpaceButtonClicked should not update state if active Print block has no active child`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + assertEquals(initialState, state) + assertContainsSpaceButtonClickedAnalyticEvent(actions) + } + + @Test + fun `SpaceButtonClicked should add a new child to active Print code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(Suggestion.ConstantString("suggestion")), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(Suggestion.ConstantString("suggestion")), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSpaceButtonClickedAnalyticEvent(actions) + } + + @Test + fun `SpaceButtonClicked should add a new child to active Variable code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(Suggestion.ConstantString("x")), + selectedSuggestion = Suggestion.ConstantString("x") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(Suggestion.ConstantString("suggestion")), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(Suggestion.ConstantString("x")), + selectedSuggestion = Suggestion.ConstantString("x") + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(Suggestion.ConstantString("suggestion")), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSpaceButtonClickedAnalyticEvent(actions) + } + + @Test + fun `SpaceButtonClicked should add a new child with operations suggestions after closing parentheses`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + step = Step.stub( + id = 1, + block = Block.stub( + options = Block.Options(codeBlanksOperations = listOf("*", "+")) + ) + ), + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(Suggestion.ConstantString(")")), + selectedSuggestion = Suggestion.ConstantString(")") + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(Suggestion.ConstantString(")")), + selectedSuggestion = Suggestion.ConstantString(")") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = initialState.codeBlanksOperationsSuggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSpaceButtonClickedAnalyticEvent(actions) + } + + private fun assertContainsSpaceButtonClickedAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedSpaceHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt new file mode 100644 index 000000000..5eedd6de7 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt @@ -0,0 +1,263 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.StepRoute +import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedSuggestionHyperskillAnalyticEvent +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer + +class StepQuizCodeBlanksReducerSuggestionClickedTest { + private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + + @Test + fun `SuggestionClicked should not update state if no active code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = false, + suggestions = emptyList() + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Print) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should not update state if suggestion does not exist`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = emptyList() + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.ConstantString("test")) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Print) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + + @Test + fun `SuggestionClicked should update active Blank code block to Print if suggestion exists`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = listOf(Suggestion.Print) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Print) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = initialState.codeBlanksStringsSuggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update active Blank code block to Variable if suggestion exists`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = listOf(Suggestion.Print, Suggestion.Variable) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.Variable) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = initialState.codeBlanksVariablesSuggestions, + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = initialState.codeBlanksStringsSuggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update Print code block with selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, actions) = reducer.reduce(initialState, message) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(suggestion, (state.codeBlocks[0] as CodeBlock.Print).children[0].selectedSuggestion) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update Variable code block with selected suggestion for name`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(suggestion), + selectedSuggestion = suggestion + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(expectedState.codeBlocks, state.codeBlocks) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update Variable code block with selected suggestion for value`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(suggestion), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = listOf(suggestion), + selectedSuggestion = suggestion + ) + ) + ) + ) + ) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(expectedState.codeBlocks, state.codeBlocks) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + private fun assertContainsSuggestionClickedAnalyticEvent(actions: Set) { + assertTrue { + actions.any { + it is StepQuizCodeBlanksFeature.InternalAction.LogAnalyticEvent && + it.analyticEvent is StepQuizCodeBlanksClickedSuggestionHyperskillAnalyticEvent + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensionsTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensionsTest.kt new file mode 100644 index 000000000..02dc52f29 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksStateExtensionsTest.kt @@ -0,0 +1,85 @@ +package org.hyperskill.step_quiz_code_blanks.presentation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.Block +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.presentation.activeCodeBlockIndex +import org.hyperskill.app.step_quiz_code_blanks.presentation.isVariableSuggestionsAvailable +import org.hyperskill.step.domain.model.stub + +class StepQuizCodeBlanksStateExtensionsTest { + @Test + fun `activeCodeBlockIndex should return null if no active code block`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + assertNull(state.activeCodeBlockIndex()) + } + + @Test + fun `activeCodeBlockIndex should return index of the active code block`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + assertEquals(1, state.activeCodeBlockIndex()) + } + + @Test + fun `isVariableSuggestionsAvailable should return true if variable suggestions are available`() { + val step = Step.stub( + id = 1, + block = Block.stub(options = Block.Options(codeBlanksVariables = listOf("a", "b"))) + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = emptyList() + ) + + assertTrue(state.isVariableSuggestionsAvailable) + } + + @Test + fun `isVariableSuggestionsAvailable should return false if variable suggestions are not available`() { + listOf(null, emptyList()).forEach { codeBlanksVariables -> + val step = Step.stub( + id = 1, + block = Block.stub(options = Block.Options(codeBlanksVariables = codeBlanksVariables)) + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = emptyList() + ) + + assertFalse(state.isVariableSuggestionsAvailable) + } + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDecreaseIndentLevelButtonHiddenTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDecreaseIndentLevelButtonHiddenTest.kt new file mode 100644 index 000000000..e69afd6c1 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDecreaseIndentLevelButtonHiddenTest.kt @@ -0,0 +1,104 @@ +package org.hyperskill.step_quiz_code_blanks.view + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.view.mapper.StepQuizCodeBlanksViewStateMapper +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState +import org.hyperskill.step_quiz_code_blanks.presentation.stub + +class StepQuizCodeBlanksViewStateMapperIsDecreaseIndentLevelButtonHiddenTest { + @Test + fun `isDecreaseIndentLevelButtonHidden should be true when no active code block`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = false, + suggestions = emptyList() + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be true when active code block's indent level is less than 1`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be false when active code block's indent level is 1 or more`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be true when previous code block is IfStatement`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement(indentLevel = 1, children = emptyList()), + CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDecreaseIndentLevelButtonHidden) + } + + @Test + fun `isDecreaseIndentLevelButtonHidden should be false when previous code block is not IfStatement`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print(indentLevel = 1, children = emptyList()), + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDecreaseIndentLevelButtonHidden) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDeleteButtonEnabledTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDeleteButtonEnabledTest.kt new file mode 100644 index 000000000..15de1d104 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsDeleteButtonEnabledTest.kt @@ -0,0 +1,403 @@ +package org.hyperskill.step_quiz_code_blanks.view + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.view.mapper.StepQuizCodeBlanksViewStateMapper +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState +import org.hyperskill.step_quiz_code_blanks.presentation.stub + +class StepQuizCodeBlanksViewStateMapperIsDeleteButtonEnabledTest { + @Test + fun `isDeleteButtonEnabled should be false when active code block is Blank and single`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when active code block is Print and single`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(Suggestion.Print), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when Variable active name is unselected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when Variable active name is selected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when Variable active value child index is greater than one`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[1] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be false when Variable name is selected and active value is unselected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when Variable name is selected and active value is selected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[1] + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when Variable name is unselected and active value is unselected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when Variable name is unselected and active value is selected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when IfStatement active child index greater than zero`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + @Test + fun `isDeleteButtonEnabled should be true when IfStatement child is selected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + /* ktlint-disable */ + @Test + fun `isDeleteButtonEnabled should be true when IfStatement child is unselected and next code block on same indent level`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ), + CodeBlock.Print( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isDeleteButtonEnabled) + } + + /* ktlint-disable */ + @Test + fun `isDeleteButtonEnabled should be false when IfStatement child is unselected and next code block on different indent level`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + indentLevel = 1, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ), + CodeBlock.Print( + indentLevel = 2, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isDeleteButtonEnabled) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsSpaceButtonHiddenTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsSpaceButtonHiddenTest.kt new file mode 100644 index 000000000..027aedd37 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperIsSpaceButtonHiddenTest.kt @@ -0,0 +1,239 @@ +package org.hyperskill.step_quiz_code_blanks.view + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.hyperskill.app.step.domain.model.Block +import org.hyperskill.app.step.domain.model.Step +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.view.mapper.StepQuizCodeBlanksViewStateMapper +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState +import org.hyperskill.step.domain.model.stub +import org.hyperskill.step_quiz_code_blanks.presentation.stub + +class StepQuizCodeBlanksViewStateMapperIsSpaceButtonHiddenTest { + private val step = Step.stub( + id = 0, + block = Block.stub( + options = Block.Options( + codeBlanksOperations = listOf("+") + ) + ) + ) + + @Test + fun `isSpaceButtonHidden should be true when codeBlanksOperationsSuggestions is empty`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = Step.stub(id = 0), + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when no active code block`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.Blank( + isActive = false, + suggestions = emptyList() + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active Print code block has no active child`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active Print code block child has no selected suggestion`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be false when active Print code block child has selected suggestion`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isSpaceButtonHidden) + } + + /* ktlint-disable */ + @Test + fun `isSpaceButtonHidden should be true when active Variable code block's first child has no selected suggestion`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active Variable code block's second child has no selected suggestion`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("x") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be false when active IfStatement code block child has selected suggestion`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("if") + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertFalse(viewState.isSpaceButtonHidden) + } + + @Test + fun `isSpaceButtonHidden should be true when active IfStatement code block child has no selected suggestion`() { + val state = StepQuizCodeBlanksFeature.State.Content.stub( + step = step, + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.isSpaceButtonHidden) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSequencesTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSequencesTest.kt new file mode 100644 index 000000000..e7c16c1df --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSequencesTest.kt @@ -0,0 +1,170 @@ +package org.hyperskill.step_quiz_code_blanks.view + +import kotlin.test.Test +import kotlin.test.assertEquals +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.view.mapper.StepQuizCodeBlanksViewStateMapper +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState +import org.hyperskill.step_quiz_code_blanks.presentation.stub + +class StepQuizCodeBlanksViewStateMapperSequencesTest { + @Test + fun `map should return Idle view state for Idle state`() { + val state = StepQuizCodeBlanksFeature.State.Idle + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + assertEquals(StepQuizCodeBlanksViewState.Idle, viewState) + } + + @Test + fun `Content with active not filled Print`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.Print( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ) + ) + ) + ), + suggestions = suggestions, + isDeleteButtonEnabled = true, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + @Test + fun `Content with sequence of filled Print and active Blank`() { + val printSuggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = printSuggestions, + selectedSuggestion = printSuggestions[0] + ) + ) + ), + CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print)) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.Print( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = false, + value = printSuggestions[0].text + ) + ) + ), + StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 1, isActive = true) + ), + suggestions = listOf(Suggestion.Print), + isDeleteButtonEnabled = true, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } + + @Test + fun `Content with sequence of filled Print and active not filled Print`() { + val printSuggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = printSuggestions, + selectedSuggestion = printSuggestions[0] + ) + ) + ), + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = printSuggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + val expectedViewState = StepQuizCodeBlanksViewState.Content( + codeBlocks = listOf( + StepQuizCodeBlanksViewState.CodeBlockItem.Print( + id = 0, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = false, + value = printSuggestions[0].text + ) + ) + ), + StepQuizCodeBlanksViewState.CodeBlockItem.Print( + id = 1, + children = listOf( + StepQuizCodeBlanksViewState.CodeBlockChildItem( + id = 0, + isActive = true, + value = null + ) + ) + ) + ), + suggestions = printSuggestions, + isDeleteButtonEnabled = true, + isSpaceButtonHidden = true, + isDecreaseIndentLevelButtonHidden = true + ) + + val actualViewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertEquals(expectedViewState, actualViewState) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSuggestionsTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSuggestionsTest.kt new file mode 100644 index 000000000..fbfaf6d78 --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateMapperSuggestionsTest.kt @@ -0,0 +1,114 @@ +package org.hyperskill.step_quiz_code_blanks.view + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature +import org.hyperskill.app.step_quiz_code_blanks.view.mapper.StepQuizCodeBlanksViewStateMapper +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState +import org.hyperskill.step_quiz_code_blanks.presentation.stub + +class StepQuizCodeBlanksViewStateMapperSuggestionsTest { + @Test + fun `Non empty suggestions when active code block is Blank`() { + val suggestions = listOf(Suggestion.Print, Suggestion.Variable) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = suggestions)) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertEquals(suggestions, viewState.suggestions) + } + + @Test + fun `Empty suggestions when code block active child has selected suggestion`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = suggestions[0] + ) + ) + val codeBlocks = listOf( + CodeBlock.Print(children = children), + CodeBlock.Variable(children = children), + CodeBlock.IfStatement(children = children) + ) + + codeBlocks.forEach { codeBlock -> + val state = StepQuizCodeBlanksFeature.State.Content.stub(codeBlocks = listOf(codeBlock)) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertTrue(viewState.suggestions.isEmpty()) + } + } + + @Test + fun `Non empty suggestions when code block active child is unselected`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + val codeBlocks = listOf( + CodeBlock.Print(children = children), + CodeBlock.Variable(children = children), + CodeBlock.IfStatement(children = children) + ) + + codeBlocks.forEach { codeBlock -> + val state = StepQuizCodeBlanksFeature.State.Content.stub(codeBlocks = listOf(codeBlock)) + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertEquals(suggestions, viewState.suggestions) + } + } + + @Test + fun `Non empty suggestions when active code block is Variable and active child has no selected suggestion`() { + val suggestions = listOf( + Suggestion.ConstantString("1"), + Suggestion.ConstantString("2") + ) + val state = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = suggestions, + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = suggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + val viewState = StepQuizCodeBlanksViewStateMapper.map(state) + + assertTrue(viewState is StepQuizCodeBlanksViewState.Content) + assertEquals(suggestions, viewState.suggestions) + } +} \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt new file mode 100644 index 000000000..ac692810f --- /dev/null +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/view/StepQuizCodeBlanksViewStateTest.kt @@ -0,0 +1,39 @@ +package org.hyperskill.step_quiz_code_blanks.view + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState +import org.hyperskill.app.step_quiz_code_blanks.view.model.StepQuizCodeBlanksViewState + +class StepQuizCodeBlanksViewStateTest { + @Test + fun `isActionButtonsHidden should be true when onboarding is available`() { + val viewState = stubContentViewState(onboardingState = OnboardingState.HighlightSuggestions) + assertTrue(viewState.isActionButtonsHidden) + } + + @Test + fun `isActionButtonsHidden should be false when onboarding is unavailable`() { + val viewState = stubContentViewState(onboardingState = OnboardingState.Unavailable) + assertFalse(viewState.isActionButtonsHidden) + } + + @Test + fun `isSuggestionsHighlightEffectActive should be true when onboardingState is HighlightSuggestions`() { + val viewState = stubContentViewState(onboardingState = OnboardingState.HighlightSuggestions) + assertTrue(viewState.isSuggestionsHighlightEffectActive) + } + + private fun stubContentViewState( + onboardingState: OnboardingState + ): StepQuizCodeBlanksViewState.Content = + StepQuizCodeBlanksViewState.Content( + codeBlocks = emptyList(), + suggestions = emptyList(), + isDeleteButtonEnabled = false, + isSpaceButtonHidden = false, + isDecreaseIndentLevelButtonHidden = false, + onboardingState = onboardingState + ) +} \ No newline at end of file From 4c2c708f040d51b500d32f810b7a9c329599fe9e Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 13:01:47 +0900 Subject: [PATCH 20/28] Fix tests --- ...anksReducerDecreaseIndentLevelButtonClickedTest.kt | 11 +++++++++-- ...ifAndElseStatementsSuggestionsAvailabilityTest.kt} | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) rename shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/{StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt => presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt} (95%) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt index 63c0f39d7..309a2afd5 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt @@ -6,6 +6,7 @@ import kotlin.test.assertTrue import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlanksClickedDecreaseIndentLevelHyperskillAnalyticEvent import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock +import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer @@ -54,7 +55,13 @@ class StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest { ) val expectedState = initialState.copy( - codeBlocks = listOf(CodeBlock.Blank(isActive = true, indentLevel = 0, suggestions = emptyList())) + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + indentLevel = 0, + suggestions = listOf(Suggestion.Print) + ) + ) ) assertEquals(expectedState, state) @@ -78,7 +85,7 @@ class StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest { val expectedState = initialState.copy( codeBlocks = listOf( CodeBlock.Blank(isActive = false, indentLevel = 3, suggestions = emptyList()), - CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = emptyList()) + CodeBlock.Blank(isActive = true, indentLevel = 1, suggestions = listOf(Suggestion.Print)) ) ) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt similarity index 95% rename from shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt rename to shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt index 7b38d627e..a9ff044f7 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest.kt @@ -1,4 +1,4 @@ -package org.hyperskill.step_quiz_code_blanks +package org.hyperskill.step_quiz_code_blanks.presentation import kotlin.test.Test import kotlin.test.assertFalse @@ -7,7 +7,7 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksReducer -class StepQuizCodeBlanksElifAndElseStatementsSuggestionsAvailabilityTest { +class StepQuizCodeBlanksReducerElifAndElseStatementsSuggestionsAvailabilityTest { private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) @Test From ca7b82f0acc284e0fd32ec4c38bc10f9e25a8be4 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 13:24:17 +0900 Subject: [PATCH 21/28] Add helpers for copy code block --- .../domain/model/CodeBlock.kt | 17 +++++++ .../presentation/StepQuizCodeBlanksReducer.kt | 44 ++++--------------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt index 10ddd9da6..241028bbd 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/domain/model/CodeBlock.kt @@ -1,6 +1,7 @@ package org.hyperskill.app.step_quiz_code_blanks.domain.model import org.hyperskill.app.core.utils.indexOfFirstOrNull +import ru.nobird.app.core.model.cast sealed class CodeBlock { companion object; @@ -139,4 +140,20 @@ internal fun CodeBlock.Companion.joinChildrenToReplyString(children: List): CodeBlock = + when (this) { + is CodeBlock.Blank -> this + is CodeBlock.Print -> copy(children = children.cast()) + is CodeBlock.Variable -> copy(children = children.cast()) + is CodeBlock.IfStatement -> copy(children = children.cast()) + } + +internal fun CodeBlock.updatedIndentLevel(indentLevel: Int): CodeBlock = + when (this) { + is CodeBlock.Blank -> copy(indentLevel = indentLevel) + is CodeBlock.Print -> copy(indentLevel = indentLevel) + is CodeBlock.Variable -> copy(indentLevel = indentLevel) + is CodeBlock.IfStatement -> copy(indentLevel = indentLevel) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 1b64a390d..31f62faa4 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -13,6 +13,8 @@ import org.hyperskill.app.step_quiz_code_blanks.domain.analytic.StepQuizCodeBlan import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlock import org.hyperskill.app.step_quiz_code_blanks.domain.model.CodeBlockChild import org.hyperskill.app.step_quiz_code_blanks.domain.model.Suggestion +import org.hyperskill.app.step_quiz_code_blanks.domain.model.updatedChildren +import org.hyperskill.app.step_quiz_code_blanks.domain.model.updatedIndentLevel import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.Action import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.InternalAction import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.InternalMessage @@ -137,12 +139,7 @@ class StepQuizCodeBlanksReducer( ) } .cast>() - - when (activeCodeBlock) { - is CodeBlock.Print -> activeCodeBlock.copy(children = newChildren) - is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) - else -> activeCodeBlock - } + activeCodeBlock.updatedChildren(newChildren) } ?: activeCodeBlock } is CodeBlock.Variable -> { @@ -275,12 +272,7 @@ class StepQuizCodeBlanksReducer( targetCodeBlock?.let { targetCodeBlock -> set( targetCodeBlockIndex, - when (targetCodeBlock) { - is CodeBlock.Print -> targetCodeBlock.copy(children = newChildren) - is CodeBlock.Variable -> targetCodeBlock.copy(children = newChildren) - is CodeBlock.IfStatement -> targetCodeBlock.copy(children = newChildren) - else -> targetCodeBlock - } + targetCodeBlock.updatedChildren(newChildren) ) } } @@ -584,12 +576,7 @@ class StepQuizCodeBlanksReducer( newChildren?.let { set( activeCodeBlockIndex, - when (activeCodeBlock) { - is CodeBlock.Print -> activeCodeBlock.copy(children = newChildren) - is CodeBlock.Variable -> activeCodeBlock.copy(children = newChildren) - is CodeBlock.IfStatement -> activeCodeBlock.copy(children = newChildren) - else -> activeCodeBlock - } + activeCodeBlock.updatedChildren(newChildren) ) } } @@ -625,12 +612,7 @@ class StepQuizCodeBlanksReducer( codeBlocks = state.codeBlocks.mutate { set( activeCodeBlockIndex, - when (activeCodeBlock) { - is CodeBlock.Blank -> activeCodeBlock.copy(indentLevel = newIndentLevel) - is CodeBlock.Print -> activeCodeBlock.copy(indentLevel = newIndentLevel) - is CodeBlock.Variable -> activeCodeBlock.copy(indentLevel = newIndentLevel) - is CodeBlock.IfStatement -> activeCodeBlock.copy(indentLevel = newIndentLevel) - } + activeCodeBlock.updatedIndentLevel(newIndentLevel) ) } ) to actions @@ -654,24 +636,14 @@ class StepQuizCodeBlanksReducer( child.copy(isActive = false) } } - when (codeBlock) { - is CodeBlock.Print -> codeBlock.copy(children = newChildren) - is CodeBlock.Variable -> codeBlock.copy(children = newChildren) - is CodeBlock.IfStatement -> codeBlock.copy(children = newChildren) - else -> codeBlock - } + codeBlock.updatedChildren(newChildren) } } else { val newChildren = codeBlock.children.map { child -> require(child is CodeBlockChild.SelectSuggestion) child.copy(isActive = false) } - when (codeBlock) { - is CodeBlock.Print -> codeBlock.copy(children = newChildren) - is CodeBlock.Variable -> codeBlock.copy(children = newChildren) - is CodeBlock.IfStatement -> codeBlock.copy(children = newChildren) - else -> codeBlock - } + codeBlock.updatedChildren(newChildren) } } } From f1802b920dcd6e09bccddab1a4a2f21171b9297b Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 16:36:27 +0900 Subject: [PATCH 22/28] Update StepQuizCodeBlanksReducerSuggestionClickedTest --- .../presentation/StepQuizCodeBlanksReducer.kt | 2 +- ...zCodeBlanksReducerSuggestionClickedTest.kt | 164 +++++++++++++++++- 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 377cc5570..7501ef46d 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -191,7 +191,7 @@ class StepQuizCodeBlanksReducer( val newCodeBlocks = state.codeBlocks.mutate { set(activeCodeBlockIndex, newCodeBlock) - if (newCodeBlock is CodeBlock.ElseStatement) { + if (newCodeBlock is CodeBlock.ElseStatement && activeCodeBlock !== newCodeBlock) { val blankInsertIndex = activeCodeBlockIndex + 1 val blankIndentLevel = newCodeBlock.indentLevel + 1 add( diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt index 5eedd6de7..5e3970aff 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSuggestionClickedTest.kt @@ -61,7 +61,7 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest { } @Test - fun `SuggestionClicked should update active Blank code block to Print if suggestion exists`() { + fun `SuggestionClicked should update Blank to Print`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( codeBlocks = listOf( CodeBlock.Blank( @@ -93,7 +93,7 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest { } @Test - fun `SuggestionClicked should update active Blank code block to Variable if suggestion exists`() { + fun `SuggestionClicked should update Blank to Variable`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( codeBlocks = listOf( CodeBlock.Blank( @@ -129,6 +129,102 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest { assertContainsSuggestionClickedAnalyticEvent(actions) } + @Test + fun `SuggestionClicked should update Blank to IfStatement`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = listOf(Suggestion.Print, Suggestion.IfStatement) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.IfStatement) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = initialState.codeBlanksVariablesAndStringsSuggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update Blank to ElifStatement`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = listOf(Suggestion.Print, Suggestion.ElifStatement) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.ElifStatement) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.ElifStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = initialState.codeBlanksVariablesAndStringsSuggestions, + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update Blank to ElseStatement and add Blank with next indentLevel`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = listOf(Suggestion.Print, Suggestion.ElseStatement) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(Suggestion.ElseStatement) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.ElseStatement( + isActive = false, + indentLevel = 0 + ), + CodeBlock.Blank( + isActive = true, + indentLevel = 1, + suggestions = listOf(Suggestion.Print) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + @Test fun `SuggestionClicked should update Print code block with selected suggestion`() { val suggestion = Suggestion.ConstantString("suggestion") @@ -154,6 +250,56 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest { assertContainsSuggestionClickedAnalyticEvent(actions) } + @Test + fun `SuggestionClicked should update IfStatement code block with selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, actions) = reducer.reduce(initialState, message) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(suggestion, (state.codeBlocks[0] as CodeBlock.IfStatement).children[0].selectedSuggestion) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + + @Test + fun `SuggestionClicked should update ElifStatement code block with selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.ElifStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, actions) = reducer.reduce(initialState, message) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(suggestion, (state.codeBlocks[0] as CodeBlock.ElifStatement).children[0].selectedSuggestion) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + @Test fun `SuggestionClicked should update Variable code block with selected suggestion for name`() { val suggestion = Suggestion.ConstantString("suggestion") @@ -252,6 +398,20 @@ class StepQuizCodeBlanksReducerSuggestionClickedTest { assertContainsSuggestionClickedAnalyticEvent(actions) } + @Test + fun `SuggestionClicked should not update ElseStatement code block with selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.ElseStatement(isActive = true)) + ) + + val message = StepQuizCodeBlanksFeature.Message.SuggestionClicked(suggestion) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsSuggestionClickedAnalyticEvent(actions) + } + private fun assertContainsSuggestionClickedAnalyticEvent(actions: Set) { assertTrue { actions.any { From 3209444b48b5058474b8c8adc9622ea2ead8beb0 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 16:55:51 +0900 Subject: [PATCH 23/28] Update StepQuizCodeBlanksReducerCodeBlockClickedTest --- ...izCodeBlanksReducerCodeBlockClickedTest.kt | 112 +++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt index 7a88b7b36..84deb40e1 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockClickedTest.kt @@ -26,6 +26,36 @@ class StepQuizCodeBlanksReducerCodeBlockClickedTest { assertTrue(actions.isEmpty()) } + @Test + fun `CodeBlockClicked should not update state if no target code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 1, isActive = true) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsCodeBlockClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockClicked should not update state if target code block is active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = true) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsCodeBlockClickedAnalyticEvent(actions) + } + @Test fun `CodeBlockClicked should update active Print code block`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( @@ -64,7 +94,32 @@ class StepQuizCodeBlanksReducerCodeBlockClickedTest { } @Test - fun `CodeBlockClicked should update active Variable code block`() { + fun `CodeBlockClicked should update active ElseStatement code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.ElseStatement(isActive = true) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = false) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank(isActive = true, suggestions = emptyList()), + CodeBlock.ElseStatement(isActive = false) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockClicked should update active Variable code block to not active`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( codeBlocks = listOf( CodeBlock.Print( @@ -118,6 +173,61 @@ class StepQuizCodeBlanksReducerCodeBlockClickedTest { assertContainsCodeBlockClickedAnalyticEvent(actions) } + @Test + fun `CodeBlockClicked should update not active Variable code block to active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 1, isActive = false) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, suggestions = emptyList(), selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, suggestions = emptyList(), selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockClickedAnalyticEvent(actions) + } + private fun assertContainsCodeBlockClickedAnalyticEvent(actions: Set) { assertTrue { actions.any { From d9380526f23d0b2a92b7b93160192fe3c4ca0d01 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 17:03:18 +0900 Subject: [PATCH 24/28] Update StepQuizCodeBlanksReducerCodeBlockChildClickedTest --- ...eBlanksReducerCodeBlockChildClickedTest.kt | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt index feea44ebf..9c864bf95 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerCodeBlockChildClickedTest.kt @@ -53,6 +53,43 @@ class StepQuizCodeBlanksReducerCodeBlockChildClickedTest { assertContainsCodeBlockChildClickedAnalyticEvent(actions) } + @Test + fun `CodeBlockChildClicked should not update state if target code block is Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = false, + suggestions = emptyList() + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.Blank(id = 0, isActive = false), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockChildClicked should not update state if target code block is ElseStatement`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.ElseStatement(isActive = false)) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.ElseStatement(id = 0, isActive = false), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + assertEquals(initialState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + @Test fun `CodeBlockChildClicked should update state to activate the clicked Variable child`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( @@ -153,6 +190,86 @@ class StepQuizCodeBlanksReducerCodeBlockChildClickedTest { assertContainsCodeBlockChildClickedAnalyticEvent(actions) } + @Test + fun `CodeBlockChildClicked should update state to activate the clicked IfStatement child`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.IfStatement(id = 0, children = emptyList()), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + + @Test + fun `CodeBlockChildClicked should update state to activate the clicked ElifStatement child`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.ElifStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val message = StepQuizCodeBlanksFeature.Message.CodeBlockChildClicked( + codeBlockItem = StepQuizCodeBlanksViewState.CodeBlockItem.ElifStatement(id = 0, children = emptyList()), + codeBlockChildItem = StepQuizCodeBlanksViewState.CodeBlockChildItem(id = 0, isActive = false, value = null) + ) + val (state, actions) = reducer.reduce(initialState, message) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.ElifStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsCodeBlockChildClickedAnalyticEvent(actions) + } + private fun assertContainsCodeBlockChildClickedAnalyticEvent(actions: Set) { assertTrue { actions.any { From 282bf3eab0d4d04187103089deb70e0ef437742a Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 17:36:29 +0900 Subject: [PATCH 25/28] Update StepQuizCodeBlanksReducerDeleteButtonClickedTest --- ...odeBlanksReducerDeleteButtonClickedTest.kt | 533 ++++++++++++++++-- 1 file changed, 496 insertions(+), 37 deletions(-) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt index 2ab0d37fe..c01e8a93e 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDeleteButtonClickedTest.kt @@ -62,43 +62,6 @@ class StepQuizCodeBlanksReducerDeleteButtonClickedTest { assertContainsDeleteButtonClickedAnalyticEvent(actions) } - @Test - fun `DeleteButtonClicked should clear suggestion if active Print code block has selected suggestion`() { - val suggestion = Suggestion.ConstantString("suggestion") - val initialState = StepQuizCodeBlanksFeature.State.Content.stub( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = suggestion - ) - ) - ) - ) - ) - - val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) - - val expectedState = initialState.copy( - codeBlocks = listOf( - CodeBlock.Print( - children = listOf( - CodeBlockChild.SelectSuggestion( - isActive = true, - suggestions = listOf(suggestion), - selectedSuggestion = null - ) - ) - ) - ) - ) - - assertEquals(expectedState, state) - assertContainsDeleteButtonClickedAnalyticEvent(actions) - } - @Test fun `DeleteButtonClicked should set next code block as active if no code block before deleted`() { val initialStates = listOf( @@ -402,6 +365,84 @@ class StepQuizCodeBlanksReducerDeleteButtonClickedTest { } } + @Test + fun `DeleteButtonClicked should clear suggestion if active Print code block has selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = suggestion + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove child for Print code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Print( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + @Test fun `DeleteButtonClicked should replace single Print code block with Blank`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( @@ -428,6 +469,94 @@ class StepQuizCodeBlanksReducerDeleteButtonClickedTest { assertContainsDeleteButtonClickedAnalyticEvent(actions) } + @Test + fun `DeleteButtonClicked should clear suggestion if active Variable code block has selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = suggestion + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove child for Variable code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + @Test fun `DeleteButtonClicked should replace single Variable code block with Blank`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( @@ -459,6 +588,336 @@ class StepQuizCodeBlanksReducerDeleteButtonClickedTest { assertContainsDeleteButtonClickedAnalyticEvent(actions) } + @Test + fun `DeleteButtonClicked should remove Variable code block and set previous active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove Variable code block and set next active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Variable( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Blank(isActive = false, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should clear suggestion if active IfStatement code block has selected suggestion`() { + val suggestion = Suggestion.ConstantString("suggestion") + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = suggestion + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = listOf(suggestion), + selectedSuggestion = null + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove child for IfStatement code block`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ), + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = Suggestion.ConstantString("suggestion") + ) + ) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should replace single IfStatement code block with Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + /* ktlint-disable */ + @Test + fun `DeleteButtonClicked should not replace single IfStatement code block with Blank when next code block has different indent level`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + indentLevel = 0, + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Blank(indentLevel = 1, isActive = false, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + assertEquals(initialState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove IfStatement code block and set previous active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove IfStatement code block and set next active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ), + CodeBlockChild.SelectSuggestion( + isActive = false, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.Blank(isActive = false, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + /* ktlint-disable */ + @Test + fun `DeleteButtonClicked should not replace single ElseStatement code block with Blank when next code block has different indent level`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.ElseStatement( + indentLevel = 0, + isActive = true + ), + CodeBlock.Blank(indentLevel = 1, isActive = false, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + assertEquals(initialState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should replace single ElseStatement code block with Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.ElseStatement(isActive = true)) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = listOf(Suggestion.Print))) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove ElseStatement code block and set previous active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank(isActive = false, suggestions = emptyList()), + CodeBlock.ElseStatement(isActive = true) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + + @Test + fun `DeleteButtonClicked should remove ElseStatement code block and set next active`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.ElseStatement(isActive = true), + CodeBlock.Blank(isActive = false, suggestions = emptyList()) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.DeleteButtonClicked) + + val expectedState = initialState.copy( + codeBlocks = listOf(CodeBlock.Blank(isActive = true, suggestions = emptyList())) + ) + + assertEquals(expectedState, state) + assertContainsDeleteButtonClickedAnalyticEvent(actions) + } + private fun assertContainsDeleteButtonClickedAnalyticEvent(actions: Set) { assertTrue { actions.any { From f511bf4d34b7adb81a5dd6f15d7280e53395a802 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 17:48:06 +0900 Subject: [PATCH 26/28] Update StepQuizCodeBlanksReducerEnterButtonClickedTest --- ...CodeBlanksReducerEnterButtonClickedTest.kt | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt index 1f52b1479..b25d8cd19 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerEnterButtonClickedTest.kt @@ -24,7 +24,7 @@ class StepQuizCodeBlanksReducerEnterButtonClickedTest { } @Test - fun `EnterButtonClicked should log analytic event and not update state if no active code block`() { + fun `EnterButtonClicked should not update state if no active code block`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( codeBlocks = listOf( CodeBlock.Blank( @@ -41,7 +41,7 @@ class StepQuizCodeBlanksReducerEnterButtonClickedTest { } @Test - fun `EnterButtonClicked should log analytic event and add new active Blank block if active code block exists`() { + fun `EnterButtonClicked should append new active Blank block if active code block exists`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( codeBlocks = listOf( CodeBlock.Blank( @@ -103,6 +103,41 @@ class StepQuizCodeBlanksReducerEnterButtonClickedTest { assertContainsEnterButtonClickedAnalyticEvent(actions) } + @Test + fun `EnterButtonClicked should add Blank with next indentLevel if active code block is condition`() { + val codeBlocks = listOf( + CodeBlock.IfStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.ElifStatement( + children = listOf( + CodeBlockChild.SelectSuggestion( + isActive = true, + suggestions = emptyList(), + selectedSuggestion = null + ) + ) + ), + CodeBlock.ElseStatement(isActive = true) + ) + + codeBlocks.forEach { codeBlock -> + val initialState = StepQuizCodeBlanksFeature.State.Content.stub(codeBlocks = listOf(codeBlock)) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.EnterButtonClicked) + + assertTrue(state is StepQuizCodeBlanksFeature.State.Content) + assertEquals(1, state.codeBlocks[1].indentLevel) + assertContainsEnterButtonClickedAnalyticEvent(actions) + } + } + private fun assertContainsEnterButtonClickedAnalyticEvent(actions: Set) { assertTrue { actions.any { From a087399e68335232a30a06957905261eb15b4727 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 17:54:23 +0900 Subject: [PATCH 27/28] Update StepQuizCodeBlanksReducerSpaceButtonClickedTest --- .../presentation/StepQuizCodeBlanksReducer.kt | 11 +++---- ...CodeBlanksReducerSpaceButtonClickedTest.kt | 31 ++++++++++++++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt index 7501ef46d..be8532bdc 100644 --- a/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt +++ b/shared/src/commonMain/kotlin/org/hyperskill/app/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducer.kt @@ -21,7 +21,6 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksF import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.Message import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.OnboardingState import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksFeature.State -import ru.nobird.app.core.model.cast import ru.nobird.app.core.model.mutate import ru.nobird.app.core.model.slice import ru.nobird.app.presentation.redux.reducer.StateReducer @@ -636,12 +635,10 @@ class StepQuizCodeBlanksReducer( selectedSuggestion = null ) - activeCodeBlock.children - .mutate { - set(activeChildIndex, activeChild.copy(isActive = false)) - add(activeChildIndex + 1, newChild) - } - .cast>() + activeCodeBlock.children.mutate { + set(activeChildIndex, activeChild.copy(isActive = false)) + add(activeChildIndex + 1, newChild) + } } } is CodeBlock.Blank, diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt index b1295d945..3c8166d55 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerSpaceButtonClickedTest.kt @@ -27,7 +27,7 @@ class StepQuizCodeBlanksReducerSpaceButtonClickedTest { } @Test - fun `SpaceButtonClicked should not update state if active Print block has no active child`() { + fun `SpaceButtonClicked should not update state if no active code block`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( codeBlocks = listOf( CodeBlock.Print( @@ -48,6 +48,35 @@ class StepQuizCodeBlanksReducerSpaceButtonClickedTest { assertContainsSpaceButtonClickedAnalyticEvent(actions) } + @Test + fun `SpaceButtonClicked should not update state if active code block is Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + suggestions = emptyList() + ) + ) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + assertEquals(initialState, state) + assertContainsSpaceButtonClickedAnalyticEvent(actions) + } + + @Test + fun `SpaceButtonClicked should not update state if active code block is ElseStatement`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf(CodeBlock.ElseStatement(isActive = true)) + ) + + val (state, actions) = reducer.reduce(initialState, StepQuizCodeBlanksFeature.Message.SpaceButtonClicked) + + assertEquals(initialState, state) + assertContainsSpaceButtonClickedAnalyticEvent(actions) + } + @Test fun `SpaceButtonClicked should add a new child to active Print code block`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( From 90a7bf150e4aac0ca407e79afed17bef8bc387af Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 11 Sep 2024 17:57:50 +0900 Subject: [PATCH 28/28] Update StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest --- ...cerDecreaseIndentLevelButtonClickedTest.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt index 309a2afd5..9e16f08b9 100644 --- a/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt +++ b/shared/src/commonTest/kotlin/org/hyperskill/step_quiz_code_blanks/presentation/StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest.kt @@ -13,6 +13,19 @@ import org.hyperskill.app.step_quiz_code_blanks.presentation.StepQuizCodeBlanksR class StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest { private val reducer = StepQuizCodeBlanksReducer(StepRoute.Learn.Step(1, null)) + @Test + fun `DecreaseIndentLevelButtonClicked should not update state if state is not Content`() { + val initialState = StepQuizCodeBlanksFeature.State.Idle + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + assertEquals(initialState, state) + assertTrue(actions.isEmpty()) + } + @Test fun `DecreaseIndentLevelButtonClicked should not update state if no active code block`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub( @@ -68,6 +81,37 @@ class StepQuizCodeBlanksReducerDecreaseIndentLevelButtonClickedTest { assertContainsDecreaseIndentLevelAnalyticEvent(actions) } + @Test + fun `DecreaseIndentLevelButtonClicked should decrease indent level by 1 and update suggestions for Blank`() { + val initialState = StepQuizCodeBlanksFeature.State.Content.stub( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + indentLevel = 1, + suggestions = emptyList() + ) + ) + ) + + val (state, actions) = reducer.reduce( + initialState, + StepQuizCodeBlanksFeature.Message.DecreaseIndentLevelButtonClicked + ) + + val expectedState = initialState.copy( + codeBlocks = listOf( + CodeBlock.Blank( + isActive = true, + indentLevel = 0, + suggestions = listOf(Suggestion.Print) + ) + ) + ) + + assertEquals(expectedState, state) + assertContainsDecreaseIndentLevelAnalyticEvent(actions) + } + @Test fun `DecreaseIndentLevelButtonClicked should decrease indent level for active code block only`() { val initialState = StepQuizCodeBlanksFeature.State.Content.stub(