Этот коммит содержится в:
Conor Macpherson
2023-03-28 16:15:14 -04:00
родитель 5df7e62f8a
Коммит 58fead7d9d
4 изменённых файлов: 64 добавлений и 19 удалений

Просмотреть файл

@@ -88,18 +88,18 @@
font-size: 12px;
}
.totalCost {
.totalCostWarning {
width: 141px;
}
.totalCost > span:first-child {
.totalCostWarning > span:first-child {
color: var(--sys-denim-center-channel-text);
font-family: 'Open Sans';
font-size: 14px;
font-weight: 700;
}
.totalCost > span:last-child {
.totalCostWarning > span:last-child {
color: rgba(var(--sys-denim-center-channel-text-rgb), 0.72);
font-family: 'Open Sans';
font-size: 12px;

Просмотреть файл

@@ -18,7 +18,6 @@ import {findSelfHostedProductBySku} from 'utils/hosted_customer';
import ExternalLink from 'components/external_link';
const MONTHS_IN_YEAR = 12;
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
const MAX_TRANSACTION_VALUE = 1_000_000 - 1;
interface Props {
@@ -35,14 +34,14 @@ export default function SelfHostedExpansionCard(props: Props) {
const endsAt = moment(parseInt(license.ExpiresAt, 10)).format('MMM. D, YYYY');
const [additionalSeats, setAdditionalSeats] = useState(props.initialSeats);
const [overMaxSeats, setOverMaxSeats] = useState(false);
const licenseExpiry = parseInt(license.ExpiresAt, 10);
const licenseExpiry = new Date(parseInt(license.ExpiresAt, 10));
const invalidAdditionalSeats = additionalSeats === 0 || isNaN(additionalSeats);
const [products] = useGetSelfHostedProducts();
const currentProduct = findSelfHostedProductBySku(products, license.SkuShortName);
const getMonthsUntilExpiry = () => {
const now = new Date();
return Math.ceil((licenseExpiry - now.getTime()) / MILLISECONDS_PER_DAY / 30);
return (licenseExpiry.getMonth() - now.getMonth()) + 12 * (licenseExpiry.getFullYear() - now.getFullYear());
};
const getMonthlyPrice = () => {
@@ -209,7 +208,7 @@ export default function SelfHostedExpansionCard(props: Props) {
<br/>
<FormattedMessage
id='self_hosted_expansion_rhs_card_cost_per_user_breakdown'
defaultMessage='{costPerUser} x {monthsUntilExpiry} months'
defaultMessage='${costPerUser} x {monthsUntilExpiry} months'
values={{
costPerUser: getMonthlyPrice().toFixed(2),
monthsUntilExpiry: getMonthsUntilExpiry(),
@@ -219,7 +218,7 @@ export default function SelfHostedExpansionCard(props: Props) {
<div className='costAmount'>
<span>{'$' + getCostPerUser().toFixed(2)}</span>
</div>
<div className='totalCost'>
<div className='totalCostWarning'>
<FormattedMessage
id='self_hosted_expansion_rhs_card_total_title'
defaultMessage='Total'
@@ -230,7 +229,7 @@ export default function SelfHostedExpansionCard(props: Props) {
defaultMessage='The total will be prorated'
/>
</div>
<span className='costAmount'>
<span className='totalCostAmount'>
<span>{'$' + getTotal().toFixed(2)}</span>
</span>
</div>

Просмотреть файл

@@ -3,7 +3,7 @@
import React from 'react';
import {screen, fireEvent} from '@testing-library/react';
import {screen, fireEvent, waitFor} from '@testing-library/react';
import {GlobalState} from 'types/store';
@@ -11,7 +11,7 @@ import {SelfHostedSignupForm, SelfHostedSignupProgress} from '@mattermost/types/
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
import {TestHelper as TH} from 'utils/test_helper';
import {SelfHostedProducts, ModalIdentifiers} from 'utils/constants';
import {SelfHostedProducts, ModalIdentifiers, RecurringIntervals} from 'utils/constants';
import {DeepPartial} from '@mattermost/types/utilities';
@@ -76,6 +76,7 @@ const mockProfessionalProduct = TH.getProductMock({
name: 'Professional',
sku: SelfHostedProducts.PROFESSIONAL,
price_per_seat: 7.5,
recurring_interval: RecurringIntervals.MONTH
});
jest.mock('mattermost-redux/client', () => {
@@ -129,6 +130,11 @@ jest.mock('utils/hosted_customer', () => {
const productName = SelfHostedProducts.PROFESSIONAL;
// Licensed expiry set as 3 months from the current date (rolls over to new years).
const licenseExpiry = new Date();
const monthsUntilLicenseExpiry = 3;
licenseExpiry.setMonth(licenseExpiry.getMonth() + monthsUntilLicenseExpiry);
const initialState: DeepPartial<GlobalState> = {
views: {
modals: {
@@ -143,11 +149,6 @@ const initialState: DeepPartial<GlobalState> = {
storage: {},
},
entities: {
admin: {
analytics: {
TOTAL_USERS: existingUsers,
},
},
teams: {
currentTeamId: '',
},
@@ -163,6 +164,7 @@ const initialState: DeepPartial<GlobalState> = {
license: {
Sku: productName,
Users: '50',
ExpiresAt: licenseExpiry.getTime().toString()
},
},
cloud: {
@@ -224,6 +226,7 @@ interface PurchaseForm {
state: string;
zip: string;
seats: string;
agree: boolean;
}
const defaultSuccessForm: PurchaseForm = {
@@ -236,6 +239,7 @@ const defaultSuccessForm: PurchaseForm = {
state: 'MN',
zip: '55423',
seats: '10',
agree: true,
};
function fillForm(form: PurchaseForm) {
@@ -248,6 +252,9 @@ function fillForm(form: PurchaseForm) {
selectDropdownValue('selfHostedExpansionStateSelector', form.state);
changeByPlaceholder('Zip/Postal Code', form.zip);
changeByTestId('seatsInput', form.seats);
if (form.agree) {
fireEvent.click(screen.getByText('I have read and agree', {exact: false}));
}
expect(document.getElementsByClassName('SelfHostedExpansionRHSCard__AddSeatsWarning')[0] as HTMLElement).toBeEnabled();
@@ -263,7 +270,7 @@ function fillForm(form: PurchaseForm) {
return completeButton;
}
describe('SelfHostedExpansionModal', () => {
describe('SelfHostedExpansionModal Open', () => {
it('renders the form', () => {
renderWithIntlAndStore(<div id='root-portal'><SelfHostedExpansionModal/></div>, initialState);
@@ -321,7 +328,45 @@ describe('SelfHostedExpansionModal', () => {
// });
});
describe('SelfHostedExpansionModal :: canSubmit', () => {
describe('SelfHostedExpansionModal RHS Card', () => {
it("New seats input should be pre-populated with the difference from the active users and licensed seats", () => {
renderWithIntlAndStore(<div id='root-portal'><SelfHostedExpansionModal/></div>, initialState);
const expectedPrePopulatedSeats = (initialState.entities?.users?.filteredStats?.total_users_count || 1) - parseInt(initialState.entities?.general?.license?.Users || '0', 10);
const seatsField = screen.getByTestId('seatsInput').querySelector('input');
expect(seatsField).toBeInTheDocument();
expect(seatsField?.value).toBe(expectedPrePopulatedSeats.toString());
});
it("Cost per User should be represented as the current subscription price multiplied by the remaining months", () => {
renderWithIntlAndStore(<div id='root-portal'><SelfHostedExpansionModal/></div>, initialState);
const expectedCostPerUser = monthsUntilLicenseExpiry * mockProfessionalProduct.price_per_seat;
const costPerUser = document.getElementsByClassName('costPerUser')[0];
expect(costPerUser).toBeInTheDocument();
expect(costPerUser.innerHTML).toContain('Cost per user<br>$' + mockProfessionalProduct.price_per_seat.toFixed(2) + ' x ' + monthsUntilLicenseExpiry + ' months');
const costAmount = document.getElementsByClassName('costAmount')[0];
expect(costAmount).toBeInTheDocument();
expect(costAmount.innerHTML).toContain('$' + expectedCostPerUser)
});
it("Total cost User should be represented as the current subscription price multiplied by the remaining months multiplied by the number of users", () => {
renderWithIntlAndStore(<div id='root-portal'><SelfHostedExpansionModal/></div>, initialState);
const seatsInputValue = 100;
changeByTestId('seatsInput', seatsInputValue.toString());
const expectedTotalCost = monthsUntilLicenseExpiry * mockProfessionalProduct.price_per_seat * seatsInputValue;
const costAmount = document.getElementsByClassName('totalCostAmount')[0];
expect(costAmount).toBeInTheDocument();
expect(costAmount.innerHTML).toContain('$' + expectedTotalCost)
});
});
describe('SelfHostedExpansionModal Submit', () => {
function makeHappyPathState(): FormState {
return {
address: 'string',
@@ -366,7 +411,6 @@ describe('SelfHostedExpansionModal :: canSubmit', () => {
expect(canSubmit(state, SelfHostedSignupProgress.PAID)).toBe(true);
});
// TODO: Needed?
it('if created subscription, can submit', () => {
const state = makeInitialState(1);
state.submitting = false;

Просмотреть файл

@@ -409,6 +409,7 @@ export default function SelfHostedExpansionModal() {
/>
</span>
<Address
testPrefix='selfHostedExpansion'
type='billing'
country={formState.country}
changeCountry={(option) => {
@@ -450,6 +451,7 @@ export default function SelfHostedExpansionModal() {
/>
</div>
<Address
testPrefix='selfHostedExpansion'
type='shipping'
country={formState.shippingCountry}
changeCountry={(option) => {