module.exports = Question
diff --git a/src/components/resize-listener-mixin.cjsx b/src/components/resize-listener-mixin.cjsx
index 6527638..5ba2221 100644
--- a/src/components/resize-listener-mixin.cjsx
+++ b/src/components/resize-listener-mixin.cjsx
@@ -1,4 +1,5 @@
React = require 'react'
+ReactDom = require 'react-dom'
_ = require 'underscore'
module.exports =
@@ -41,7 +42,7 @@ module.exports =
_getComponentSize: ->
return {height: 0, width: 0} unless @isMounted()
- componentNode = @getDOMNode()
+ componentNode = ReactDom.findDOMNode(@)
width: componentNode.offsetWidth
height: componentNode.offsetHeight
diff --git a/src/components/smart-overflow.cjsx b/src/components/smart-overflow.cjsx
index 0b232fa..4942e18 100644
--- a/src/components/smart-overflow.cjsx
+++ b/src/components/smart-overflow.cjsx
@@ -1,4 +1,5 @@
React = require 'react'
+ReactDom = require 'react-dom'
_ = require 'underscore'
classnames = require 'classnames'
@@ -21,7 +22,7 @@ SmartOverflow = React.createClass
mixins: [ResizeListenerMixin]
getOffset: ->
- componentNode = @getDOMNode()
+ componentNode = ReactDom.findDOMNode(@)
topOffset = componentNode.getBoundingClientRect().top
getTriggerHeight: ->
diff --git a/src/helpers/html-videos.coffee b/src/helpers/html-videos.coffee
new file mode 100644
index 0000000..6b8461b
--- /dev/null
+++ b/src/helpers/html-videos.coffee
@@ -0,0 +1,26 @@
+_ = require 'underscore'
+
+getRatioClass = (frame) ->
+ if (not frame.width or not frame.height)
+ return "embed-responsive-16by9"
+
+ ratio = frame.width / frame.height
+ if (Math.abs(ratio - 16 / 9) > Math.abs(ratio - 4 / 3))
+ return "embed-responsive-4by3"
+ else
+ return "embed-responsive-16by9"
+
+
+wrapFrames = (dom) ->
+ _.each(dom.getElementsByTagName('iframe'), (frame) ->
+ if (frame.parentNode?.classList.contains('embed-responsive')) then return
+
+ wrapper = document.createElement("div")
+ wrapper.className = "frame-wrapper embed-responsive #{getRatioClass(frame)}"
+ dom.replaceChild(wrapper, frame)
+ wrapper.appendChild(frame)
+ )
+
+ dom
+
+module.exports = { wrapFrames, getRatioClass }
diff --git a/stubs/exercise-preview/data.json b/stubs/exercise-preview/data.json
new file mode 100644
index 0000000..fc26d28
--- /dev/null
+++ b/stubs/exercise-preview/data.json
@@ -0,0 +1,197 @@
+{
+ "id": "3",
+ "url": "https://exercises-dev.openstax.org/exercises/3@43",
+ "content": {
+ "tags": [
+ "book:stax-apbio",
+ "apbio-ch01",
+ "apbio-ch01-s01",
+ "apbio-ch01-s01-lo02",
+ "apbio-ch01-ex003",
+ "ost-chapter-review",
+ "review",
+ "dok1",
+ "time-short",
+ "blooms-2",
+ "blooms:2",
+ "time:short",
+ "dok:1",
+ "exid:stax-apbio:3",
+ "cnxmod:4d5fc58c-cdea-4950-a91d-a5f141a38744",
+ "type:conceptual-or-recall",
+ "type:practice",
+ "filter-type:chapter-review"
+ ],
+ "uid": "3@43",
+ "number": 3,
+ "version": 43,
+ "published_at": "2015-12-16T20:46:58.862Z",
+ "editors": [],
+ "authors": [{
+ "user_id": 1,
+ "name": "OpenStax"
+ }],
+ "copyright_holders": [{
+ "user_id": 2,
+ "name": "Rice University"
+ }],
+ "derived_from": [],
+ "stimulus_html": "",
+ "questions": [{
+ "id": 5882,
+ "is_answer_order_important": true,
+ "stimulus_html": "",
+ "stem_html": "What is a suggested and testable explanation for an event called?",
+ "answers": [{
+ "id": 23238,
+ "content_html": "discovery",
+ "correctness": "0.0",
+ "feedback_html": "A hypothesis is a suggested and testable explanation for an event."
+ }, {
+ "id": 23239,
+ "content_html": "hypothesis",
+ "correctness": "1.0",
+ "feedback_html": "A hypothesis is a proposed explanation for a phenomenon that can be tested."
+ }, {
+ "id": 23237,
+ "content_html": "scientific method",
+ "correctness": "0.0",
+ "feedback_html": "A hypothesis refers to the suggested and testable explanation of an event."
+ }, {
+ "id": 23236,
+ "content_html": "theory",
+ "correctness": "0.0",
+ "feedback_html": "A theory is a broad explanation for a long number of observations and tested hypotheses while a hypothesis refers to the suggested and testable explanation for an event."
+ }],
+ "collaborator_solutions": [{
+ "attachments": [],
+ "solution_type": "detailed",
+ "content_html": "A hypothesis is a proposed explanation for an occurance or event based on previous observations that can be tested."
+ }],
+ "solutions": [{
+ "uid": "1@1",
+ "number": 1,
+ "version": 1,
+ "published_at": "2015-09-16T20:13:32.533Z",
+ "editors": [],
+ "authors": [{
+ "user_id": 1,
+ "name": "OpenStax"
+ }],
+ "copyright_holders": [{
+ "user_id": 2,
+ "name": "Rice University"
+ }],
+ "derived_from": [],
+ "attachments": [],
+ "solution_type": "detailed",
+ "content_html": "\n
C
\n
F
\n
A
\n
B
\n
D
\n
E
\n\n\n
The original hypothesis is incorrect, as the coffeemaker works when plugged into the outlet. Alternative hypotheses include that the toaster might be broken or that the toaster wasn’t turned on.
In the example below, the scientific method is used to solve an everyday problem. Order the scientific method steps (numbered items) with the process of solving the everyday problem (lettered items). Based on the results of the experiment, is the hypothesis correct? If it is incorrect, propose some alternative hypotheses.
\n\n
\n \n
\n
\n
Scientific Method
\n
\n
Everyday process
\n
\n \n \n
\n
1
\n
Observation
\n
A
\n
There is something wrong with the electrical outlet.
\n
\n
\n
2
\n
Question
\n
B
\n
If something is wrong with the outlet, my coffeemaker also won’t work when plugged into it.
\n
\n
\n
3
\n
Hypothesis (answer)
\n
C
\n
My toaster doesn’t toast my bread.
\n
\n
\n
4
\n
Prediction
\n
D
\n
I plug my coffee maker into the outlet.
\n
\n
\n
5
\n
Experiment
\n
E
\n
My coffeemaker works.
\n
\n
\n
6
\n
Result
\n
F
\n
What is preventing my coffeemaker from working?
\n
\n \n
\n",
"answers": [{
"id": "4",
- "content_html": "The original hypothesis is incorrect. Alternative hypotheses includes that both coffee maker and toaster were broken. -4.81 \\times 10^{-19}\\,\\text{C}."
+ "content_html": "The original hypothesis is incorrect. Alternative hypotheses includes that both coffee maker and toaster were broken.",
+ "feedback_html": "Why wouldn't this be true?"
}, {
"id": "3",
- "content_html": "The original hypothesis is correct. The coffee maker and the toaster do not work when plugged into the outlet. -1.602 \\times 10^{-19}\\,\\text{C}."
+ "content_html": "The original hypothesis is correct. The coffee maker and the toaster do not work when plugged into the outlet.",
+ "feedback_html": "Correct"
}, {
"id": "2",
- "content_html": "The original hypothesis is incorrect. Alternative hypothesis includes that toaster wasn’t turned on. 1.602 \\times 10^{-19}\\,\\text{C}."
+ "content_html": "The original hypothesis is incorrect. Alternative hypothesis includes that toaster wasn’t turned on.",
+ "feedback_html": "Why wouldn't this be true? are you sure?"
}, {
"id": "1",
- "content_html": "The original hypothesis is correct. There is something wrong with the electrical outlet and therefore the toaster doesn’t work. 4.81 \\times 10^{-19}\\,\\text{C}."
+ "content_html": "The original hypothesis is correct. There is something wrong with the electrical outlet and therefore the toaster doesn’t work.",
+ "feedback_html": "Why wouldn't this be true? The electrical outlet is good."
}],
"solutions": [{
"uid": "1@1",
@@ -96,4 +100,4 @@
"feedback_html": "The original hypothesis is incorrect because when the coffee maker was plugged in it worked. Therefore, it is incorrect to hypothesize that there is something wrong with the outlet. Alternative hypothesis includes that the toaster wasn’t turned on.",
"correct_answer_id": "4",
"is_correct": false
-}
\ No newline at end of file
+}
diff --git a/test/components/exercise/preview.spec.coffee b/test/components/exercise/preview.spec.coffee
new file mode 100644
index 0000000..29d7026
--- /dev/null
+++ b/test/components/exercise/preview.spec.coffee
@@ -0,0 +1,35 @@
+{Testing, expect, sinon, _} = require 'test/helpers'
+
+ExercisePreview = require 'components/exercise/preview'
+
+EXERCISE = require '../../../stubs/exercise/review'
+ANSWERS = EXERCISE.content.questions[0].answers
+
+describe 'Exercise Preview Component', ->
+
+ beforeEach ->
+ @props = {
+ exercise: EXERCISE
+ }
+
+ it 'displays the exercise answers', ->
+ Testing.renderComponent( ExercisePreview, props: @props ).then ({dom}) ->
+ for answer, i in _.pluck(dom.querySelectorAll('.answers-answer .answer .choice'), 'textContent')
+ expect(answer).to.equal( ANSWERS[i].content_html )
+
+
+ it 'renders the feedback', ->
+ Testing.renderComponent( ExercisePreview, props: @props ).then ({dom}) ->
+ for answer, i in _.pluck(dom.querySelectorAll('.answers-answer .answer .feedback'), 'textContent')
+ expect(answer).to.equal( ANSWERS[i].feedback_html )
+
+ it 'sets the className when displaying feedback', ->
+ _.extend(@props, displayFeedback: true)
+ Testing.renderComponent( ExercisePreview, props: @props ).then ({dom}) ->
+ expect(dom.classList.contains('is-displaying-feedback')).to.be.true
+
+ it 'can hide the answers', ->
+ _.extend(@props, hideAnswers: true)
+ Testing.renderComponent( ExercisePreview, props: @props ).then ({dom}) ->
+ expect(dom.querySelector('.answers-table')).to.be.not.ok
+ expect(dom.classList.contains('answers-hidden')).to.be.true
diff --git a/test/components/html.spec.coffee b/test/components/html.spec.coffee
index de237d0..4cae026 100644
--- a/test/components/html.spec.coffee
+++ b/test/components/html.spec.coffee
@@ -11,6 +11,13 @@ describe 'Arbitrary Html Component', ->
processHtmlAndMath: sinon.spy()
block: true
+ @frameProps =
+ className: 'html'
+ html: """"""
+ processHtmlAndMath: sinon.spy()
+ block: true
+
it 'renders html', ->
Testing.renderComponent( Html, props: @props ).then ({dom}) ->
expect(dom.tagName).equal('DIV')
@@ -24,3 +31,7 @@ describe 'Arbitrary Html Component', ->
@props.block = false
Testing.renderComponent( Html, props: @props ).then ({dom}) ->
expect(dom.tagName).equal('SPAN')
+
+ it 'wraps iframes with embed classes', ->
+ Testing.renderComponent( Html, props: @frameProps ).then ({dom}) ->
+ expect(dom.getElementsByClassName('embed-responsive').length).equal(1)
diff --git a/test/components/pinned-header-footer-card/index.spec.coffee b/test/components/pinned-header-footer-card/index.spec.coffee
index bc18c32..ad22734 100644
--- a/test/components/pinned-header-footer-card/index.spec.coffee
+++ b/test/components/pinned-header-footer-card/index.spec.coffee
@@ -29,6 +29,5 @@ describe 'Pinned Header/Footer Card Component', ->
Testing.renderComponent( PinnedHeaderFooterCard, unmountAfter: 20, props: @props ).then ({dom, element}) ->
expect(document.body.classList.contains('pinned-shy')).to.be.false
element.setState(scrollTop: 400) # imitate react-scroll-components
- _.defer ->
- expect(document.body.classList.contains('pinned-shy')).to.be.true
- done()
+ expect(document.body.classList.contains('pinned-shy')).to.be.true
+ done()
diff --git a/test/components/question/answer.spec.coffee b/test/components/question/answer.spec.coffee
index 638b133..c6c5465 100644
--- a/test/components/question/answer.spec.coffee
+++ b/test/components/question/answer.spec.coffee
@@ -14,11 +14,13 @@ describe 'Answer Component', ->
@props =
answer: ANSWER
type: 'student'
+ chosenAnswer: []
@propsWithFeedback =
answer: ANSWER
type: 'student'
show_all_feedback: true
+ chosenAnswer: []
it 'renders answer', ->
Testing.renderComponent( Answer, props: @props ).then ({dom}) ->
@@ -28,5 +30,5 @@ describe 'Answer Component', ->
it 'renders answer feedback based on props', ->
Testing.renderComponent( Answer, props: @propsWithFeedback ).then ({dom}) ->
- answers = _.pluck dom.querySelectorAll('.question-feedback'), 'textContent'
+ answers = _.pluck dom.querySelectorAll('.question-feedback-content'), 'textContent'
expect(answers).to.deep.equal(['feedback yo'])
diff --git a/test/helpers/html-videos.coffee b/test/helpers/html-videos.coffee
new file mode 100644
index 0000000..9a33525
--- /dev/null
+++ b/test/helpers/html-videos.coffee
@@ -0,0 +1,47 @@
+HtmlVideo = require 'helpers/html-videos'
+
+describe 'Html Video Helper', ->
+ it 'can wrap an html video frame in a div', ->
+ dom = document.createElement('div')
+ html = """"""
+
+ dom.innerHTML = html
+ dom = HtmlVideo.wrapFrames(dom)
+ expect(dom.getElementsByClassName('embed-responsive').length).to.equal(1)
+
+ it 'can wrap multiple html videos frame each in a div', ->
+ dom = document.createElement('div')
+ html = """
+ """
+
+ dom.innerHTML = html
+ dom = HtmlVideo.wrapFrames(dom)
+ expect(dom.getElementsByClassName('embed-responsive').length).to.equal(2)
+
+ it 'can will not wrap frames if a wrapper already exists', ->
+ dom = document.createElement('div')
+ html = """