Last active
January 4, 2022 23:23
-
-
Save maximjs/0d0d8192bf1f90abc1bc50ad6fad5966 to your computer and use it in GitHub Desktop.
2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
}) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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