<commit_before>package com.snowble.android.widget.verticalstepper;

import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.AppCompatButton;
import android.text.TextPaint;
import android.view.View;

import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;

import java.util.List;

import static org.assertj.core.api.Java6Assertions.*;
import static org.mockito.Mockito.*;

@RunWith(Enclosed.class)
public class VerticalStepperTest {

    private static class MockedStep {
        View innerView;
        VerticalStepper.LayoutParams innerLayoutParams;
        VerticalStepper.InternalTouchView touchView;
        AppCompatButton continueButton;
        VerticalStepper.LayoutParams continueLayoutParams;
        Step step;

        MockedStep() {
            innerView = mock(View.class);
            innerLayoutParams = mock(VerticalStepper.LayoutParams.class);
            when(innerView.getLayoutParams()).thenReturn(innerLayoutParams);

            continueButton = mock(AppCompatButton.class);
            continueLayoutParams = mock(VerticalStepper.LayoutParams.class);
            when(continueButton.getLayoutParams()).thenReturn(continueLayoutParams);

            touchView = mock(VerticalStepper.InternalTouchView.class);

            step = mock(Step.class);
            when(step.getInnerView()).thenReturn(innerView);
            when(step.getTouchView()).thenReturn(touchView);
            when(step.getContinueButton()).thenReturn(continueButton);
        }
    }

    public abstract static class GivenAStepper extends GivenAnActivity {
        VerticalStepper stepper;

        @Before
        public void givenAStepper() {
            stepper = new VerticalStepper(activity);
        }

        void mockActiveState(MockedStep step, boolean isActive) {
            when(step.step.isActive()).thenReturn(isActive);
            int visibility = isActive ? View.VISIBLE : View.GONE;
            when(step.innerView.getVisibility()).thenReturn(visibility);
            when(step.continueButton.getVisibility()).thenReturn(visibility);
        }

        void assertActiveState(MockedStep step, boolean expectedActiveState) {
            verify(step.step).setActive(expectedActiveState);
            int visibility = expectedActiveState ? View.VISIBLE : View.GONE;
            verify(step.innerView).setVisibility(visibility);
            verify(step.continueButton).setVisibility(visibility);
        }
    }

    public static class GivenZeroSteps extends GivenAStepper {
        private int getColor(int colorRes) {
            return ResourcesCompat.getColor(activity.getResources(), colorRes, activity.getTheme());
        }

        @SuppressLint("PrivateResource") // https://code.google.com/p/android/issues/detail?id=230985
        @Test
        public void initPropertiesFromAttrs_NoAttrsSet_ShouldUseDefaults() {
            stepper.initPropertiesFromAttrs(null, 0, 0);

            assertThat(stepper.iconActiveColor).isEqualTo(getColor(R.color.bg_active_icon));
            assertThat(stepper.iconInactiveColor).isEqualTo(getColor(R.color.bg_inactive_icon));
            assertThat(stepper.continueButtonStyle)
                    .isEqualTo(android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Colored);
        }

        @SuppressLint("PrivateResource") // https://code.google.com/p/android/issues/detail?id=230985
        @Test
        public void initPropertiesFromAttrs_AttrsSet_ShouldUseAttrs() {
            Robolectric.AttributeSetBuilder builder = Robolectric.buildAttributeSet();
            builder.addAttribute(R.attr.iconColorActive, "@android:color/black");
            builder.addAttribute(R.attr.iconColorInactive, "@android:color/darker_gray");
            builder.addAttribute(R.attr.continueButtonStyle, "@style/Widget.AppCompat.Button.Borderless");

            stepper.initPropertiesFromAttrs(builder.build(), 0, 0);

            assertThat(stepper.iconActiveColor).isEqualTo(getColor(android.R.color.black));
            assertThat(stepper.iconInactiveColor).isEqualTo(getColor(android.R.color.darker_gray));
            assertThat(stepper.continueButtonStyle)
                    .isEqualTo(android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Borderless);
        }

        @Test
        public void initSteps_ShouldHaveEmptyInnerViews() {
            stepper.initSteps();

            assertThat(stepper.steps).isEmpty();
        }

        @Test
        public void calculateWidth_ShouldReturnHorizontalPadding() {
            int width = stepper.calculateWidth();

            assertThat(width)
                    .isEqualTo(stepper.calculateHorizontalPadding());
        }

        @Test
        public void doMeasurement_UnspecifiedSpecs_ShouldMeasurePadding() {
            int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

            stepper.doMeasurement(ms, ms);

            assertThat(stepper.getMeasuredHeight()).isEqualTo(stepper.calculateVerticalPadding());
            assertThat(stepper.getMeasuredWidth()).isEqualTo(stepper.calculateHorizontalPadding());
        }

        @Test
        public void doMeasurement_AtMostSpecsRequiresClipping_ShouldMeasureToAtMostValues() {
            int width = stepper.calculateHorizontalPadding() / 2;
            int height = stepper.calculateVerticalPadding() / 2;
            int wms = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST);
            int hms = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);

            stepper.doMeasurement(wms, hms);

