[next] Implement configuration panes (#2173)

* feat: moderation config

* feat: configure banned and suspect words

* chore: upgrade react and test libs to the newest version <3

* chore: upgrade typescript + some refactor

* feat: general, organization and advanced configuration panes

* fix: translation

* feat: speedup fetching markdown editor

* feat: localize markdown editor

* chore: refactor container names

* chore: rename infobox to communityGuidelines

* feat: closing comment streams duration config

* test: add feature tests for configurations

* fix: mock only console.error

* chore: upgrade node

* chore: require node >= 10

* fix: better validation and default values

* feat: Make DurationField a general purpose component and reuse for Edit Comment Timeframe

* test: add unit test for duration field

* fix: patch for bug when built in production

* chore: bump npm version to latest

* fix: adapted Dockerfile to new version of node

* refactor: harmonized seconds/milliseconds to seconds

* fix: resolve bug from merge conflict
This commit is contained in:
Kiwi
2019-02-07 01:10:51 +01:00
committed by GitHub
parent 9fa5900acc
commit 53e168ae93
228 changed files with 10582 additions and 2774 deletions
@@ -22,16 +22,20 @@ exports[`accepts valid email 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -42,16 +46,20 @@ exports[`accepts valid email 1`] = `
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -104,16 +112,20 @@ exports[`accepts valid email confirmation 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={true}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={true}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -124,16 +136,20 @@ exports[`accepts valid email confirmation 1`] = `
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={true}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={true}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
@@ -173,16 +189,20 @@ exports[`checks for invalid email 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="invalid-email"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="invalid-email"
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -206,16 +226,20 @@ exports[`checks for invalid email 1`] = `
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="invalid-confirmation-email"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="invalid-confirmation-email"
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -297,16 +321,20 @@ exports[`renders addEmailAddress view 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -317,16 +345,20 @@ exports[`renders addEmailAddress view 1`] = `
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
@@ -371,16 +403,20 @@ exports[`shows error when submitting empty form 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -404,16 +440,20 @@ exports[`shows error when submitting empty form 1`] = `
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -478,16 +518,20 @@ GraphQL request (4:3)
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -498,16 +542,20 @@ GraphQL request (4:3)
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
@@ -547,16 +595,20 @@ exports[`successfully sets email 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -567,16 +619,20 @@ exports[`successfully sets email 1`] = `
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
@@ -27,16 +27,20 @@ exports[`accepts valid username 1`] = `
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={true}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="hans"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={true}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="hans"
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
@@ -81,16 +85,20 @@ exports[`checks for invalid username 1`] = `
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="x"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="x"
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -177,16 +185,20 @@ exports[`renders createUsername view 1`] = `
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
@@ -236,16 +248,20 @@ exports[`shows error when submitting empty form 1`] = `
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -315,16 +331,20 @@ GraphQL request (4:3)
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="hans"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="hans"
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
@@ -369,16 +389,20 @@ exports[`successfully sets username 1`] = `
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
@@ -17,16 +17,20 @@ exports[`accepts correct password 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -124,16 +128,20 @@ exports[`accepts valid email 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -231,16 +239,20 @@ exports[`checks for invalid email 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="invalid-email"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="invalid-email"
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -428,16 +440,20 @@ exports[`renders sign in form 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -530,16 +546,20 @@ exports[`shows error when submitting empty form 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorError"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
@@ -655,16 +675,20 @@ exports[`shows server error 1`] = `
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
<div
className="TextField-root TextField-fullWidth"
>
<input
className="TextField-input TextField-colorRegular"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
@@ -0,0 +1,191 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders configure advanced 1`] = `
<div
className="MainLayout-root"
data-testid="configure-container"
>
<form
autoComplete="off"
id="configure-form"
onSubmit={[Function]}
>
<div
className="Flex-root Layout-root Flex-flex"
>
<div
className="SideBar-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li>
<a
className="Link-link"
href="/admin/configure/general"
onClick={[Function]}
>
General
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/organization"
onClick={[Function]}
>
Organization
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/moderation"
onClick={[Function]}
>
Moderation
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/wordList"
onClick={[Function]}
>
Banned and Suspect Words
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/auth"
onClick={[Function]}
>
Authentication
</a>
</li>
<li>
<a
className="Link-link Link-linkActive"
href="/admin/configure/advanced"
onClick={[Function]}
>
Advanced
</a>
</li>
</ul>
</nav>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
data-testid="configure-sideBar-saveChanges"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Save Changes
</button>
</div>
</div>
<div
className="Main-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-advancedContainer"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<label
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
htmlFor="configure-advanced-customCssUrl"
>
Custom CSS
</label>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.
</p>
<div
className="TextField-root TextField-fullWidth"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-advanced-customCssUrl"
name="customCssUrl"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value=""
/>
</div>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<label
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
htmlFor="configure-advanced-domains"
>
Permitted Domains
</label>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Domains where your Talk instance is allowed to be embedded.
Typical use is localhost, staging.yourdomain.com,
yourdomain.com, etc.
</p>
<div
className="TextField-root TextField-fullWidth"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-advanced-domains"
name="domains"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value="localhost:8080"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
`;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,604 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders configure general 1`] = `
<div
className="MainLayout-root"
data-testid="configure-container"
>
<form
autoComplete="off"
id="configure-form"
onSubmit={[Function]}
>
<div
className="Flex-root Layout-root Flex-flex"
>
<div
className="SideBar-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li>
<a
className="Link-link Link-linkActive"
href="/admin/configure/general"
onClick={[Function]}
>
General
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/organization"
onClick={[Function]}
>
Organization
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/moderation"
onClick={[Function]}
>
Moderation
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/wordList"
onClick={[Function]}
>
Banned and Suspect Words
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/auth"
onClick={[Function]}
>
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/advanced"
onClick={[Function]}
>
Advanced
</a>
</li>
</ul>
</nav>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
data-testid="configure-sideBar-saveChanges"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Save Changes
</button>
</div>
</div>
<div
className="Main-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-generalContainer"
>
<fieldset
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<legend
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Community Guidelines Summary
</legend>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Show Community Guidelines Summary
</legend>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="communityGuidelinesEnable-true"
name="communityGuidelinesEnable"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="communityGuidelinesEnable-true"
>
On
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="communityGuidelinesEnable-fase"
name="communityGuidelinesEnable"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="communityGuidelinesEnable-fase"
>
Off
</label>
</div>
</div>
</fieldset>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-general-guidelines-content"
>
Community Guidelines Summary
</label>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Write a summary of your community guidelines that will appear
at the top of each comment stream sitewide. Your summary can be
formatted using Markdown Syntax. More information on how to use
Markdown can be found
<a
className="ExternalLink-root"
href="#"
target="_blank"
>
here
</a>
.
</p>
</div>
<div
className="MarkdownEditor-wrapper"
>
<textarea
id="configure-general-guidelines-content"
name="communityGuidelines"
onChange={[Function]}
value=""
/>
</div>
</fieldset>
<fieldset
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<legend
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Comment Length
</legend>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Set a limit on the length of comments sitewide
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Limit Comment Length
</label>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="charCount.enabled-true"
name="charCount.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="charCount.enabled-true"
>
On
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="charCount.enabled-fase"
name="charCount.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="charCount.enabled-fase"
>
Off
</label>
</div>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-general-commentLength-min"
>
Minimum Comment Length
</label>
<div
className="TextField-root CommentLengthConfig-commentLengthTextInput"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
disabled={false}
id="configure-general-commentLength-min"
name="charCount.min"
onChange={[Function]}
placeholder="No limit"
spellCheck={false}
type="text"
value={3}
/>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Characters
</p>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-general-commentLength-max"
>
Maximum Comment Length
</label>
<div
className="TextField-root CommentLengthConfig-commentLengthTextInput"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
disabled={false}
id="configure-general-commentLength-max"
name="charCount.max"
onChange={[Function]}
placeholder="No limit"
spellCheck={false}
type="text"
value={1000}
/>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Characters
</p>
</div>
</div>
</fieldset>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<h1
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Comment Editing
</h1>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Set a limit on how long commenters have to edit their comments sitewide.
Edited comments are marked as (Edited) on the comment stream and the
moderation panel.
</p>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Comment Edit Timeframe
</legend>
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
<div
className="TextField-root DurationField-value"
>
<input
aria-label="value"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
disabled={false}
name="editCommentWindowLength-value"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value="500"
/>
</div>
<span
className="SelectField-root"
>
<select
aria-label="unit"
className="SelectField-select DurationField-unit"
disabled={false}
name="editCommentWindowLength-unit"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
value="60"
>
<option
value="1"
>
Seconds
</option>
<option
value="60"
>
Minutes
</option>
<option
value="3600"
>
Hours
</option>
</select>
<span
aria-hidden={true}
className="SelectField-iconWrapper"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
expand_more
</span>
</span>
</span>
</div>
</fieldset>
</div>
<fieldset
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<legend
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Closing Comment Streams
</legend>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Set comment streams to close after a defined period of time after a storys publication
</p>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Close Comments Automatically
</legend>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="autoCloseStream-true"
name="autoCloseStream"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="autoCloseStream-true"
>
On
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="autoCloseStream-fase"
name="autoCloseStream"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="autoCloseStream-fase"
>
Off
</label>
</div>
</div>
</fieldset>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Close Comments After
</legend>
<div
className="Flex-root Flex-flex Flex-itemGutter"
>
<div
className="TextField-root DurationField-value"
>
<input
aria-label="value"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
disabled={false}
name="closedTimeout-value"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value="1"
/>
</div>
<span
className="SelectField-root"
>
<select
aria-label="unit"
className="SelectField-select DurationField-unit"
disabled={false}
name="closedTimeout-unit"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
value="604800"
>
<option
value="3600"
>
Hour
</option>
<option
value="86400"
>
Day
</option>
<option
value="604800"
>
Week
</option>
</select>
<span
aria-hidden={true}
className="SelectField-iconWrapper"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
expand_more
</span>
</span>
</span>
</div>
</fieldset>
</fieldset>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<label
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
htmlFor="configure-general-closedStreamMessage-content"
>
Closed Stream Message
</label>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Write a message to appear after a story is closed for commenting.
</p>
<div
className="MarkdownEditor-wrapper"
>
<textarea
id="configure-general-closedStreamMessage-content"
name="closedMessage"
onChange={[Function]}
value=""
/>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
`;
@@ -0,0 +1,536 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders configure moderation 1`] = `
<div
className="MainLayout-root"
data-testid="configure-container"
>
<form
autoComplete="off"
id="configure-form"
onSubmit={[Function]}
>
<div
className="Flex-root Layout-root Flex-flex"
>
<div
className="SideBar-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li>
<a
className="Link-link"
href="/admin/configure/general"
onClick={[Function]}
>
General
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/organization"
onClick={[Function]}
>
Organization
</a>
</li>
<li>
<a
className="Link-link Link-linkActive"
href="/admin/configure/moderation"
onClick={[Function]}
>
Moderation
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/wordList"
onClick={[Function]}
>
Banned and Suspect Words
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/auth"
onClick={[Function]}
>
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/advanced"
onClick={[Function]}
>
Advanced
</a>
</li>
</ul>
</nav>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
data-testid="configure-sideBar-saveChanges"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Save Changes
</button>
</div>
</div>
<div
className="Main-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-moderationContainer"
>
<fieldset
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<legend
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Perspective Toxic Comment Filter
</legend>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Using the Perspective API, the Toxic Comment filter warns users when comments exceed the predefined toxicity
threshold. Comments with a toxicity score above the threshold
<strong>
will not be published
</strong>
and are placed in
the
<strong>
Pending Queue for review by a moderator
</strong>
. If approved by a moderator, the comment will be published.
</p>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Toxic Comment Filter
</legend>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="integrations.perspective.enabled-true"
name="integrations.perspective.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="integrations.perspective.enabled-true"
>
On
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="integrations.perspective.enabled-fase"
name="integrations.perspective.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="integrations.perspective.enabled-fase"
>
Off
</label>
</div>
</div>
</fieldset>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-moderation-perspective-threshold"
>
Toxicity Threshold
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
This value can be set a percentage between 0 and 100. This number represents the likelihood that a
comment is toxic, according to Perspective API. By default the treshold is set to 80.
</p>
<div
className="TextField-root PerspectiveConfig-thresholdTextField"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
disabled={false}
id="configure-moderation-perspective-threshold"
name="integrations.perspective.threshold"
onChange={[Function]}
placeholder="80"
spellCheck={false}
type="text"
value=""
/>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
%
</p>
</div>
</div>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Allow Google to Store Comment Data
</legend>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Stored comments will be used for future research and community model building purposes to
improve the API over time
</p>
<div>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="integrations.perspective.doNotStore-allow"
name="integrations.perspective.doNotStore"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="integrations.perspective.doNotStore-allow"
>
Allow
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="integrations.perspective.doNotStore-dontAllow"
name="integrations.perspective.doNotStore"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="integrations.perspective.doNotStore-dontAllow"
>
Don't allow
</label>
</div>
</div>
</div>
</fieldset>
<div>
<h1
className="Typography-root Typography-heading3 Typography-colorTextPrimary Subheader-root"
>
Configuration
</h1>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
For additional information on how to set up the Perspective Toxic Comment Filter please visit:
<a
className="ExternalLink-root"
href="https://github.com/conversationai/perspectiveapi/blob/master/quickstart.md"
target="_blank"
>
https://github.com/conversationai/perspectiveapi/blob/master/quickstart.md
</a>
</p>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-moderation-integrations.perspective.key"
>
API Key
</label>
<div
className="TextField-root"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-moderation-integrations.perspective.key"
name="integrations.perspective.key"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value=""
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-moderation-perspective-customEndpoint"
>
Custom Endpoint
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
By default the endpoint is set to https://commentanalyzer.googleapis.com/v1alpha1. You may override this here.
</p>
<div
className="TextField-root"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-moderation-perspective-customEndpoint"
name="integrations.perspective.endpoint"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value=""
/>
</div>
</div>
</fieldset>
<fieldset
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<legend
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Akismet Spam Detection Filter
</legend>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Submitted comments are passed to the Akismet API for spam detection.
If a comment is determined to be spam, it will prompt the user,
indicating that the comment might be considered spam.
If the user continues after this point with the still spam-like comment,
the comment will be marked as containing spam,
<strong>
will not be published
</strong>
and
are placed in the
<strong>
Pending Queue for review by a moderator
</strong>
. If approved by a moderator,
the comment will be published.
</p>
<fieldset
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Spam Detection Filter
</legend>
<div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={false}
className="RadioButton-input"
disabled={false}
id="integrations.akismet.enabled-true"
name="integrations.akismet.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={true}
/>
<label
className="RadioButton-label"
htmlFor="integrations.akismet.enabled-true"
>
On
</label>
</div>
<div
className="Flex-root RadioButton-root Flex-flex Flex-alignCenter"
>
<input
checked={true}
className="RadioButton-input"
disabled={false}
id="integrations.akismet.enabled-fase"
name="integrations.akismet.enabled"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="radio"
value={false}
/>
<label
className="RadioButton-label"
htmlFor="integrations.akismet.enabled-fase"
>
Off
</label>
</div>
</div>
</fieldset>
<div>
<h1
className="Typography-root Typography-heading3 Typography-colorTextPrimary Subheader-root"
>
Configuration
</h1>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Note: You must add your active domain(s)
in your Akismet account:
<a
className="ExternalLink-root"
href="https://akismet.com/account/"
target="_blank"
>
https://akismet.com/account/
</a>
</p>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-moderation-integrations.akismet.key"
>
API Key
</label>
<div
className="TextField-root"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-moderation-integrations.akismet.key"
name="integrations.akismet.key"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value=""
/>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-moderation-akismet-site"
>
Site URL
</label>
<div
className="TextField-root"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-moderation-akismet-site"
name="integrations.akismet.site"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value=""
/>
</div>
</div>
</fieldset>
</div>
</div>
</div>
</form>
</div>
`;
@@ -0,0 +1,192 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders configure organization 1`] = `
<div
className="MainLayout-root"
data-testid="configure-container"
>
<form
autoComplete="off"
id="configure-form"
onSubmit={[Function]}
>
<div
className="Flex-root Layout-root Flex-flex"
>
<div
className="SideBar-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li>
<a
className="Link-link"
href="/admin/configure/general"
onClick={[Function]}
>
General
</a>
</li>
<li>
<a
className="Link-link Link-linkActive"
href="/admin/configure/organization"
onClick={[Function]}
>
Organization
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/moderation"
onClick={[Function]}
>
Moderation
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/wordList"
onClick={[Function]}
>
Banned and Suspect Words
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/auth"
onClick={[Function]}
>
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/advanced"
onClick={[Function]}
>
Advanced
</a>
</li>
</ul>
</nav>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
data-testid="configure-sideBar-saveChanges"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Save Changes
</button>
</div>
</div>
<div
className="Main-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-organizationContainer"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<label
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
htmlFor="configure-organization-organizationName"
>
Organization Name
</label>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Your organization name will appear on emails sent by Talk to your community and organization members.
</p>
<div
className="TextField-root TextField-fullWidth"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-organization-organizationName"
name="organizationName"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value="Coral"
/>
</div>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<label
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
htmlFor="configure-organization-organizationContactEmail"
>
Organization Email
</label>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
This email address will be used as in emails and across
the platform for community members to get in touch with
the organization should they have any questions about the
status of their accounts or moderation questions.
</p>
<div
className="TextField-root TextField-fullWidth"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular"
disabled={false}
id="configure-organization-organizationContactEmail"
name="organizationContactEmail"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value="coral@test.com"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
`;
@@ -0,0 +1,229 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders configure wordList 1`] = `
<div
className="MainLayout-root"
data-testid="configure-container"
>
<form
autoComplete="off"
id="configure-form"
onSubmit={[Function]}
>
<div
className="Flex-root Layout-root Flex-flex"
>
<div
className="SideBar-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<nav
className="Navigation-root"
>
<ul
className="Navigation-ul"
>
<li>
<a
className="Link-link"
href="/admin/configure/general"
onClick={[Function]}
>
General
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/organization"
onClick={[Function]}
>
Organization
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/moderation"
onClick={[Function]}
>
Moderation
</a>
</li>
<li>
<a
className="Link-link Link-linkActive"
href="/admin/configure/wordList"
onClick={[Function]}
>
Banned and Suspect Words
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/auth"
onClick={[Function]}
>
Authentication
</a>
</li>
<li>
<a
className="Link-link"
href="/admin/configure/advanced"
onClick={[Function]}
>
Advanced
</a>
</li>
</ul>
</nav>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorSuccess Button-variantFilled Button-disabled"
data-testid="configure-sideBar-saveChanges"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Save Changes
</button>
</div>
</div>
<div
className="Main-root"
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-wordListContainer"
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<h1
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Banned Words and Phrases
</h1>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Comments containing a word or phrase in the banned words list are
<strong>
automatically rejected and are not published
</strong>
.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-wordlist-banned"
>
Banned Word List
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Separate banned words or phrases with a new line. Attempting to copy
and paste a comma separated list?
<a
className="ExternalLink-root"
href="#"
target="_blank"
>
Learn how to convert your list
to a new line separated list.
</a>
</p>
<div>
<textarea
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="BannedWordListConfig-textArea WordListTextArea-textArea"
disabled={false}
id="configure-wordlist-banned"
name="wordList.banned"
onChange={[Function]}
spellCheck={false}
value="fuck"
/>
</div>
</div>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<h1
className="Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
>
Suspect Words and Phrases
</h1>
<p
className="Typography-root Typography-detail Typography-colorTextPrimary"
>
Comments containing a word or phrase in the Suspect Words List
are
<strong>
placed into the Reported Queue for moderator review and are
published (if comments are not pre-moderated).
</strong>
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="configure-wordlist-suspect"
>
Suspect Word List
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Separate suspect words or phrases with a new line. Attempting to copy
and paste a comma separated list?
<a
className="ExternalLink-root"
href="#"
target="_blank"
>
Learn how to convert your list
to a new line separated list.
</a>
</p>
<div>
<textarea
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="SuspectWordListConfig-textArea WordListTextArea-textArea"
disabled={false}
id="configure-wordlist-suspect"
name="wordList.suspect"
onChange={[Function]}
spellCheck={false}
value="idiot
stupid"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
`;
@@ -0,0 +1,211 @@
import mockConsole from "jest-mock-console";
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import {
createSinonStub,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/advanced");
// Test might pass even when it fails with errors in the log due to:
// https://github.com/facebook/jest/issues/3917
// We check the console to be error free..
mockConsole("error");
});
afterEach(() => {
// Check that there are no errors in the console.
// tslint:disable-next-line: no-console
expect(console.error).not.toHaveBeenCalled();
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
},
});
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
const advancedContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-advancedContainer")
);
const saveChangesButton = within(configureContainer).getByTestID(
"configure-sideBar-saveChanges"
);
return {
testRenderer,
configureContainer,
advancedContainer,
saveChangesButton,
};
};
it("renders configure advanced", async () => {
const { configureContainer } = await createTestRenderer();
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("change custom css", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.customCssUrl).toEqual("./custom.css");
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
advancedContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const customCSSField = within(advancedContainer).getByLabelText("Custom CSS");
// Let's change the customCSS field.
customCSSField.props.onChange("./custom.css");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(customCSSField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(customCSSField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
it("change permitted domains to be empty", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.domains).toEqual([]);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
advancedContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const permittedDomainsField = within(advancedContainer).getByLabelText(
"Permitted Domains"
);
// Let's change the permitted domains.
permittedDomainsField.props.onChange("");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(permittedDomainsField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(permittedDomainsField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
it("change permitted domains to include more domains", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.domains).toEqual([
"localhost:8080",
"localhost:3000",
]);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
advancedContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const permittedDomainsField = within(advancedContainer).getByLabelText(
"Permitted Domains"
);
// Let's change the permitted domains.
permittedDomainsField.props.onChange("localhost:8080, localhost:3000");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(permittedDomainsField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(permittedDomainsField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
@@ -0,0 +1,406 @@
import mockConsole from "jest-mock-console";
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import {
createSinonStub,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/general");
// Test might pass even when it fails with errors in the log due to:
// https://github.com/facebook/jest/issues/3917
// We check the console to be error free..
mockConsole("error");
});
afterEach(() => {
// Check that there are no errors in the console.
// tslint:disable-next-line: no-console
expect(console.error).not.toHaveBeenCalled();
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
},
});
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
const generalContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-generalContainer")
);
const saveChangesButton = within(configureContainer).getByTestID(
"configure-sideBar-saveChanges"
);
return {
testRenderer,
configureContainer,
generalContainer,
saveChangesButton,
};
};
it("renders configure general", async () => {
const { configureContainer } = await createTestRenderer();
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("change community guidlines", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.communityGuidelines).toEqual(
"This is the community guidlines summary"
);
expect(data.input.settings.communityGuidelinesEnable).toEqual(true);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const guidelinesContainer = within(generalContainer).getAllByText(
"Community Guidelines Summary",
{ selector: "fieldset" }
)[0];
const onField = within(guidelinesContainer).getByLabelText("On");
const contentField = within(guidelinesContainer).getByLabelText(
"Community Guidelines Summary"
);
// Let's enable it.
onField.props.onChange(onField.props.value.toString());
// Let's change the content.
contentField.props.onChange("This is the community guidlines summary");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(onField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(onField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
it("change closed stream message", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.closedMessage).toEqual(
"The stream has been closed"
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const contentField = within(generalContainer).getByLabelText(
"Closed Stream Message"
);
// Let's change the content.
contentField.props.onChange("The stream has been closed");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(updateSettingsStub.called).toBe(true);
});
});
it("change comment editing time", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.editCommentWindowLength).toEqual(108000);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const durationFieldset = within(generalContainer).getByText(
"Comment Edit Timeframe",
{ selector: "fieldset" }
);
const valueField = within(durationFieldset).getByLabelText("value");
const unitField = within(durationFieldset).getByLabelText("unit");
const hoursOption = within(unitField).getByText(/Hours?/);
// Let's turn on and set some invalid values.
valueField.props.onChange({ target: { value: "" } });
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
expect(
within(generalContainer).queryAllByText("This field is required.").length
).toBe(1);
// Let's change to sth valid.
valueField.props.onChange({ target: { value: "30" } });
unitField.props.onChange({
target: { value: hoursOption.props.value.toString() },
});
expect(
within(generalContainer).queryAllByText(
"Please enter a whole number greater than or equal to 0"
).length
).toBe(0);
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(updateSettingsStub.called).toBe(true);
});
});
it("change comment length limitations", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.charCount).toEqual({
enabled: true,
min: null,
max: 3000,
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const commentLengthContainer = within(generalContainer).getByText(
"Comment Length",
{ selector: "fieldset" }
);
const onField = within(commentLengthContainer).getByLabelText("On");
const minField = within(commentLengthContainer).getByLabelText(
"Minimum Comment Length"
);
const maxField = within(commentLengthContainer).getByLabelText(
"Maximum Comment Length"
);
// Let's turn on and set some invalid values.
onField.props.onChange(onField.props.value.toString());
minField.props.onChange("invalid");
maxField.props.onChange("-1");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
expect(
within(generalContainer).queryAllByText(
"Please enter a whole number greater than 0"
).length
).toBe(2);
// Make max smaller than min.
minField.props.onChange("1000");
maxField.props.onChange("500");
expect(
within(generalContainer).queryAllByText(
"Please enter a number longer than the minimum length"
).length
).toBe(1);
// Let's change to sth valid.
minField.props.onChange("");
maxField.props.onChange("3000");
expect(
within(generalContainer).queryAllByText(
"Please enter a valid whole number >= 0"
).length
).toBe(0);
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(minField.props.disabled).toBe(true);
expect(maxField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(minField.props.disabled).toBe(false);
expect(maxField.props.disabled).toBe(false);
});
expect(updateSettingsStub.called).toBe(true);
});
it("change closing comment streams", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.autoCloseStream).toEqual(true);
expect(data.input.settings.closedTimeout).toEqual(2592000);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
generalContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const closingCommentStreamsContainer = within(generalContainer).getByText(
"Closing Comment Streams",
{ selector: "fieldset" }
);
const onField = within(closingCommentStreamsContainer).getByLabelText("On");
const durationFieldset = within(closingCommentStreamsContainer).getByText(
"Close Comments After",
{ selector: "fieldset" }
);
const valueField = within(durationFieldset).getByLabelText("value");
const unitField = within(durationFieldset).getByLabelText("unit");
const daysOption = within(unitField).getByText(/Days?/);
// Let's turn on and set some invalid values.
onField.props.onChange(onField.props.value.toString());
valueField.props.onChange({ target: { value: "" } });
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
expect(
within(generalContainer).queryAllByText("This field is required.").length
).toBe(1);
// Let's change to sth valid.
valueField.props.onChange({ target: { value: "30" } });
unitField.props.onChange({
target: { value: daysOption.props.value.toString() },
});
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(valueField.props.disabled).toBe(true);
expect(unitField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(valueField.props.disabled).toBe(false);
expect(unitField.props.disabled).toBe(false);
});
expect(updateSettingsStub.called).toBe(true);
});
@@ -0,0 +1,293 @@
import mockConsole from "jest-mock-console";
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import {
createSinonStub,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/moderation");
// Test might pass even when it fails with errors in the log due to:
// https://github.com/facebook/jest/issues/3917
// We check the console to be error free..
mockConsole("error");
});
afterEach(() => {
// Check that there are no errors in the console.
// tslint:disable-next-line: no-console
expect(console.error).not.toHaveBeenCalled();
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
},
});
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
const moderationContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-moderationContainer")
);
const saveChangesButton = within(configureContainer).getByTestID(
"configure-sideBar-saveChanges"
);
return {
testRenderer,
configureContainer,
moderationContainer,
saveChangesButton,
};
};
it("renders configure moderation", async () => {
const { configureContainer } = await createTestRenderer();
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("change akismet settings", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.integrations.akismet).toEqual({
enabled: true,
key: "my api key",
site: "https://coralproject.net",
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
moderationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const akismetContainer = within(moderationContainer).getByText(
"Akismet Spam Detection Filter",
{ selector: "fieldset" }
);
const onField = within(akismetContainer).getByLabelText("On");
const keyField = within(akismetContainer).getByLabelText("API Key");
const siteField = within(akismetContainer).getByLabelText("Site URL");
// Let's turn it on.
onField.props.onChange(onField.props.value.toString());
expect(saveChangesButton.props.disabled).toBe(false);
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
expect(
within(akismetContainer).queryAllByText("This field is required.").length
).toBe(2);
// Input valid api key
keyField.props.onChange("my api key");
// Input malformed site.
siteField.props.onChange("malformed url");
expect(
within(akismetContainer).queryAllByText("This field is required.").length
).toBe(0);
expect(within(akismetContainer).queryAllByText("Invalid URL").length).toBe(1);
// Input correct site.
siteField.props.onChange("https://coralproject.net");
expect(within(akismetContainer).queryAllByText("Invalid URL").length).toBe(0);
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(onField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(onField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
it("change perspective settings", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(
s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.integrations.perspective).toEqual({
doNotStore: false,
enabled: true,
endpoint: "https://custom-endpoint.net",
key: "my api key",
threshold: 0.1,
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
}),
s =>
s.onSecondCall().callsFake((_: any, data: any) => {
expect(
data.input.settings.integrations.perspective.threshold
).toBeNull();
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
moderationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const perspectiveContainer = within(moderationContainer).getByText(
"Perspective Toxic Comment Filter",
{ selector: "fieldset" }
);
const onField = within(perspectiveContainer).getByLabelText("On");
const allowField = within(perspectiveContainer).getByLabelText("Allow");
const keyField = within(perspectiveContainer).getByLabelText("API Key");
const thresholdField = within(perspectiveContainer).getByLabelText(
"Toxicity Threshold"
);
const endpointField = within(perspectiveContainer).getByLabelText(
"Custom Endpoint"
);
// Let's turn it on.
onField.props.onChange(onField.props.value.toString());
allowField.props.onChange(allowField.props.value.toString());
expect(saveChangesButton.props.disabled).toBe(false);
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
expect(
within(perspectiveContainer).queryAllByText("This field is required.")
.length
).toBe(1);
// Input valid api key
keyField.props.onChange("my api key");
// Input malformed endpoint.
endpointField.props.onChange("malformed url");
// Input malformed threshold.
thresholdField.props.onChange("abc");
expect(
within(perspectiveContainer).queryAllByText("This field is required.")
.length
).toBe(0);
expect(
within(perspectiveContainer).queryAllByText("Invalid URL").length
).toBe(1);
expect(
within(perspectiveContainer).queryAllByText(
"Please enter a whole number between 0 and 100."
).length
).toBe(1);
// Input correct site.
endpointField.props.onChange("https://custom-endpoint.net");
// Input valid threshold.
thresholdField.props.onChange(10);
expect(
within(perspectiveContainer).queryAllByText("Invalid URL").length
).toBe(0);
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(onField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(onField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.calledOnce).toBe(true);
// Use default threshold.
thresholdField.props.onChange("");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(onField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(onField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.calledTwice).toBe(true);
});
@@ -0,0 +1,197 @@
import mockConsole from "jest-mock-console";
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import {
createSinonStub,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/organization");
// Test might pass even when it fails with errors in the log due to:
// https://github.com/facebook/jest/issues/3917
// We check the console to be error free..
mockConsole("error");
});
afterEach(() => {
// Check that there are no errors in the console.
// tslint:disable-next-line: no-console
expect(console.error).not.toHaveBeenCalled();
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
},
});
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
const organizationContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-organizationContainer")
);
const saveChangesButton = within(configureContainer).getByTestID(
"configure-sideBar-saveChanges"
);
return {
testRenderer,
configureContainer,
organizationContainer,
saveChangesButton,
};
};
it("renders configure organization", async () => {
const { configureContainer } = await createTestRenderer();
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("change organization name", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.organizationName).toEqual("Coral Test");
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
organizationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const organizationNameField = within(organizationContainer).getByLabelText(
"Organization Name"
);
// Let's change some organization name.
organizationNameField.props.onChange("");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Should show validation error.
within(organizationContainer).getByText("This field is required.");
// Let's change to some valid organization name.
organizationNameField.props.onChange("Coral Test");
// Should not show validation error.
expect(
within(organizationContainer).queryByText("This field is required.")
).toBeNull();
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(organizationNameField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(organizationNameField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
it("change organization contact email", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.organizationContactEmail).toEqual(
"test@coralproject.net"
);
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
organizationContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const organizationEmailField = within(organizationContainer).getByLabelText(
"Organization Email"
);
// Let's change some organization name.
organizationEmailField.props.onChange("");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Should show validation error.
within(organizationContainer).getByText("This field is required.");
// Let's change to some valid organization name.
organizationEmailField.props.onChange("test@coralproject.net");
// Should not show validation error.
expect(
within(organizationContainer).queryByText("This field is required.")
).toBeNull();
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(organizationEmailField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(organizationEmailField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
@@ -0,0 +1,125 @@
import mockConsole from "jest-mock-console";
import { cloneDeep, get, merge } from "lodash";
import sinon from "sinon";
import {
createSinonStub,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
beforeEach(() => {
replaceHistoryLocation("http://localhost/admin/configure/wordList");
// Test might pass even when it fails with errors in the log due to:
// https://github.com/facebook/jest/issues/3917
// We check the console to be error free..
mockConsole("error");
});
afterEach(() => {
// Check that there are no errors in the console.
// tslint:disable-next-line: no-console
expect(console.error).not.toHaveBeenCalled();
});
const createTestRenderer = async (resolver: any = {}) => {
const resolvers = {
...resolver,
Query: {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({
// Set this to true, to see graphql responses.
logNetwork: false,
resolvers,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
},
});
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
const wordListContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-wordListContainer")
);
const saveChangesButton = within(configureContainer).getByTestID(
"configure-sideBar-saveChanges"
);
return {
testRenderer,
configureContainer,
wordListContainer,
saveChangesButton,
};
};
it("renders configure wordList", async () => {
const { configureContainer } = await createTestRenderer();
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("change banned and suspect words", async () => {
let settingsRecord = cloneDeep(settings);
const updateSettingsStub = createSinonStub(s =>
s.onFirstCall().callsFake((_: any, data: any) => {
expect(data.input.settings.wordList).toEqual({
banned: ["Fuck", "Asshole"],
suspect: ["idiot", "shame"],
});
settingsRecord = merge(settingsRecord, data.input.settings);
return {
settings: settingsRecord,
clientMutationId: data.input.clientMutationId,
};
})
);
const {
configureContainer,
wordListContainer,
saveChangesButton,
} = await createTestRenderer({
Mutation: {
updateSettings: updateSettingsStub,
},
});
const bannedField = within(wordListContainer).getByLabelText(
"Banned Word List"
);
const suspectField = within(wordListContainer).getByLabelText(
"Suspect Word List"
);
// Let's change the wordlist contents.
bannedField.props.onChange("Fuck\nAsshole");
suspectField.props.onChange("idiot\nshame");
// Send form
within(configureContainer)
.getByType("form")
.props.onSubmit();
// Submit button and text field should be disabled.
expect(saveChangesButton.props.disabled).toBe(true);
expect(bannedField.props.disabled).toBe(true);
expect(suspectField.props.disabled).toBe(true);
// Wait for submission to be finished
await wait(() => {
expect(bannedField.props.disabled).toBe(false);
expect(suspectField.props.disabled).toBe(false);
});
// Should have successfully sent with server.
expect(updateSettingsStub.called).toBe(true);
});
+25 -2
View File
@@ -3,8 +3,31 @@ import { merge } from "lodash";
export const settings = {
id: "settings",
wordList: {
banned: [],
suspect: [],
suspect: ["idiot", "stupid"],
banned: ["fuck"],
},
charCount: {
enabled: false,
max: 1000,
min: 3,
},
closedTimeout: 604800,
autoCloseStream: false,
closedMessage: null,
customCssUrl: null,
domains: ["localhost:8080"],
editCommentWindowLength: 30000,
communityGuidelinesEnable: false,
communityGuidelines: "",
organizationContactEmail: "coral@test.com",
organizationName: "Coral",
integrations: {
akismet: {
enabled: false,
},
perspective: {
enabled: false,
},
},
auth: {
displayName: {