Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
954431f3
Commit
954431f3
authored
Dec 12, 2019
by
Alex Buijs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add billing address component for paid signup flow
This is part of the paid signup flow
parent
ff77bdf6
Changes
16
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
818 additions
and
80 deletions
+818
-80
ee/app/assets/javascripts/subscriptions/new/components/checkout.vue
...ets/javascripts/subscriptions/new/components/checkout.vue
+3
-1
ee/app/assets/javascripts/subscriptions/new/components/checkout/billing_address.vue
...subscriptions/new/components/checkout/billing_address.vue
+172
-0
ee/app/assets/javascripts/subscriptions/new/constants.js
ee/app/assets/javascripts/subscriptions/new/constants.js
+5
-1
ee/app/assets/javascripts/subscriptions/new/store/actions.js
ee/app/assets/javascripts/subscriptions/new/store/actions.js
+73
-1
ee/app/assets/javascripts/subscriptions/new/store/mutation_types.js
...ets/javascripts/subscriptions/new/store/mutation_types.js
+16
-0
ee/app/assets/javascripts/subscriptions/new/store/mutations.js
...p/assets/javascripts/subscriptions/new/store/mutations.js
+32
-0
ee/app/assets/javascripts/subscriptions/new/store/state.js
ee/app/assets/javascripts/subscriptions/new/store/state.js
+8
-0
ee/app/assets/stylesheets/pages/subscriptions.scss
ee/app/assets/stylesheets/pages/subscriptions.scss
+6
-3
ee/spec/frontend/subscriptions/new/components/checkout/billing_address_spec.js
...criptions/new/components/checkout/billing_address_spec.js
+137
-0
ee/spec/frontend/subscriptions/new/components/checkout/components/step_spec.js
...criptions/new/components/checkout/components/step_spec.js
+19
-19
ee/spec/frontend/subscriptions/new/components/checkout/progress_bar_spec.js
...ubscriptions/new/components/checkout/progress_bar_spec.js
+8
-5
ee/spec/frontend/subscriptions/new/components/checkout/subscription_details_spec.js
...ions/new/components/checkout/subscription_details_spec.js
+4
-5
ee/spec/frontend/subscriptions/new/store/actions_spec.js
ee/spec/frontend/subscriptions/new/store/actions_spec.js
+240
-0
ee/spec/frontend/subscriptions/new/store/mutations_spec.js
ee/spec/frontend/subscriptions/new/store/mutations_spec.js
+26
-37
ee/spec/frontend/subscriptions/new/store/state_spec.js
ee/spec/frontend/subscriptions/new/store/state_spec.js
+36
-8
locale/gitlab.pot
locale/gitlab.pot
+33
-0
No files found.
ee/app/assets/javascripts/subscriptions/new/components/checkout.vue
View file @
954431f3
...
...
@@ -2,9 +2,10 @@
import
{
s__
}
from
'
~/locale
'
;
import
ProgressBar
from
'
./checkout/progress_bar.vue
'
;
import
SubscriptionDetails
from
'
./checkout/subscription_details.vue
'
;
import
BillingAddress
from
'
./checkout/billing_address.vue
'
;
export
default
{
components
:
{
ProgressBar
,
SubscriptionDetails
},
components
:
{
ProgressBar
,
SubscriptionDetails
,
BillingAddress
},
i18n
:
{
checkout
:
s__
(
'
Checkout|Checkout
'
),
},
...
...
@@ -17,6 +18,7 @@ export default {
<div
class=
"flash-container"
></div>
<h2
class=
"mt-4 mb-3 mb-lg-5"
>
{{
$options
.
i18n
.
checkout
}}
</h2>
<subscription-details
/>
<billing-address
/>
</div>
</div>
</
template
>
ee/app/assets/javascripts/subscriptions/new/components/checkout/billing_address.vue
0 → 100644
View file @
954431f3
<
script
>
import
_
from
'
underscore
'
;
import
autofocusonshow
from
'
~/vue_shared/directives/autofocusonshow
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
GlFormGroup
,
GlFormInput
,
GlFormSelect
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Step
from
'
./components/step.vue
'
;
export
default
{
components
:
{
Step
,
GlFormGroup
,
GlFormInput
,
GlFormSelect
,
},
directives
:
{
autofocusonshow
,
},
computed
:
{
...
mapState
([
'
country
'
,
'
streetAddressLine1
'
,
'
streetAddressLine2
'
,
'
city
'
,
'
countryState
'
,
'
zipCode
'
,
'
countryOptions
'
,
'
stateOptions
'
,
]),
countryModel
:
{
get
()
{
return
this
.
country
;
},
set
(
country
)
{
this
.
updateCountry
(
country
);
},
},
streetAddressLine1Model
:
{
get
()
{
return
this
.
streetAddressLine1
;
},
set
(
streetAddressLine1
)
{
this
.
updateStreetAddressLine1
(
streetAddressLine1
);
},
},
streetAddressLine2Model
:
{
get
()
{
return
this
.
streetAddressLine2
;
},
set
(
streetAddressLine2
)
{
this
.
updateStreetAddressLine2
(
streetAddressLine2
);
},
},
cityModel
:
{
get
()
{
return
this
.
city
;
},
set
(
city
)
{
this
.
updateCity
(
city
);
},
},
countryStateModel
:
{
get
()
{
return
this
.
countryState
;
},
set
(
countryState
)
{
this
.
updateCountryState
(
countryState
);
},
},
zipCodeModel
:
{
get
()
{
return
this
.
zipCode
;
},
set
(
zipCode
)
{
this
.
updateZipCode
(
zipCode
);
},
},
isValid
()
{
return
(
!
_
.
isEmpty
(
this
.
country
)
&&
!
_
.
isEmpty
(
this
.
streetAddressLine1
)
&&
!
_
.
isEmpty
(
this
.
city
)
&&
!
_
.
isEmpty
(
this
.
zipCode
)
);
},
countryOptionsWithDefault
()
{
return
[
{
text
:
this
.
$options
.
i18n
.
countrySelectPrompt
,
value
:
null
,
},
...
this
.
countryOptions
,
];
},
stateOptionsWithDefault
()
{
return
[
{
text
:
this
.
$options
.
i18n
.
stateSelectPrompt
,
value
:
null
,
},
...
this
.
stateOptions
,
];
},
},
mounted
()
{
this
.
fetchCountries
();
},
methods
:
{
...
mapActions
([
'
fetchCountries
'
,
'
fetchStates
'
,
'
updateCountry
'
,
'
updateStreetAddressLine1
'
,
'
updateStreetAddressLine2
'
,
'
updateCity
'
,
'
updateCountryState
'
,
'
updateZipCode
'
,
]),
},
i18n
:
{
stepTitle
:
s__
(
'
Checkout|Billing address
'
),
nextStepButtonText
:
s__
(
'
Checkout|Continue to payment
'
),
countryLabel
:
s__
(
'
Checkout|Country
'
),
countrySelectPrompt
:
s__
(
'
Checkout|Please select a country
'
),
streetAddressLabel
:
s__
(
'
Checkout|Street address
'
),
cityLabel
:
s__
(
'
Checkout|City
'
),
stateLabel
:
s__
(
'
Checkout|State
'
),
stateSelectPrompt
:
s__
(
'
Checkout|Please select a state
'
),
zipCodeLabel
:
s__
(
'
Checkout|Zip code
'
),
},
};
</
script
>
<
template
>
<step
step=
"billingAddress"
:title=
"$options.i18n.stepTitle"
:is-valid=
"isValid"
:next-step-button-text=
"$options.i18n.nextStepButtonText"
>
<template
#body
>
<gl-form-group
:label=
"$options.i18n.countryLabel"
label-size=
"sm"
class=
"mb-3"
>
<gl-form-select
v-model=
"countryModel"
v-autofocusonshow
:options=
"countryOptionsWithDefault"
class=
"js-country"
@
change=
"fetchStates"
/>
</gl-form-group>
<gl-form-group
:label=
"$options.i18n.streetAddressLabel"
label-size=
"sm"
class=
"mb-3"
>
<gl-form-input
v-model=
"streetAddressLine1Model"
type=
"text"
/>
<gl-form-input
v-model=
"streetAddressLine2Model"
type=
"text"
class=
"mt-2"
/>
</gl-form-group>
<gl-form-group
:label=
"$options.i18n.cityLabel"
label-size=
"sm"
class=
"mb-3"
>
<gl-form-input
v-model=
"cityModel"
type=
"text"
/>
</gl-form-group>
<div
class=
"combined d-flex"
>
<gl-form-group
:label=
"$options.i18n.stateLabel"
label-size=
"sm"
class=
"mr-3 w-50"
>
<gl-form-select
v-model=
"countryStateModel"
:options=
"stateOptionsWithDefault"
/>
</gl-form-group>
<gl-form-group
:label=
"$options.i18n.zipCodeLabel"
label-size=
"sm"
class=
"w-50"
>
<gl-form-input
v-model=
"zipCodeModel"
type=
"text"
/>
</gl-form-group>
</div>
</
template
>
<
template
#summary
>
<div
class=
"js-summary-line-1"
>
{{
streetAddressLine1
}}
</div>
<div
class=
"js-summary-line-2"
>
{{
streetAddressLine2
}}
</div>
<div
class=
"js-summary-line-3"
>
{{
city
}}
,
{{
countryState
}}
{{
zipCode
}}
</div>
</
template
>
</step>
</template>
ee/app/assets/javascripts/subscriptions/new/constants.js
View file @
954431f3
export
const
STEPS
=
[
'
subscriptionDetails
'
];
export
const
STEPS
=
[
'
subscriptionDetails
'
,
'
billingAddress
'
];
export
const
COUNTRIES_URL
=
'
/-/countries
'
;
export
const
STATES_URL
=
'
/-/country_states
'
;
export
const
TAX_RATE
=
0
;
ee/app/assets/javascripts/subscriptions/new/store/actions.js
View file @
954431f3
import
*
as
types
from
'
./mutation_types
'
;
import
{
STEPS
}
from
'
../constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
s__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
{
STEPS
,
COUNTRIES_URL
,
STATES_URL
}
from
'
../constants
'
;
export
const
activateStep
=
({
commit
},
currentStep
)
=>
{
if
(
STEPS
.
includes
(
currentStep
))
{
...
...
@@ -32,3 +35,72 @@ export const updateNumberOfUsers = ({ commit }, numberOfUsers) => {
export
const
updateOrganizationName
=
({
commit
},
organizationName
)
=>
{
commit
(
types
.
UPDATE_ORGANIZATION_NAME
,
organizationName
);
};
export
const
fetchCountries
=
({
dispatch
})
=>
{
axios
.
get
(
COUNTRIES_URL
)
.
then
(({
data
})
=>
dispatch
(
'
fetchCountriesSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
fetchCountriesError
'
));
};
export
const
fetchCountriesSuccess
=
({
commit
},
data
=
[])
=>
{
const
countries
=
data
.
map
(
country
=>
({
text
:
country
[
0
],
value
:
country
[
1
]
}));
commit
(
types
.
UPDATE_COUNTRY_OPTIONS
,
countries
);
};
export
const
fetchCountriesError
=
()
=>
{
createFlash
(
s__
(
'
Checkout|Failed to load countries. Please try again.
'
));
};
export
const
fetchStates
=
({
state
,
dispatch
})
=>
{
dispatch
(
'
resetStates
'
);
if
(
!
state
.
country
)
{
return
;
}
axios
.
get
(
STATES_URL
,
{
params
:
{
country
:
state
.
country
}
})
.
then
(({
data
})
=>
dispatch
(
'
fetchStatesSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
fetchStatesError
'
));
};
export
const
fetchStatesSuccess
=
({
commit
},
data
=
{})
=>
{
const
states
=
Object
.
keys
(
data
).
map
(
state
=>
({
text
:
state
,
value
:
data
[
state
]
}));
commit
(
types
.
UPDATE_STATE_OPTIONS
,
states
);
};
export
const
fetchStatesError
=
()
=>
{
createFlash
(
s__
(
'
Checkout|Failed to load states. Please try again.
'
));
};
export
const
resetStates
=
({
commit
})
=>
{
commit
(
types
.
UPDATE_COUNTRY_STATE
,
null
);
commit
(
types
.
UPDATE_STATE_OPTIONS
,
[]);
};
export
const
updateCountry
=
({
commit
},
country
)
=>
{
commit
(
types
.
UPDATE_COUNTRY
,
country
);
};
export
const
updateStreetAddressLine1
=
({
commit
},
streetAddressLine1
)
=>
{
commit
(
types
.
UPDATE_STREET_ADDRESS_LINE_ONE
,
streetAddressLine1
);
};
export
const
updateStreetAddressLine2
=
({
commit
},
streetAddressLine2
)
=>
{
commit
(
types
.
UPDATE_STREET_ADDRESS_LINE_TWO
,
streetAddressLine2
);
};
export
const
updateCity
=
({
commit
},
city
)
=>
{
commit
(
types
.
UPDATE_CITY
,
city
);
};
export
const
updateCountryState
=
({
commit
},
countryState
)
=>
{
commit
(
types
.
UPDATE_COUNTRY_STATE
,
countryState
);
};
export
const
updateZipCode
=
({
commit
},
zipCode
)
=>
{
commit
(
types
.
UPDATE_ZIP_CODE
,
zipCode
);
};
ee/app/assets/javascripts/subscriptions/new/store/mutation_types.js
View file @
954431f3
...
...
@@ -7,3 +7,19 @@ export const UPDATE_IS_SETUP_FOR_COMPANY = 'UPDATE_IS_SETUP_FOR_COMPANY';
export
const
UPDATE_NUMBER_OF_USERS
=
'
UPDATE_NUMBER_OF_USERS
'
;
export
const
UPDATE_ORGANIZATION_NAME
=
'
UPDATE_ORGANIZATION_NAME
'
;
export
const
UPDATE_COUNTRY_OPTIONS
=
'
UPDATE_COUNTRY_OPTIONS
'
;
export
const
UPDATE_STATE_OPTIONS
=
'
UPDATE_STATE_OPTIONS
'
;
export
const
UPDATE_COUNTRY
=
'
UPDATE_COUNTRY
'
;
export
const
UPDATE_STREET_ADDRESS_LINE_ONE
=
'
UPDATE_STREET_ADDRESS_LINE_ONE
'
;
export
const
UPDATE_STREET_ADDRESS_LINE_TWO
=
'
UPDATE_STREET_ADDRESS_LINE_TWO
'
;
export
const
UPDATE_CITY
=
'
UPDATE_CITY
'
;
export
const
UPDATE_COUNTRY_STATE
=
'
UPDATE_COUNTRY_STATE
'
;
export
const
UPDATE_ZIP_CODE
=
'
UPDATE_ZIP_CODE
'
;
ee/app/assets/javascripts/subscriptions/new/store/mutations.js
View file @
954431f3
...
...
@@ -20,4 +20,36 @@ export default {
[
types
.
UPDATE_ORGANIZATION_NAME
](
state
,
organizationName
)
{
state
.
organizationName
=
organizationName
;
},
[
types
.
UPDATE_COUNTRY_OPTIONS
](
state
,
countryOptions
)
{
state
.
countryOptions
=
countryOptions
;
},
[
types
.
UPDATE_STATE_OPTIONS
](
state
,
stateOptions
)
{
state
.
stateOptions
=
stateOptions
;
},
[
types
.
UPDATE_COUNTRY
](
state
,
country
)
{
state
.
country
=
country
;
},
[
types
.
UPDATE_STREET_ADDRESS_LINE_ONE
](
state
,
streetAddressLine1
)
{
state
.
streetAddressLine1
=
streetAddressLine1
;
},
[
types
.
UPDATE_STREET_ADDRESS_LINE_TWO
](
state
,
streetAddressLine2
)
{
state
.
streetAddressLine2
=
streetAddressLine2
;
},
[
types
.
UPDATE_CITY
](
state
,
city
)
{
state
.
city
=
city
;
},
[
types
.
UPDATE_COUNTRY_STATE
](
state
,
countryState
)
{
state
.
countryState
=
countryState
;
},
[
types
.
UPDATE_ZIP_CODE
](
state
,
zipCode
)
{
state
.
zipCode
=
zipCode
;
},
};
ee/app/assets/javascripts/subscriptions/new/store/state.js
View file @
954431f3
...
...
@@ -27,6 +27,14 @@ export default ({ planData = '[]', planId, setupForCompany, fullName }) => {
fullName
,
organizationName
:
null
,
numberOfUsers
:
parseBoolean
(
setupForCompany
)
?
0
:
1
,
country
:
null
,
streetAddressLine1
:
null
,
streetAddressLine2
:
null
,
city
:
null
,
countryState
:
null
,
zipCode
:
null
,
countryOptions
:
[],
stateOptions
:
[],
taxRate
:
TAX_RATE
,
startDate
:
new
Date
(
Date
.
now
()),
};
...
...
ee/app/assets/stylesheets/pages/subscriptions.scss
View file @
954431f3
...
...
@@ -83,15 +83,19 @@ $subscriptions-full-width-lg: 541px;
flex-grow
:
1
;
max-width
:
420px
;
legend
{
border-bottom
:
0
;
font-weight
:
$gl-font-weight-bold
;
}
.gl-form-input
,
.gl-form-select
{
height
:
32px
;
line-height
:
1rem
;
}
}
.number
{
width
:
50%
;
@media
(
min-width
:
map-get
(
$grid-breakpoints
,
lg
))
{
max-width
:
202px
;
}
...
...
@@ -99,7 +103,6 @@ $subscriptions-full-width-lg: 541px;
.label
{
padding
:
0
;
width
:
50%
;
.gl-link
{
font-size
:
inherit
;
...
...
ee/spec/frontend/subscriptions/new/components/checkout/billing_address_spec.js
0 → 100644
View file @
954431f3
import
Vuex
from
'
vuex
'
;
import
{
mount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
createStore
from
'
ee/subscriptions/new/store
'
;
import
*
as
types
from
'
ee/subscriptions/new/store/mutation_types
'
;
import
Step
from
'
ee/subscriptions/new/components/checkout/components/step.vue
'
;
import
Component
from
'
ee/subscriptions/new/components/checkout/billing_address.vue
'
;
describe
(
'
Billing Address
'
,
()
=>
{
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
let
store
;
let
wrapper
;
const
methodMocks
=
{
fetchCountries
:
jest
.
fn
(),
fetchStates
:
jest
.
fn
(),
};
const
createComponent
=
()
=>
{
wrapper
=
mount
(
Component
,
{
localVue
,
store
,
methods
:
methodMocks
,
});
};
beforeEach
(()
=>
{
store
=
createStore
();
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
mounted
'
,
()
=>
{
it
(
'
should load the countries
'
,
()
=>
{
expect
(
methodMocks
.
fetchCountries
).
toHaveBeenCalled
();
});
});
describe
(
'
country options
'
,
()
=>
{
const
countrySelect
=
()
=>
wrapper
.
find
(
'
.js-country
'
);
beforeEach
(()
=>
{
store
.
commit
(
types
.
UPDATE_COUNTRY_OPTIONS
,
[{
text
:
'
Netherlands
'
,
value
:
'
NL
'
}]);
});
it
(
'
should display the select prompt
'
,
()
=>
{
expect
(
countrySelect
().
html
()).
toContain
(
'
<option value="">Please select a country</option>
'
);
});
it
(
'
should display the countries returned from the server
'
,
()
=>
{
expect
(
countrySelect
().
html
()).
toContain
(
'
<option value="NL">Netherlands</option>
'
);
});
it
(
'
should fetch states when selecting a country
'
,
()
=>
{
countrySelect
().
trigger
(
'
change
'
);
return
localVue
.
nextTick
().
then
(()
=>
{
expect
(
methodMocks
.
fetchStates
).
toHaveBeenCalled
();
});
});
});
describe
(
'
validations
'
,
()
=>
{
const
isStepValid
=
()
=>
wrapper
.
find
(
Step
).
props
(
'
isValid
'
);
beforeEach
(()
=>
{
store
.
commit
(
types
.
UPDATE_COUNTRY
,
'
country
'
);
store
.
commit
(
types
.
UPDATE_STREET_ADDRESS_LINE_ONE
,
'
address line 1
'
);
store
.
commit
(
types
.
UPDATE_CITY
,
'
city
'
);
store
.
commit
(
types
.
UPDATE_ZIP_CODE
,
'
zip
'
);
});
it
(
'
should be valid when country, streetAddressLine1, city and zipCode have been entered
'
,
()
=>
{
expect
(
isStepValid
()).
toBe
(
true
);
});
it
(
'
should be invalid when country is undefined
'
,
()
=>
{
store
.
commit
(
types
.
UPDATE_COUNTRY
,
null
);
return
localVue
.
nextTick
().
then
(()
=>
{
expect
(
isStepValid
()).
toBe
(
false
);
});
});
it
(
'
should be invalid when streetAddressLine1 is undefined
'
,
()
=>
{
store
.
commit
(
types
.
UPDATE_STREET_ADDRESS_LINE_ONE
,
null
);
return
localVue
.
nextTick
().
then
(()
=>
{
expect
(
isStepValid
()).
toBe
(
false
);
});
});
it
(
'
should be invalid when city is undefined
'
,
()
=>
{
store
.
commit
(
types
.
UPDATE_CITY
,
null
);
return
localVue
.
nextTick
().
then
(()
=>
{
expect
(
isStepValid
()).
toBe
(
false
);
});
});
it
(
'
should be invalid when zipCode is undefined
'
,
()
=>
{
store
.
commit
(
types
.
UPDATE_ZIP_CODE
,
null
);
return
localVue
.
nextTick
().
then
(()
=>
{
expect
(
isStepValid
()).
toBe
(
false
);
});
});
});
describe
(
'
showing the summary
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
commit
(
types
.
UPDATE_COUNTRY
,
'
country
'
);
store
.
commit
(
types
.
UPDATE_STREET_ADDRESS_LINE_ONE
,
'
address line 1
'
);
store
.
commit
(
types
.
UPDATE_STREET_ADDRESS_LINE_TWO
,
'
address line 2
'
);
store
.
commit
(
types
.
UPDATE_COUNTRY_STATE
,
'
state
'
);
store
.
commit
(
types
.
UPDATE_CITY
,
'
city
'
);
store
.
commit
(
types
.
UPDATE_ZIP_CODE
,
'
zip
'
);
store
.
commit
(
types
.
UPDATE_CURRENT_STEP
,
'
nextStep
'
);
});
it
(
'
should show the entered address line 1
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-summary-line-1
'
).
text
()).
toEqual
(
'
address line 1
'
);
});
it
(
'
should show the entered address line 2
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-summary-line-2
'
).
text
()).
toEqual
(
'
address line 2
'
);
});
it
(
'
should show the entered address city, state and zip code
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-summary-line-3
'
).
text
()).
toEqual
(
'
city, state zip
'
);
});
});
});
ee/spec/frontend/subscriptions/new/components/checkout/components/step_spec.js
View file @
954431f3
...
...
@@ -3,17 +3,16 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
createStore
from
'
ee/subscriptions/new/store
'
;
import
*
as
constants
from
'
ee/subscriptions/new/constants
'
;
import
c
omponent
from
'
ee/subscriptions/new/components/checkout/components/step.vue
'
;
import
C
omponent
from
'
ee/subscriptions/new/components/checkout/components/step.vue
'
;
import
StepSummary
from
'
ee/subscriptions/new/components/checkout/components/step_summary.vue
'
;
describe
(
'
Step
'
,
()
=>
{
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
let
store
;
let
wrapper
;
const
store
=
createStore
();
const
initialProps
=
{
step
:
'
secondStep
'
,
isValid
:
true
,
...
...
@@ -21,11 +20,11 @@ describe('Step', () => {
nextStepButtonText
:
'
next
'
,
};
const
factory
=
propsData
=>
{
wrapper
=
shallowMount
(
component
,
{
store
,
const
createComponent
=
propsData
=>
{
wrapper
=
shallowMount
(
Component
,
{
propsData
:
{
...
initialProps
,
...
propsData
},
localVue
,
store
,
});
};
...
...
@@ -36,6 +35,7 @@ describe('Step', () => {
constants
.
STEPS
=
[
'
firstStep
'
,
'
secondStep
'
];
beforeEach
(()
=>
{
store
=
createStore
();
store
.
dispatch
(
'
activateStep
'
,
'
secondStep
'
);
});
...
...
@@ -45,14 +45,14 @@ describe('Step', () => {
describe
(
'
Step Body
'
,
()
=>
{
it
(
'
should display the step body when this step is the current step
'
,
()
=>
{
factory
();
createComponent
();
expect
(
wrapper
.
find
(
'
.card > div
'
).
attributes
(
'
style
'
)).
toBeUndefined
();
});
it
(
'
should not display the step body when this step is not the current step
'
,
()
=>
{
activatePreviousStep
();
factory
();
createComponent
();
expect
(
wrapper
.
find
(
'
.card > div
'
).
attributes
(
'
style
'
)).
toBe
(
'
display: none;
'
);
});
...
...
@@ -61,26 +61,26 @@ describe('Step', () => {
describe
(
'
Step Summary
'
,
()
=>
{
it
(
'
should be shown when this step is valid and not active
'
,
()
=>
{
activatePreviousStep
();
factory
();
createComponent
();
expect
(
wrapper
.
find
(
StepSummary
).
exists
()).
toBe
(
true
);
});
it
(
'
should not be shown when this step is not valid and not active
'
,
()
=>
{
activatePreviousStep
();
factory
({
isValid
:
false
});
createComponent
({
isValid
:
false
});
expect
(
wrapper
.
find
(
StepSummary
).
exists
()).
toBe
(
false
);
});
it
(
'
should not be shown when this step is valid and active
'
,
()
=>
{
factory
();
createComponent
();
expect
(
wrapper
.
find
(
StepSummary
).
exists
()).
toBe
(
false
);
});
it
(
'
should not be shown when this step is not valid and active
'
,
()
=>
{
factory
({
isValid
:
false
});
createComponent
({
isValid
:
false
});
expect
(
wrapper
.
find
(
StepSummary
).
exists
()).
toBe
(
false
);
});
...
...
@@ -88,7 +88,7 @@ describe('Step', () => {
describe
(
'
isEditable
'
,
()
=>
{
it
(
'
should set the isEditable property to true when this step is finished and comes before the current step
'
,
()
=>
{
factory
({
step
:
'
firstStep
'
});
createComponent
({
step
:
'
firstStep
'
});
expect
(
wrapper
.
find
(
StepSummary
).
props
(
'
isEditable
'
)).
toBe
(
true
);
});
...
...
@@ -97,13 +97,13 @@ describe('Step', () => {
describe
(
'
Showing the summary
'
,
()
=>
{
it
(
'
shows the summary when this step is finished
'
,
()
=>
{
activatePreviousStep
();
factory
();
createComponent
();
expect
(
wrapper
.
find
(
StepSummary
).
exists
()).
toBe
(
true
);
});
it
(
'
does not show the summary when this step is not finished
'
,
()
=>
{
factory
();
createComponent
();
expect
(
wrapper
.
find
(
StepSummary
).
exists
()).
toBe
(
false
);
});
...
...
@@ -111,25 +111,25 @@ describe('Step', () => {
describe
(
'
Next button
'
,
()
=>
{
it
(
'
shows the next button when the text was passed
'
,
()
=>
{
factory
();
createComponent
();
expect
(
wrapper
.
text
()).
toBe
(
'
next
'
);
});
it
(
'
does not show the next button when no text was passed
'
,
()
=>
{
factory
({
nextStepButtonText
:
''
});
createComponent
({
nextStepButtonText
:
''
});
expect
(
wrapper
.
text
()).
toBe
(
''
);
});
it
(
'
is disabled when this step is not valid
'
,
()
=>
{
factory
({
isValid
:
false
});
createComponent
({
isValid
:
false
});
expect
(
wrapper
.
find
(
GlButton
).
attributes
(
'
disabled
'
)).
toBe
(
'
true
'
);
});
it
(
'
is enabled when this step is valid
'
,
()
=>
{
factory
();
createComponent
();
expect
(
wrapper
.
find
(
GlButton
).
attributes
(
'
disabled
'
)).
toBeUndefined
();
});
...
...
ee/spec/frontend/subscriptions/new/components/checkout/progress_bar_spec.js
View file @
954431f3
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
c
omponent
from
'
ee/subscriptions/new/components/checkout/progress_bar.vue
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
C
omponent
from
'
ee/subscriptions/new/components/checkout/progress_bar.vue
'
;
describe
(
'
Progress Bar
'
,
()
=>
{
const
localVue
=
createLocalVue
();
let
wrapper
;
const
factory
=
propsData
=>
{
wrapper
=
shallowMount
(
c
omponent
,
{
const
createComponent
=
propsData
=>
{
wrapper
=
shallowMount
(
C
omponent
,
{
propsData
,
localVue
,
});
};
...
...
@@ -14,7 +17,7 @@ describe('Progress Bar', () => {
const
secondStep
=
()
=>
wrapper
.
find
(
'
.bar div:nth-child(2)
'
);
beforeEach
(()
=>
{
factory
({
step
:
2
});
createComponent
({
step
:
2
});
});
afterEach
(()
=>
{
...
...
ee/spec/frontend/subscriptions/new/components/checkout/subscription_details_spec.js
View file @
954431f3
...
...
@@ -9,6 +9,7 @@ describe('Subscription Details', () => {
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
let
store
;
let
wrapper
;
const
planData
=
[
...
...
@@ -23,17 +24,17 @@ describe('Subscription Details', () => {
fullName
:
'
Full Name
'
,
};
const
store
=
createStore
(
initialData
);
const
isStepValid
=
()
=>
wrapper
.
find
(
Step
).
props
(
'
isValid
'
);
const
createComponent
=
(
opts
=
{}
)
=>
{
const
createComponent
=
()
=>
{
wrapper
=
mount
(
Component
,
{
localVue
,
store
,
...
opts
,
});
};
beforeEach
(()
=>
{
store
=
createStore
(
initialData
);
createComponent
();
});
...
...
@@ -41,8 +42,6 @@ describe('Subscription Details', () => {
wrapper
.
destroy
();
});
const
isStepValid
=
()
=>
wrapper
.
find
(
Step
).
props
(
'
isValid
'
);
describe
(
'
Setting up for personal use
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
commit
(
types
.
UPDATE_IS_SETUP_FOR_COMPANY
,
false
);
...
...
ee/spec/frontend/subscriptions/new/store/actions_spec.js
View file @
954431f3
import
testAction
from
'
helpers/vuex_action_helper
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createFlash
from
'
~/flash
'
;
import
*
as
actions
from
'
ee/subscriptions/new/store/actions
'
;
import
*
as
constants
from
'
ee/subscriptions/new/constants
'
;
jest
.
mock
(
'
~/flash
'
);
constants
.
STEPS
=
[
'
firstStep
'
,
'
secondStep
'
];
let
mock
;
describe
(
'
Subscriptions Actions
'
,
()
=>
{
describe
(
'
activateStep
'
,
()
=>
{
it
(
'
set the currentStep to the provided value
'
,
done
=>
{
...
...
@@ -101,4 +108,237 @@ describe('Subscriptions Actions', () => {
);
});
});
describe
(
'
fetchCountries
'
,
()
=>
{
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
calls fetchCountriesSuccess with the returned data on success
'
,
done
=>
{
mock
.
onGet
(
constants
.
COUNTRIES_URL
).
replyOnce
(
200
,
[
'
Netherlands
'
,
'
NL
'
]);
testAction
(
actions
.
fetchCountries
,
null
,
{},
[],
[{
type
:
'
fetchCountriesSuccess
'
,
payload
:
[
'
Netherlands
'
,
'
NL
'
]
}],
done
,
);
});
it
(
'
calls fetchCountriesError on error
'
,
done
=>
{
mock
.
onGet
(
constants
.
COUNTRIES_URL
).
replyOnce
(
500
);
testAction
(
actions
.
fetchCountries
,
null
,
{},
[],
[{
type
:
'
fetchCountriesError
'
}],
done
);
});
});
describe
(
'
fetchCountriesSuccess
'
,
()
=>
{
it
(
'
transforms and adds fetched countryOptions
'
,
done
=>
{
testAction
(
actions
.
fetchCountriesSuccess
,
[[
'
Netherlands
'
,
'
NL
'
]],
{},
[{
type
:
'
UPDATE_COUNTRY_OPTIONS
'
,
payload
:
[{
text
:
'
Netherlands
'
,
value
:
'
NL
'
}]
}],
[],
done
,
);
});
it
(
'
adds an empty array when no data provided
'
,
done
=>
{
testAction
(
actions
.
fetchCountriesSuccess
,
undefined
,
{},
[{
type
:
'
UPDATE_COUNTRY_OPTIONS
'
,
payload
:
[]
}],
[],
done
,
);
});
});
describe
(
'
fetchCountriesError
'
,
()
=>
{
it
(
'
creates a flash
'
,
done
=>
{
testAction
(
actions
.
fetchCountriesError
,
null
,
{},
[],
[],
()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
(
'
Failed to load countries. Please try again.
'
);
done
();
});
});
});
describe
(
'
fetchStates
'
,
()
=>
{
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
calls resetStates and fetchStatesSuccess with the returned data on success
'
,
done
=>
{
mock
.
onGet
(
constants
.
STATES_URL
,
{
params
:
{
country
:
'
NL
'
}
})
.
replyOnce
(
200
,
{
utrecht
:
'
UT
'
});
testAction
(
actions
.
fetchStates
,
null
,
{
country
:
'
NL
'
},
[],
[{
type
:
'
resetStates
'
},
{
type
:
'
fetchStatesSuccess
'
,
payload
:
{
utrecht
:
'
UT
'
}
}],
done
,
);
});
it
(
'
only calls resetStates when no country selected
'
,
done
=>
{
mock
.
onGet
(
constants
.
STATES_URL
).
replyOnce
(
500
);
testAction
(
actions
.
fetchStates
,
null
,
{
country
:
null
},
[],
[{
type
:
'
resetStates
'
}],
done
);
});
it
(
'
calls resetStates and fetchStatesError on error
'
,
done
=>
{
mock
.
onGet
(
constants
.
STATES_URL
).
replyOnce
(
500
);
testAction
(
actions
.
fetchStates
,
null
,
{
country
:
'
NL
'
},
[],
[{
type
:
'
resetStates
'
},
{
type
:
'
fetchStatesError
'
}],
done
,
);
});
});
describe
(
'
fetchStatesSuccess
'
,
()
=>
{
it
(
'
transforms and adds received stateOptions
'
,
done
=>
{
testAction
(
actions
.
fetchStatesSuccess
,
{
Utrecht
:
'
UT
'
},
{},
[{
type
:
'
UPDATE_STATE_OPTIONS
'
,
payload
:
[{
text
:
'
Utrecht
'
,
value
:
'
UT
'
}]
}],
[],
done
,
);
});
it
(
'
adds an empty array when no data provided
'
,
done
=>
{
testAction
(
actions
.
fetchStatesSuccess
,
undefined
,
{},
[{
type
:
'
UPDATE_STATE_OPTIONS
'
,
payload
:
[]
}],
[],
done
,
);
});
});
describe
(
'
fetchStatesError
'
,
()
=>
{
it
(
'
creates a flash
'
,
done
=>
{
testAction
(
actions
.
fetchStatesError
,
null
,
{},
[],
[],
()
=>
{
expect
(
createFlash
).
toHaveBeenCalledWith
(
'
Failed to load states. Please try again.
'
);
done
();
});
});
});
describe
(
'
resetStates
'
,
()
=>
{
it
(
'
resets the selected state and sets the stateOptions to the initial value
'
,
done
=>
{
testAction
(
actions
.
resetStates
,
null
,
{},
[
{
type
:
'
UPDATE_COUNTRY_STATE
'
,
payload
:
null
},
{
type
:
'
UPDATE_STATE_OPTIONS
'
,
payload
:
[]
},
],
[],
done
,
);
});
});
describe
(
'
updateCountry
'
,
()
=>
{
it
(
'
updates country to the provided value
'
,
done
=>
{
testAction
(
actions
.
updateCountry
,
'
country
'
,
{},
[{
type
:
'
UPDATE_COUNTRY
'
,
payload
:
'
country
'
}],
[],
done
,
);
});
});
describe
(
'
updateStreetAddressLine1
'
,
()
=>
{
it
(
'
updates streetAddressLine1 to the provided value
'
,
done
=>
{
testAction
(
actions
.
updateStreetAddressLine1
,
'
streetAddressLine1
'
,
{},
[{
type
:
'
UPDATE_STREET_ADDRESS_LINE_ONE
'
,
payload
:
'
streetAddressLine1
'
}],
[],
done
,
);
});
});
describe
(
'
updateStreetAddressLine2
'
,
()
=>
{
it
(
'
updates streetAddressLine2 to the provided value
'
,
done
=>
{
testAction
(
actions
.
updateStreetAddressLine2
,
'
streetAddressLine2
'
,
{},
[{
type
:
'
UPDATE_STREET_ADDRESS_LINE_TWO
'
,
payload
:
'
streetAddressLine2
'
}],
[],
done
,
);
});
});
describe
(
'
updateCity
'
,
()
=>
{
it
(
'
updates city to the provided value
'
,
done
=>
{
testAction
(
actions
.
updateCity
,
'
city
'
,
{},
[{
type
:
'
UPDATE_CITY
'
,
payload
:
'
city
'
}],
[],
done
,
);
});
});
describe
(
'
updateCountryState
'
,
()
=>
{
it
(
'
updates countryState to the provided value
'
,
done
=>
{
testAction
(
actions
.
updateCountryState
,
'
countryState
'
,
{},
[{
type
:
'
UPDATE_COUNTRY_STATE
'
,
payload
:
'
countryState
'
}],
[],
done
,
);
});
});
describe
(
'
updateZipCode
'
,
()
=>
{
it
(
'
updates zipCode to the provided value
'
,
done
=>
{
testAction
(
actions
.
updateZipCode
,
'
zipCode
'
,
{},
[{
type
:
'
UPDATE_ZIP_CODE
'
,
payload
:
'
zipCode
'
}],
[],
done
,
);
});
});
});
ee/spec/frontend/subscriptions/new/store/mutations_spec.js
View file @
954431f3
...
...
@@ -7,6 +7,8 @@ const state = () => ({
isSetupForCompany
:
true
,
numberOfUsers
:
1
,
organizationName
:
'
name
'
,
countryOptions
:
[],
stateOptions
:
[],
});
let
stateCopy
;
...
...
@@ -15,42 +17,29 @@ beforeEach(() => {
stateCopy
=
state
();
});
describe
(
'
UPDATE_CURRENT_STEP
'
,
()
=>
{
it
(
'
should set the currentStep to the given step
'
,
()
=>
{
mutations
[
types
.
UPDATE_CURRENT_STEP
](
stateCopy
,
'
secondStep
'
);
expect
(
stateCopy
.
currentStep
).
toEqual
(
'
secondStep
'
);
});
});
describe
(
'
UPDATE_SELECTED_PLAN
'
,
()
=>
{
it
(
'
should set the selectedPlan to the given plan
'
,
()
=>
{
mutations
[
types
.
UPDATE_SELECTED_PLAN
](
stateCopy
,
'
secondPlan
'
);
expect
(
stateCopy
.
selectedPlan
).
toEqual
(
'
secondPlan
'
);
});
});
describe
(
'
UPDATE_IS_SETUP_FOR_COMPANY
'
,
()
=>
{
it
(
'
should set the isSetupForCompany to the given boolean
'
,
()
=>
{
mutations
[
types
.
UPDATE_IS_SETUP_FOR_COMPANY
](
stateCopy
,
false
);
expect
(
stateCopy
.
isSetupForCompany
).
toEqual
(
false
);
});
});
describe
(
'
UPDATE_NUMBER_OF_USERS
'
,
()
=>
{
it
(
'
should set the numberOfUsers to the given number
'
,
()
=>
{
mutations
[
types
.
UPDATE_NUMBER_OF_USERS
](
stateCopy
,
2
);
expect
(
stateCopy
.
numberOfUsers
).
toEqual
(
2
);
describe
(
'
ee/subscriptions/new/store/mutation
'
,
()
=>
{
describe
.
each
`
mutation | value | stateProp
${
types
.
UPDATE_CURRENT_STEP
}
|
${
'
secondStep
'
}
|
${
'
currentStep
'
}
${
types
.
UPDATE_SELECTED_PLAN
}
|
${
'
secondPlan
'
}
|
${
'
selectedPlan
'
}
${
types
.
UPDATE_IS_SETUP_FOR_COMPANY
}
|
${
false
}
|
${
'
isSetupForCompany
'
}
${
types
.
UPDATE_NUMBER_OF_USERS
}
|
${
2
}
|
${
'
numberOfUsers
'
}
${
types
.
UPDATE_ORGANIZATION_NAME
}
|
${
'
new name
'
}
|
${
'
organizationName
'
}
${
types
.
UPDATE_COUNTRY_OPTIONS
}
|
${[{
text
:
'
country
'
,
value
:
'
id
'
}]}
|
${
'
countryOptions
'
}
${
types
.
UPDATE_STATE_OPTIONS
}
|
${[{
text
:
'
state
'
,
value
:
'
id
'
}]}
|
${
'
stateOptions
'
}
${
types
.
UPDATE_COUNTRY
}
|
${
'
NL
'
}
|
${
'
country
'
}
${
types
.
UPDATE_STREET_ADDRESS_LINE_ONE
}
|
${
'
streetAddressLine1
'
}
|
${
'
streetAddressLine1
'
}
${
types
.
UPDATE_STREET_ADDRESS_LINE_TWO
}
|
${
'
streetAddressLine2
'
}
|
${
'
streetAddressLine2
'
}
${
types
.
UPDATE_CITY
}
|
${
'
city
'
}
|
${
'
city
'
}
${
types
.
UPDATE_COUNTRY_STATE
}
|
${
'
countryState
'
}
|
${
'
countryState
'
}
${
types
.
UPDATE_ZIP_CODE
}
|
${
'
zipCode
'
}
|
${
'
zipCode
'
}
`
(
'
$mutation
'
,
({
mutation
,
value
,
stateProp
})
=>
{
it
(
`should set the
${
stateProp
}
to the given value`
,
()
=>
{
expect
(
stateCopy
[
stateProp
]).
not
.
toEqual
(
value
);
mutations
[
mutation
](
stateCopy
,
value
);
expect
(
stateCopy
[
stateProp
]).
toEqual
(
value
);
});
});
describe
(
'
UPDATE_ORGANIZATION_NAME
'
,
()
=>
{
it
(
'
should set the organizationName to the given name
'
,
()
=>
{
mutations
[
types
.
UPDATE_ORGANIZATION_NAME
](
stateCopy
,
'
new name
'
);
expect
(
stateCopy
.
organizationName
).
toEqual
(
'
new name
'
);
});
});
ee/spec/frontend/subscriptions/new/store/state_spec.js
View file @
954431f3
...
...
@@ -98,15 +98,43 @@ describe('projectsSelector default state', () => {
});
});
describe
(
'
taxRate
'
,
()
=>
{
it
(
'
sets the country to null
'
,
()
=>
{
expect
(
state
.
country
).
toBeNull
();
});
it
(
'
sets the streetAddressLine1 to null
'
,
()
=>
{
expect
(
state
.
streetAddressLine1
).
toBeNull
();
});
it
(
'
sets the streetAddressLine2 to null
'
,
()
=>
{
expect
(
state
.
streetAddressLine2
).
toBeNull
();
});
it
(
'
sets the city to null
'
,
()
=>
{
expect
(
state
.
city
).
toBeNull
();
});
it
(
'
sets the countryState to null
'
,
()
=>
{
expect
(
state
.
countryState
).
toBeNull
();
});
it
(
'
sets the zipCode to null
'
,
()
=>
{
expect
(
state
.
zipCode
).
toBeNull
();
});
it
(
'
sets the countryOptions to an empty array
'
,
()
=>
{
expect
(
state
.
countryOptions
).
toEqual
([]);
});
it
(
'
sets the stateOptions to an empty array
'
,
()
=>
{
expect
(
state
.
stateOptions
).
toEqual
([]);
});
it
(
'
sets the taxRate to the TAX_RATE constant
'
,
()
=>
{
expect
(
state
.
taxRate
).
toEqual
(
0
);
});
});
describe
(
'
startDate
'
,
()
=>
{
it
(
'
sets the startDate to the current date
'
,
()
=>
{
expect
(
state
.
startDate
).
toEqual
(
currentDate
);
});
});
});
locale/gitlab.pot
View file @
954431f3
...
...
@@ -3359,15 +3359,33 @@ msgstr ""
msgid "Checkout|3. Your GitLab group"
msgstr ""
msgid "Checkout|Billing address"
msgstr ""
msgid "Checkout|Checkout"
msgstr ""
msgid "Checkout|City"
msgstr ""
msgid "Checkout|Continue to billing"
msgstr ""
msgid "Checkout|Continue to payment"
msgstr ""
msgid "Checkout|Country"
msgstr ""
msgid "Checkout|Edit"
msgstr ""
msgid "Checkout|Failed to load countries. Please try again."
msgstr ""
msgid "Checkout|Failed to load states. Please try again."
msgstr ""
msgid "Checkout|GitLab plan"
msgstr ""
...
...
@@ -3383,6 +3401,18 @@ msgstr ""
msgid "Checkout|Number of users"
msgstr ""
msgid "Checkout|Please select a country"
msgstr ""
msgid "Checkout|Please select a state"
msgstr ""
msgid "Checkout|State"
msgstr ""
msgid "Checkout|Street address"
msgstr ""
msgid "Checkout|Subscription details"
msgstr ""
...
...
@@ -3401,6 +3431,9 @@ msgstr ""
msgid "Checkout|Your organization"
msgstr ""
msgid "Checkout|Zip code"
msgstr ""
msgid "Checkout|company or team"
msgstr ""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment