Skip to content

Instantly share code, notes, and snippets.

@maximjs
Last active January 4, 2022 23:23
Show Gist options
  • Save maximjs/0d0d8192bf1f90abc1bc50ad6fad5966 to your computer and use it in GitHub Desktop.
Save maximjs/0d0d8192bf1f90abc1bc50ad6fad5966 to your computer and use it in GitHub Desktop.
2
let React = require('react');
const App = props => <div id="app"><Notification {...props.notification}/></div>
let map = { success: 'success', message: 'info', caution: 'warning', error: 'danger' }
class Notification extends React.Component {
render() {
let props = this.props;
let type = map[props.type] || 'info';
let className = ['alert', `alert-${type}`].join(' ');
if(props.message) {
return (
<div className={className}>
<p>{props.message}</p>
{props.children}
</div>
)
}
return null;
}
}
class Confirmation extends React.Component {
constructor(props) {
super(props);
this.state = { open: true }
this.accept = this.accept.bind(this);
this.decline = this.decline.bind(this);
}
accept() {
let { accept } = this.props;
accept();
this.setState({ open: false })
}
decline() {
let { decline } = this.props;
decline();
this.setState({ open: false })
}
render() {
if(this.state.open) {
return (
<Notification {...this.props}>
<div className="btn btn-primary" onClick={this.accept}>Sure</div>
<div className="btn btn-danger" onClick={this.decline}>Nah</div>
</Notification>
)
}
return null;
}
}
Component Composition
Let's build on our Notification component by creating a Confirmation component. This Confirmation component should be a Notification with two additional buttons to get the user's response accept and decline.
The props passed to Confirmation will look like this:
{
message: 'Should we bake a pie?',
type: 'message',
accept: function() {
// parent component can do something with accept
},
decline: function() {
// parent component can do something with decline
}
}
The resulting Component markup should look like this:
<div class="alert alert-info">
<p>Should we bake a pie?</p>
<div class="btn btn-primary">Sure</div>
<div class="btn btn-danger">No Thanks</div>
</div>
Which, rendered, should look like this:
Should we bake a pie?
Not the prettiest component in the world, but it'll do for now.
Hiding the Confirmation
For now the Confirmation should mantain it's own state. When accept or decline are clicked, the confirmation should no longer render. Instead it should render null.
Container Component
QuestionList
Ok now we'll tie everything altogether by creating a few more components. We'll want to create a QuestionList component which expects a list of questions in its props. Will look something like this:
{
questions: [{
question: 'What is the average the airspeed velocity of a (European) unladen swallow?',
answer: '11 meters per second'
}, { /* more questions... */ }]
}
QuestionContainer
Each of these questions should be rendered in a QuestionContainer component. This component expects a question and an answer in its props, something like this:
{
question: 'What are the first 10 digits of PI?',
answer: '3.141592653'
}
The QuestionContainer should contain at most four children:
The question display, which is an element with the class 'question' that displays the particular question
The 'Show Answer' button which should render the confirmation when pressed. It will have the class btn btn-primary show-answer
The Confirmation component should only be rendered after 'Show Answer' is pressed and it should ask the user if they want to reveal the answer.
The answer display, which should simply display the answer if the user confirms the Confirmation component
You may divide these down into subcomponents as necessary. Here is the expected HTML output for the QuestionContainer element throughout the interaction states:
Before Show Answer is Clicked
<div class="container">
<p class="question">What are the first 10 digits of PI?</p>
<div class="btn btn-primary show-answer">Show Answer</div>
</div>
After Show Answer is Clicked
<div class="container">
<div class="alert alert-info">
<p>Reveal the answer?</p>
<div class="btn-primary">Yes Please</div>
<div class="btn-danger">Not Yet</div>
</div>
<p class="question">What are the first 10 digits of PI?</p>
<div class="btn btn-primary show-answer">Show Answer</div>
</div>
If the Confirmation is Accepted
<div class="container">
<p class="question">What are the first 10 digits of PI?</p>
<div class="btn btn-primary show-answer" disabled>Show Answer</div>
<p class="answer">3.141592653</p>
</div>
Please note the disabled attribute on the btn btn-primary
If the Confirmation is Declined (back to initial state)
<div class="container">
<p class="question">What are the first 10 digits of PI?</p>
<div class="btn btn-primary show-answer">Show Answer</div>
</div>
let React = require('react');
function App(props) {
return (
<div id="app">
<Notification {...props.notification}/>
</div>
)
}
let map = {
success: 'success',
message: 'info',
caution: 'warning',
error: 'danger'
}
class Notification extends React.Component {
render() {
// console.log(this.props);
let props = this.props;
let type = map[props.type] || 'info';
let className = ['alert', `alert-${type}`].join(' ');
if(props.message) {
return (
<div className={className}>{props.message}</div>
)
}
return null;
}
}
// TODO: Create a Confirmation Component
class Confirmation extends React.Component {
// state = { clicked: false };
constructor(props) {
super(props);
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick(type) {
return () => {
const { accept, decline} = this.props;
this.setState({ clicked: true });
if (type === 'accept') {
accept();
} else {
decline();
}
}
};
render() {
console.log(this.props);
const { type, message } = this.props;
const messageComp = (
<React.Fragment>
<p>{message}</p>
<div className="btn btn-primary" onClick={this.handleClick('accept')}>Sure</div>
<div className="btn btn-danger" onClick={this.handleClick('decline')}>No Thanks</div>
</React.Fragment>
);
if(!this.state.clicked) {
return (
<Notification message={messageComp} type={type} />
)
}
return null;
}
}
Object Oriented Tests
For this challenge, you are going to build a mock comments section.
Design
We're going to focus on two aspects:
Users
Users come in 3 flavors, normal users, moderators, and admins. Normal users can only create new comments, and edit the their own comments. Moderators have the added ability to delete comments (to remove trolls), while admins have the ability to edit or delete any comment.
Users can log in and out, and we track when they last logged in
Comments
Comments are simply a message, a timestamp, and the author.
Comments can also be a reply, so we'll store what the parent comment was.
Your Challenge
This is challenge is not about building a fully functional API, but more about focusing on the design from an object-oriented point-of-view.
We've provided the basic API (which is incomplete), which we would like you to complete being aware of the following Object-Oriented Programming concepts:
Encapsulation of Properties
The method-based API is provided. These must be completed as-is.
Additional methods are allowed, though remember to keep read-only properties read-only.
Instantiation
Classes should be instantiated with properties (as provided), to create instances with values already assigned.
User/Moderator/Admin defaults:
Should be marked as not logged in
Should return nullthe default DateTime valueNoneNil for the last logged in at property
Comment defaults:
Should set the current timestamp for the created at property upon instantiation
Replied To is optional, and should be nullNoneNil if not provided.
Inheritance & Access Control
Note: for the sake of simplicity, you can simply treat object equality as "equal", though more complete solutions will also pass.
User
Users can be logged in and out.
When logging in, set the lastLoggedInAtlast_logged_in_at$lastLoggedInAt timestamp. Do not modify this timestamp when logging out
Users can only edit their own comments
Users cannot delete any comments
Moderator is a User
Moderators can only edit their own comments
Moderators can delete any comments
Admin is both a User and a Moderator
Admins can edit any comments
Admins can delete any comments
Composition
Comments contain a reference to the User who created it (author)
Comments optionally contain a reference to another comment (repliedToreplied_to$repliedTo)
When converting to a string (toStringto_string$toString), the following format is used:
No replied to:
"$message by ${author->name}"
With replied to:
"$message by ${author->name} (replied to ${repliedTo->author->name})"
Beyond these basics, you are free to add to the API, but only these concepts will be scored automatically.
class QuestionList extends React.Component {
render() {
const { questions } = this.props;
const renderQuestions = questions.map(el => <QuestionContainer key={el.id} question={el.question} answer ={el.answer} />);
if(questions.length > 0) {
return (
<div>
{renderQuestions}
</div>
)
}
return null;
}
}
const QuestionList = props => {
const { questions } = props;
console.log(questions);
const renderQuestions = questions.map(el => <QuestionContainer key={el.id} question={el.question} answer ={el.answer} />);
if(questions.length > 0) {
return (
<div>
{renderQuestions}
</div>
);
}
return null;
};
let React = require('react');
function App(props) {
return (
<div id="app">
<Notification {...props.notification}/>
</div>
)
}
let map = {
success: 'success',
message: 'info',
caution: 'warning',
error: 'danger'
}
class Notification extends React.Component {
render() {
// console.log(this.props);
let props = this.props;
let type = map[props.type] || 'info';
let className = ['alert', `alert-${type}`].join(' ');
if(props.message) {
return (
<div className={className}>{props.message}</div>
)
}
return null;
}
}
// TODO: Create a Confirmation Component
class Confirmation extends React.Component {
// state = { clicked: false };
constructor(props) {
super(props);
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick(type) {
return () => {
const { accept, decline} = this.props;
this.setState({ clicked: true });
if (type === 'accept') {
accept();
} else {
decline();
}
}
};
render() {
console.log(this.props);
// const { message } = this.props;
if(!this.state.clicked) {
return (
<Notification {...this.props} >
{ this.props.accept ? <div className="btn btn-primary" onClick={this.handleClick('accept')}>Sure</div> : null }
{ this.props.decline ? <div className="btn btn-danger" onClick={this.handleClick('decline')}>No Thanks</div> : null }
</Notification>
)
}
return null;
}
}
---------------------------------------------------------------------------
const React = require('react'),
ReactDOM = require('react-dom'),
ReactDomServer = require('react-dom/server'),
assert = require('chai').assert,
esc = require('escape-html'),
ReactTestUtils = require('react-addons-test-utils');
const toMarkup = ReactDomServer.renderToStaticMarkup;
const toString = ReactDomServer.renderToString;
const create = React.createElement;
const renderer = ReactTestUtils.createRenderer();
const noop = () => {};
const { findRenderedDOMComponentWithClass, isDOMComponent, isCompositeComponent, renderIntoDocument, Simulate } = ReactTestUtils;
describe('Confirmation', function() {
it('should render to markup', function() {
withBootstrapStyles();
let markup = toMarkup(create(Confirmation, { message: 'Is the pie a lie?' }));
console.log(markup);
assert(markup, 'No markup was returned from Confirmation');
});
it('should be a composite component', function() {
const result = renderIntoDocument(create(Confirmation, { message: '3.14' }));
assert(isCompositeComponent(result))
})
it('should be of type notification', function() {
renderer.render(create(Confirmation, { message: 'Still want the pie?' }));
const result = renderer.getRenderOutput();
assert.equal(result.type, Notification)
});
it('should implement the accept button', function() {
var called = false;
function accept() { called = true }
let result = renderIntoDocument(create(Confirmation, { message: 'Accept the pie.', accept }));
let primary = findRenderedDOMComponentWithClass(result, 'btn btn-primary');
Simulate.click(primary);
assert(called, 'Clicking accept did not work (remember to call the function passed in via props)')
});
it('should implement the decline button', function() {
var called = false;
function decline() { called = true }
let result = renderIntoDocument(create(Confirmation, { message: 'DECLINE the PIE?', decline }));
let danger = findRenderedDOMComponentWithClass(result, 'btn btn-danger');
Simulate.click(danger);
assert(called, 'Clicking decline did not work (remember to call the function passed in via props)')
});
it('should not render the confirmation after accepting', function() {
let confirmation = create(Confirmation, { message: 'Acceptapie.', accept: noop, decline: noop});
let result = renderIntoDocument(confirmation);
let primary = findRenderedDOMComponentWithClass(result, 'btn btn-primary');
Simulate.click(primary);
assert.isNull(ReactDOM.findDOMNode(result), 'Expected the confirmation to render null after accepting')
})
it('should not render the confirmation after declining', function() {
let confirmation = create(Confirmation, { message: 'Just take a bite.', accept: noop, decline: noop});
let result = renderIntoDocument(confirmation);
let danger = findRenderedDOMComponentWithClass(result, 'btn btn-danger');
Simulate.click(danger);
assert.isNull(ReactDOM.findDOMNode(result), 'Expected the confirmation to render null after declining')
})
})
class User {
constructor(name) {
this.name = name;
this.lastLoggedInAt = null;
this.loggedIn = false;
}
isLoggedIn() {
return this.loggedIn;
}
getLastLoggedInAt() {
return this.lastLoggedInAt;
}
logIn() {
this.loggedIn = true;
this.lastLoggedInAt = new Date();
}
logOut() {
this.loggedIn = false;
}
getName() {
return this.name;
}
setName(name) {
this.name= name;
}
canEdit(comment) {
if (comment.getAuthor().getName() === this.name) {
return true;
}
return false;
}
canDelete(comment) {
return false;
}
}
class Moderator extends User {
constructor(name) {
super(name);
}
canDelete(comment) {
return true;
}
}
class Admin extends Moderator {
constructor(name) {
super(name);
}
canEdit(comment) {
return true;
}
}
class Comment {
constructor(author, message, repliedTo) {
this.createdAt = new Date();
this.author = author;
this.message = message;
this.repliedTo = repliedTo || null;
}
getMessage() {
return this.message;
}
setMessage(message) {
this.message = message;
}
getCreatedAt() {
return this.createdAt;
}
getAuthor() {
return this.author;
}
getRepliedTo() {
return this.repliedTo;
}
getString(comment) {
const authorName = comment.getAuthor().getName();
if (!comment.getRepliedTo()) return authorName;
return `${comment.getMessage()} by ${authorName} (replied to ${this.getString(comment.getRepliedTo())})`;
}
toString() {
const authorName = this.getAuthor().getName();
if (!this.getRepliedTo()) {
return `${this.message} by ${authorName}`;
}
return this.getString(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment