mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-18 18:18:23 -05:00
[MM-67030] Remove newsletter signup and replace with terms/privacy agreement (#34801)
* remove newsletter signup and replace with terms/privacy agreement * removed subscribeToSecurityNewsletter, made checkbox required * update signup test to remove newsletter and ensure the terms checkbox is required * update unit test and e2e test to reflect changes * fix e2e test * Removed susbcribe-newsletter endpoint in server * Update signup.test.tsx * remove unused css * remove unused css * fixed broken tests * fixed linter issues * Remove redundant IntlProvider and comments * Remove usage of test IDs from Signup tests * Remove usage of fireEvent * Remove usage of mountWithIntl from Signup tests * update e2e tests * fix playwright test * Fix Lint in signup.ts --------- Co-authored-by: maria.nunez <maria.nunez@mattermost.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com> Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
This commit is contained in:
parent
3a394b25e4
commit
09c4a61fed
25 changed files with 316 additions and 488 deletions
|
|
@ -61,6 +61,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(`test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was created successfully and we are at the select team page
|
||||
|
|
@ -113,6 +115,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(`test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was not created successfully
|
||||
|
|
@ -146,6 +150,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(username);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was created successfully and we are on the team joining page
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_password-input').clear().type('less');
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert the error is what is expected;
|
||||
|
|
@ -68,6 +70,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_password-input').clear().type('greaterthan7');
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert that we are not shown an MFA screen and instead a Teams You Can join page
|
||||
|
|
@ -112,6 +116,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(`BestUsernameInTheWorld${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
['NOLOWERCASE123!', 'noupppercase123!', 'NoNumber!', 'NoSymbol123'].forEach((option) => {
|
||||
cy.get('#input_password-input').clear().type(option);
|
||||
cy.findByText('Create account').click();
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_password-input').type('Test123456!');
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
['1user', 'te', 'user#1', 'user!1'].forEach((option) => {
|
||||
cy.get('#input_name').clear().type(option);
|
||||
cy.findByText('Create account').click();
|
||||
|
|
@ -183,6 +185,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(`Test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was created successfully and we are on the team joining page
|
||||
|
|
@ -245,6 +249,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(`Test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was not created successfully
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ describe('Authentication', () => {
|
|||
|
||||
cy.get('#input_name').clear().type(`Test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was not created successfully
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ function signupWithEmail(name, pw) {
|
|||
// # Type 'unique1pw' for password
|
||||
cy.get('#input_password-input').type(pw);
|
||||
|
||||
// # Check the terms and privacy checkbox
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
// # Click on Create Account button
|
||||
cy.findByText('Create account').click();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ describe('Onboarding', () => {
|
|||
cy.get('#input_email').should('be.focused').and('be.visible').type(email);
|
||||
cy.get('#input_name').should('be.visible').type(username);
|
||||
cy.get('#input_password-input').should('be.visible').type(password);
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
cy.findByText('You’re almost done!').should('be.visible');
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ describe('Onboarding', () => {
|
|||
cy.get('#input_name').should('be.visible').type(username);
|
||||
cy.get('#input_password-input').should('be.visible').type(password);
|
||||
|
||||
// # Check the terms and privacy checkbox
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
// # Attempt to create an account by clicking on the 'Create account' button
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
|
|
|
|||
|
|
@ -72,15 +72,22 @@ describe('Signup Email page', () => {
|
|||
cy.get('#input_password-input').should('be.visible').and('have.attr', 'placeholder', 'Choose a Password');
|
||||
cy.findByText('Your password must be 5-72 characters long.').should('be.visible');
|
||||
|
||||
cy.get('#saveSetting').scrollIntoView().should('be.visible');
|
||||
cy.get('#saveSetting').should('contain', 'Create account');
|
||||
// * Check terms and privacy checkbox
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').should('be.visible').and('not.be.checked');
|
||||
cy.findByText(/I agree to the/i).should('be.visible');
|
||||
|
||||
// * Check newsletter subscription checkbox text and links
|
||||
cy.findByText('I would like to receive Mattermost security updates via newsletter.').should('be.visible');
|
||||
cy.findByText(/By subscribing, I consent to receive emails from Mattermost with product updates, promotions, and company news\./).should('be.visible');
|
||||
cy.findByText(/I have read the/).parent().within(() => {
|
||||
cy.findByRole('link', {name: 'Privacy Policy'}).should('be.visible').and('have.attr', 'href').and('include', 'mattermost.com/pl/privacy-policy/');
|
||||
cy.findByRole('link', {name: 'unsubscribe'}).should('be.visible').and('have.attr', 'href').and('include', 'forms.mattermost.com/UnsubscribePage.html');
|
||||
// * Check that submit button is disabled without accepting terms
|
||||
cy.get('#saveSetting').scrollIntoView().should('be.visible');
|
||||
cy.get('#saveSetting').should('contain', 'Create account').and('be.disabled');
|
||||
|
||||
// * Check terms and privacy links (now part of checkbox label)
|
||||
cy.get('label[for="signup-body-card-form-check-terms-and-privacy"]').within(() => {
|
||||
cy.findByText('Acceptable Use Policy').should('be.visible').
|
||||
and('have.attr', 'href').
|
||||
and('include', config.SupportSettings.TermsOfServiceLink || TERMS_OF_SERVICE_LINK);
|
||||
cy.findByText('Privacy Policy').should('be.visible').
|
||||
and('have.attr', 'href').
|
||||
and('include', config.SupportSettings.PrivacyPolicyLink || PRIVACY_POLICY_LINK);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -116,4 +123,23 @@ describe('Signup Email page', () => {
|
|||
cy.get('.footer-copyright').should('contain', `© ${currentYear} Mattermost Inc.`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should enable submit button when terms checkbox is checked', () => {
|
||||
// # Fill in valid form data
|
||||
cy.get('#input_email').type('test@example.com');
|
||||
cy.get('#input_name').type('testuser');
|
||||
cy.get('#input_password-input').type('validPassword123');
|
||||
|
||||
// * Verify submit button is disabled
|
||||
cy.get('#saveSetting').should('be.disabled');
|
||||
|
||||
// # Check the terms and privacy checkbox
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
// * Verify checkbox is now checked
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').should('be.checked');
|
||||
|
||||
// * Verify submit button is now enabled
|
||||
cy.get('#saveSetting').should('not.be.disabled');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ describe('Team Settings', () => {
|
|||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#input_password-input').type(password);
|
||||
|
||||
// # Check the terms and privacy checkbox
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
// # Attempt to create an account by clicking on the 'Create Account' button
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,10 @@ describe('Team Settings', () => {
|
|||
cy.get('#input_name').type(username);
|
||||
cy.get('#input_password-input').type(password);
|
||||
|
||||
// # Attempt to create an account by clicking on the 'Create account' button
|
||||
// # Check the terms and privacy checkbox
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
// # Attempt to create an account by clicking on the 'Create Account' button
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert that the expected error message from creating an account with an email not from the allowed email domain exists and is visible
|
||||
|
|
|
|||
|
|
@ -16,11 +16,9 @@ export default class SignupPage {
|
|||
readonly usernameInput;
|
||||
readonly passwordInput;
|
||||
readonly passwordToggleButton;
|
||||
readonly newsLetterCheckBox;
|
||||
readonly newsLetterPrivacyPolicyLink;
|
||||
readonly newsLetterUnsubscribeLink;
|
||||
readonly agreementTermsOfUseLink;
|
||||
readonly agreementPrivacyPolicyLink;
|
||||
readonly termsAndPrivacyCheckBox;
|
||||
readonly termsAndPrivacyAcceptableUsePolicyLink;
|
||||
readonly termsAndPrivacyPrivacyPolicyLink;
|
||||
readonly createAccountButton;
|
||||
readonly loginLink;
|
||||
readonly emailError;
|
||||
|
|
@ -48,14 +46,12 @@ export default class SignupPage {
|
|||
);
|
||||
this.passwordError = page.locator('text=Must be 5-72 characters long.');
|
||||
|
||||
const newsletterBlock = page.locator('.check-input');
|
||||
this.newsLetterCheckBox = newsletterBlock.getByRole('checkbox', {name: 'newsletter checkbox'});
|
||||
this.newsLetterPrivacyPolicyLink = newsletterBlock.locator('text=Privacy Policy');
|
||||
this.newsLetterUnsubscribeLink = newsletterBlock.locator('text=unsubscribe');
|
||||
|
||||
const agreementBlock = page.locator('.signup-body-card-agreement');
|
||||
this.agreementTermsOfUseLink = agreementBlock.locator('text=Terms of Use');
|
||||
this.agreementPrivacyPolicyLink = agreementBlock.locator('text=Privacy Policy');
|
||||
const termsAndPrivacyBlock = page.locator('.check-input');
|
||||
this.termsAndPrivacyCheckBox = termsAndPrivacyBlock.getByRole('checkbox', {
|
||||
name: 'Terms and privacy policy checkbox',
|
||||
});
|
||||
this.termsAndPrivacyAcceptableUsePolicyLink = termsAndPrivacyBlock.locator('text=Acceptable Use Policy');
|
||||
this.termsAndPrivacyPrivacyPolicyLink = termsAndPrivacyBlock.locator('text=Privacy Policy');
|
||||
|
||||
this.header = new components.MainHeader(page.locator('.hfroute-header'));
|
||||
this.footer = new components.Footer(page.locator('.hfroute-footer'));
|
||||
|
|
@ -79,6 +75,7 @@ export default class SignupPage {
|
|||
await this.emailInput.fill(user.email);
|
||||
await this.usernameInput.fill(user.username);
|
||||
await this.passwordInput.fill(user.password);
|
||||
await this.termsAndPrivacyCheckBox.check();
|
||||
await this.createAccountButton.click();
|
||||
|
||||
if (waitForRedirect) {
|
||||
|
|
|
|||
|
|
@ -43,25 +43,21 @@ test('/signup_user_complete accessibility tab support', async ({pw}, testInfo) =
|
|||
await pw.signupPage.passwordInput.press('Tab');
|
||||
expect(await pw.signupPage.passwordToggleButton).toBeFocused();
|
||||
|
||||
// * Should move focus to newsletter checkbox after tab
|
||||
// * Should move focus to terms and privacy checkbox after tab
|
||||
await pw.signupPage.passwordToggleButton.press('Tab');
|
||||
expect(await pw.signupPage.newsLetterCheckBox).toBeFocused();
|
||||
expect(await pw.signupPage.termsAndPrivacyCheckBox).toBeFocused();
|
||||
|
||||
// * Should move focus to newsletter privacy policy link after tab
|
||||
await pw.signupPage.newsLetterCheckBox.press('Tab');
|
||||
expect(await pw.signupPage.newsLetterPrivacyPolicyLink).toBeFocused();
|
||||
// * Should move focus to acceptable use policy link after tab
|
||||
await pw.signupPage.termsAndPrivacyCheckBox.press('Tab');
|
||||
expect(await pw.signupPage.termsAndPrivacyAcceptableUsePolicyLink).toBeFocused();
|
||||
|
||||
// * Should move focus to newsletter unsubscribe link after tab
|
||||
await pw.signupPage.newsLetterPrivacyPolicyLink.press('Tab');
|
||||
expect(await pw.signupPage.newsLetterUnsubscribeLink).toBeFocused();
|
||||
// * Should move focus to privacy policy link after tab
|
||||
await pw.signupPage.termsAndPrivacyAcceptableUsePolicyLink.press('Tab');
|
||||
expect(await pw.signupPage.termsAndPrivacyPrivacyPolicyLink).toBeFocused();
|
||||
|
||||
// * Should move focus to agreement terms of use link after tab
|
||||
await pw.signupPage.newsLetterUnsubscribeLink.press('Tab');
|
||||
expect(await pw.signupPage.agreementTermsOfUseLink).toBeFocused();
|
||||
|
||||
// * Should move focus to agreement privacy policy link after tab
|
||||
await pw.signupPage.agreementTermsOfUseLink.press('Tab');
|
||||
expect(await pw.signupPage.agreementPrivacyPolicyLink).toBeFocused();
|
||||
// * Should move focus to about link after tab (skips disabled create account button)
|
||||
await pw.signupPage.termsAndPrivacyPrivacyPolicyLink.press('Tab');
|
||||
expect(await pw.signupPage.footer.aboutLink).toBeFocused();
|
||||
|
||||
// * Should move focus to privacy policy link after tab
|
||||
await pw.signupPage.footer.aboutLink.press('Tab');
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
package api4
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
|
|
@ -16,40 +14,9 @@ import (
|
|||
func (api *API) InitHostedCustomer() {
|
||||
// POST /api/v4/hosted_customer/available
|
||||
api.BaseRoutes.HostedCustomer.Handle("/signup_available", api.APISessionRequired(handleSignupAvailable)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.HostedCustomer.Handle("/subscribe-newsletter", api.APIHandler(handleSubscribeToNewsletter)).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
func handleSignupAvailable(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.handleSignupAvailable"
|
||||
c.Err = model.NewAppError(where, "api.server.hosted_signup_unavailable.error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
func handleSubscribeToNewsletter(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
const where = "Api4.handleSubscribeToNewsletter"
|
||||
ensured := ensureCloudInterface(c, where)
|
||||
if !ensured {
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
req := new(model.SubscribeNewsletterRequest)
|
||||
err = json.Unmarshal(bodyBytes, req)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError(where, "api.cloud.request_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
req.ServerID = c.App.Srv().ServerId()
|
||||
|
||||
if err := c.App.Cloud().SubscribeToNewsletter("", req); err != nil {
|
||||
c.Err = model.NewAppError(where, "api.server.cws.subscribe_to_newsletter.app_error", nil, "CWS Server failed to subscribe to newsletter.", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ type CloudInterface interface {
|
|||
|
||||
CheckCWSConnection(userId string) error
|
||||
|
||||
SubscribeToNewsletter(userID string, req *model.SubscribeNewsletterRequest) error
|
||||
|
||||
ApplyIPFilters(userID string, ranges *model.AllowedIPRanges) (*model.AllowedIPRanges, error)
|
||||
GetIPFilters(userID string) (*model.AllowedIPRanges, error)
|
||||
GetInstallation(userID string) (*model.Installation, error)
|
||||
|
|
|
|||
|
|
@ -503,24 +503,6 @@ func (_m *CloudInterface) RemoveAuditLoggingCert(userID string) error {
|
|||
return r0
|
||||
}
|
||||
|
||||
// SubscribeToNewsletter provides a mock function with given fields: userID, req
|
||||
func (_m *CloudInterface) SubscribeToNewsletter(userID string, req *model.SubscribeNewsletterRequest) error {
|
||||
ret := _m.Called(userID, req)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SubscribeToNewsletter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, *model.SubscribeNewsletterRequest) error); ok {
|
||||
r0 = rf(userID, req)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateCloudCustomer provides a mock function with given fields: userID, customerInfo
|
||||
func (_m *CloudInterface) UpdateCloudCustomer(userID string, customerInfo *model.CloudCustomerInfo) (*model.CloudCustomer, error) {
|
||||
ret := _m.Called(userID, customerInfo)
|
||||
|
|
|
|||
|
|
@ -3158,10 +3158,6 @@
|
|||
"id": "api.server.cws.needs_enterprise_edition",
|
||||
"translation": "Service only available in Mattermost Enterprise edition"
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.subscribe_to_newsletter.app_error",
|
||||
"translation": "CWS Server failed to subscribe to newsletter."
|
||||
},
|
||||
{
|
||||
"id": "api.server.hosted_signup_unavailable.error",
|
||||
"translation": "Portal unavailable for self-hosted signup."
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
type SubscribeNewsletterRequest struct {
|
||||
Email string `json:"email"`
|
||||
ServerID string `json:"server_id"`
|
||||
SubscribedContent string `json:"subscribed_content"`
|
||||
}
|
||||
|
|
@ -40,7 +40,6 @@ OUTPUT_EXCLUDING_IGNORED=$(echo "$OUTPUT" | grep -Fv \
|
|||
-e 'Cannot find /api/v4/hosted_customer/confirm-expand method: POST in OpenAPI 3 spec.' \
|
||||
-e 'Cannot find /api/v4/hosted_customer/invoices method: GET in OpenAPI 3 spec.' \
|
||||
-e 'Cannot find /api/v4/hosted_customer/invoices/{invoice_id:in_[A-Za-z0-9]+}/pdf method: GET in OpenAPI 3 spec.' \
|
||||
-e 'Cannot find /api/v4/hosted_customer/subscribe-newsletter method: POST in OpenAPI 3 spec.' \
|
||||
-e 'Cannot find /api/v4/license/review method: POST in OpenAPI 3 spec.' \
|
||||
-e 'Cannot find /api/v4/license/review/status method: GET in OpenAPI 3 spec.' \
|
||||
-e 'Cannot find /api/v4/posts/{post_id}/edit_history method: GET in OpenAPI 3 spec.' \
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
autoFocus={true}
|
||||
className="signup-body-card-form-email-input"
|
||||
customMessage={null}
|
||||
data-testid="signup-body-card-form-email-input"
|
||||
disabled={false}
|
||||
inputSize="large"
|
||||
name="email"
|
||||
|
|
@ -136,7 +135,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
"value": "You can use lowercase letters, numbers, periods, dashes, and underscores.",
|
||||
}
|
||||
}
|
||||
data-testid="signup-body-card-form-name-input"
|
||||
disabled={false}
|
||||
inputSize="large"
|
||||
name="name"
|
||||
|
|
@ -148,7 +146,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
<ForwardRef
|
||||
className="signup-body-card-form-password-input"
|
||||
createMode={true}
|
||||
data-testid="signup-body-card-form-password-input"
|
||||
disabled={false}
|
||||
error=""
|
||||
info="Your password must be 5-72 characters long."
|
||||
|
|
@ -156,28 +153,29 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="newsletter"
|
||||
>
|
||||
<span
|
||||
className="interested"
|
||||
>
|
||||
Interested in receiving Mattermost security, product, promotions, and company updates updates via newsletter?
|
||||
</span>
|
||||
<span
|
||||
className="link"
|
||||
>
|
||||
Sign up at
|
||||
<ForwardRef
|
||||
href="https://mattermost.com/security-updates/"
|
||||
key="1/.1"
|
||||
location="signup"
|
||||
>
|
||||
https://mattermost.com/security-updates/
|
||||
</ForwardRef>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
<CheckInput
|
||||
ariaLabel="Terms and privacy policy checkbox"
|
||||
checked={false}
|
||||
id="signup-body-card-form-check-terms-and-privacy"
|
||||
name="terms"
|
||||
onChange={[Function]}
|
||||
text={
|
||||
Array [
|
||||
"I agree to the ",
|
||||
<ForwardRef
|
||||
location="signup-terms-of-use"
|
||||
>
|
||||
Acceptable Use Policy
|
||||
</ForwardRef>,
|
||||
" and the ",
|
||||
<ForwardRef
|
||||
location="signup-privacy-policy"
|
||||
>
|
||||
Privacy Policy
|
||||
</ForwardRef>,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<SaveButton
|
||||
defaultMessage="Create account"
|
||||
disabled={true}
|
||||
|
|
@ -214,21 +212,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
url="/oauth/gitlab/signup"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
className="signup-body-card-agreement"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <termsOfUseLink>Terms of Use</termsOfUseLink> and <privacyPolicyLink>Privacy Policy</privacyPolicyLink>. If you do not agree, you cannot use {siteName}."
|
||||
id="signup.agreement"
|
||||
values={
|
||||
Object {
|
||||
"privacyPolicyLink": [Function],
|
||||
"siteName": "Mattermost",
|
||||
"termsOfUseLink": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -284,7 +267,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
autoFocus={true}
|
||||
className="signup-body-card-form-email-input"
|
||||
customMessage={null}
|
||||
data-testid="signup-body-card-form-email-input"
|
||||
disabled={false}
|
||||
inputSize="large"
|
||||
name="email"
|
||||
|
|
@ -302,7 +284,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
"value": "You can use lowercase letters, numbers, periods, dashes, and underscores.",
|
||||
}
|
||||
}
|
||||
data-testid="signup-body-card-form-name-input"
|
||||
disabled={false}
|
||||
inputSize="large"
|
||||
name="name"
|
||||
|
|
@ -314,7 +295,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
<ForwardRef
|
||||
className="signup-body-card-form-password-input"
|
||||
createMode={true}
|
||||
data-testid="signup-body-card-form-password-input"
|
||||
disabled={false}
|
||||
error=""
|
||||
info="Your password must be 5-72 characters long."
|
||||
|
|
@ -322,28 +302,29 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="newsletter"
|
||||
>
|
||||
<span
|
||||
className="interested"
|
||||
>
|
||||
Interested in receiving Mattermost security, product, promotions, and company updates updates via newsletter?
|
||||
</span>
|
||||
<span
|
||||
className="link"
|
||||
>
|
||||
Sign up at
|
||||
<ForwardRef
|
||||
href="https://mattermost.com/security-updates/"
|
||||
key="1/.1"
|
||||
location="signup"
|
||||
>
|
||||
https://mattermost.com/security-updates/
|
||||
</ForwardRef>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
<CheckInput
|
||||
ariaLabel="Terms and privacy policy checkbox"
|
||||
checked={false}
|
||||
id="signup-body-card-form-check-terms-and-privacy"
|
||||
name="terms"
|
||||
onChange={[Function]}
|
||||
text={
|
||||
Array [
|
||||
"I agree to the ",
|
||||
<ForwardRef
|
||||
location="signup-terms-of-use"
|
||||
>
|
||||
Acceptable Use Policy
|
||||
</ForwardRef>,
|
||||
" and the ",
|
||||
<ForwardRef
|
||||
location="signup-privacy-policy"
|
||||
>
|
||||
Privacy Policy
|
||||
</ForwardRef>,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<SaveButton
|
||||
defaultMessage="Create account"
|
||||
disabled={true}
|
||||
|
|
@ -426,21 +407,6 @@ exports[`components/signup/Signup should match snapshot for all signup options e
|
|||
url="/login/sso/saml?action=signup"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
className="signup-body-card-agreement"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <termsOfUseLink>Terms of Use</termsOfUseLink> and <privacyPolicyLink>Privacy Policy</privacyPolicyLink>. If you do not agree, you cannot use {siteName}."
|
||||
id="signup.agreement"
|
||||
values={
|
||||
Object {
|
||||
"privacyPolicyLink": [Function],
|
||||
"siteName": "Mattermost",
|
||||
"termsOfUseLink": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -167,22 +167,6 @@
|
|||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.newsletter {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 32px;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||
font-family: 'Open Sans';
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
|
||||
.interested {
|
||||
display: block;
|
||||
color: var(--center-channel-color);
|
||||
}
|
||||
}
|
||||
|
||||
.signup-body-card-form-button-submit {
|
||||
@include mixins.primary-button;
|
||||
@include mixins.button-large;
|
||||
|
|
@ -222,19 +206,6 @@
|
|||
row-gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.signup-body-card-agreement {
|
||||
margin-top: 32px;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
|
||||
a {
|
||||
@include mixins.link;
|
||||
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,18 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ReactWrapper} from 'enzyme';
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
|
||||
import type {ClientConfig} from '@mattermost/types/config';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import * as useCWSAvailabilityCheckAll from 'components/common/hooks/useCWSAvailabilityCheck';
|
||||
import SaveButton from 'components/save_button';
|
||||
import Signup from 'components/signup/signup';
|
||||
import Input from 'components/widgets/inputs/input/input';
|
||||
import PasswordInput from 'components/widgets/inputs/password_input/password_input';
|
||||
import {redirectUserToDefaultTeam} from 'actions/global_actions';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {act, renderWithContext, screen, fireEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import Signup from 'components/signup/signup';
|
||||
|
||||
import {renderWithContext, screen, waitFor, userEvent} from 'tests/react_testing_utils';
|
||||
import {WindowSizes} from 'utils/constants';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
|
@ -30,12 +24,6 @@ let mockLicense = {IsLicensed: 'true', Cloud: 'false'};
|
|||
let mockConfig: Partial<ClientConfig>;
|
||||
let mockDispatch = jest.fn();
|
||||
|
||||
const intlProviderProps = {
|
||||
defaultLocale: 'en',
|
||||
locale: 'en',
|
||||
messages: {},
|
||||
};
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useSelector: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
||||
|
|
@ -56,13 +44,21 @@ jest.mock('mattermost-redux/selectors/entities/general', () => ({
|
|||
getConfig: () => mockConfig,
|
||||
}));
|
||||
|
||||
let mockCurrentUserId = '';
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/users', () => ({
|
||||
...jest.requireActual('mattermost-redux/selectors/entities/users') as typeof import('mattermost-redux/selectors/entities/users'),
|
||||
getCurrentUserId: () => '',
|
||||
getCurrentUserId: () => mockCurrentUserId,
|
||||
}));
|
||||
|
||||
jest.mock('actions/global_actions', () => ({
|
||||
...jest.requireActual('actions/global_actions'),
|
||||
redirectUserToDefaultTeam: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('actions/team_actions', () => ({
|
||||
...jest.requireActual('actions/team_actions') as typeof import('actions/team_actions'),
|
||||
addUserToTeamFromInvite: jest.fn().mockResolvedValue({data: {}}),
|
||||
addUsersToTeamFromInvite: jest.fn().mockResolvedValue({name: 'teamName'}),
|
||||
}));
|
||||
|
||||
|
|
@ -76,27 +72,14 @@ jest.mock('actions/views/login', () => ({
|
|||
loginById: jest.fn().mockResolvedValue({data: {}}),
|
||||
}));
|
||||
|
||||
jest.mock('actions/team_actions', () => ({
|
||||
...jest.requireActual('actions/team_actions') as typeof import('actions/team_actions'),
|
||||
addUserToTeamFromInvite: jest.fn().mockResolvedValue({data: {}}),
|
||||
}));
|
||||
|
||||
jest.mock('actions/storage');
|
||||
|
||||
const actImmediate = (wrapper: ReactWrapper) =>
|
||||
act(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
setImmediate(() => {
|
||||
wrapper.update();
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
describe('components/signup/Signup', () => {
|
||||
beforeEach(() => {
|
||||
mockLocation = {pathname: '', search: '', hash: ''};
|
||||
mockHistoryPush.mockClear();
|
||||
mockDispatch.mockClear();
|
||||
mockCurrentUserId = '';
|
||||
|
||||
mockLicense = {IsLicensed: 'true', Cloud: 'false'};
|
||||
|
||||
|
|
@ -208,33 +191,27 @@ describe('components/signup/Signup', () => {
|
|||
mockResolvedValueOnce({data: {id: 'userId', password: 'password', email: 'jdoe@mm.com}'}}). // createUser
|
||||
mockResolvedValueOnce({error: {server_error_id: 'api.user.login.not_verified.app_error'}}); // loginById
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<BrowserRouter>
|
||||
<Signup/>
|
||||
</BrowserRouter>
|
||||
</IntlProvider>,
|
||||
renderWithContext(
|
||||
<Signup/>,
|
||||
);
|
||||
|
||||
const emailInput = wrapper.find(Input).first().find('input').first();
|
||||
emailInput.simulate('change', {target: {value: 'jdoe@mm.com'}});
|
||||
const emailInput = screen.getByLabelText('Email address');
|
||||
const usernameInput = screen.getByLabelText('Choose a Username');
|
||||
const passwordInput = screen.getByLabelText('Choose a Password');
|
||||
const termsCheckbox = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
const submitButton = screen.getByRole('button', {name: 'Create account'});
|
||||
|
||||
const nameInput = wrapper.find('#input_name').first();
|
||||
nameInput.simulate('change', {target: {value: 'jdoe'}});
|
||||
await userEvent.type(emailInput, 'jdoe@mm.com');
|
||||
await userEvent.type(usernameInput, 'jdoe');
|
||||
await userEvent.type(passwordInput, 'password');
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
const passwordInput = wrapper.find(PasswordInput).first().find('input').first();
|
||||
passwordInput.simulate('change', {target: {value: 'password'}});
|
||||
expect(submitButton).not.toBeDisabled();
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
const saveButton = wrapper.find(SaveButton).first();
|
||||
expect(saveButton.props().disabled).toEqual(false);
|
||||
|
||||
saveButton.find('button').first().simulate('click');
|
||||
|
||||
await actImmediate(wrapper);
|
||||
|
||||
expect(wrapper.find(Input).first().props().disabled).toEqual(true);
|
||||
expect(wrapper.find('#input_name').first().props().disabled).toEqual(true);
|
||||
expect(wrapper.find(PasswordInput).first().props().disabled).toEqual(true);
|
||||
expect(emailInput).toBeDisabled();
|
||||
expect(usernameInput).toBeDisabled();
|
||||
expect(passwordInput).toBeDisabled();
|
||||
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith('/should_verify_email?email=jdoe%40mm.com&teamname=teamName');
|
||||
});
|
||||
|
|
@ -245,67 +222,77 @@ describe('components/signup/Signup', () => {
|
|||
mockResolvedValueOnce({data: {id: 'userId', password: 'password', email: 'jdoe@mm.com}'}}). // createUser
|
||||
mockResolvedValueOnce({}); // loginById
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<IntlProvider {...intlProviderProps}>
|
||||
<BrowserRouter>
|
||||
<Signup/>
|
||||
</BrowserRouter>
|
||||
</IntlProvider>,
|
||||
renderWithContext(
|
||||
<Signup/>,
|
||||
);
|
||||
|
||||
const emailInput = wrapper.find(Input).first().find('input').first();
|
||||
emailInput.simulate('change', {target: {value: 'jdoe@mm.com'}});
|
||||
const emailInput = screen.getByLabelText('Email address');
|
||||
const usernameInput = screen.getByLabelText('Choose a Username');
|
||||
const passwordInput = screen.getByLabelText('Choose a Password');
|
||||
const termsCheckbox = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
const submitButton = screen.getByRole('button', {name: 'Create account'});
|
||||
|
||||
const nameInput = wrapper.find('#input_name').first();
|
||||
nameInput.simulate('change', {target: {value: 'jdoe'}});
|
||||
await userEvent.type(emailInput, 'jdoe@mm.com');
|
||||
await userEvent.type(usernameInput, 'jdoe');
|
||||
await userEvent.type(passwordInput, 'password');
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
const passwordInput = wrapper.find(PasswordInput).first().find('input').first();
|
||||
passwordInput.simulate('change', {target: {value: 'password'}});
|
||||
expect(submitButton).not.toBeDisabled();
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
const saveButton = wrapper.find(SaveButton).first();
|
||||
expect(saveButton.props().disabled).toEqual(false);
|
||||
expect(emailInput).toBeDisabled();
|
||||
expect(usernameInput).toBeDisabled();
|
||||
expect(passwordInput).toBeDisabled();
|
||||
|
||||
saveButton.find('button').first().simulate('click');
|
||||
|
||||
await actImmediate(wrapper);
|
||||
|
||||
expect(wrapper.find(Input).first().props().disabled).toEqual(true);
|
||||
expect(wrapper.find('#input_name').first().props().disabled).toEqual(true);
|
||||
expect(wrapper.find(PasswordInput).first().props().disabled).toEqual(true);
|
||||
expect(redirectUserToDefaultTeam).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should focus email input when email validation fails', async () => {
|
||||
renderWithContext(<Signup/>, mockState);
|
||||
|
||||
const emailInput = screen.getByTestId('signup-body-card-form-email-input');
|
||||
const submitButton = screen.getByText('Create account');
|
||||
const emailInput = screen.getByLabelText('Email address');
|
||||
const usernameInput = screen.getByLabelText('Choose a Username');
|
||||
const passwordInput = screen.getByLabelText('Choose a Password');
|
||||
const termsCheckbox = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
const submitButton = screen.getByRole('button', {name: 'Create account'});
|
||||
|
||||
// Submit with invalid email
|
||||
fireEvent.change(emailInput, {target: {value: 'invalid-email'}});
|
||||
fireEvent.click(submitButton);
|
||||
await userEvent.type(emailInput, 'invalid-email');
|
||||
await userEvent.type(usernameInput, 'testuser');
|
||||
await userEvent.type(passwordInput, '123');
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(emailInput).toHaveFocus();
|
||||
});
|
||||
// The focus should no longer be on the email input before clicking submit
|
||||
expect(emailInput).not.toHaveFocus();
|
||||
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
// And now the focus should move back to the email input
|
||||
expect(emailInput).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should focus password input when password validation fails', async () => {
|
||||
renderWithContext(<Signup/>, mockState);
|
||||
|
||||
const emailInput = screen.getByTestId('signup-body-card-form-email-input');
|
||||
const usernameInput = screen.getByTestId('signup-body-card-form-name-input');
|
||||
const passwordInput = screen.getByTestId('signup-body-card-form-password-input');
|
||||
const emailInput = screen.getByLabelText('Email address');
|
||||
const usernameInput = screen.getByLabelText('Choose a Username');
|
||||
const passwordInput = screen.getByLabelText('Choose a Password');
|
||||
const termsCheckbox = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
const submitButton = screen.getByText('Create account');
|
||||
|
||||
// Submit with valid email and username but invalid password
|
||||
fireEvent.change(emailInput, {target: {value: 'test@example.com'}});
|
||||
fireEvent.change(usernameInput, {target: {value: 'testuser'}});
|
||||
fireEvent.change(passwordInput, {target: {value: '123'}});
|
||||
fireEvent.click(submitButton);
|
||||
await userEvent.type(emailInput, 'test@example.com');
|
||||
await userEvent.type(usernameInput, 'testuser');
|
||||
await userEvent.type(passwordInput, '123');
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(passwordInput).toHaveFocus();
|
||||
});
|
||||
// The focus should no longer be on the password input before clicking submit
|
||||
expect(emailInput).not.toHaveFocus();
|
||||
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
// And now the focus should move back to the password input
|
||||
expect(passwordInput).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should focus username input when server returns username exists error', async () => {
|
||||
|
|
@ -319,87 +306,102 @@ describe('components/signup/Signup', () => {
|
|||
|
||||
renderWithContext(<Signup/>, mockState);
|
||||
|
||||
const emailInput = screen.getByTestId('signup-body-card-form-email-input');
|
||||
const usernameInput = screen.getByTestId('signup-body-card-form-name-input');
|
||||
const passwordInput = screen.getByTestId('signup-body-card-form-password-input');
|
||||
const emailInput = screen.getByLabelText('Email address');
|
||||
const usernameInput = screen.getByLabelText('Choose a Username');
|
||||
const passwordInput = screen.getByLabelText('Choose a Password');
|
||||
const termsCheckbox = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
const submitButton = screen.getByText('Create account');
|
||||
|
||||
// Submit with valid data that will trigger server error
|
||||
fireEvent.change(emailInput, {target: {value: 'test@example.com'}});
|
||||
fireEvent.change(usernameInput, {target: {value: 'existinguser'}});
|
||||
fireEvent.change(passwordInput, {target: {value: 'password123'}});
|
||||
fireEvent.click(submitButton);
|
||||
await userEvent.type(emailInput, 'test@example.com');
|
||||
await userEvent.type(usernameInput, 'existinguser');
|
||||
await userEvent.type(passwordInput, 'password123');
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(usernameInput).toHaveFocus();
|
||||
});
|
||||
// The focus should no longer be on the email input before clicking submit
|
||||
expect(usernameInput).not.toHaveFocus();
|
||||
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
// And now the focus should move back to the username input
|
||||
expect(usernameInput).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should add user to team and redirect when team invite valid and logged in', async () => {
|
||||
mockLocation.search = '?id=ppni7a9t87fn3j4d56rwocdctc';
|
||||
mockCurrentUserId = 'user1'; // Simulate logged-in user
|
||||
|
||||
const wrapper = shallow(
|
||||
mockDispatch = jest.fn().
|
||||
mockResolvedValueOnce({}). // removeGlobalItem in useEffect
|
||||
mockResolvedValueOnce({data: {name: 'teamName'}}); // addUserToTeamFromInvite
|
||||
|
||||
renderWithContext(
|
||||
<Signup/>,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).toHaveBeenCalledWith('/teamName/channels/town-square');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should handle failure adding user to team when team invite and logged in', () => {
|
||||
mockLocation.search = '?id=ppni7a9t87fn3j4d56rwocdctc';
|
||||
|
||||
const wrapper = shallow(
|
||||
<Signup/>,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(mockHistoryPush).not.toHaveBeenCalled();
|
||||
expect(wrapper.find('.content-layout-column-title').text()).toEqual('This invite link is invalid');
|
||||
});
|
||||
});
|
||||
|
||||
it('should show newsletter check box opt-in for self-hosted non airgapped workspaces', async () => {
|
||||
jest.spyOn(useCWSAvailabilityCheckAll, 'default').mockImplementation(() => useCWSAvailabilityCheckAll.CSWAvailabilityCheckTypes.Available);
|
||||
mockLicense = {IsLicensed: 'true', Cloud: 'false'};
|
||||
it('should handle failure adding user to team when team invite and logged in', async () => {
|
||||
mockLocation.search = '?id=ppni7a9t87fn3j4d56rwocdctc';
|
||||
mockCurrentUserId = 'user1'; // Simulate logged-in user
|
||||
|
||||
const {container: signupContainer} = renderWithContext(
|
||||
<Signup/>,
|
||||
);
|
||||
mockDispatch = jest.fn().
|
||||
mockResolvedValueOnce({}). // removeGlobalItem in useEffect
|
||||
mockResolvedValueOnce({
|
||||
error: {
|
||||
server_error_id: 'api.team.add_user_to_team_from_invite.invalid.app_error',
|
||||
message: 'Invalid invite',
|
||||
},
|
||||
}); // addUserToTeamFromInvite with error
|
||||
|
||||
screen.getByTestId('signup-body-card-form-check-newsletter');
|
||||
const checkInput = screen.getByTestId('signup-body-card-form-check-newsletter');
|
||||
expect(checkInput).toHaveAttribute('type', 'checkbox');
|
||||
renderWithContext(<Signup/>, mockState);
|
||||
|
||||
expect(signupContainer).toHaveTextContent('I would like to receive Mattermost security updates via newsletter. By subscribing, I consent to receive emails from Mattermost with product updates, promotions, and company news. I have read the Privacy Policy and understand that I can unsubscribe at any time');
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryPush).not.toHaveBeenCalled();
|
||||
expect(screen.getByText('This invite link is invalid')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT show newsletter check box opt-in for self-hosted AND airgapped workspaces', async () => {
|
||||
jest.spyOn(useCWSAvailabilityCheckAll, 'default').mockImplementation(() => useCWSAvailabilityCheckAll.CSWAvailabilityCheckTypes.Unavailable);
|
||||
mockLicense = {IsLicensed: 'true', Cloud: 'false'};
|
||||
it('should show terms and privacy checkbox', async () => {
|
||||
mockConfig.TermsOfServiceLink = 'https://mattermost.com/terms';
|
||||
mockConfig.PrivacyPolicyLink = 'https://mattermost.com/privacy';
|
||||
|
||||
const {container: signupContainer} = renderWithContext(
|
||||
<Signup/>,
|
||||
);
|
||||
|
||||
expect(() => screen.getByTestId('signup-body-card-form-check-newsletter')).toThrow();
|
||||
expect(signupContainer).toHaveTextContent('Interested in receiving Mattermost security, product, promotions, and company updates updates via newsletter?Sign up at https://mattermost.com/security-updates/.');
|
||||
const checkInput = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
expect(checkInput).toHaveAttribute('type', 'checkbox');
|
||||
expect(checkInput).not.toBeChecked();
|
||||
|
||||
expect(signupContainer).toHaveTextContent('I agree to the Acceptable Use Policy and the Privacy Policy');
|
||||
});
|
||||
|
||||
it('should show newsletter related opt-in or text for cloud', async () => {
|
||||
jest.spyOn(useCWSAvailabilityCheckAll, 'default').mockImplementation(() => useCWSAvailabilityCheckAll.CSWAvailabilityCheckTypes.Available);
|
||||
mockLicense = {IsLicensed: 'true', Cloud: 'true'};
|
||||
it('should require terms acceptance before enabling submit button', async () => {
|
||||
renderWithContext(<Signup/>, mockState);
|
||||
|
||||
const {container: signupContainer} = renderWithContext(
|
||||
<Signup/>,
|
||||
);
|
||||
const emailInput = screen.getByLabelText('Email address');
|
||||
const usernameInput = screen.getByLabelText('Choose a Username');
|
||||
const passwordInput = screen.getByLabelText('Choose a Password');
|
||||
const termsCheckbox = screen.getByRole('checkbox', {name: /terms and privacy policy checkbox/i});
|
||||
|
||||
screen.getByTestId('signup-body-card-form-check-newsletter');
|
||||
const checkInput = screen.getByTestId('signup-body-card-form-check-newsletter');
|
||||
expect(checkInput).toHaveAttribute('type', 'checkbox');
|
||||
// Fill in all fields but don't check terms
|
||||
await userEvent.type(emailInput, 'test@example.com');
|
||||
await userEvent.type(usernameInput, 'testuser');
|
||||
await userEvent.type(passwordInput, 'ValidPassword123!');
|
||||
|
||||
expect(signupContainer).toHaveTextContent('I would like to receive Mattermost security updates via newsletter. By subscribing, I consent to receive emails from Mattermost with product updates, promotions, and company news. I have read the Privacy Policy and understand that I can unsubscribe at any time');
|
||||
// Submit button should be disabled (SaveButton uses disabled prop on inner button)
|
||||
const submitButton = screen.getByRole('button', {name: /Create account/i});
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
// Check terms
|
||||
await userEvent.click(termsCheckbox);
|
||||
|
||||
// Now submit button should be enabled
|
||||
const enabledButton = screen.getByRole('button', {name: /Create account/i});
|
||||
expect(enabledButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import classNames from 'classnames';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, {useState, useEffect, useRef, useCallback} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useSelector, useDispatch} from 'react-redux';
|
||||
import {useLocation, useHistory, Route} from 'react-router-dom';
|
||||
|
||||
|
|
@ -27,7 +27,6 @@ import {getGlobalItem} from 'selectors/storage';
|
|||
|
||||
import AlertBanner from 'components/alert_banner';
|
||||
import type {ModeType, AlertBannerProps} from 'components/alert_banner';
|
||||
import useCWSAvailabilityCheck, {CSWAvailabilityCheckTypes} from 'components/common/hooks/useCWSAvailabilityCheck';
|
||||
import DesktopAuthToken from 'components/desktop_auth_token';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import ExternalLoginButton from 'components/external_login_button/external_login_button';
|
||||
|
|
@ -48,7 +47,7 @@ import Input, {SIZE} from 'components/widgets/inputs/input/input';
|
|||
import type {CustomMessageInputType} from 'components/widgets/inputs/input/input';
|
||||
import PasswordInput from 'components/widgets/inputs/password_input/password_input';
|
||||
|
||||
import {Constants, HostedCustomerLinks, ItemStatus, ValidationErrors} from 'utils/constants';
|
||||
import {Constants, ItemStatus, ValidationErrors} from 'utils/constants';
|
||||
import {isValidPassword} from 'utils/password';
|
||||
import {isDesktopApp} from 'utils/user_agent';
|
||||
import {isValidUsername} from 'utils/utils';
|
||||
|
|
@ -139,28 +138,17 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
const [teamName, setTeamName] = useState(parsedTeamName ?? '');
|
||||
const [alertBanner, setAlertBanner] = useState<AlertBannerProps | null>(null);
|
||||
const [isMobileView, setIsMobileView] = useState(false);
|
||||
const [subscribeToSecurityNewsletter, setSubscribeToSecurityNewsletter] = useState(false);
|
||||
const [acceptedTerms, setAcceptedTerms] = useState(false);
|
||||
const [submitClicked, setSubmitClicked] = useState(false);
|
||||
|
||||
const cwsAvailability = useCWSAvailabilityCheck();
|
||||
|
||||
const enableExternalSignup = enableSignUpWithGitLab || enableSignUpWithOffice365 || enableSignUpWithGoogle || enableSignUpWithOpenId || enableLDAP || enableSAML;
|
||||
const hasError = Boolean(emailError || nameError || passwordError || serverError || alertBanner);
|
||||
const canSubmit = Boolean(email && name && password) && !hasError && !loading;
|
||||
const canSubmit = Boolean(email && name && password && acceptedTerms) && !hasError && !loading;
|
||||
const passwordConfig = useSelector(getPasswordConfig);
|
||||
const {error: passwordInfo} = isValidPassword('', passwordConfig, intl);
|
||||
|
||||
const [desktopLoginLink, setDesktopLoginLink] = useState('');
|
||||
|
||||
const subscribeToSecurityNewsletterFunc = () => {
|
||||
try {
|
||||
Client4.subscribeToNewsletter({email, subscribed_content: 'security_newsletter'});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getExternalSignupOptions = () => {
|
||||
const externalLoginOptions: ExternalLoginButtonType[] = [];
|
||||
|
||||
|
|
@ -595,9 +583,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
}
|
||||
|
||||
await handleSignupSuccess(user, data!);
|
||||
if (subscribeToSecurityNewsletter) {
|
||||
subscribeToSecurityNewsletterFunc();
|
||||
}
|
||||
} else {
|
||||
setIsWaiting(false);
|
||||
}
|
||||
|
|
@ -605,68 +590,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
|
||||
const handleReturnButtonOnClick = () => history.replace('/');
|
||||
|
||||
const getNewsletterCheck = () => {
|
||||
if (cwsAvailability === CSWAvailabilityCheckTypes.Available) {
|
||||
return (
|
||||
<CheckInput
|
||||
id='signup-body-card-form-check-newsletter'
|
||||
ariaLabel={formatMessage({id: 'newsletter_optin.checkmark.box', defaultMessage: 'newsletter checkbox'})}
|
||||
name='newsletter'
|
||||
onChange={() => setSubscribeToSecurityNewsletter(!subscribeToSecurityNewsletter)}
|
||||
text={
|
||||
formatMessage(
|
||||
{id: 'newsletter_optin.checkmark.text', defaultMessage: '<span>I would like to receive Mattermost security updates via newsletter.</span> By subscribing, I consent to receive emails from Mattermost with product updates, promotions, and company news. I have read the <a>Privacy Policy</a> and understand that I can <aa>unsubscribe</aa> at any time'},
|
||||
{
|
||||
a: (chunks: React.ReactNode | React.ReactNodeArray) => (
|
||||
<ExternalLink
|
||||
location='signup-newsletter-checkmark'
|
||||
href={HostedCustomerLinks.PRIVACY}
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
aa: (chunks: React.ReactNode | React.ReactNodeArray) => (
|
||||
<ExternalLink
|
||||
location='signup-newsletter-checkmark'
|
||||
href={HostedCustomerLinks.NEWSLETTER_UNSUBSCRIBE_LINK}
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
span: (chunks: React.ReactNode | React.ReactNodeArray) => (
|
||||
<span className='header'>{chunks}</span>
|
||||
),
|
||||
},
|
||||
)}
|
||||
checked={subscribeToSecurityNewsletter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className='newsletter'>
|
||||
<span className='interested'>
|
||||
{formatMessage({id: 'newsletter_optin.title', defaultMessage: 'Interested in receiving Mattermost security, product, promotions, and company updates updates via newsletter?'})}
|
||||
</span>
|
||||
<span className='link'>
|
||||
{formatMessage(
|
||||
{id: 'newsletter_optin.desc', defaultMessage: 'Sign up at <a>{link}</a>.'},
|
||||
{
|
||||
link: HostedCustomerLinks.SECURITY_UPDATES,
|
||||
a: (chunks: React.ReactNode | React.ReactNodeArray) => (
|
||||
<ExternalLink
|
||||
location='signup'
|
||||
href={HostedCustomerLinks.SECURITY_UPDATES}
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getContent = () => {
|
||||
if (!enableSignUpWithEmail && !enableExternalSignup) {
|
||||
return (
|
||||
|
|
@ -780,7 +703,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
{enableSignUpWithEmail && (
|
||||
<form className='signup-body-card-form'>
|
||||
<Input
|
||||
data-testid='signup-body-card-form-email-input'
|
||||
ref={emailInput}
|
||||
name='email'
|
||||
className='signup-body-card-form-email-input'
|
||||
|
|
@ -797,7 +719,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
customMessage={emailCustomLabelForInput}
|
||||
/>
|
||||
<Input
|
||||
data-testid='signup-body-card-form-name-input'
|
||||
ref={nameInput}
|
||||
name='name'
|
||||
className='signup-body-card-form-name-input'
|
||||
|
|
@ -819,7 +740,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
}
|
||||
/>
|
||||
<PasswordInput
|
||||
data-testid='signup-body-card-form-password-input'
|
||||
ref={passwordInput}
|
||||
className='signup-body-card-form-password-input'
|
||||
value={password}
|
||||
|
|
@ -830,7 +750,35 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
info={passwordInfo as string}
|
||||
error={passwordError}
|
||||
/>
|
||||
{getNewsletterCheck()}
|
||||
<CheckInput
|
||||
id='signup-body-card-form-check-terms-and-privacy'
|
||||
ariaLabel={formatMessage({id: 'signup.terms_and_privacy.checkmark.box', defaultMessage: 'Terms and privacy policy checkbox'})}
|
||||
name='terms'
|
||||
onChange={() => setAcceptedTerms(!acceptedTerms)}
|
||||
text={
|
||||
formatMessage(
|
||||
{id: 'signup.terms_and_privacy.checkmark.text', defaultMessage: 'I agree to the <termsOfUseLink>Acceptable Use Policy</termsOfUseLink> and the <privacyPolicyLink>Privacy Policy</privacyPolicyLink>'},
|
||||
{
|
||||
privacyPolicyLink: (chunks: React.ReactNode | React.ReactNodeArray) => (
|
||||
<ExternalLink
|
||||
href={PrivacyPolicyLink as string}
|
||||
location='signup-privacy-policy'
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
termsOfUseLink: (chunks: React.ReactNode | React.ReactNodeArray) => (
|
||||
<ExternalLink
|
||||
href={TermsOfServiceLink as string}
|
||||
location='signup-terms-of-use'
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
},
|
||||
)}
|
||||
checked={acceptedTerms}
|
||||
/>
|
||||
<SaveButton
|
||||
extraClasses='signup-body-card-form-button-submit large'
|
||||
saving={isWaiting}
|
||||
|
|
@ -859,33 +807,6 @@ const Signup = ({onCustomizeHeader}: SignupProps) => {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
{enableSignUpWithEmail && !serverError && (
|
||||
<p className='signup-body-card-agreement'>
|
||||
<FormattedMessage
|
||||
id='signup.agreement'
|
||||
defaultMessage='By proceeding to create your account and use {siteName}, you agree to our <termsOfUseLink>Terms of Use</termsOfUseLink> and <privacyPolicyLink>Privacy Policy</privacyPolicyLink>. If you do not agree, you cannot use {siteName}.'
|
||||
values={{
|
||||
siteName: SiteName,
|
||||
termsOfUseLink: (chunks) => (
|
||||
<ExternalLink
|
||||
href={TermsOfServiceLink as string}
|
||||
location='signup-terms-of-use'
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
privacyPolicyLink: (chunks) => (
|
||||
<ExternalLink
|
||||
href={PrivacyPolicyLink as string}
|
||||
location='signup-privacy-policy'
|
||||
>
|
||||
{chunks}
|
||||
</ExternalLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5144,10 +5144,6 @@
|
|||
"new_window_button.tooltip": "Open in new window",
|
||||
"newChannelWithBoard.tutorialTip.description": "The board you just created can be quickly accessed by clicking on the Boards icon in the App bar. You can view the boards that are linked to this channel in the right-hand sidebar and open one in full view.",
|
||||
"newChannelWithBoard.tutorialTip.title": "Access linked boards from the App Bar",
|
||||
"newsletter_optin.checkmark.box": "newsletter checkbox",
|
||||
"newsletter_optin.checkmark.text": "<span>I would like to receive Mattermost security updates via newsletter.</span> By subscribing, I consent to receive emails from Mattermost with product updates, promotions, and company news. I have read the <a>Privacy Policy</a> and understand that I can <aa>unsubscribe</aa> at any time",
|
||||
"newsletter_optin.desc": "Sign up at <a>{link}</a>.",
|
||||
"newsletter_optin.title": "Interested in receiving Mattermost security, product, promotions, and company updates updates via newsletter?",
|
||||
"next_steps_view.welcomeToMattermost": "Welcome to Mattermost",
|
||||
"no_results.channel_files_filtered.subtitle": "This channel doesn't contains any file with the selected file format.",
|
||||
"no_results.channel_files_filtered.title": "No files found",
|
||||
|
|
@ -5953,8 +5949,9 @@
|
|||
"signup_user_completed.userHelp": "You can use lowercase letters, numbers, periods, dashes, and underscores.",
|
||||
"signup_user_completed.usernameLength": "Usernames have to begin with a lowercase letter and be {min}-{max} characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.",
|
||||
"signup_user_completed.validEmail": "Please enter a valid email address",
|
||||
"signup.agreement": "By proceeding to create your account and use {siteName}, you agree to our <termsOfUseLink>Terms of Use</termsOfUseLink> and <privacyPolicyLink>Privacy Policy</privacyPolicyLink>. If you do not agree, you cannot use {siteName}.",
|
||||
"signup.ldap": "AD/LDAP Credentials",
|
||||
"signup.terms_and_privacy.checkmark.box": "Terms and privacy policy checkbox",
|
||||
"signup.terms_and_privacy.checkmark.text": "I agree to the <termsOfUseLink>Acceptable Use Policy</termsOfUseLink> and the <privacyPolicyLink>Privacy Policy</privacyPolicyLink>",
|
||||
"signup.title": "Create Account | {siteName}",
|
||||
"single_image_view.copied_link_tooltip": "Copied",
|
||||
"single_image_view.copy_link_tooltip": "Copy link",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import type {
|
|||
NotifyAdminRequest,
|
||||
Subscription,
|
||||
ValidBusinessEmail,
|
||||
NewsletterRequestBody,
|
||||
Installation,
|
||||
PreviewModalContentData,
|
||||
} from '@mattermost/types/cloud';
|
||||
|
|
@ -4228,13 +4227,6 @@ export default class Client4 {
|
|||
);
|
||||
};
|
||||
|
||||
subscribeToNewsletter = (newletterRequestBody: NewsletterRequestBody) => {
|
||||
return this.doFetch<StatusOK>(
|
||||
`${this.getHostedCustomerRoute()}/subscribe-newsletter`,
|
||||
{method: 'post', body: JSON.stringify(newletterRequestBody)},
|
||||
);
|
||||
};
|
||||
|
||||
cwsAvailabilityCheck = () => {
|
||||
return this.doFetchWithResponse(
|
||||
`${this.getCloudRoute()}/check-cws-connection`,
|
||||
|
|
|
|||
|
|
@ -191,11 +191,6 @@ export type ValidBusinessEmail = {
|
|||
is_valid: boolean;
|
||||
}
|
||||
|
||||
export interface NewsletterRequestBody {
|
||||
email: string;
|
||||
subscribed_content: string;
|
||||
}
|
||||
|
||||
export const areShippingDetailsValid = (address: Address | null | undefined): boolean => {
|
||||
if (!address) {
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue