mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 04:04:36 +08:00
Merge branch 'decoupling-settings-page' of https://github.com/biosistemika/scinote-web into zd_SCI_1566
This commit is contained in:
commit
c3562e08e9
15 changed files with 440 additions and 15 deletions
|
@ -0,0 +1,81 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { string, number, func } from "prop-types";
|
||||||
|
import { Grid, Row, Col } from "react-bootstrap";
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
margin-bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
class Alert extends Component {
|
||||||
|
static alertClass(type) {
|
||||||
|
const classes = {
|
||||||
|
error: "alert-danger",
|
||||||
|
alert: "alert-warning",
|
||||||
|
notice: "alert-info",
|
||||||
|
success: "alert-success"
|
||||||
|
};
|
||||||
|
return classes[type] || classes.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static glyphiconClass(type) {
|
||||||
|
const classes = {
|
||||||
|
error: "glyphicon-exclamation-sign",
|
||||||
|
alert: "glyphicon-exclamation-sign",
|
||||||
|
notice: "glyphicon-info-sign",
|
||||||
|
success: "glyphicon-ok-sign"
|
||||||
|
};
|
||||||
|
return classes[type] || classes.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.timer = setTimeout(
|
||||||
|
this.props.onClose,
|
||||||
|
this.props.timeout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const alertClassName =
|
||||||
|
`alert
|
||||||
|
${Alert.alertClass(this.props.type)}
|
||||||
|
alert-dismissable
|
||||||
|
alert-floating`;
|
||||||
|
const glyphiconClassName =
|
||||||
|
`glyphicon
|
||||||
|
${Alert.glyphiconClass(this.props.type)}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper className={alertClassName}>
|
||||||
|
<Grid>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<button type="button"
|
||||||
|
className="close"
|
||||||
|
data-dismiss="alert"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={this.props.onClose}>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<span className={glyphiconClassName} />
|
||||||
|
<span> {this.props.message}</span>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Grid>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert.propTypes = {
|
||||||
|
message: string.isRequired,
|
||||||
|
type: string.isRequired,
|
||||||
|
timeout: number.isRequired,
|
||||||
|
onClose: func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Alert;
|
65
app/javascript/src/components/AlertsContainer/index.jsx
Normal file
65
app/javascript/src/components/AlertsContainer/index.jsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import TransitionGroup from 'react-transition-group/TransitionGroup';
|
||||||
|
import CSSTransition from 'react-transition-group/CSSTransition';
|
||||||
|
import { shape, arrayOf, string, number, func } from "prop-types";
|
||||||
|
import { clearAlert } from "../actions/AlertsActions";
|
||||||
|
import Alert from "./components/Alert";
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
class AlertsContainer extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.renderAlert = this.renderAlert.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAlert(alert) {
|
||||||
|
return (
|
||||||
|
<Alert message={alert.message}
|
||||||
|
type={alert.type}
|
||||||
|
timeout={alert.timeout}
|
||||||
|
onClose={() => this.props.clearAlert(alert.id)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<TransitionGroup>
|
||||||
|
{this.props.alerts.map((alert) =>
|
||||||
|
<CSSTransition key={alert.id}
|
||||||
|
timeout={500}
|
||||||
|
classNames="alert-animated">
|
||||||
|
{this.renderAlert(alert)}
|
||||||
|
</CSSTransition>
|
||||||
|
)}
|
||||||
|
</TransitionGroup>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertsContainer.propTypes = {
|
||||||
|
alerts: arrayOf(
|
||||||
|
shape({
|
||||||
|
message: string.isRequired,
|
||||||
|
type: string.isRequired,
|
||||||
|
id: string.isRequired,
|
||||||
|
timeout: number,
|
||||||
|
onClose: func
|
||||||
|
}).isRequired
|
||||||
|
).isRequired,
|
||||||
|
clearAlert: func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = ({ alerts }) => ({ alerts });
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, { clearAlert })(AlertsContainer);
|
|
@ -55,7 +55,7 @@ class InviteUsersModal extends Component {
|
||||||
team_id: this.props.team.id
|
team_id: this.props.team.id
|
||||||
})
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
this.setState({ inviteResults: data, showInviteUsersResults: true});
|
this.setState({ inviteResults: data, showInviteUsersResults: true });
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log("Invite As Error: ", error);
|
console.log("Invite As Error: ", error);
|
||||||
|
@ -89,17 +89,23 @@ class InviteUsersModal extends Component {
|
||||||
<Modal.Title>
|
<Modal.Title>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="invite_users.modal_title"
|
id="invite_users.modal_title"
|
||||||
values={{ team: this.props.team.name }}
|
values={{
|
||||||
|
team: this.props.team.name
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>{modalBody}</Modal.Body>
|
<Modal.Body>{modalBody}</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<StyledButtonToolbar>
|
<StyledButtonToolbar>
|
||||||
<Button onClick={this.handleCloseModal}>
|
|
||||||
<FormattedMessage id="general.cancel" />
|
|
||||||
</Button>
|
|
||||||
{inviteButton}
|
{inviteButton}
|
||||||
|
<Button onClick={this.handleCloseModal}>
|
||||||
|
<FormattedMessage
|
||||||
|
id={`general.${this.state.showInviteUsersResults
|
||||||
|
? "close"
|
||||||
|
: "cancel"}`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
</StyledButtonToolbar>
|
</StyledButtonToolbar>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -110,10 +116,7 @@ class InviteUsersModal extends Component {
|
||||||
InviteUsersModal.propTypes = {
|
InviteUsersModal.propTypes = {
|
||||||
showModal: bool.isRequired,
|
showModal: bool.isRequired,
|
||||||
onCloseModal: func.isRequired,
|
onCloseModal: func.isRequired,
|
||||||
team: shape({
|
team: shape({ id: number.isRequired, name: string.isRequired }).isRequired,
|
||||||
id: number.isRequired,
|
|
||||||
name: string.isRequired
|
|
||||||
}).isRequired,
|
|
||||||
updateUsersCallback: func.isRequired
|
updateUsersCallback: func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import UserAccountDropdown from "./components/UserAccountDropdown";
|
||||||
const StyledNavbar = styled(Navbar)`
|
const StyledNavbar = styled(Navbar)`
|
||||||
background-color: ${WHITE_COLOR};
|
background-color: ${WHITE_COLOR};
|
||||||
border-color: ${BORDER_GRAY_COLOR};
|
border-color: ${BORDER_GRAY_COLOR};
|
||||||
|
margin-bottom: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledBrand = styled.a`
|
const StyledBrand = styled.a`
|
||||||
|
|
29
app/javascript/src/components/actions/AlertsActions.js
Normal file
29
app/javascript/src/components/actions/AlertsActions.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import shortid from "shortid";
|
||||||
|
import {
|
||||||
|
ADD_ALERT,
|
||||||
|
CLEAR_ALERT,
|
||||||
|
CLEAR_ALL_ALERTS
|
||||||
|
} from "../../config/action_types";
|
||||||
|
|
||||||
|
export function addAlert(message,
|
||||||
|
type,
|
||||||
|
id = shortid.generate(),
|
||||||
|
timeout = 5000) {
|
||||||
|
return {
|
||||||
|
payload: {
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
id,
|
||||||
|
timeout
|
||||||
|
},
|
||||||
|
type: ADD_ALERT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAlert(id) {
|
||||||
|
return { payload: id, type: CLEAR_ALERT }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAllAlerts() {
|
||||||
|
return { type: CLEAR_ALL_ALERTS };
|
||||||
|
}
|
31
app/javascript/src/components/reducers/AlertsReducers.js
Normal file
31
app/javascript/src/components/reducers/AlertsReducers.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {
|
||||||
|
ADD_ALERT,
|
||||||
|
CLEAR_ALERT,
|
||||||
|
CLEAR_ALL_ALERTS
|
||||||
|
} from "../../config/action_types";
|
||||||
|
|
||||||
|
export const alerts = (
|
||||||
|
state = [],
|
||||||
|
action
|
||||||
|
) => {
|
||||||
|
switch(action.type) {
|
||||||
|
case ADD_ALERT:
|
||||||
|
return [
|
||||||
|
...state,
|
||||||
|
{
|
||||||
|
message: action.payload.message,
|
||||||
|
type: action.payload.type,
|
||||||
|
id: action.payload.id,
|
||||||
|
timeout: action.payload.timeout
|
||||||
|
}
|
||||||
|
];
|
||||||
|
case CLEAR_ALERT:
|
||||||
|
return state.filter((alert) => (
|
||||||
|
alert.id !== action.payload
|
||||||
|
));
|
||||||
|
case CLEAR_ALL_ALERTS:
|
||||||
|
return [];
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -35,3 +35,8 @@ export const UPDATE_TEAM_DESCRIPTION_MODAL = "UPDATE_TEAM_DESCRIPTION_MODAL";
|
||||||
// spinner
|
// spinner
|
||||||
export const SPINNER_ON = "SPINNER_ON";
|
export const SPINNER_ON = "SPINNER_ON";
|
||||||
export const SPINNER_OFF = "SPINNER_OFF";
|
export const SPINNER_OFF = "SPINNER_OFF";
|
||||||
|
|
||||||
|
// alerts
|
||||||
|
export const ADD_ALERT = "ADD_ALERT";
|
||||||
|
export const CLEAR_ALERT = "CLEAR_ALERT";
|
||||||
|
export const CLEAR_ALL_ALERTS = "CLEAR_ALL_ALERTS";
|
|
@ -6,11 +6,13 @@ import {
|
||||||
} from "../components/reducers/TeamReducers";
|
} from "../components/reducers/TeamReducers";
|
||||||
import { globalActivities } from "../components/reducers/ActivitiesReducers";
|
import { globalActivities } from "../components/reducers/ActivitiesReducers";
|
||||||
import { currentUser } from "../components/reducers/UsersReducer";
|
import { currentUser } from "../components/reducers/UsersReducer";
|
||||||
|
import { alerts } from "../components/reducers/AlertsReducers";
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
current_team: setCurrentTeam,
|
current_team: setCurrentTeam,
|
||||||
all_teams: getListOfTeams,
|
all_teams: getListOfTeams,
|
||||||
global_activities: globalActivities,
|
global_activities: globalActivities,
|
||||||
current_user: currentUser,
|
current_user: currentUser,
|
||||||
showLeaveTeamModal
|
showLeaveTeamModal,
|
||||||
|
alerts
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,11 +3,13 @@ import { BrowserRouter } from "react-router-dom";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { IntlProvider, addLocaleData } from "react-intl";
|
import { IntlProvider, addLocaleData } from "react-intl";
|
||||||
import enLocaleData from "react-intl/locale-data/en";
|
import enLocaleData from "react-intl/locale-data/en";
|
||||||
|
import styled from "styled-components";
|
||||||
import { flattenMessages } from "./config/locales/utils";
|
import { flattenMessages } from "./config/locales/utils";
|
||||||
import messages from "./config/locales/messages";
|
import messages from "./config/locales/messages";
|
||||||
import store from "./config/store";
|
import store from "./config/store";
|
||||||
|
|
||||||
import Spinner from "./components/Spinner";
|
import Spinner from "./components/Spinner";
|
||||||
|
import AlertsContainer from "./components/AlertsContainer";
|
||||||
import ModalsContainer from "./components/ModalsContainer";
|
import ModalsContainer from "./components/ModalsContainer";
|
||||||
import SettingsPage from "./scenes/SettingsPage";
|
import SettingsPage from "./scenes/SettingsPage";
|
||||||
import Navigation from "./components/Navigation";
|
import Navigation from "./components/Navigation";
|
||||||
|
@ -15,14 +17,22 @@ import Navigation from "./components/Navigation";
|
||||||
addLocaleData([...enLocaleData]);
|
addLocaleData([...enLocaleData]);
|
||||||
const locale = "en-US";
|
const locale = "en-US";
|
||||||
|
|
||||||
export default () =>
|
const ContentWrapper = styled.div`
|
||||||
|
margin-top: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ScinoteApp = () =>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<IntlProvider locale={locale} messages={flattenMessages(messages[locale])}>
|
<IntlProvider locale={locale}
|
||||||
|
messages={flattenMessages(messages[locale])}>
|
||||||
<div>
|
<div>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div>
|
<div>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<SettingsPage />
|
<AlertsContainer />
|
||||||
|
<ContentWrapper>
|
||||||
|
<SettingsPage />
|
||||||
|
</ContentWrapper>
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|
||||||
|
@ -31,3 +41,5 @@ export default () =>
|
||||||
</div>
|
</div>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</Provider>;
|
</Provider>;
|
||||||
|
|
||||||
|
export default ScinoteApp;
|
||||||
|
|
30
app/javascript/src/styles/animations.scss
Normal file
30
app/javascript/src/styles/animations.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
.alert-animated-enter {
|
||||||
|
opacity: .01;
|
||||||
|
|
||||||
|
&.alert-animated-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 150ms ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-animated-exit {
|
||||||
|
opacity: 1;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
&.alert-animated-exit-active {
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: .01;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
height: 0;
|
||||||
|
transition: opacity 300ms ease-in,
|
||||||
|
padding-top 500ms ease-in,
|
||||||
|
padding-bottom 500ms ease-in,
|
||||||
|
margin-bottom 500ms ease-in,
|
||||||
|
height 500ms ease-in;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@import 'constants';
|
@import 'constants';
|
||||||
|
@import 'animations';
|
||||||
@import 'react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css';
|
@import 'react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css';
|
||||||
@import '~react-bootstrap-table/dist/react-bootstrap-table.min';
|
@import '~react-bootstrap-table/dist/react-bootstrap-table.min';
|
||||||
@import 'react-tagsinput/react-tagsinput.css';
|
@import 'react-tagsinput/react-tagsinput.css';
|
||||||
|
@ -14,6 +15,10 @@ body {
|
||||||
background-color: $color-theme-primary;
|
background-color: $color-theme-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: $color-theme-secondary;
|
background-color: $color-theme-secondary;
|
||||||
border-color: $primary-hover-color;
|
border-color: $primary-hover-color;
|
||||||
|
@ -30,3 +35,23 @@ body {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tags input
|
||||||
|
.react-tagsinput--focused {
|
||||||
|
border-color: $color-theme-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tagsinput-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tagsinput-tag {
|
||||||
|
background-color: $color-theme-primary;
|
||||||
|
border: 0;
|
||||||
|
color: $color-white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-tagsinput-remove {
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
|
|
@ -68,6 +68,11 @@
|
||||||
"react-bootstrap-table": "^4.0.0",
|
"react-bootstrap-table": "^4.0.0",
|
||||||
"react-bootstrap-timezone-picker": "^1.0.11",
|
"react-bootstrap-timezone-picker": "^1.0.11",
|
||||||
"react-data-grid": "^2.0.2",
|
"react-data-grid": "^2.0.2",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"react-tagsinput": "^3.17.0",
|
||||||
|
"react-transition-group": "^2.2.0",
|
||||||
|
>>>>>>> fcea55c2a102470bd4520ea0ac1ef771710c17d8
|
||||||
"react-dom": "^15.6.1",
|
"react-dom": "^15.6.1",
|
||||||
"react-intl": "^2.3.0",
|
"react-intl": "^2.3.0",
|
||||||
"react-intl-redux": "^0.6.0",
|
"react-intl-redux": "^0.6.0",
|
||||||
|
@ -82,6 +87,7 @@
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"resolve-url-loader": "^2.1.0",
|
"resolve-url-loader": "^2.1.0",
|
||||||
"sass-loader": "^6.0.6",
|
"sass-loader": "^6.0.6",
|
||||||
|
"shortid": "^2.2.8",
|
||||||
"style-loader": "^0.18.2",
|
"style-loader": "^0.18.2",
|
||||||
"styled-components": "^2.1.1",
|
"styled-components": "^2.1.1",
|
||||||
"webpack": "^3.2.0",
|
"webpack": "^3.2.0",
|
||||||
|
|
|
@ -6,8 +6,10 @@ describe ClientApi::Teams::TeamsController, type: :controller do
|
||||||
before do
|
before do
|
||||||
@user_one = User.first
|
@user_one = User.first
|
||||||
@user_two = FactoryGirl.create :user, email: 'sec_user@asdf.com'
|
@user_two = FactoryGirl.create :user, email: 'sec_user@asdf.com'
|
||||||
@team_one = FactoryGirl.create :team
|
@team_one = FactoryGirl.create :team, created_by: @user_one
|
||||||
@team_two = FactoryGirl.create :team, name: 'Team two'
|
@team_two = FactoryGirl.create :team,
|
||||||
|
name: 'Team two',
|
||||||
|
created_by: @user_two
|
||||||
FactoryGirl.create :user_team, team: @team_one, user: @user_one, role: 2
|
FactoryGirl.create :user_team, team: @team_one, user: @user_one, role: 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -19,6 +21,45 @@ describe ClientApi::Teams::TeamsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST #create' do
|
||||||
|
before do
|
||||||
|
@team_one.update_attribute(:name, 'My Team')
|
||||||
|
@team_one.update_attribute(:description, 'Lorem ipsum ipsum')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return HTTP success response' do
|
||||||
|
post :create, params: { team: @team_one }, as: :json
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return HTTP unprocessable_entity response if name too short' do
|
||||||
|
@team_one.update_attribute(
|
||||||
|
:name,
|
||||||
|
('a' * (Constants::NAME_MIN_LENGTH - 1)).to_s
|
||||||
|
)
|
||||||
|
post :create, params: { team: @team_one }, as: :json
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return HTTP unprocessable_entity response if name too long' do
|
||||||
|
@team_one.update_attribute(
|
||||||
|
:name,
|
||||||
|
('a' * (Constants::NAME_MAX_LENGTH + 1)).to_s
|
||||||
|
)
|
||||||
|
post :create, params: { team: @team_one }, as: :json
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return HTTP unprocessable_entity response if description too long' do
|
||||||
|
@team_one.update_attribute(
|
||||||
|
:description,
|
||||||
|
('a' * (Constants::TEXT_MAX_LENGTH + 1)).to_s
|
||||||
|
)
|
||||||
|
post :create, params: { team: @team_one }, as: :json
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST #change_team' do
|
describe 'POST #change_team' do
|
||||||
it 'should return HTTP success response' do
|
it 'should return HTTP success response' do
|
||||||
FactoryGirl.create :user_team, team: @team_two, user: @user_one, role: 2
|
FactoryGirl.create :user_team, team: @team_two, user: @user_one, role: 2
|
||||||
|
|
75
spec/services/client_api/teams/create_service_spec.rb
Normal file
75
spec/services/client_api/teams/create_service_spec.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
include ClientApi::Teams
|
||||||
|
|
||||||
|
describe ClientApi::Teams::CreateService do
|
||||||
|
let(:user) { create :user, email: 'user@asdf.com' }
|
||||||
|
let(:team) do
|
||||||
|
build :team, name: 'My Team', description: 'My Description'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should raise a StandardError if current_user is not assigned' do
|
||||||
|
expect { CreateService.new }.to raise_error(StandardError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should create a new team' do
|
||||||
|
service = CreateService.new(
|
||||||
|
current_user: user,
|
||||||
|
params: { name: team.name, description: team.description }
|
||||||
|
)
|
||||||
|
result = service.execute
|
||||||
|
expect(result[:status]).to eq :success
|
||||||
|
|
||||||
|
team_n = Team.order(created_at: :desc).first
|
||||||
|
expect(team_n.name).to eq team.name
|
||||||
|
expect(team_n.description).to eq team.description
|
||||||
|
expect(team_n.created_by).to eq user
|
||||||
|
expect(team_n.users.count).to eq 1
|
||||||
|
expect(team_n.users.take).to eq user
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return error response if params = {}' do
|
||||||
|
service = CreateService.new(current_user: user, params: {})
|
||||||
|
result = service.execute
|
||||||
|
expect(result[:status]).to eq :error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return error response if params are missing :name attribute' do
|
||||||
|
service = CreateService.new(
|
||||||
|
current_user: user,
|
||||||
|
params: { description: team.description }
|
||||||
|
)
|
||||||
|
result = service.execute
|
||||||
|
expect(result[:status]).to eq :error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return error response if name too short' do
|
||||||
|
team.name = ('a' * (Constants::NAME_MIN_LENGTH - 1)).to_s
|
||||||
|
service = CreateService.new(
|
||||||
|
current_user: user,
|
||||||
|
params: { name: team.name, description: team.description }
|
||||||
|
)
|
||||||
|
result = service.execute
|
||||||
|
expect(result[:status]).to eq :error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return error response if name too long' do
|
||||||
|
team.name = ('a' * (Constants::NAME_MAX_LENGTH + 1)).to_s
|
||||||
|
service = CreateService.new(
|
||||||
|
current_user: user,
|
||||||
|
params: { name: team.name, description: team.description }
|
||||||
|
)
|
||||||
|
result = service.execute
|
||||||
|
expect(result[:status]).to eq :error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return error response if description too long' do
|
||||||
|
team.description = ('a' * (Constants::TEXT_MAX_LENGTH + 1)).to_s
|
||||||
|
service = CreateService.new(
|
||||||
|
current_user: user,
|
||||||
|
params: { name: team.name, description: team.description }
|
||||||
|
)
|
||||||
|
result = service.execute
|
||||||
|
expect(result[:status]).to eq :error
|
||||||
|
end
|
||||||
|
end
|
19
yarn.lock
19
yarn.lock
|
@ -1156,6 +1156,10 @@ center-align@^0.1.1:
|
||||||
align-text "^0.1.3"
|
align-text "^0.1.3"
|
||||||
lazy-cache "^1.0.3"
|
lazy-cache "^1.0.3"
|
||||||
|
|
||||||
|
chain-function@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
|
||||||
|
|
||||||
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||||
|
@ -4825,6 +4829,17 @@ react-timezone@^0.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.1"
|
classnames "^2.2.1"
|
||||||
|
|
||||||
|
react-transition-group@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.0.tgz#793bf8cb15bfe91b3101b24bce1c1d2891659575"
|
||||||
|
dependencies:
|
||||||
|
chain-function "^1.0.0"
|
||||||
|
classnames "^2.2.5"
|
||||||
|
dom-helpers "^3.2.0"
|
||||||
|
loose-envify "^1.3.1"
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
warning "^3.0.0"
|
||||||
|
|
||||||
react@^15.6.1:
|
react@^15.6.1:
|
||||||
version "15.6.1"
|
version "15.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
|
resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
|
||||||
|
@ -5298,6 +5313,10 @@ shelljs@^0.7.5:
|
||||||
interpret "^1.0.0"
|
interpret "^1.0.0"
|
||||||
rechoir "^0.6.2"
|
rechoir "^0.6.2"
|
||||||
|
|
||||||
|
shortid@^2.2.8:
|
||||||
|
version "2.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
|
||||||
|
|
||||||
signal-exit@^3.0.0:
|
signal-exit@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||||
|
|
Loading…
Add table
Reference in a new issue