            assertThat(stepper.getMeasuredWidth()).isEqualTo(width);
            assertThat(stepper.getMeasuredHeight()).isEqualTo(height);
        }

        @Test
        public void doMeasurement_ExactlySpecsRequiresClipping_ShouldMeasureToExactValues() {
            int width = stepper.calculateHorizontalPadding() / 2;
            int height = stepper.calculateVerticalPadding() / 2;
            int wms = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
            int hms = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

            stepper.doMeasurement(wms, hms);

            assertThat(stepper.getMeasuredWidth()).isEqualTo(width);
            assertThat(stepper.getMeasuredHeight()).isEqualTo(height);
        }

        @Test
        public void doMeasurement_ExactlySpecsRequiresExpanding_ShouldMeasureToExactValues() {
            int width = stepper.calculateHorizontalPadding() * 2;
            int height = stepper.calculateVerticalPadding() * 2;
            int wms = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
            int hms = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

            stepper.doMeasurement(wms, hms);

            assertThat(stepper.getMeasuredWidth()).isEqualTo(width);
            assertThat(stepper.getMeasuredHeight()).isEqualTo(height);
        }

        @Test
        public void calculateHeight_ShouldReturnVerticalPadding() {
            int width = stepper.calculateHeight();

            assertThat(width)
                    .isEqualTo(stepper.calculateVerticalPadding());
        }

        @Test
        public void calculateHorizontalPadding_ShouldReturnAllPadding() {
            int horizontalPadding = stepper.calculateHorizontalPadding();

            assertThat(horizontalPadding)
                    .isEqualTo((stepper.outerHorizontalPadding * 2) +
                            stepper.getPaddingLeft() + stepper.getPaddingRight());
        }

        @Test
        public void calculateVerticalPadding_ShouldReturnAllPadding() {
            int verticalPadding = stepper.calculateVerticalPadding();

            assertThat(verticalPadding)
                    .isEqualTo((stepper.outerVerticalPadding * 2) +
                            stepper.getPaddingTop() + stepper.getPaddingBottom());
        }

        @Test
        public void layoutTouchView_WhenNotEnoughSpace_ShouldClip() {
            int leftPadding = 20;
            int topPadding = 4;
            int rightPadding = 10;
            int bottomPadding = 2;
            stepper.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);

            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            int adjustedLeft = left + stepper.outerHorizontalPadding + leftPadding;
            int adjustedTop = top + stepper.outerVerticalPadding + topPadding;
            int adjustedRight = right - stepper.outerHorizontalPadding - rightPadding;
            int adjustedBottom = bottom - stepper.outerVerticalPadding - bottomPadding;

            VerticalStepper.InternalTouchView touchView = mock(VerticalStepper.InternalTouchView.class);
            when(touchView.getMeasuredHeight()).thenReturn(bottom * 2);

            stepper.layoutTouchView(new Rect(adjustedLeft, adjustedTop, adjustedRight, adjustedBottom), touchView);

            verify(touchView).layout(eq(left + leftPadding), eq(top + topPadding),
                    eq(right - left - rightPadding), eq(bottom - top - bottomPadding));
        }

        @Test
        public void layoutTouchView_WhenEnoughSpace_ShouldUseFullWidthAndMeasuredHeight() {
            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            int adjustedLeft = left + stepper.outerHorizontalPadding;
            int adjustedTop = top + stepper.outerVerticalPadding;
            int adjustedRight = right - stepper.outerHorizontalPadding;
            int adjustedBottom = bottom - stepper.outerVerticalPadding;

            VerticalStepper.InternalTouchView touchView = mock(VerticalStepper.InternalTouchView.class);
            int touchMeasuredHeight = bottom / 2;
            when(touchView.getMeasuredHeight()).thenReturn(touchMeasuredHeight);

            stepper.layoutTouchView(new Rect(adjustedLeft, adjustedTop, adjustedRight, adjustedBottom), touchView);

            verify(touchView).layout(eq(left), eq(top), eq(right - left), eq(top + touchMeasuredHeight));
        }

        @Test
        public void layoutActiveView_WhenNotEnoughSpace_ShouldClip() {
            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            int leftMargin = 5;
            int topMargin = 20;
            int rightMargin = 10;
            int bottomMargin = 15;

            View activeView = mock(View.class);
            when(activeView.getMeasuredWidth()).thenReturn(right * 2);
            when(activeView.getMeasuredHeight()).thenReturn(bottom * 2);
            when(activeView.getLayoutParams()).thenReturn(
                    createTestLayoutParams(leftMargin, topMargin, rightMargin, bottomMargin));

            stepper.layoutActiveView(new Rect(left, top, right, bottom), activeView);

            verify(activeView).layout(eq(left + leftMargin), eq(top + topMargin),
                    eq(right - rightMargin), eq(bottom - bottomMargin));
        }

        @Test
        public void layoutActiveView_WhenEnoughSpace_ShouldUseFullWidthAndMeasuredHeight() {
            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            View activeView = mock(View.class);
            int measuredWidth = right / 2;
            when(activeView.getMeasuredWidth()).thenReturn(measuredWidth);
            int measuredHeight = bottom / 2;
            when(activeView.getMeasuredHeight()).thenReturn(measuredHeight);
            when(activeView.getLayoutParams()).thenReturn(mock(VerticalStepper.LayoutParams.class));

            stepper.layoutActiveView(new Rect(left, top, right, bottom), activeView);

            verify(activeView).layout(eq(left), eq(top), eq(left + measuredWidth), eq(top + measuredHeight));
        }
    }

    public abstract static class GivenOneStep extends GivenAStepper {
        MockedStep mockedStep1;

        @Before
        public void givenOneStep() {
            mockedStep1 = new MockedStep();

            stepper.initStep(mockedStep1.step);

            clearInvocations(mockedStep1.innerView);
            clearInvocations(mockedStep1.innerLayoutParams);
            clearInvocations(mockedStep1.continueButton);
            clearInvocations(mockedStep1.continueLayoutParams);
            clearInvocations(mockedStep1.touchView);
            clearInvocations(mockedStep1.step);
        }

        void mockStep1Widths(int decoratorWidth, int innerWidth,
                             int continueWidth) {
            mockStepWidths(mockedStep1, decoratorWidth, innerWidth, continueWidth);
        }

        void mockStepWidths(MockedStep mockedStep, int decoratorWidth,
                            int innerWidth, int continueWidth) {
            when(mockedStep.step.calculateStepDecoratorWidth()).thenReturn(decoratorWidth);
            when(mockedStep.innerView.getMeasuredWidth()).thenReturn(innerWidth);
            when(mockedStep.continueButton.getMeasuredWidth()).thenReturn(continueWidth);
        }

        void mockStepHeights(int decoratorHeight, int childrenVisibleHeight, int bottomMarginHeight, Step step) {
            when(step.getDecoratorHeight()).thenReturn(decoratorHeight);
            when(step.getChildrenVisibleHeight()).thenReturn(childrenVisibleHeight);
            when(step.getBottomMarginHeight()).thenReturn(bottomMarginHeight);
        }

        void assertExpectedStep1MeasureSpecs(int maxWidth, int maxHeight,
                                             int additionalInnerUsedSpace, int additionalContinueUsedSpace) {
            assertExpectedStepMeasureSpecs(captureStep1MeasureSpecs(), mockedStep1.step, maxWidth, maxHeight,
                    additionalInnerUsedSpace, additionalContinueUsedSpace);
        }

        List<Integer> captureStep1MeasureSpecs() {
            return captureStepMeasureSpecs(mockedStep1.innerView, mockedStep1.continueButton);
        }

        List<Integer> captureStepMeasureSpecs(View innerView, View continueButton) {
            ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
            verify(innerView).measure(captor.capture(), captor.capture());
            verify(continueButton).measure(captor.capture(), captor.capture());
            return captor.getAllValues();
        }

        void assertExpectedStepMeasureSpecs(List<Integer> measureSpecs, Step step,
                                            int maxWidth, int maxHeight,
                                            int additionalInnerVerticalUsedSpace,
                                            int additionalContinueVerticalUsedSpace) {
            int innerWms = measureSpecs.get(0);
            assertExpectedWidthMeasureSpec(maxWidth, innerWms,
                    stepper.calculateHorizontalUsedSpace(step.getInnerView()));
            int innerHms = measureSpecs.get(1);
            assertExpectedHeightMeasureSpec(maxHeight, innerHms, additionalInnerVerticalUsedSpace);

            int continueWms = measureSpecs.get(2);
            assertExpectedWidthMeasureSpec(maxWidth, continueWms,
                    stepper.calculateHorizontalUsedSpace(step.getContinueButton()));
            int continueHms = measureSpecs.get(3);
            assertExpectedHeightMeasureSpec(maxHeight, continueHms, additionalContinueVerticalUsedSpace);
        }

        void assertExpectedHeightMeasureSpec(int maxHeight, int heightMeasureSpec, int additionalUsedSpace) {
            int verticalUsedSpace =
                    stepper.calculateVerticalPadding() + additionalUsedSpace;
            assertThat(View.MeasureSpec.getSize(heightMeasureSpec))
                    .isEqualTo(maxHeight - verticalUsedSpace);
        }

        void assertExpectedWidthMeasureSpec(int maxWidth, int widthMeasureSpec, int additionalUsedSpace) {
            int horizontalUsedSpace =
                    stepper.calculateHorizontalPadding() + additionalUsedSpace;
            assertThat(View.MeasureSpec.getSize(widthMeasureSpec))
                    .isEqualTo(maxWidth - horizontalUsedSpace);
        }

        void measureActiveViews(int maxWidth, int maxHeight) {
            int wms = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST);
            int hms = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST);
            stepper.measureActiveViews(wms, hms);
        }
    }

    public static class GivenExactlyOneStep extends GivenOneStep {
        @Test
        public void initSteps_ShouldHaveInnerViewsWithSingleElement() {
            assertThat(stepper.steps)
                    .hasSize(1)
                    .doesNotContainNull();
        }

        @Test
        public void initInnerView_ShouldSetVisibilityToGone() {
            stepper.initStep(mockedStep1.step);

            verify(mockedStep1.innerView).setVisibility(View.GONE);
        }

        @Test
        public void initInnerView_ShouldInitializeStepViews() {
            assertThat(stepper.steps)
                    .hasSize(1)
                    .doesNotContainNull();

            Step step = stepper.steps.get(0);
            assertThat(step.getTouchView())
                    .isNotNull();
            assertThat(step.getContinueButton())
                    .isNotNull();
        }

        @Test
        public void initTouchView_ShouldSetClickListener() {
            stepper.initTouchView(mockedStep1.step);

            verify(mockedStep1.touchView).setOnClickListener((View.OnClickListener) notNull());
        }

        @Test
        public void initTouchView_ShouldAttachToStepper() {
            stepper.initTouchView(mockedStep1.step);

            assertThat(stepper.getChildCount()).isEqualTo(1);
        }

        @Test
        public void initNavButtons_ShouldSetVisibilityToGone() {
            stepper.initNavButtons(mockedStep1.step);

            verify(mockedStep1.continueButton).setVisibility(View.GONE);
        }

        @Test
        public void initNavButtons_ShouldSetTextToContinue() {
            stepper.initNavButtons(mockedStep1.step);

            verify(mockedStep1.continueButton).setText(R.string.continue_button);
        }

        @Test
        public void initNavButtons_ShouldSetLayoutParamsWithTopMarginAndHeight() {
            stepper.initNavButtons(mockedStep1.step);

            ArgumentCaptor<VerticalStepper.LayoutParams> lpCaptor =
                    ArgumentCaptor.forClass(VerticalStepper.LayoutParams.class);
            verify(mockedStep1.continueButton).setLayoutParams(lpCaptor.capture());

            VerticalStepper.LayoutParams lp = lpCaptor.getValue();
            assertThat(lp.topMargin).isEqualTo(stepper.getCommonStepValues().getNavButtonTopMargin());
            assertThat(lp.height).isEqualTo(stepper.getCommonStepValues().getNavButtonHeight());
        }

        @Test
        public void initNavButtons_ShouldSetClickListener() {
            stepper.initNavButtons(mockedStep1.step);

            verify(mockedStep1.continueButton).setOnClickListener((View.OnClickListener) notNull());
        }

        @Test
        public void initNavButtons_ShouldAttachToStepper() {
            stepper.initNavButtons(mockedStep1.step);

            assertThat(stepper.getChildCount()).isEqualTo(1);
        }

        @Test
        public void measureBottomMarginHeights_ShouldNotMeasureBottomMarginToNextStep() {
            stepper.measureStepBottomMarginHeights();

            verify(mockedStep1.step, never()).measureBottomMarginToNextStep();
        }

        @Test
        public void calculateHorizontalUsedSpace_ShouldReturnPaddingPlusIconLeftAdjustment() {
            int leftMargin = 20;
            int rightMargin = 10;
            mockedStep1.innerLayoutParams.leftMargin = leftMargin;
            mockedStep1.innerLayoutParams.rightMargin = rightMargin;

            int horizontalPadding = stepper.calculateHorizontalUsedSpace(mockedStep1.innerView);

            assertThat(horizontalPadding)
                    .isEqualTo(leftMargin + rightMargin
                            + stepper.getCommonStepValues().calculateStepDecoratorIconWidth());
        }

        @Test
        public void calculateVerticalUsedSpace_ShouldReturnAllMargins() {
            int topMargin = 10;
            int bottomMargin = 20;
            mockedStep1.innerLayoutParams.topMargin = topMargin;
            mockedStep1.innerLayoutParams.bottomMargin = bottomMargin;

            int verticalPadding = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);

            assertThat(verticalPadding).isEqualTo(topMargin + bottomMargin);
        }

        @Test
        public void calculateWidth_ShouldReturnHorizontalPaddingAndStepWidth() {
            int decoratorWidth = 20;
            int innerWidth = decoratorWidth * 4;
            int continueWidth = 0;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int width = stepper.calculateWidth();

            int innerUsedSpace = stepper.calculateHorizontalUsedSpace(mockedStep1.innerView);
            assertThat(width)
                    .isEqualTo(stepper.calculateHorizontalPadding()
                            + innerWidth + innerUsedSpace);
        }

        @Test
        public void calculateMaxStepWidth_DecoratorsHaveMaxWidth_ShouldReturnDecoratorsWidth() {
            int decoratorWidth = stepper.getCommonStepValues().calculateStepDecoratorIconWidth() * 2;
            int innerWidth = 0;
            int continueWidth = 0;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int maxWidth = stepper.calculateMaxStepWidth();

            assertThat(maxWidth)
                    .isEqualTo(decoratorWidth);
        }

        @Test
        public void calculateMaxStepWidth_InnerViewHasMaxWidth_ShouldReturnInnerViewWidth() {
            int decoratorWidth = 20;
            int innerWidth = decoratorWidth * 4;
            int continueWidth = 0;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int maxWidth = stepper.calculateMaxStepWidth();

            int innerUsedSpace = stepper.calculateHorizontalUsedSpace(mockedStep1.innerView);
            assertThat(maxWidth)
                    .isEqualTo(innerWidth + innerUsedSpace);
        }

        @Test
        public void calculateMaxStepWidth_NavButtonsHaveMaxWidth_ShouldReturnNavButtonsWidth() {
            int decoratorWidth = 20;
            int innerWidth = 0;
            int continueWidth = decoratorWidth * 4;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int maxWidth = stepper.calculateMaxStepWidth();

            int continueUsedSpace = stepper.calculateHorizontalUsedSpace(mockedStep1.continueButton);
            assertThat(maxWidth)
                    .isEqualTo(continueWidth + continueUsedSpace);
        }

        @Test
        public void calculateHeight_ShouldReturnVerticalPaddingPlusTotalStepHeight() {
            int decoratorHeight = 100;
            int childrenVisibleHeight = 400;
            int bottomMarginHeight = 48;
            mockStepHeights(decoratorHeight, childrenVisibleHeight, bottomMarginHeight, mockedStep1.step);

            int width = stepper.calculateHeight();

            assertThat(width)
                    .isEqualTo(stepper.calculateVerticalPadding()
                            + decoratorHeight + childrenVisibleHeight + bottomMarginHeight);
        }

        @Test
        public void layoutActiveViews_ShouldNotModifyInputRect() {
            Rect rect = new Rect(1, 2, 3, 4);

            stepper.layoutActiveViews(rect, mockedStep1.step);

            assertThat(rect).isEqualTo(new Rect(1, 2, 3, 4));
        }

    }

    public static class GivenExactlyOneActiveStep extends GivenOneStep {

        @Before
        public void givenExactlyOneActiveStep() {
            mockActiveState(mockedStep1, true);
        }

        @Test
        public void toggleStepExpandedState_ShouldBecomeInactiveAndCollapsed() {
            stepper.toggleStepExpandedState(mockedStep1.step);

            assertActiveState(mockedStep1, false);
        }

        @Test
        public void measureActiveViews_ShouldHaveActiveViewsHeightsWithActualHeight() {
            final int innerViewHeight = 100;
            final int buttonHeight = 50;
            when(mockedStep1.innerView.getMeasuredHeight()).thenReturn(innerViewHeight);
            when(mockedStep1.continueButton.getMeasuredHeight()).thenReturn(buttonHeight);

            int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            stepper.measureActiveViews(ms, ms);

            verify(mockedStep1.step).setActiveViewsHeight(innerViewHeight + buttonHeight);
        }

        @Test
        public void measureActiveViews_ShouldMeasureNavButtonsAccountingForInnerView() {
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(createTestLayoutParams());
            int innerHeight = 200;
            when(mockedStep1.innerView.getMeasuredHeight()).thenReturn(innerHeight);

            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(createTestLayoutParams());

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight, innerVerticalUsedSpace,
                    innerHeight + innerVerticalUsedSpace + continueVerticalUsedSpace);
        }
    }

    public static class GivenExactlyOneInactiveStep extends GivenOneStep {

        @Before
        public void givenExactlyOneInactiveStep() {
            mockActiveState(mockedStep1, false);
        }

        @Test
        public void toggleStepExpandedState_ShouldBecomeActiveAndExpanded() {
            stepper.toggleStepExpandedState(mockedStep1.step);

            assertActiveState(mockedStep1, true);
        }

        @Test
        public void measureActiveViews_NoMargins_ShouldMeasureActiveViewsAccountingForUsedSpace() {
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(createTestLayoutParams());

            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(createTestLayoutParams());

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight, innerVerticalUsedSpace, continueVerticalUsedSpace);
        }

        @Test
        public void measureActiveViews_HasMargins_ShouldMeasureActiveViewsAccountingForUsedSpace() {
            VerticalStepper.LayoutParams innerLp = createTestLayoutParams(5, 10, 5, 10);
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(innerLp);
            VerticalStepper.LayoutParams continueLp = createTestLayoutParams(10, 20, 10, 20);
            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(continueLp);

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight, innerVerticalUsedSpace, continueVerticalUsedSpace);
        }

        @Test
        public void measureActiveViews_ShouldMeasureActiveViewsAccountingForDecorator() {
            int decoratorHeight = 100;
            when(mockedStep1.step.getDecoratorHeight()).thenReturn(decoratorHeight);
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(createTestLayoutParams());
            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(createTestLayoutParams());

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight,
                    innerVerticalUsedSpace + decoratorHeight, continueVerticalUsedSpace + decoratorHeight);
        }
    }

    public static abstract class GivenTwoSteps extends GivenOneStep {
        MockedStep mockedStep2;

        @Before
        public void givenTwoSteps() {
            mockedStep2 = new MockedStep();

            stepper.initStep(mockedStep2.step);

            clearInvocations(mockedStep2.innerView);
            clearInvocations(mockedStep2.innerLayoutParams);
            clearInvocations(mockedStep2.continueButton);
            clearInvocations(mockedStep2.continueLayoutParams);
            clearInvocations(mockedStep2.touchView);
            clearInvocations(mockedStep2.step);
        }

        void assertExpectedStep2MeasureSpecs(int maxWidth, int maxHeight,
                                             int additionalInnerUsedSpace, int additionalContinueUsedSpace) {
            assertExpectedStepMeasureSpecs(captureStep2MeasureSpecs(), mockedStep2.step, maxWidth, maxHeight,
                    additionalInnerUsedSpace, additionalContinueUsedSpace);
        }

        List<Integer> captureStep2MeasureSpecs() {
            return captureStepMeasureSpecs(mockedStep2.innerView, mockedStep2.continueButton);
        }

        void mockStep2Widths(int decoratorWidth, int innerWidth,
                             int continueWidth) {
            mockStepWidths(mockedStep2, decoratorWidth, innerWidth, continueWidth);
        }
    }

    public static class GivenExactlyTwoSteps extends GivenTwoSteps {
        @Test
        public void collapseOtherSteps_ShouldCollapseAnyOtherActiveSteps() {
            mockActiveState(mockedStep2, true);

            stepper.collapseOtherSteps(mockedStep1.step);

            assertActiveState(mockedStep2, false);
        }

        @Test
        public void collapseOtherSteps_ShouldNotCollapseListenerStep() {<commit_msg>Rename test utility method to be clearer<commit_after>package com.snowble.android.widget.verticalstepper;

import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.AppCompatButton;
import android.text.TextPaint;
import android.view.View;

import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;

import java.util.List;

import static org.assertj.core.api.Java6Assertions.*;
import static org.mockito.Mockito.*;

@RunWith(Enclosed.class)
public class VerticalStepperTest {

    private static class MockedStep {
        View innerView;
        VerticalStepper.LayoutParams innerLayoutParams;
        VerticalStepper.InternalTouchView touchView;
        AppCompatButton continueButton;
        VerticalStepper.LayoutParams continueLayoutParams;
        Step step;

        MockedStep() {
            innerView = mock(View.class);
            innerLayoutParams = mock(VerticalStepper.LayoutParams.class);
            when(innerView.getLayoutParams()).thenReturn(innerLayoutParams);

            continueButton = mock(AppCompatButton.class);
            continueLayoutParams = mock(VerticalStepper.LayoutParams.class);
            when(continueButton.getLayoutParams()).thenReturn(continueLayoutParams);

            touchView = mock(VerticalStepper.InternalTouchView.class);

            step = mock(Step.class);
            when(step.getInnerView()).thenReturn(innerView);
            when(step.getTouchView()).thenReturn(touchView);
            when(step.getContinueButton()).thenReturn(continueButton);
        }
    }

    public abstract static class GivenAStepper extends GivenAnActivity {
        VerticalStepper stepper;

        @Before
        public void givenAStepper() {
            stepper = new VerticalStepper(activity);
        }

        void mockActiveState(MockedStep step, boolean isActive) {
            when(step.step.isActive()).thenReturn(isActive);
            int visibility = isActive ? View.VISIBLE : View.GONE;
            when(step.innerView.getVisibility()).thenReturn(visibility);
            when(step.continueButton.getVisibility()).thenReturn(visibility);
        }

        void verifyActiveState(MockedStep step, boolean expectedActiveState) {
            verify(step.step).setActive(expectedActiveState);
            int visibility = expectedActiveState ? View.VISIBLE : View.GONE;
            verify(step.innerView).setVisibility(visibility);
            verify(step.continueButton).setVisibility(visibility);
        }
    }

    public static class GivenZeroSteps extends GivenAStepper {
        private int getColor(int colorRes) {
            return ResourcesCompat.getColor(activity.getResources(), colorRes, activity.getTheme());
        }

        @SuppressLint("PrivateResource") // https://code.google.com/p/android/issues/detail?id=230985
        @Test
        public void initPropertiesFromAttrs_NoAttrsSet_ShouldUseDefaults() {
            stepper.initPropertiesFromAttrs(null, 0, 0);

            assertThat(stepper.iconActiveColor).isEqualTo(getColor(R.color.bg_active_icon));
            assertThat(stepper.iconInactiveColor).isEqualTo(getColor(R.color.bg_inactive_icon));
            assertThat(stepper.continueButtonStyle)
                    .isEqualTo(android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Colored);
        }

        @SuppressLint("PrivateResource") // https://code.google.com/p/android/issues/detail?id=230985
        @Test
        public void initPropertiesFromAttrs_AttrsSet_ShouldUseAttrs() {
            Robolectric.AttributeSetBuilder builder = Robolectric.buildAttributeSet();
            builder.addAttribute(R.attr.iconColorActive, "@android:color/black");
            builder.addAttribute(R.attr.iconColorInactive, "@android:color/darker_gray");
            builder.addAttribute(R.attr.continueButtonStyle, "@style/Widget.AppCompat.Button.Borderless");

            stepper.initPropertiesFromAttrs(builder.build(), 0, 0);

            assertThat(stepper.iconActiveColor).isEqualTo(getColor(android.R.color.black));
            assertThat(stepper.iconInactiveColor).isEqualTo(getColor(android.R.color.darker_gray));
            assertThat(stepper.continueButtonStyle)
                    .isEqualTo(android.support.v7.appcompat.R.style.Widget_AppCompat_Button_Borderless);
        }

        @Test
        public void initSteps_ShouldHaveEmptyInnerViews() {
            stepper.initSteps();

            assertThat(stepper.steps).isEmpty();
        }

        @Test
        public void calculateWidth_ShouldReturnHorizontalPadding() {
            int width = stepper.calculateWidth();

            assertThat(width)
                    .isEqualTo(stepper.calculateHorizontalPadding());
        }

        @Test
        public void doMeasurement_UnspecifiedSpecs_ShouldMeasurePadding() {
            int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

            stepper.doMeasurement(ms, ms);

            assertThat(stepper.getMeasuredHeight()).isEqualTo(stepper.calculateVerticalPadding());
            assertThat(stepper.getMeasuredWidth()).isEqualTo(stepper.calculateHorizontalPadding());
        }

        @Test
        public void doMeasurement_AtMostSpecsRequiresClipping_ShouldMeasureToAtMostValues() {
            int width = stepper.calculateHorizontalPadding() / 2;
            int height = stepper.calculateVerticalPadding() / 2;
            int wms = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST);
            int hms = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);

            stepper.doMeasurement(wms, hms);

            assertThat(stepper.getMeasuredWidth()).isEqualTo(width);
            assertThat(stepper.getMeasuredHeight()).isEqualTo(height);
        }

        @Test
        public void doMeasurement_ExactlySpecsRequiresClipping_ShouldMeasureToExactValues() {
            int width = stepper.calculateHorizontalPadding() / 2;
            int height = stepper.calculateVerticalPadding() / 2;
            int wms = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
            int hms = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

            stepper.doMeasurement(wms, hms);

            assertThat(stepper.getMeasuredWidth()).isEqualTo(width);
            assertThat(stepper.getMeasuredHeight()).isEqualTo(height);
        }

        @Test
        public void doMeasurement_ExactlySpecsRequiresExpanding_ShouldMeasureToExactValues() {
            int width = stepper.calculateHorizontalPadding() * 2;
            int height = stepper.calculateVerticalPadding() * 2;
            int wms = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
            int hms = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

            stepper.doMeasurement(wms, hms);

            assertThat(stepper.getMeasuredWidth()).isEqualTo(width);
            assertThat(stepper.getMeasuredHeight()).isEqualTo(height);
        }

        @Test
        public void calculateHeight_ShouldReturnVerticalPadding() {
            int width = stepper.calculateHeight();

            assertThat(width)
                    .isEqualTo(stepper.calculateVerticalPadding());
        }

        @Test
        public void calculateHorizontalPadding_ShouldReturnAllPadding() {
            int horizontalPadding = stepper.calculateHorizontalPadding();

            assertThat(horizontalPadding)
                    .isEqualTo((stepper.outerHorizontalPadding * 2) +
                            stepper.getPaddingLeft() + stepper.getPaddingRight());
        }

        @Test
        public void calculateVerticalPadding_ShouldReturnAllPadding() {
            int verticalPadding = stepper.calculateVerticalPadding();

            assertThat(verticalPadding)
                    .isEqualTo((stepper.outerVerticalPadding * 2) +
                            stepper.getPaddingTop() + stepper.getPaddingBottom());
        }

        @Test
        public void layoutTouchView_WhenNotEnoughSpace_ShouldClip() {
            int leftPadding = 20;
            int topPadding = 4;
            int rightPadding = 10;
            int bottomPadding = 2;
            stepper.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);

            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            int adjustedLeft = left + stepper.outerHorizontalPadding + leftPadding;
            int adjustedTop = top + stepper.outerVerticalPadding + topPadding;
            int adjustedRight = right - stepper.outerHorizontalPadding - rightPadding;
            int adjustedBottom = bottom - stepper.outerVerticalPadding - bottomPadding;

            VerticalStepper.InternalTouchView touchView = mock(VerticalStepper.InternalTouchView.class);
            when(touchView.getMeasuredHeight()).thenReturn(bottom * 2);

            stepper.layoutTouchView(new Rect(adjustedLeft, adjustedTop, adjustedRight, adjustedBottom), touchView);

            verify(touchView).layout(eq(left + leftPadding), eq(top + topPadding),
                    eq(right - left - rightPadding), eq(bottom - top - bottomPadding));
        }

        @Test
        public void layoutTouchView_WhenEnoughSpace_ShouldUseFullWidthAndMeasuredHeight() {
            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            int adjustedLeft = left + stepper.outerHorizontalPadding;
            int adjustedTop = top + stepper.outerVerticalPadding;
            int adjustedRight = right - stepper.outerHorizontalPadding;
            int adjustedBottom = bottom - stepper.outerVerticalPadding;

            VerticalStepper.InternalTouchView touchView = mock(VerticalStepper.InternalTouchView.class);
            int touchMeasuredHeight = bottom / 2;
            when(touchView.getMeasuredHeight()).thenReturn(touchMeasuredHeight);

            stepper.layoutTouchView(new Rect(adjustedLeft, adjustedTop, adjustedRight, adjustedBottom), touchView);

            verify(touchView).layout(eq(left), eq(top), eq(right - left), eq(top + touchMeasuredHeight));
        }

        @Test
        public void layoutActiveView_WhenNotEnoughSpace_ShouldClip() {
            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            int leftMargin = 5;
            int topMargin = 20;
            int rightMargin = 10;
            int bottomMargin = 15;

            View activeView = mock(View.class);
            when(activeView.getMeasuredWidth()).thenReturn(right * 2);
            when(activeView.getMeasuredHeight()).thenReturn(bottom * 2);
            when(activeView.getLayoutParams()).thenReturn(
                    createTestLayoutParams(leftMargin, topMargin, rightMargin, bottomMargin));

            stepper.layoutActiveView(new Rect(left, top, right, bottom), activeView);

            verify(activeView).layout(eq(left + leftMargin), eq(top + topMargin),
                    eq(right - rightMargin), eq(bottom - bottomMargin));
        }

        @Test
        public void layoutActiveView_WhenEnoughSpace_ShouldUseFullWidthAndMeasuredHeight() {
            int left = 0;
            int top = 0;
            int right = 300;
            int bottom = 500;

            View activeView = mock(View.class);
            int measuredWidth = right / 2;
            when(activeView.getMeasuredWidth()).thenReturn(measuredWidth);
            int measuredHeight = bottom / 2;
            when(activeView.getMeasuredHeight()).thenReturn(measuredHeight);
            when(activeView.getLayoutParams()).thenReturn(mock(VerticalStepper.LayoutParams.class));

            stepper.layoutActiveView(new Rect(left, top, right, bottom), activeView);

            verify(activeView).layout(eq(left), eq(top), eq(left + measuredWidth), eq(top + measuredHeight));
        }
    }

    public abstract static class GivenOneStep extends GivenAStepper {
        MockedStep mockedStep1;

        @Before
        public void givenOneStep() {
            mockedStep1 = new MockedStep();

            stepper.initStep(mockedStep1.step);

            clearInvocations(mockedStep1.innerView);
            clearInvocations(mockedStep1.innerLayoutParams);
            clearInvocations(mockedStep1.continueButton);
            clearInvocations(mockedStep1.continueLayoutParams);
            clearInvocations(mockedStep1.touchView);
            clearInvocations(mockedStep1.step);
        }

        void mockStep1Widths(int decoratorWidth, int innerWidth,
                             int continueWidth) {
            mockStepWidths(mockedStep1, decoratorWidth, innerWidth, continueWidth);
        }

        void mockStepWidths(MockedStep mockedStep, int decoratorWidth,
                            int innerWidth, int continueWidth) {
            when(mockedStep.step.calculateStepDecoratorWidth()).thenReturn(decoratorWidth);
            when(mockedStep.innerView.getMeasuredWidth()).thenReturn(innerWidth);
            when(mockedStep.continueButton.getMeasuredWidth()).thenReturn(continueWidth);
        }

        void mockStepHeights(int decoratorHeight, int childrenVisibleHeight, int bottomMarginHeight, Step step) {
            when(step.getDecoratorHeight()).thenReturn(decoratorHeight);
            when(step.getChildrenVisibleHeight()).thenReturn(childrenVisibleHeight);
            when(step.getBottomMarginHeight()).thenReturn(bottomMarginHeight);
        }

        void assertExpectedStep1MeasureSpecs(int maxWidth, int maxHeight,
                                             int additionalInnerUsedSpace, int additionalContinueUsedSpace) {
            assertExpectedStepMeasureSpecs(captureStep1MeasureSpecs(), mockedStep1.step, maxWidth, maxHeight,
                    additionalInnerUsedSpace, additionalContinueUsedSpace);
        }

        List<Integer> captureStep1MeasureSpecs() {
            return captureStepMeasureSpecs(mockedStep1.innerView, mockedStep1.continueButton);
        }

        List<Integer> captureStepMeasureSpecs(View innerView, View continueButton) {
            ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
            verify(innerView).measure(captor.capture(), captor.capture());
            verify(continueButton).measure(captor.capture(), captor.capture());
            return captor.getAllValues();
        }

        void assertExpectedStepMeasureSpecs(List<Integer> measureSpecs, Step step,
                                            int maxWidth, int maxHeight,
                                            int additionalInnerVerticalUsedSpace,
                                            int additionalContinueVerticalUsedSpace) {
            int innerWms = measureSpecs.get(0);
            assertExpectedWidthMeasureSpec(maxWidth, innerWms,
                    stepper.calculateHorizontalUsedSpace(step.getInnerView()));
            int innerHms = measureSpecs.get(1);
            assertExpectedHeightMeasureSpec(maxHeight, innerHms, additionalInnerVerticalUsedSpace);

            int continueWms = measureSpecs.get(2);
            assertExpectedWidthMeasureSpec(maxWidth, continueWms,
                    stepper.calculateHorizontalUsedSpace(step.getContinueButton()));
            int continueHms = measureSpecs.get(3);
            assertExpectedHeightMeasureSpec(maxHeight, continueHms, additionalContinueVerticalUsedSpace);
        }

        void assertExpectedHeightMeasureSpec(int maxHeight, int heightMeasureSpec, int additionalUsedSpace) {
            int verticalUsedSpace =
                    stepper.calculateVerticalPadding() + additionalUsedSpace;
            assertThat(View.MeasureSpec.getSize(heightMeasureSpec))
                    .isEqualTo(maxHeight - verticalUsedSpace);
        }

        void assertExpectedWidthMeasureSpec(int maxWidth, int widthMeasureSpec, int additionalUsedSpace) {
            int horizontalUsedSpace =
                    stepper.calculateHorizontalPadding() + additionalUsedSpace;
            assertThat(View.MeasureSpec.getSize(widthMeasureSpec))
                    .isEqualTo(maxWidth - horizontalUsedSpace);
        }

        void measureActiveViews(int maxWidth, int maxHeight) {
            int wms = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST);
            int hms = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST);
            stepper.measureActiveViews(wms, hms);
        }
    }

    public static class GivenExactlyOneStep extends GivenOneStep {
        @Test
        public void initSteps_ShouldHaveInnerViewsWithSingleElement() {
            assertThat(stepper.steps)
                    .hasSize(1)
                    .doesNotContainNull();
        }

        @Test
        public void initInnerView_ShouldSetVisibilityToGone() {
            stepper.initStep(mockedStep1.step);

            verify(mockedStep1.innerView).setVisibility(View.GONE);
        }

        @Test
        public void initInnerView_ShouldInitializeStepViews() {
            assertThat(stepper.steps)
                    .hasSize(1)
                    .doesNotContainNull();

            Step step = stepper.steps.get(0);
            assertThat(step.getTouchView())
                    .isNotNull();
            assertThat(step.getContinueButton())
                    .isNotNull();
        }

        @Test
        public void initTouchView_ShouldSetClickListener() {
            stepper.initTouchView(mockedStep1.step);

            verify(mockedStep1.touchView).setOnClickListener((View.OnClickListener) notNull());
        }

        @Test
        public void initTouchView_ShouldAttachToStepper() {
            stepper.initTouchView(mockedStep1.step);

            assertThat(stepper.getChildCount()).isEqualTo(1);
        }

        @Test
        public void initNavButtons_ShouldSetVisibilityToGone() {
            stepper.initNavButtons(mockedStep1.step);

            verify(mockedStep1.continueButton).setVisibility(View.GONE);
        }

        @Test
        public void initNavButtons_ShouldSetTextToContinue() {
            stepper.initNavButtons(mockedStep1.step);

            verify(mockedStep1.continueButton).setText(R.string.continue_button);
        }

        @Test
        public void initNavButtons_ShouldSetLayoutParamsWithTopMarginAndHeight() {
            stepper.initNavButtons(mockedStep1.step);

            ArgumentCaptor<VerticalStepper.LayoutParams> lpCaptor =
                    ArgumentCaptor.forClass(VerticalStepper.LayoutParams.class);
            verify(mockedStep1.continueButton).setLayoutParams(lpCaptor.capture());

            VerticalStepper.LayoutParams lp = lpCaptor.getValue();
            assertThat(lp.topMargin).isEqualTo(stepper.getCommonStepValues().getNavButtonTopMargin());
            assertThat(lp.height).isEqualTo(stepper.getCommonStepValues().getNavButtonHeight());
        }

        @Test
        public void initNavButtons_ShouldSetClickListener() {
            stepper.initNavButtons(mockedStep1.step);

            verify(mockedStep1.continueButton).setOnClickListener((View.OnClickListener) notNull());
        }

        @Test
        public void initNavButtons_ShouldAttachToStepper() {
            stepper.initNavButtons(mockedStep1.step);

            assertThat(stepper.getChildCount()).isEqualTo(1);
        }

        @Test
        public void measureBottomMarginHeights_ShouldNotMeasureBottomMarginToNextStep() {
            stepper.measureStepBottomMarginHeights();

            verify(mockedStep1.step, never()).measureBottomMarginToNextStep();
        }

        @Test
        public void calculateHorizontalUsedSpace_ShouldReturnPaddingPlusIconLeftAdjustment() {
            int leftMargin = 20;
            int rightMargin = 10;
            mockedStep1.innerLayoutParams.leftMargin = leftMargin;
            mockedStep1.innerLayoutParams.rightMargin = rightMargin;

            int horizontalPadding = stepper.calculateHorizontalUsedSpace(mockedStep1.innerView);

            assertThat(horizontalPadding)
                    .isEqualTo(leftMargin + rightMargin
                            + stepper.getCommonStepValues().calculateStepDecoratorIconWidth());
        }

        @Test
        public void calculateVerticalUsedSpace_ShouldReturnAllMargins() {
            int topMargin = 10;
            int bottomMargin = 20;
            mockedStep1.innerLayoutParams.topMargin = topMargin;
            mockedStep1.innerLayoutParams.bottomMargin = bottomMargin;

            int verticalPadding = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);

            assertThat(verticalPadding).isEqualTo(topMargin + bottomMargin);
        }

        @Test
        public void calculateWidth_ShouldReturnHorizontalPaddingAndStepWidth() {
            int decoratorWidth = 20;
            int innerWidth = decoratorWidth * 4;
            int continueWidth = 0;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int width = stepper.calculateWidth();

            int innerUsedSpace = stepper.calculateHorizontalUsedSpace(mockedStep1.innerView);
            assertThat(width)
                    .isEqualTo(stepper.calculateHorizontalPadding()
                            + innerWidth + innerUsedSpace);
        }

        @Test
        public void calculateMaxStepWidth_DecoratorsHaveMaxWidth_ShouldReturnDecoratorsWidth() {
            int decoratorWidth = stepper.getCommonStepValues().calculateStepDecoratorIconWidth() * 2;
            int innerWidth = 0;
            int continueWidth = 0;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int maxWidth = stepper.calculateMaxStepWidth();

            assertThat(maxWidth)
                    .isEqualTo(decoratorWidth);
        }

        @Test
        public void calculateMaxStepWidth_InnerViewHasMaxWidth_ShouldReturnInnerViewWidth() {
            int decoratorWidth = 20;
            int innerWidth = decoratorWidth * 4;
            int continueWidth = 0;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int maxWidth = stepper.calculateMaxStepWidth();

            int innerUsedSpace = stepper.calculateHorizontalUsedSpace(mockedStep1.innerView);
            assertThat(maxWidth)
                    .isEqualTo(innerWidth + innerUsedSpace);
        }

        @Test
        public void calculateMaxStepWidth_NavButtonsHaveMaxWidth_ShouldReturnNavButtonsWidth() {
            int decoratorWidth = 20;
            int innerWidth = 0;
            int continueWidth = decoratorWidth * 4;
            mockStep1Widths(decoratorWidth, innerWidth, continueWidth);

            int maxWidth = stepper.calculateMaxStepWidth();

            int continueUsedSpace = stepper.calculateHorizontalUsedSpace(mockedStep1.continueButton);
            assertThat(maxWidth)
                    .isEqualTo(continueWidth + continueUsedSpace);
        }

        @Test
        public void calculateHeight_ShouldReturnVerticalPaddingPlusTotalStepHeight() {
            int decoratorHeight = 100;
            int childrenVisibleHeight = 400;
            int bottomMarginHeight = 48;
            mockStepHeights(decoratorHeight, childrenVisibleHeight, bottomMarginHeight, mockedStep1.step);

            int width = stepper.calculateHeight();

            assertThat(width)
                    .isEqualTo(stepper.calculateVerticalPadding()
                            + decoratorHeight + childrenVisibleHeight + bottomMarginHeight);
        }

        @Test
        public void layoutActiveViews_ShouldNotModifyInputRect() {
            Rect rect = new Rect(1, 2, 3, 4);

            stepper.layoutActiveViews(rect, mockedStep1.step);

            assertThat(rect).isEqualTo(new Rect(1, 2, 3, 4));
        }

    }

    public static class GivenExactlyOneActiveStep extends GivenOneStep {

        @Before
        public void givenExactlyOneActiveStep() {
            mockActiveState(mockedStep1, true);
        }

        @Test
        public void toggleStepExpandedState_ShouldBecomeInactiveAndCollapsed() {
            stepper.toggleStepExpandedState(mockedStep1.step);

            verifyActiveState(mockedStep1, false);
        }

        @Test
        public void measureActiveViews_ShouldHaveActiveViewsHeightsWithActualHeight() {
            final int innerViewHeight = 100;
            final int buttonHeight = 50;
            when(mockedStep1.innerView.getMeasuredHeight()).thenReturn(innerViewHeight);
            when(mockedStep1.continueButton.getMeasuredHeight()).thenReturn(buttonHeight);

            int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            stepper.measureActiveViews(ms, ms);

            verify(mockedStep1.step).setActiveViewsHeight(innerViewHeight + buttonHeight);
        }

        @Test
        public void measureActiveViews_ShouldMeasureNavButtonsAccountingForInnerView() {
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(createTestLayoutParams());
            int innerHeight = 200;
            when(mockedStep1.innerView.getMeasuredHeight()).thenReturn(innerHeight);

            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(createTestLayoutParams());

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight, innerVerticalUsedSpace,
                    innerHeight + innerVerticalUsedSpace + continueVerticalUsedSpace);
        }
    }

    public static class GivenExactlyOneInactiveStep extends GivenOneStep {

        @Before
        public void givenExactlyOneInactiveStep() {
            mockActiveState(mockedStep1, false);
        }

        @Test
        public void toggleStepExpandedState_ShouldBecomeActiveAndExpanded() {
            stepper.toggleStepExpandedState(mockedStep1.step);

            verifyActiveState(mockedStep1, true);
        }

        @Test
        public void measureActiveViews_NoMargins_ShouldMeasureActiveViewsAccountingForUsedSpace() {
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(createTestLayoutParams());

            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(createTestLayoutParams());

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight, innerVerticalUsedSpace, continueVerticalUsedSpace);
        }

        @Test
        public void measureActiveViews_HasMargins_ShouldMeasureActiveViewsAccountingForUsedSpace() {
            VerticalStepper.LayoutParams innerLp = createTestLayoutParams(5, 10, 5, 10);
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(innerLp);
            VerticalStepper.LayoutParams continueLp = createTestLayoutParams(10, 20, 10, 20);
            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(continueLp);

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight, innerVerticalUsedSpace, continueVerticalUsedSpace);
        }

        @Test
        public void measureActiveViews_ShouldMeasureActiveViewsAccountingForDecorator() {
            int decoratorHeight = 100;
            when(mockedStep1.step.getDecoratorHeight()).thenReturn(decoratorHeight);
            when(mockedStep1.innerView.getLayoutParams()).thenReturn(createTestLayoutParams());
            when(mockedStep1.continueButton.getLayoutParams()).thenReturn(createTestLayoutParams());

            int maxWidth = 1080;
            int maxHeight = 1920;
            measureActiveViews(maxWidth, maxHeight);

            int innerVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.innerView);
            int continueVerticalUsedSpace = stepper.calculateVerticalUsedSpace(mockedStep1.continueButton);
            assertExpectedStep1MeasureSpecs(maxWidth, maxHeight,
                    innerVerticalUsedSpace + decoratorHeight, continueVerticalUsedSpace + decoratorHeight);
        }
    }

    public static abstract class GivenTwoSteps extends GivenOneStep {
        MockedStep mockedStep2;

        @Before
        public void givenTwoSteps() {
            mockedStep2 = new MockedStep();

            stepper.initStep(mockedStep2.step);

            clearInvocations(mockedStep2.innerView);
            clearInvocations(mockedStep2.innerLayoutParams);
            clearInvocations(mockedStep2.continueButton);
            clearInvocations(mockedStep2.continueLayoutParams);
            clearInvocations(mockedStep2.touchView);
            clearInvocations(mockedStep2.step);
        }

        void assertExpectedStep2MeasureSpecs(int maxWidth, int maxHeight,
                                             int additionalInnerUsedSpace, int additionalContinueUsedSpace) {
            assertExpectedStepMeasureSpecs(captureStep2MeasureSpecs(), mockedStep2.step, maxWidth, maxHeight,
                    additionalInnerUsedSpace, additionalContinueUsedSpace);
        }

        List<Integer> captureStep2MeasureSpecs() {
            return captureStepMeasureSpecs(mockedStep2.innerView, mockedStep2.continueButton);
        }

        void mockStep2Widths(int decoratorWidth, int innerWidth,
                             int continueWidth) {
            mockStepWidths(mockedStep2, decoratorWidth, innerWidth, continueWidth);
        }
    }

    public static class GivenExactlyTwoSteps extends GivenTwoSteps {
        @Test
        public void collapseOtherSteps_ShouldCollapseAnyOtherActiveSteps() {
            mockActiveState(mockedStep2, true);

            stepper.collapseOtherSteps(mockedStep1.step);

            verifyActiveState(mockedStep2, false);
        }

        @Test
        public void collapseOtherSteps_ShouldNotCollapseListenerStep() {
{'fraction_non_alphanumeric': 0.08031184060048645, 'fraction_numerical': 0.009181257349032747, 'mean_word_length': 2.620691666180673, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6652890', 'n_tokens_mistral': 16103, 'n_tokens_neox': 15471, 'n_words': 2464}