mirror of
https://github.com/wassname/talk.git
synced 2026-06-30 05:26:17 +08:00
Fix merge conflicts
This commit is contained in:
@@ -15,6 +15,10 @@ client/coral-framework/graphql/introspection.json
|
||||
*.DS_STORE
|
||||
|
||||
coverage/
|
||||
test/e2e/reports/
|
||||
test/e2e/bslocal.log
|
||||
test/e2e/selenium-debug.log
|
||||
browserstack.err
|
||||
|
||||
plugins.json
|
||||
plugins/*
|
||||
|
||||
+3
-175
@@ -1,161 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const program = require('./commander');
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('talk:cli:serve');
|
||||
const errors = require('../errors');
|
||||
const {createServer} = require('http');
|
||||
const scraper = require('../services/scraper');
|
||||
const mailer = require('../services/mailer');
|
||||
const MigrationService = require('../services/migration');
|
||||
const SetupService = require('../services/setup');
|
||||
const kue = require('../services/kue');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const util = require('./util');
|
||||
const cache = require('../services/cache');
|
||||
const {createSubscriptionManager} = require('../graph/subscriptions');
|
||||
const {
|
||||
PORT
|
||||
} = require('../config');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
const port = normalizePort(PORT);
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
const server = createServer(app);
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let bind = typeof port === 'string'
|
||||
? `Pipe ${port}`
|
||||
: `Port ${port}`;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(`${bind} requires elevated privileges`);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(`${bind} is already in use`);
|
||||
break;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
let port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
async function onListening() {
|
||||
|
||||
// Start the cache instance.
|
||||
await cache.init();
|
||||
|
||||
let addr = server.address();
|
||||
let bind = typeof addr === 'string'
|
||||
? `pipe ${addr}`
|
||||
: `port ${addr.port}`;
|
||||
debug(`API Server Listening on ${bind}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the app.
|
||||
*/
|
||||
async function startApp(program) {
|
||||
|
||||
try {
|
||||
|
||||
// Check to see if the application is installed. If the application
|
||||
// has been installed, then it will throw errors.ErrSettingsNotInit, this
|
||||
// just means we don't have to check that the migrations have run.
|
||||
await SetupService.isAvailable();
|
||||
|
||||
debug('setup is currently available, migrations not being checked');
|
||||
|
||||
} catch (e) {
|
||||
|
||||
// Check the error.
|
||||
switch (e) {
|
||||
case errors.ErrInstallLock, errors.ErrSettingsInit:
|
||||
|
||||
debug('setup is not currently available, migrations now being checked');
|
||||
|
||||
// The error was expected, just continue.
|
||||
break;
|
||||
default:
|
||||
|
||||
// The error was not expected, throw the error!
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Now try and check the migration status.
|
||||
try {
|
||||
|
||||
// Verify that the minimum migration version is met.
|
||||
await MigrationService.verify();
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
debug('migrations do not have to be run');
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
server.on('listening', () => {
|
||||
|
||||
});
|
||||
server.listen(port, () => {
|
||||
|
||||
// Mount the websocket server if requested.
|
||||
if (program.websockets) {
|
||||
debug(`Websocket Server Listening on ${port}`);
|
||||
|
||||
// Mount the subscriptions server on the application server.
|
||||
createSubscriptionManager(server);
|
||||
}
|
||||
});
|
||||
}
|
||||
const serve = require('../serve');
|
||||
|
||||
//==============================================================================
|
||||
// Setting up the program command line arguments.
|
||||
@@ -166,24 +12,6 @@ program
|
||||
.option('-w, --websockets', 'enable the websocket (subscriptions) handler on this thread')
|
||||
.parse(process.argv);
|
||||
|
||||
// Start the application serving.
|
||||
startApp(program);
|
||||
// Start serving.
|
||||
serve({jobs: program.jobs, websockets: program.websockets});
|
||||
|
||||
// Enable job processing on the thread if enabled.
|
||||
if (program.jobs) {
|
||||
|
||||
// Start the scraper processor.
|
||||
scraper.process();
|
||||
|
||||
// Start the mail processor.
|
||||
mailer.process();
|
||||
}
|
||||
|
||||
// Define a safe shutdown function to call in the event we need to shutdown
|
||||
// because the node hooks are below which will interrupt the shutdown process.
|
||||
// Shutdown the mongoose connection, the app server, and the scraper.
|
||||
util.onshutdown([
|
||||
() => program.jobs ? kue.Task.shutdown() : null,
|
||||
() => mongoose.disconnect(),
|
||||
() => server.close()
|
||||
]);
|
||||
|
||||
+12
-3
@@ -7,9 +7,7 @@ machine:
|
||||
environment:
|
||||
PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
|
||||
NODE_ENV: "test"
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
pre:
|
||||
# TODO: use the following to add in support for MongoDB 3.4.
|
||||
# # Upgrade the database version to 3.4.
|
||||
# - sudo apt-get purge mongodb-org*
|
||||
@@ -19,10 +17,20 @@ dependencies:
|
||||
# - sudo apt-get install -y mongodb-org
|
||||
# - sudo service mongod restart
|
||||
|
||||
# Install chromium for e2e and remove old google-chrome
|
||||
- sudo rm -rf /opt/google/chrome
|
||||
- sudo rm -f /usr/bin/google-chrome*
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install chromium-browser
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
|
||||
# Install node dependencies.
|
||||
- yarn --version
|
||||
- yarn global add node-gyp nsp --force
|
||||
- yarn
|
||||
|
||||
post:
|
||||
# Build the static assets.
|
||||
- yarn build
|
||||
@@ -42,6 +50,7 @@ test:
|
||||
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml MOCHA_REPORTER=mocha-junit-reporter yarn test
|
||||
# Check dependancies using nsp.
|
||||
- nsp check
|
||||
- yarn e2e-ci
|
||||
|
||||
deployment:
|
||||
release:
|
||||
|
||||
@@ -4,6 +4,7 @@ import Layout from 'coral-admin/src/components/ui/Layout';
|
||||
import styles from './NotFound.css';
|
||||
import {Button, TextField, Alert, Success} from 'coral-ui';
|
||||
import Recaptcha from 'react-recaptcha';
|
||||
import cn from 'classnames';
|
||||
|
||||
class AdminLogin extends React.Component {
|
||||
|
||||
@@ -34,19 +35,22 @@ class AdminLogin extends React.Component {
|
||||
render () {
|
||||
const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props;
|
||||
const signInForm = (
|
||||
<form onSubmit={this.handleSignIn}>
|
||||
<form className="talk-admin-login-sign-in" onSubmit={this.handleSignIn}>
|
||||
{errorMessage && <Alert>{errorMessage}</Alert>}
|
||||
<TextField
|
||||
id="email"
|
||||
label='Email Address'
|
||||
value={this.state.email}
|
||||
onChange={(e) => this.setState({email: e.target.value})} />
|
||||
<TextField
|
||||
id="password"
|
||||
label='Password'
|
||||
value={this.state.password}
|
||||
onChange={(e) => this.setState({password: e.target.value})}
|
||||
type='password' />
|
||||
<div style={{height: 10}}></div>
|
||||
<Button
|
||||
className="talk-admin-login-sign-in-button"
|
||||
type='submit'
|
||||
cStyle='black'
|
||||
full
|
||||
@@ -90,7 +94,7 @@ class AdminLogin extends React.Component {
|
||||
);
|
||||
return (
|
||||
<Layout fixedDrawer restricted={true}>
|
||||
<div className={styles.loginLayout}>
|
||||
<div className={cn(styles.loginLayout, 'talk-admin-login')}>
|
||||
<h1 className={styles.loginHeader}>Team sign in</h1>
|
||||
<p className={styles.loginCTA}>Sign in to interact with your community.</p>
|
||||
{ this.state.requestPassword ? requestPasswordForm : signInForm }
|
||||
|
||||
@@ -2,17 +2,25 @@ import React from 'react';
|
||||
import TagsInput from 'react-tagsinput';
|
||||
import styles from './TagsInput.css';
|
||||
import AutosizeInput from 'react-input-autosize';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
|
||||
const autosizingRenderInput = ({onChange, value, addTag: _, ...other}) =>
|
||||
<AutosizeInput type='text' onChange={onChange} value={value} {...other} />;
|
||||
|
||||
export default (props) => {
|
||||
autosizingRenderInput.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
addTag: PropTypes.func,
|
||||
};
|
||||
|
||||
const TagsInputComponent = ({className = '', ...props}) => {
|
||||
return (
|
||||
<TagsInput
|
||||
addOnBlur={true}
|
||||
addOnPaste={true}
|
||||
pasteSplit={(data) => data.split(',').map((d) => d.trim())}
|
||||
className={styles.root}
|
||||
className={cn(styles.root, 'tags-input', className)}
|
||||
focusedClassName={styles.rootFocus}
|
||||
renderInput={autosizingRenderInput}
|
||||
{...props}
|
||||
@@ -29,3 +37,11 @@ export default (props) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
TagsInputComponent.propTypes = {
|
||||
className: PropTypes.string,
|
||||
inputProps: PropTypes.object,
|
||||
tagProps: PropTypes.object,
|
||||
};
|
||||
|
||||
export default TagsInputComponent;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import styles from './style.css';
|
||||
import {TextField, Button} from 'coral-ui';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import cn from 'classnames';
|
||||
|
||||
const AddOrganizationName = (props) => {
|
||||
const {handleSettingsChange, handleSettingsSubmit, install} = props;
|
||||
return (
|
||||
<div className={styles.step}>
|
||||
<div className={cn(styles.step, 'talk-install-step-2')}>
|
||||
<p>{t('install.add_organization.description')}</p>
|
||||
<div className={styles.form}>
|
||||
<form onSubmit={handleSettingsSubmit}>
|
||||
@@ -19,11 +20,17 @@ const AddOrganizationName = (props) => {
|
||||
onChange={handleSettingsChange}
|
||||
showErrors={install.showErrors}
|
||||
errorMsg={install.errors.organizationName} />
|
||||
<Button type="submit" cStyle='black' full>{t('install.add_organization.save')}</Button>
|
||||
<Button className="talk-install-step-2-save-button" type="submit" cStyle='black' full>{t('install.add_organization.save')}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AddOrganizationName.propTypes = {
|
||||
handleSettingsChange: PropTypes.func,
|
||||
handleSettingsSubmit: PropTypes.func,
|
||||
install: PropTypes.object,
|
||||
};
|
||||
|
||||
export default AddOrganizationName;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import styles from './style.css';
|
||||
import {TextField, Button, Spinner} from 'coral-ui';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import cn from 'classnames';
|
||||
|
||||
const InitialStep = (props) => {
|
||||
const {handleUserChange, handleUserSubmit, install} = props;
|
||||
return (
|
||||
<div className={styles.step}>
|
||||
<div className={cn(styles.step, 'talk-install-step-3')}>
|
||||
<div className={styles.form}>
|
||||
<form onSubmit={handleUserSubmit}>
|
||||
<TextField
|
||||
@@ -53,7 +54,7 @@ const InitialStep = (props) => {
|
||||
|
||||
{
|
||||
!props.install.isLoading ?
|
||||
<Button cStyle='black' type="submit" full>{t('install.create.save')}</Button>
|
||||
<Button className="talk-install-step-3-save-button" cStyle='black' type="submit" full>{t('install.create.save')}</Button>
|
||||
:
|
||||
<Spinner />
|
||||
}
|
||||
@@ -64,4 +65,10 @@ const InitialStep = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
InitialStep.propTypes = {
|
||||
handleUserChange: PropTypes.func,
|
||||
handleUserSubmit: PropTypes.func,
|
||||
install: PropTypes.object,
|
||||
};
|
||||
|
||||
export default InitialStep;
|
||||
|
||||
@@ -2,12 +2,13 @@ import React from 'react';
|
||||
import styles from './style.css';
|
||||
import {Button} from 'coral-ui';
|
||||
import {Link} from 'react-router';
|
||||
import cn from 'classnames';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const InitialStep = () => {
|
||||
return (
|
||||
<div className={`${styles.step} ${styles.finalStep}`}>
|
||||
<div className={cn(styles.step, styles.finalStep, 'talk-install-step-5')}>
|
||||
<p>{t('install.final.description')}</p>
|
||||
<Button raised><Link to='/admin'>{t('install.final.launch')}</Link></Button>
|
||||
<Button cStyle='black' raised><a href="http://coralproject.net">{t('install.final.close')}</a></Button>
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import React from 'react';
|
||||
import styles from './style.css';
|
||||
import {Button} from 'coral-ui';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import cn from 'classnames';
|
||||
|
||||
const InitialStep = (props) => {
|
||||
const {nextStep} = props;
|
||||
return (
|
||||
<div className={styles.step}>
|
||||
<div className={cn(styles.step, 'talk-install-step-1')}>
|
||||
<p>{t('install.initial.description')}</p>
|
||||
<Button cStyle='green' onClick={nextStep} raised>{t('install.initial.submit')}</Button>
|
||||
<Button className={'talk-install-get-started-button'} cStyle='green' onClick={nextStep} raised>{t('install.initial.submit')}</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InitialStep.propTypes = {
|
||||
nextStep: PropTypes.func,
|
||||
};
|
||||
|
||||
export default InitialStep;
|
||||
|
||||
@@ -2,26 +2,34 @@ import React from 'react';
|
||||
import styles from './style.css';
|
||||
import {Button, Card} from 'coral-ui';
|
||||
import TagsInput from 'coral-admin/src/components/TagsInput';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const PermittedDomainsStep = (props) => {
|
||||
const {finishInstall, install, handleDomainsChange} = props;
|
||||
const domains = install.data.settings.domains.whitelist;
|
||||
return (
|
||||
<div className={styles.step}>
|
||||
<div className={cn(styles.step, 'talk-install-step-4')}>
|
||||
<h3>{t('install.permitted_domains.title')}</h3>
|
||||
<Card className={styles.card}>
|
||||
<p>{t('install.permitted_domains.description')}</p>
|
||||
<TagsInput
|
||||
className="talk-install-step-4-permited-domains-input"
|
||||
value={domains}
|
||||
inputProps={{placeholder: 'URL'}}
|
||||
onChange={(tags) => handleDomainsChange(tags)}
|
||||
/>
|
||||
</Card>
|
||||
<Button cStyle='green' onClick={finishInstall} raised>{t('install.permitted_domains.submit')}</Button>
|
||||
<Button className="talk-install-step-4-save-button" cStyle='green' onClick={finishInstall} raised>{t('install.permitted_domains.submit')}</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PermittedDomainsStep.propTypes = {
|
||||
finishInstall: PropTypes.func,
|
||||
handleDomainsChange: PropTypes.func,
|
||||
install: PropTypes.object,
|
||||
};
|
||||
|
||||
export default PermittedDomainsStep;
|
||||
|
||||
@@ -519,6 +519,7 @@ export default class Comment extends React.Component {
|
||||
: <div>
|
||||
<Slot
|
||||
fill="commentContent"
|
||||
className='talk-stream-comment-content'
|
||||
defaultComponent={CommentContent}
|
||||
{...slotProps}
|
||||
queryData={queryData}
|
||||
|
||||
@@ -75,13 +75,13 @@ export default class Embed extends React.Component {
|
||||
activeTab={activeTab}
|
||||
id='talk-embed-stream-tab-content'
|
||||
>
|
||||
<TabPane tabId={'stream'}>
|
||||
<TabPane tabId={'stream'} className={'talk-embed-stream-comments-tab-pane'}>
|
||||
<Stream data={data} root={root} />
|
||||
</TabPane>
|
||||
<TabPane tabId={'profile'}>
|
||||
<TabPane tabId={'profile'} className={'talk-embed-stream-profile-tab-pane'}>
|
||||
<ProfileContainer />
|
||||
</TabPane>
|
||||
<TabPane tabId={'config'}>
|
||||
<TabPane tabId={'config'} className={'talk-embed-stream-configuration-tab-pane'}>
|
||||
<ConfigureStreamContainer />
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
|
||||
@@ -6,9 +6,9 @@ import styles from './StreamTabPanel.css';
|
||||
class StreamTabPanel extends React.Component {
|
||||
|
||||
render() {
|
||||
const {activeTab, setActiveTab, tabs, tabPanes, sub, loading} = this.props;
|
||||
const {activeTab, setActiveTab, tabs, tabPanes, sub, loading, ...rest} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div {...rest}>
|
||||
<TabBar activeTab={activeTab} onTabClick={setActiveTab} sub={sub}>
|
||||
{tabs}
|
||||
</TabBar>
|
||||
@@ -26,6 +26,7 @@ class StreamTabPanel extends React.Component {
|
||||
StreamTabPanel.propTypes = {
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
setActiveTab: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
tabs: PropTypes.oneOfType([
|
||||
PropTypes.element,
|
||||
PropTypes.arrayOf(PropTypes.element)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import styles from './NotLoggedIn.css';
|
||||
import cn from 'classnames';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
export default ({showSignInDialog}) => (
|
||||
<div className={styles.message}>
|
||||
<div className={cn(styles.message, 'talk-embed-stream-not-logged-in')}>
|
||||
<div>
|
||||
<a onClick={showSignInDialog}>{t('settings.sign_in')}</a> {t('settings.to_access')}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, {Component} from 'react';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {withQuery} from 'coral-framework/hocs';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
|
||||
import cn from 'classnames';
|
||||
import {link} from 'coral-framework/services/pym';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
import {Spinner} from 'coral-ui';
|
||||
@@ -71,21 +71,23 @@ class ProfileContainer extends Component {
|
||||
const emailAddress = localProfile && localProfile.id;
|
||||
|
||||
return (
|
||||
<div className='talk-embed-stream-profile-container'>
|
||||
<div className="talk-my-profile talk-embed-stream-profile-container">
|
||||
<h2>{me.username}</h2>
|
||||
{emailAddress ? <p>{emailAddress}</p> : null}
|
||||
|
||||
<Slot
|
||||
fill="profileSections"
|
||||
data={data}
|
||||
queryData={{root}}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
<h3>{t('framework.my_comments')}</h3>
|
||||
{me.comments.nodes.length
|
||||
? <CommentHistory data={data} root={root} comments={me.comments} link={link} loadMore={this.loadMore}/>
|
||||
: <p>{t('user_no_comment')}</p>}
|
||||
<div className={cn('talk-my-profile-comment-history', {
|
||||
'talk-my-profile-comment-history-no-comments': !me.comments.nodes.length
|
||||
})}>
|
||||
{me.comments.nodes.length
|
||||
? <CommentHistory data={data} root={root} comments={me.comments} link={link} loadMore={this.loadMore}/>
|
||||
: <p className='talk-my-profile-comment-history-no-comments-cta'>{t('user_no_comment')}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Comment from './Comment';
|
||||
import styles from './CommentHistory.css';
|
||||
import LoadMore from './LoadMore';
|
||||
import {forEachError} from 'plugin-api/beta/client/utils';
|
||||
|
||||
@@ -25,7 +24,7 @@ class CommentHistory extends React.Component {
|
||||
render() {
|
||||
const {link, comments, data, root} = this.props;
|
||||
return (
|
||||
<div className={`${styles.header} commentHistory`}>
|
||||
<div>
|
||||
<div className="commentHistory__list">
|
||||
{comments.nodes.map((comment, i) => {
|
||||
return <Comment
|
||||
@@ -49,7 +48,12 @@ class CommentHistory extends React.Component {
|
||||
}
|
||||
|
||||
CommentHistory.propTypes = {
|
||||
comments: PropTypes.object.isRequired
|
||||
comments: PropTypes.object.isRequired,
|
||||
loadMore: PropTypes.func,
|
||||
notify: PropTypes.func,
|
||||
link: PropTypes.func,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object
|
||||
};
|
||||
|
||||
export default CommentHistory;
|
||||
|
||||
@@ -119,7 +119,7 @@ const CONFIG = {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Port to bind to.
|
||||
PORT: process.env.TALK_PORT || process.env.PORT || '3000',
|
||||
PORT: process.env.TALK_PORT || process.env.PORT || process.env.NODE_ENV === 'test' ? '3001' : '3000',
|
||||
|
||||
// The URL for this Talk Instance as viewable from the outside.
|
||||
ROOT_URL: process.env.TALK_ROOT_URL || null,
|
||||
@@ -182,12 +182,13 @@ const CONFIG = {
|
||||
// CONFIG VALIDATION
|
||||
//==============================================================================
|
||||
|
||||
if (CONFIG.ROOT_URL_MOUNT_PATH && !CONFIG.ROOT_URL) {
|
||||
throw new Error('TALK_ROOT_URL must be specified if TALK_ROOT_URL_MOUNT_PATH is set to TRUE');
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test' && !CONFIG.ROOT_URL) {
|
||||
CONFIG.ROOT_URL = 'http://localhost:3000';
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
if (!CONFIG.ROOT_URL) {
|
||||
CONFIG.ROOT_URL = 'http://localhost:3001';
|
||||
}
|
||||
if (!CONFIG.STATIC_URL) {
|
||||
CONFIG.STATIC_URI = 'http://localhost:3001';
|
||||
}
|
||||
} else if (!CONFIG.ROOT_URL) {
|
||||
throw new Error('TALK_ROOT_URL must be provided');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
const {
|
||||
ROOT_URL
|
||||
} = require('./config');
|
||||
|
||||
const nightwatch_config = {
|
||||
src_folders: './test/e2e/specs/',
|
||||
output_folder: process.env.REPORTS_FOLDER || './test/e2e/reports',
|
||||
page_objects_path: './test/e2e/page_objects',
|
||||
globals_path: './test/e2e/globals',
|
||||
|
||||
selenium : {
|
||||
start_process: false,
|
||||
host: 'hub-cloud.browserstack.com',
|
||||
port: 80
|
||||
},
|
||||
|
||||
test_settings: {
|
||||
default: {
|
||||
launch_url: ROOT_URL,
|
||||
desiredCapabilities: {
|
||||
browser: 'chrome',
|
||||
'browserstack.user': process.env.BROWSERSTACK_USER || 'coralproject2',
|
||||
'browserstack.key': process.env.BROWSERSTACK_KEY,
|
||||
'browserstack.local': true,
|
||||
'browserstack.debug': true,
|
||||
'browserstack.networkLogs': true,
|
||||
}
|
||||
},
|
||||
chrome: {
|
||||
desiredCapabilities: {
|
||||
browser: 'chrome',
|
||||
browser_version: '60',
|
||||
},
|
||||
},
|
||||
firefox: {
|
||||
desiredCapabilities: {
|
||||
browser: 'firefox',
|
||||
browser_version: '56',
|
||||
},
|
||||
},
|
||||
safari: {
|
||||
desiredCapabilities: {
|
||||
|
||||
// Safari since 8 seems to have troubles with the browserstack-local tunnel (10.18.17)
|
||||
browser: 'safari',
|
||||
browser_version: '10',
|
||||
},
|
||||
},
|
||||
ie: {
|
||||
desiredCapabilities: {
|
||||
browser: 'internet explorer',
|
||||
|
||||
// Windows 10 + IE seems to have troubles with the browserstack-local tunnel (10.17.17).
|
||||
os: 'Windows',
|
||||
os_version: '8.1',
|
||||
browser_version: '11',
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
desiredCapabilities: {
|
||||
browser: 'edge',
|
||||
browser_version: '15',
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Code to copy seleniumhost/port into test settings
|
||||
for(const i in nightwatch_config.test_settings){
|
||||
const config = nightwatch_config.test_settings[i];
|
||||
config['selenium_host'] = nightwatch_config.selenium.host;
|
||||
config['selenium_port'] = nightwatch_config.selenium.port;
|
||||
}
|
||||
|
||||
module.exports = nightwatch_config;
|
||||
@@ -0,0 +1,51 @@
|
||||
const {
|
||||
ROOT_URL
|
||||
} = require('./config');
|
||||
|
||||
module.exports = {
|
||||
src_folders: './test/e2e/specs/',
|
||||
output_folder: process.env.REPORTS_FOLDER || './test/e2e/reports',
|
||||
page_objects_path: './test/e2e/page_objects',
|
||||
globals_path: './test/e2e/globals',
|
||||
selenium: {
|
||||
start_process: true,
|
||||
server_path: 'node_modules/selenium-standalone/.selenium/selenium-server/3.5.3-server.jar',
|
||||
log_path: './test/e2e/',
|
||||
host: '127.0.0.1',
|
||||
port: 6666,
|
||||
cli_args: {
|
||||
'webdriver.chrome.driver': 'node_modules/selenium-standalone/.selenium/chromedriver/2.32-x64-chromedriver',
|
||||
'webdriver.gecko.driver': 'node_modules/selenium-standalone/.selenium/geckodriver/0.18.0-x64-geckodriver',
|
||||
}
|
||||
},
|
||||
test_settings: {
|
||||
default: {
|
||||
launch_url: ROOT_URL,
|
||||
selenium_port: 6666,
|
||||
selenium_host: 'localhost',
|
||||
silent: true,
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome',
|
||||
javascriptEnabled: true,
|
||||
acceptSslCerts: true,
|
||||
webStorageEnabled: true,
|
||||
databaseEnabled: true,
|
||||
applicationCacheEnabled: false,
|
||||
nativeEvents: true,
|
||||
},
|
||||
screenshots : {
|
||||
enabled: true,
|
||||
on_failure: true,
|
||||
on_error: true,
|
||||
path: process.env.REPORTS_FOLDER || './test/e2e/reports',
|
||||
},
|
||||
},
|
||||
'chrome-headless': {
|
||||
desiredCapabilities: {
|
||||
chromeOptions : {
|
||||
args: ['--headless', '--disable-gpu'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const Nightwatch = require('nightwatch');
|
||||
const browserstack = require('browserstack-local');
|
||||
const {onshutdown} = require('../bin/util');
|
||||
|
||||
async function start() {
|
||||
try {
|
||||
const bs_local = new browserstack.Local();
|
||||
process.mainModule.filename = './node_modules/.bin/nightwatch';
|
||||
|
||||
// Code to start browserstack local before start of test
|
||||
console.log('Connecting local');
|
||||
Nightwatch.bs_local = bs_local;
|
||||
|
||||
bs_local.start({
|
||||
'key': process.env.BROWSERSTACK_KEY,
|
||||
'logFile': './test/e2e/bslocal.log'
|
||||
}, function(error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log('Connected. Now testing...');
|
||||
Nightwatch.cli(function(argv) {
|
||||
Nightwatch.CliRunner(argv)
|
||||
.setup(null, function(){
|
||||
|
||||
// Code to stop browserstack local after end of parallel test
|
||||
bs_local.stop(function(){});
|
||||
})
|
||||
.runTests(function(){
|
||||
|
||||
// Code to stop browserstack local after end of single test
|
||||
bs_local.stop(function(){});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onshutdown([
|
||||
() => bs_local.stop(function(){}),
|
||||
]);
|
||||
} catch (ex) {
|
||||
console.log('There was an error while starting the test runner:\n\n');
|
||||
process.stderr.write(`${ex.stack}\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "${CIRCLE_BRANCH}" == "master" ]]; then
|
||||
|
||||
exitCode=0
|
||||
|
||||
browserstack() {
|
||||
REPORTS_FOLDER="$CIRCLE_TEST_REPORTS/$1" yarn e2e-browserstack -- --env "$1"
|
||||
|
||||
# Determine exit code.
|
||||
result=$?
|
||||
if [ "$result" -gt "0" ]
|
||||
then
|
||||
exitCode=$result
|
||||
fi
|
||||
|
||||
# Sleep a bit to let browserstack-local to close properly.
|
||||
sleep 2
|
||||
}
|
||||
|
||||
# Test using browserstack.
|
||||
browserstack chrome
|
||||
browserstack firefox
|
||||
browserstack ie
|
||||
|
||||
# Safari >= 8 has issues connecting to browserstack-local. Safari < 8 is too old.
|
||||
# browserstack safari
|
||||
|
||||
# Edge 14 & 15 randomly fails when switching from the login popup back to the main window.
|
||||
# browserstack edge
|
||||
|
||||
exit $exitCode
|
||||
else
|
||||
# When browserstack is not available test locally using chrome headless.
|
||||
REPORTS_FOLDER="$CIRCLE_TEST_REPORTS/chrome" yarn e2e -- --env chrome-headless
|
||||
|
||||
# Will exit with status of last command.
|
||||
exit $?
|
||||
fi
|
||||
@@ -0,0 +1,171 @@
|
||||
const app = require('./app');
|
||||
const debug = require('debug')('talk:cli:serve');
|
||||
const errors = require('./errors');
|
||||
const {createServer} = require('http');
|
||||
const scraper = require('./services/scraper');
|
||||
const mailer = require('./services/mailer');
|
||||
const MigrationService = require('./services/migration');
|
||||
const SetupService = require('./services/setup');
|
||||
const kue = require('./services/kue');
|
||||
const mongoose = require('./services/mongoose');
|
||||
const cache = require('./services/cache');
|
||||
const util = require('./bin/util');
|
||||
const {createSubscriptionManager} = require('./graph/subscriptions');
|
||||
const {
|
||||
PORT
|
||||
} = require('./config');
|
||||
|
||||
const port = normalizePort(PORT);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
const server = createServer(app);
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let bind = typeof port === 'string'
|
||||
? `Pipe ${port}`
|
||||
: `Port ${port}`;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(`${bind} requires elevated privileges`);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(`${bind} is already in use`);
|
||||
break;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
let port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
async function onListening() {
|
||||
|
||||
// Start the cache instance.
|
||||
await cache.init();
|
||||
|
||||
let addr = server.address();
|
||||
let bind = typeof addr === 'string'
|
||||
? `pipe ${addr}`
|
||||
: `port ${addr.port}`;
|
||||
debug(`API Server Listening on ${bind}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the app.
|
||||
*/
|
||||
async function serve({jobs = true, websockets = true} = {}) {
|
||||
|
||||
try {
|
||||
|
||||
// Check to see if the application is installed. If the application
|
||||
// has been installed, then it will throw errors.ErrSettingsNotInit, this
|
||||
// just means we don't have to check that the migrations have run.
|
||||
await SetupService.isAvailable();
|
||||
|
||||
debug('setup is currently available, migrations not being checked');
|
||||
|
||||
} catch (e) {
|
||||
|
||||
// Check the error.
|
||||
switch (e) {
|
||||
case errors.ErrInstallLock, errors.ErrSettingsInit:
|
||||
|
||||
debug('setup is not currently available, migrations now being checked');
|
||||
|
||||
// The error was expected, just continue.
|
||||
break;
|
||||
default:
|
||||
|
||||
// The error was not expected, throw the error!
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Now try and check the migration status.
|
||||
try {
|
||||
|
||||
// Verify that the minimum migration version is met.
|
||||
await MigrationService.verify();
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
debug('migrations do not have to be run');
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
server.on('listening', () => {
|
||||
|
||||
});
|
||||
server.listen(port, () => {
|
||||
|
||||
// Mount the websocket server if requested.
|
||||
if (websockets) {
|
||||
debug(`Websocket Server Listening on ${port}`);
|
||||
|
||||
// Mount the subscriptions server on the application server.
|
||||
createSubscriptionManager(server);
|
||||
}
|
||||
});
|
||||
|
||||
// Enable job processing on the thread if enabled.
|
||||
if (jobs) {
|
||||
|
||||
// Start the scraper processor.
|
||||
scraper.process();
|
||||
|
||||
// Start the mail processor.
|
||||
mailer.process();
|
||||
}
|
||||
|
||||
// Define a safe shutdown function to call in the event we need to shutdown
|
||||
// because the node hooks are below which will interrupt the shutdown process.
|
||||
// Shutdown the mongoose connection, the app server, and the scraper.
|
||||
util.onshutdown([
|
||||
() => jobs ? kue.Task.shutdown() : null,
|
||||
() => mongoose.disconnect(),
|
||||
() => server.close()
|
||||
]);
|
||||
}
|
||||
|
||||
module.exports = serve;
|
||||
@@ -0,0 +1,32 @@
|
||||
const serve = require('../../serve');
|
||||
const mongoose = require('../../services/mongoose');
|
||||
const {shutdown} = require('../../bin/util');
|
||||
|
||||
module.exports = {
|
||||
before: async (done) => {
|
||||
await mongoose.connection.dropDatabase();
|
||||
await serve();
|
||||
done();
|
||||
},
|
||||
after: (done) => {
|
||||
shutdown();
|
||||
done();
|
||||
},
|
||||
waitForConditionTimeout: 5000,
|
||||
testData: {
|
||||
admin: {
|
||||
email: 'admin@test.com',
|
||||
username: 'admin',
|
||||
password: 'testtest',
|
||||
},
|
||||
user: {
|
||||
email: 'user@test.com',
|
||||
username: 'user',
|
||||
password: 'testtest',
|
||||
},
|
||||
comment: {
|
||||
body: 'This is a test comment'
|
||||
},
|
||||
organizationName: 'Coral',
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
commands: [{
|
||||
url: function() {
|
||||
return `${this.api.launchUrl}/admin`;
|
||||
},
|
||||
ready() {
|
||||
return this
|
||||
.waitForElementVisible('body');
|
||||
},
|
||||
}],
|
||||
elements: {
|
||||
'loginLayout': '.talk-admin-login',
|
||||
'signInForm': '.talk-admin-login-sign-in',
|
||||
'emailInput': '.talk-admin-login-sign-in #email',
|
||||
'passwordInput': '.talk-admin-login-sign-in #password',
|
||||
'signInButton': '.talk-admin-login-sign-in-button',
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
const iframeId = 'coralStreamEmbed_iframe';
|
||||
|
||||
module.exports = {
|
||||
commands: [{
|
||||
navigateToAsset: function(asset) {
|
||||
this.api.url(`${this.api.launchUrl}/assets/title/${asset}`);
|
||||
return this;
|
||||
},
|
||||
getEmbedSection: function() {
|
||||
this.waitForElementVisible('@iframe');
|
||||
this.api.frame(iframeId);
|
||||
this.expect.section('@embed').to.be.present;
|
||||
return this.section.embed;
|
||||
},
|
||||
}],
|
||||
url: function() {
|
||||
return this.api.launchUrl;
|
||||
},
|
||||
elements: {
|
||||
iframe: `#${iframeId}`,
|
||||
},
|
||||
sections: {
|
||||
embed: {
|
||||
commands: [{
|
||||
getProfileSection: function() {
|
||||
this.click('@profileTabButton');
|
||||
this.expect.section('@profile').to.be.present;
|
||||
return this.section.profile;
|
||||
},
|
||||
getCommentsSection: function() {
|
||||
this.click('@commentsTabButton');
|
||||
this.expect.section('@comments').to.be.present;
|
||||
return this.section.comments;
|
||||
},
|
||||
}],
|
||||
selector: '#talk-embed-stream-container',
|
||||
elements: {
|
||||
logoutButton: '.talk-stream-userbox-logout',
|
||||
commentsTabButton: '.talk-embed-stream-comments-tab > button',
|
||||
profileTabButton: '.talk-embed-stream-profile-tab > button',
|
||||
signInButton: '#coralSignInButton',
|
||||
commentBoxTextarea: '#commentText',
|
||||
commentBoxPostButton: '.talk-plugin-commentbox-button',
|
||||
firstCommentContent: '.talk-stream-comment.talk-stream-comment-level-0 .talk-stream-comment-content',
|
||||
respectButton: '.talk-stream-comment.talk-stream-comment-level-0 .talk-stream-comment-footer .talk-plugin-respect-button'
|
||||
},
|
||||
sections: {
|
||||
profile: {
|
||||
selector: '.talk-embed-stream-profile-tab-pane',
|
||||
elements: {
|
||||
notLoggedIn: '.talk-embed-stream-not-logged-in',
|
||||
myCommentHistory: '.talk-my-profile-comment-history',
|
||||
myCommentHistoryReactions: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions',
|
||||
myCommentHistoryReactionCount: '.talk-my-profile-comment-history .comment-summary .comment-summary-reactions .comment-summary-reaction-count',
|
||||
myCommentHistoryComment: '.talk-my-profile-comment-history .my-comment-body'
|
||||
},
|
||||
},
|
||||
comments: {
|
||||
selector: '.talk-embed-stream-comments-tab-pane',
|
||||
elements: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
module.exports = {
|
||||
commands: [{
|
||||
url: function() {
|
||||
return `${this.api.launchUrl}/admin/install`;
|
||||
},
|
||||
ready() {
|
||||
return this
|
||||
.waitForElementVisible('body');
|
||||
}
|
||||
}],
|
||||
sections: {
|
||||
step1: {
|
||||
selector: '.talk-install-step-1',
|
||||
elements: {
|
||||
getStartedButton: '.talk-install-get-started-button',
|
||||
},
|
||||
},
|
||||
step2: {
|
||||
selector: '.talk-install-step-2',
|
||||
elements: {
|
||||
organizationNameInput: '.talk-install-step-2 #organizationName',
|
||||
saveButton: '.talk-install-step-2-save-button',
|
||||
},
|
||||
},
|
||||
step3: {
|
||||
selector: '.talk-install-step-3',
|
||||
elements: {
|
||||
emailInput: '.talk-install-step-3 #email',
|
||||
usernameInput: '.talk-install-step-3 #username',
|
||||
passwordInput: '.talk-install-step-3 #password',
|
||||
confirmPasswordInput: '.talk-install-step-3 #confirmPassword',
|
||||
saveButton: '.talk-install-step-3-save-button',
|
||||
},
|
||||
},
|
||||
step4: {
|
||||
selector: '.talk-install-step-4',
|
||||
elements: {
|
||||
domainInput: '.talk-install-step-4-permited-domains-input input',
|
||||
saveButton: '.talk-install-step-4-save-button',
|
||||
},
|
||||
},
|
||||
step5: {
|
||||
selector: '.talk-install-step-5',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
elements: {
|
||||
registerButton: '#coralRegister',
|
||||
signInButton: '#coralSignInButton',
|
||||
emailInput: '#email',
|
||||
usernameInput: '#username',
|
||||
passwordInput: '#password',
|
||||
confirmPasswordInput: '#confirmPassword',
|
||||
signUpButton: '#coralSignUpButton',
|
||||
signIn: '.coral-sign-in',
|
||||
loginButton: '#coralLogInButton'
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk",
|
||||
"rules": {
|
||||
"indent": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
module.exports = {
|
||||
'@tags': ['install'],
|
||||
|
||||
'User goes to install': (client) => {
|
||||
const install = client.page.install();
|
||||
|
||||
install
|
||||
.navigate()
|
||||
.expect.section('@step1').to.be.present;
|
||||
|
||||
},
|
||||
'User clicks get started button': (client) => {
|
||||
const step1 = client.page.install().section.step1;
|
||||
|
||||
step1
|
||||
.waitForElementVisible('@getStartedButton')
|
||||
.click('@getStartedButton');
|
||||
},
|
||||
'User should see step 2 - Add Organization Name': (client) => {
|
||||
const install = client.page.install();
|
||||
|
||||
install
|
||||
.expect.section('@step2').to.be.present;
|
||||
},
|
||||
'User fills step 2': (client) => {
|
||||
const step2 = client.page.install().section.step2;
|
||||
const {testData} = client.globals;
|
||||
|
||||
step2
|
||||
.waitForElementVisible('@organizationNameInput')
|
||||
.setValue('@organizationNameInput', testData.organizationName)
|
||||
.waitForElementVisible('@saveButton')
|
||||
.click('@saveButton');
|
||||
},
|
||||
'User should see step 3 - Create your account': (client) => {
|
||||
const install = client.page.install();
|
||||
|
||||
install
|
||||
.expect.section('@step3').to.be.present;
|
||||
},
|
||||
'User fills step 3': (client) => {
|
||||
const step3 = client.page.install().section.step3;
|
||||
const {testData: {admin}} = client.globals;
|
||||
|
||||
step3
|
||||
.setValue('@emailInput', admin.email)
|
||||
.setValue('@usernameInput', admin.username)
|
||||
.setValue('@passwordInput', admin.password)
|
||||
.setValue('@confirmPasswordInput', admin.password)
|
||||
.waitForElementVisible('@saveButton')
|
||||
.click('@saveButton');
|
||||
},
|
||||
'User should see step 4 - Domain Whitelist': (client) => {
|
||||
const install = client.page.install();
|
||||
|
||||
install
|
||||
.expect.section('@step4').to.be.present;
|
||||
},
|
||||
'User fills step 4': (client) => {
|
||||
const step4 = client.page.install().section.step4;
|
||||
|
||||
step4
|
||||
.waitForElementVisible('@domainInput')
|
||||
.setValue('@domainInput', client.launchUrl);
|
||||
|
||||
client.keys(client.Keys.ENTER);
|
||||
|
||||
step4
|
||||
.waitForElementVisible('@saveButton')
|
||||
.click('@saveButton');
|
||||
},
|
||||
'User should see step 5 - Final Step': (client) => {
|
||||
const install = client.page.install();
|
||||
|
||||
install
|
||||
.expect.section('@step5').to.be.present;
|
||||
},
|
||||
after: (client) => {
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
'@tags': ['admin', 'login'],
|
||||
|
||||
'Admin logs in': (client) => {
|
||||
const adminPage = client.page.admin();
|
||||
const {testData: {admin}} = client.globals;
|
||||
|
||||
adminPage
|
||||
.navigate()
|
||||
.waitForElementVisible('@loginLayout')
|
||||
.waitForElementVisible('@signInForm')
|
||||
.setValue('@emailInput', admin.email)
|
||||
.setValue('@passwordInput', admin.password)
|
||||
.waitForElementVisible('@signInButton')
|
||||
.click('@signInButton');
|
||||
|
||||
},
|
||||
after: (client) => {
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
module.exports = {
|
||||
'@tags': ['embedStream', 'login'],
|
||||
'creates a new asset': (client) => {
|
||||
const asset = 'newAssetTest';
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
embedStream
|
||||
.navigateToAsset(asset)
|
||||
.assert.title(asset)
|
||||
.getEmbedSection();
|
||||
},
|
||||
|
||||
'creates an user and user logs in': (client) => {
|
||||
const {testData: {user}} = client.globals;
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@signInButton')
|
||||
.click('@signInButton');
|
||||
|
||||
client.pause(3000);
|
||||
|
||||
// Focusing on the Login PopUp
|
||||
client.windowHandles((result) => {
|
||||
const handle = result.value[1];
|
||||
client.switchWindow(handle);
|
||||
});
|
||||
|
||||
const login = client.page.login();
|
||||
|
||||
login
|
||||
.waitForElementVisible('@registerButton')
|
||||
.click('@registerButton')
|
||||
.setValue('@emailInput', user.email)
|
||||
.setValue('@usernameInput', user.username)
|
||||
.setValue('@passwordInput', user.password)
|
||||
.setValue('@confirmPasswordInput', user.password)
|
||||
.waitForElementVisible('@signUpButton')
|
||||
.click('@signUpButton')
|
||||
.waitForElementVisible('@signIn')
|
||||
.waitForElementVisible('@loginButton')
|
||||
.click('@loginButton');
|
||||
|
||||
// Focusing on the Embed Window
|
||||
client.windowHandles((result) => {
|
||||
const handle = result.value[0];
|
||||
client.switchWindow(handle);
|
||||
});
|
||||
},
|
||||
'user posts a comment': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
const {testData: {comment}} = client.globals;
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@commentBoxTextarea')
|
||||
.setValue('@commentBoxTextarea', comment.body)
|
||||
.waitForElementVisible('@commentBoxPostButton')
|
||||
.click('@commentBoxPostButton')
|
||||
.waitForElementVisible('@firstCommentContent')
|
||||
.getText('@firstCommentContent', (result) => {
|
||||
embed.assert.equal(result.value, comment.body);
|
||||
});
|
||||
},
|
||||
|
||||
'signed in user sees comment history': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
const {testData: {comment}} = client.globals;
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
const profile = embed
|
||||
.getProfileSection();
|
||||
|
||||
profile
|
||||
.waitForElementVisible('@myCommentHistory')
|
||||
.waitForElementVisible('@myCommentHistoryComment')
|
||||
.getText('@myCommentHistoryComment', (result) => {
|
||||
profile.assert.equal(result.value, comment.body);
|
||||
});
|
||||
},
|
||||
'user sees replies and reactions to comments': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
const profile = embed
|
||||
.getProfileSection();
|
||||
|
||||
profile
|
||||
.waitForElementVisible('@myCommentHistory')
|
||||
.waitForElementVisible('@myCommentHistoryReactions')
|
||||
.waitForElementVisible('@myCommentHistoryReactionCount')
|
||||
.getText('@myCommentHistoryReactionCount', (result) => {
|
||||
profile.assert.equal(result.value, '0');
|
||||
});
|
||||
},
|
||||
'user goes to the stream and replies and reacts to comment': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@respectButton')
|
||||
.click('@respectButton');
|
||||
|
||||
const profile = embed
|
||||
.getProfileSection();
|
||||
|
||||
profile
|
||||
.waitForElementVisible('@myCommentHistory')
|
||||
.waitForElementVisible('@myCommentHistoryReactions')
|
||||
.waitForElementVisible('@myCommentHistoryReactionCount')
|
||||
.getText('@myCommentHistoryReactionCount', (result) => {
|
||||
profile.assert.equal(result.value, '1');
|
||||
});
|
||||
},
|
||||
'user logs out': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
embed
|
||||
.waitForElementVisible('@commentsTabButton')
|
||||
.click('@commentsTabButton')
|
||||
.waitForElementVisible('@logoutButton')
|
||||
.click('@logoutButton');
|
||||
},
|
||||
'not logged in user clicks my profile tab': (client) => {
|
||||
const embedStream = client.page.embedStream();
|
||||
|
||||
const embed = embedStream
|
||||
.navigate()
|
||||
.getEmbedSection();
|
||||
|
||||
const profile = embed
|
||||
.getProfileSection();
|
||||
|
||||
profile
|
||||
.assert.visible('@notLoggedIn');
|
||||
},
|
||||
after: (client) => {
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user