fancy page revisions

test plan:
 - with draft state disabled
   * ensure the page revisions still behaves as would be expected
 - with draft state enabled
   * navigate to a page
   * using the gear menu, select 'View Page History'
   - the revisions list should open with the latest revision selected
   * the 'Next' and 'Previous' page buttons should work
   * restoring a revision should work
     - should restore title, url, and page content
     - restoring a revision that is 'Same as latest' is not allowed
   * clicking the 'X' next to Revision History or the page breadcrumb
     should navigate back to the page view

 - any browser windows opened to the revisions page should show the
   alternate page, if refreshed (when transitioning to/from draft state
   being enabled)

closes CNVS-8486

Change-Id: Ie22bf82ead396cbfa5a206c3a1234859cb2df7ff
Reviewed-on: https://gerrit.instructure.com/25922
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Sterling Cobb <sterling@instructure.com>
QA-Review: Nathan Rogowski <nathan@instructure.com>
Product-Review: Bracken Mosbacker <bracken@instructure.com>
This commit is contained in:
Mark Severson 2013-11-01 14:18:26 -06:00
parent 22227953ae
commit 12077de92d
23 changed files with 804 additions and 22 deletions

View File

@ -0,0 +1,31 @@
require [
'jquery'
'compiled/models/WikiPage'
'compiled/collections/WikiPageRevisionsCollection'
'compiled/views/wiki/WikiPageContentView'
'compiled/views/wiki/WikiPageRevisionsView'
], ($, WikiPage, WikiPageRevisionsCollection, WikiPageContentView, WikiPageRevisionsView) ->
$('body').addClass('show revisions')
wikiPage = new WikiPage ENV.WIKI_PAGE, revision: ENV.WIKI_PAGE_REVISION, contextAssetString: ENV.context_asset_string
revisions = new WikiPageRevisionsCollection [],
parentModel: wikiPage
revisionsView = new WikiPageRevisionsView
collection: revisions
revisionsView.on 'selectionChanged', (newSelection) ->
contentView.setModel(newSelection.model)
if !newSelection.model.get('title') || newSelection.model.get('title') == ''
contentView.$el.disableWhileLoading newSelection.model.fetch()
revisionsView.$el.appendTo('#wiki_page_revisions')
revisionsView.render()
contentView = new WikiPageContentView
contentView.$el.appendTo('#wiki_page_revisions')
contentView.on 'render', ->
revisionsView.reposition()
contentView.render()
revisionsView.collection.setParams per_page: 10
revisionsView.collection.fetch()

View File

@ -0,0 +1,32 @@
define [
'underscore'
'Backbone'
'compiled/collections/PaginatedCollection'
'compiled/models/WikiPageRevision'
], (_, Backbone, PaginatedCollection, WikiPageRevision) ->
revisionOptions = ['parentModel']
class WikiPageRevisionsCollection extends PaginatedCollection
model: WikiPageRevision
url: ->
"#{@parentModel.url()}/revisions"
initialize: (models, options) ->
super
_.extend(this, _.pick(options || {}, revisionOptions))
if @parentModel
collection = @
parentModel = collection.parentModel
setupModel = (model) ->
model.page = parentModel
model.pageUrl = parentModel.get('url')
model.contextAssetString = parentModel.contextAssetString
collection.latest = model if !!model.get('latest')
@on 'reset', (models) ->
models.each setupModel
@on 'add', (model) ->
setupModel(model)

View File

@ -0,0 +1,85 @@
define [
'underscore'
'jquery'
], (_, $) ->
# Floating sticky
#
# allows an element to float (using fixed positioning) as the user
# scrolls, "sticking" to the top of the window. the difference from
# a regular sticky implementation is that the element is constrained
# by a containing element (or top and bottom elements), allowing the
# element to float and stick, but only within the given bounds.
#
# to use, simply call .floatingSticky(containing_element) on a
# jQuery object. optionally the top or bottom constraining element
# can be overridden by providing {top:...} or {bottom:...} as the
# last argument when calling .floatingSticky(...).
#
# the returned array has a floating sticky instance for each object
# in the jQuery set, allowing calls to reposition() (in case the
# element should be repositioned outside of a scroll/resize event)
# or remove() to remove the floating sticky instance from the
# element.
instanceID = 0
class FloatingSticky
constructor: (el, container, options={}) ->
@instanceID = "floatingSticky#{instanceID++}"
@$window = $(window)
@$el = $(el)
@$top = $(options.top || container)
@$bottom = $(options.bottom || container)
@$el.data('floatingSticky', this)
@$window.on "scroll.#{@instanceID} resize.#{@instanceID}", =>
@reposition()
@reposition()
remove: ->
@$window.off @instanceID
@$el.data('floatingSticky', null)
reposition: ->
windowTop = @$window.scrollTop()
windowHeight = @$window.height()
# handle overscroll (up or down)
if windowTop < 0
windowTop = 0
else
windowTop = Math.min(windowTop, document.body.scrollHeight - windowHeight)
# handle top of container
containerTop = @$top.offset().top
if windowTop < containerTop
if windowTop == 0
newTop = containerTop
else
newTop = containerTop - windowTop
# handle bottom of container
else
newTop = 0
elHeight = @$el.height()
containerBottom = @$bottom.offset().top + @$bottom.height()
# stay within the container
if windowTop + elHeight > containerBottom
newTop = containerBottom - elHeight - windowTop
# but don't go above the container
if newTop < containerTop - windowTop
newTop = containerTop - windowTop
@$el.css(top: newTop)
$.fn.floatingSticky = (container, options={}) ->
@map ->
floatingSticky = $(this).data('floatingSticky')
floatingSticky = new FloatingSticky(this, container, options) unless floatingSticky
floatingSticky
FloatingSticky

View File

@ -47,7 +47,7 @@ define [
latestRevision: (options) ->
if !@_latestRevision && @get('url')
unless @_latestRevision
revisionOptions = _.extend({}, {@contextAssetString, pageUrl: @get('url'), latest: true, summary: true}, options)
revisionOptions = _.extend({}, {@contextAssetString, page: @, pageUrl: @get('url'), latest: true, summary: true}, options)
@_latestRevision = new WikiPageRevision({revision_id: @revision}, revisionOptions)
@_latestRevision

View File

@ -1,11 +1,12 @@
define [
'underscore'
'Backbone'
'i18n!pages'
'compiled/backbone-ext/DefaultUrlMixin'
'compiled/str/splitAssetString'
], (_, Backbone, DefaultUrlMixin, splitAssetString) ->
], (_, Backbone, I18n, DefaultUrlMixin, splitAssetString) ->
pageRevisionOptions = ['contextAssetString', 'pageUrl', 'latest', 'summary']
pageRevisionOptions = ['contextAssetString', 'page', 'pageUrl', 'latest', 'summary']
class WikiPageRevision extends Backbone.Model
@mixin DefaultUrlMixin
@ -13,7 +14,9 @@ define [
initialize: (attributes, options) ->
super
_.extend(this, _.pick(options || {}, pageRevisionOptions))
@set(id: attributes.url) if attributes?.url
# the CollectionView managing the revisions "accidentally" passes in a url, so we have to nuke it here...
delete @url if _.has(@, 'url')
urlRoot: ->
"/api/v1/#{@_contextPath()}/pages/#{@pageUrl}/revisions"
@ -21,7 +24,7 @@ define [
url: ->
base = @urlRoot()
return "#{base}/latest" if @latest
return "#{base}/#{@get('id')}" if @get('id')
return "#{base}/#{@get('revision_id')}" if @get('revision_id')
return base
fetch: (options={}) ->
@ -51,3 +54,9 @@ define [
toJSON: ->
_.omit super, 'id'
restore: ->
d = $.ajaxJSON(@url(), 'POST').fail ->
$.flashError I18n.t 'restore_failed', 'Failed to restore page revision'
$('#wiki_page_revisions').disableWhileLoading($.Deferred())
d

View File

@ -0,0 +1,64 @@
define [
'Backbone'
'jst/wiki/WikiPageContent'
], (Backbone, template) ->
class WikiPageContentView extends Backbone.View
tagName: 'article'
className: 'show-content user_content'
template: template
@optionProperty 'modules_path'
@optionProperty 'wiki_pages_path'
@optionProperty 'wiki_page_edit_path'
@optionProperty 'wiki_page_history_path'
@optionProperty 'WIKI_RIGHTS'
@optionProperty 'PAGE_RIGHTS'
@optionProperty 'course_id'
@optionProperty 'course_home'
@optionProperty 'course_title'
initialize: ->
super
@WIKI_RIGHTS ||= {}
@PAGE_RIGHTS ||= {}
@setModel(@model)
afterRender: ->
super
$.publish('userContent/change')
@trigger('render')
setModel: (model) ->
@model?.off null, null, @
@model = model
@model?.on 'change:title', (=> @render()), @
@model?.on 'change:body', (=> @render()), @
@render()
toJSON: ->
json = super
json.modules_path = @modules_path
json.wiki_pages_path = @wiki_pages_path
json.wiki_page_edit_path = @wiki_page_edit_path
json.wiki_page_history_path = @wiki_page_history_path
json.course_home = @course_home
json.course_title = @course_title
json.CAN =
VIEW_PAGES: !!@WIKI_RIGHTS.read
PUBLISH: !!@WIKI_RIGHTS.manage && json.contextName == 'courses'
UPDATE_CONTENT: !!@PAGE_RIGHTS.update || !!@PAGE_RIGHTS.update_content
DELETE: !!@PAGE_RIGHTS.delete && !@course_home
READ_REVISIONS: !!@PAGE_RIGHTS.read_revisions
json.CAN.ACCESS_GEAR_MENU = json.CAN.DELETE || json.CAN.READ_REVISIONS
json.CAN.VIEW_TOOLBAR = json.CAN.VIEW_PAGES || json.CAN.PUBLISH || json.CAN.UPDATE_CONTENT || json.CAN.ACCESS_GEAR_MENU
json.lock_info = _.clone(json.lock_info) if json.lock_info
if json.lock_info?.unlock_at
json.lock_info.unlock_at = if Date.parse(json.lock_info.unlock_at) < Date.now()
null
else
$.parseFromISO(json.lock_info.unlock_at).datetime_formatted
json

View File

@ -0,0 +1,39 @@
define [
'underscore'
'Backbone'
'jst/wiki/WikiPageRevision'
], (_, Backbone, template) ->
class WikiPageRevisionView extends Backbone.View
tagName: 'li'
className: 'revision clearfix'
template: template
events:
'click .restore-link': 'restore'
initialize: ->
super
@model.on 'change', => @render()
afterRender: ->
super
@$el.toggleClass('selected', !!@model.get('selected'))
@$el.toggleClass('latest', !!@model.get('latest'))
toJSON: ->
latest = @model.collection?.latest
json = _.extend {}, super,
IS:
LATEST: !!@model.get('latest')
SELECTED: !!@model.get('selected')
LOADED: !!@model.get('title') && !!@model.get('body')
json.IS.SAME_AS_LATEST = json.IS.LOADED && (@model.get('title') == latest?.get('title')) && (@model.get('body') == latest?.get('body'))
json.updated_at = $.parseFromISO(json.updated_at).datetime_formatted
json.edited_by = json.edited_by?.display_name
json
restore: (ev) ->
ev?.preventDefault()
@model.restore().done =>
window.location.reload()

View File

@ -0,0 +1,98 @@
define [
'underscore'
'Backbone'
'compiled/views/CollectionView'
'compiled/views/wiki/WikiPageRevisionView'
'jst/wiki/WikiPageRevisions'
'compiled/jquery/floatingSticky'
], (_, Backbone, CollectionView, WikiPageRevisionView, template) ->
class WikiPageRevisionsView extends CollectionView
className: 'show-revisions'
template: template
itemView: WikiPageRevisionView
@mixin
events:
'click .prev-button': 'prevPage'
'click .next-button': 'nextPage'
'click .close-button': 'close'
els:
'#ticker': '$ticker'
'aside': '$aside'
'.revisions-list': '$revisionsList'
initialize: (options) ->
super
@selectedRevision = null
# handle selection changes
@on 'selectionChanged', (newSelection, oldSelection) =>
oldSelection.model?.set('selected', false)
newSelection.model?.set('selected', true)
# reposition after rendering
@on 'render renderItem', => @reposition()
afterRender: ->
super
$.publish('userContent/change')
@trigger('render')
@floatingSticky = @$aside.floatingSticky('#main', {top: '#content'})
remove: ->
if @floatingSticky
_.each @floatingSticky, (sticky) -> sticky.remove()
@floatingSticky = null
super
renderItem: ->
super
@trigger('renderItem')
attachItemView: (model, view) ->
if !!@selectedRevision && @selectedRevision.get('revision_id') == model.get('revision_id')
model.set(@selectedRevision.attributes)
model.set('selected', true)
@setSelectedModelAndView(model, view)
else
model.set('selected', false)
selectModel = =>
@setSelectedModelAndView(model, view)
selectModel() unless @selectedModel
view.$el.on 'click', selectModel
setSelectedModelAndView: (model, view) ->
oldSelectedModel = @selectedModel
oldSelectedView = @selectedView
@selectedModel = model
@selectedView = view
@selectedRevision = model
@trigger 'selectionChanged', {model: model, view: view}, {model: oldSelectedModel, view: oldSelectedView}
reposition: ->
if @floatingSticky
_.each @floatingSticky, (sticky) -> sticky.reposition()
prevPage: (ev) ->
ev?.preventDefault()
@$el.disableWhileLoading @collection.fetch page: 'prev', reset: true
nextPage: (ev) ->
ev?.preventDefault()
@$el.disableWhileLoading @collection.fetch page: 'next', reset: true
close: (ev) ->
ev?.preventDefault()
window.location.href = @collection.parentModel.get('html_url')
toJSON: ->
json = super
json.CAN =
FETCH_PREV: @collection.canFetch('prev')
FETCH_NEXT: @collection.canFetch('next')
json

View File

@ -23,6 +23,11 @@ class WikiPageRevisionsController < ApplicationController
before_filter { |c| c.active_tab = "pages" }
def index
if @context.draft_state_enabled?
redirect_to polymorphic_url([@context, :named_page_revisions], :wiki_page_id => @page)
return
end
if authorized_action(@page, @current_user, :update_content)
respond_to do |format|
format.html {
@ -54,6 +59,11 @@ class WikiPageRevisionsController < ApplicationController
end
def show
if @context.draft_state_enabled?
redirect_to polymorphic_url([@context, :named_page_revisions], :wiki_page_id => @page)
return
end
if authorized_action(@page, @current_user, :update_content)
if params[:id] == "latest"
@revision = @page.versions[0]

View File

@ -21,8 +21,8 @@ class WikiPagesController < ApplicationController
before_filter :require_context
before_filter :get_wiki_page
before_filter :set_js_rights, :only => [:pages_index, :show_page, :edit_page]
before_filter :set_js_wiki_data, :only => [:pages_index, :show_page, :edit_page]
before_filter :set_js_rights, :only => [:pages_index, :show_page, :edit_page, :page_revisions]
before_filter :set_js_wiki_data, :only => [:pages_index, :show_page, :edit_page, :page_revisions]
add_crumb(proc { t '#crumbs.wiki_pages', "Pages"}) do |c|
url = nil
context = c.instance_variable_get('@context')
@ -221,6 +221,26 @@ class WikiPagesController < ApplicationController
end
end
def page_revisions
if !@context.draft_state_enabled?
redirect_to polymorphic_url([@context, @page, :wiki_page_revisions])
return
end
if is_authorized_action?(@page, @current_user, :read_revisions)
add_crumb(@page.title, polymorphic_url([@context, :named_page], :wiki_page_id => @page))
add_crumb(t("#crumbs.revisions", "Revisions"))
@padless = true
render
else
if authorized_action(@page, @current_user, :read)
flash[:warning] = t('notices.cannot_read_revisions', 'You are not allowed to review the historical revisions of "%{title}".', :title => @page.title)
redirect_to polymorphic_url([@context, :named_page], :wiki_page_id => @page)
end
end
end
protected
def context_wiki_page_url(opts={})

View File

@ -23,7 +23,15 @@ $item-hover-background: #eef7ff
$shadow: 0 1px 0 rgba(0,0,0,0.15)
$table-shadow: 0 1px 0 #dde0e4
.pages .sticky
$revision-sidebar-width: 270px
$revision-sidebar-background-color: #f6f8fa
$revision-sidebar-selected-color: #ffffff
$revision-sidebar-hover-color: #eef1f3
$revision-sidebar-dimmed-color: #999999
$revision-button-color: #999999
$revision-button-hover-color: #2590cf
.pages .sticky-toolbar .sticky
position: fixed
z-index: 1000
top: 0px
@ -35,15 +43,16 @@ $table-shadow: 0 1px 0 #dde0e4
max-width: $max_main_width
border-color: $menu-border-color
.pages.with-left-side .sticky
.pages.with-left-side .sticky-toolbar .sticky
margin-left: $left_side_width + 1px
.pages.with-right-side .sticky
.pages.with-right-side .sticky-toolbar .sticky
margin-right: $right_side_width + 1px
.pages
.pages:not(.revisions)
#breadcrumbs
:border-bottom $pages-border
.pages
.header-bar-outer-container
:height 64px
:clear right
@ -296,10 +305,135 @@ $table-shadow: 0 1px 0 #dde0e4
:margin-top 0
:margin-bottom 0
.pages.show.revisions
.revision:first-child
:border-top $pages-inner-border
#wiki_page_revisions
:position relative
:min-height 502px
.show-content
:float left
:margin-right $revision-sidebar-width + 1px
.show-revisions
:position absolute
:top 0
:bottom 0
:right 0
:width $revision-sidebar-width
:background-color $revision-sidebar-background-color
:border-left $pages-border
aside
:width $revision-sidebar-width
:position fixed
.revision-history
:text-transform uppercase
:font-size 14px
:font-weight bold
:line-height 40px
:padding 4px 11px
:margin-top -1px
:margin-left -1px
:border-left $pages-border
:border-top $pages-border
:border-bottom none
:color $subdued-text-color
:position relative
.revision-history .close-button
:position absolute
:top 3px
:bottom 3px
:right 3px
:width 42px
:text-align center
:color dimgrey
&:hover
:color $revision-button-hover-color
i.icon-x
:position absolute
:top 50%
:margin-top -8px
:right 13px
ul.revisions-list
:margin 0 0 0 -1px
:height 400px
.revision
:list-style-type none
:overflow hidden
:width $revision-sidebar-width + 1px
:margin-top -1px
:border-left none
:border-right none
:border-top 1px solid transparent
:border-bottom 1px solid transparent
&.selected
:border-top $pages-border
:border-bottom $pages-border
// height and transitions
:height 34px
:transition height 400ms
&.selected, &.latest
:height 56px
:transition height 400ms
.revision-content
:margin-left 1px
:padding 7px 8px
&:not(.selected):hover .revision-content
:background-color $revision-sidebar-hover-color
:cursor pointer
.revision-actions
:margin-top 2px
:font-style italic
:color $revision-sidebar-dimmed-color
.revision-actions a.restore-link
:font-style normal
:cursor pointer
&.selected
:background-color $revision-sidebar-selected-color
.revision-nav-buttons
:position relative
:padding 0
:height 40px
:margin-top 14px
:border-top $pages-inner-border
a
:user-select none
:font-weight bold
:font-size 12px
:text-transform uppercase
:padding 10px 10px
:color $revision-button-color
a:hover
:text-decoration none
:cursor pointer
:color $revision-button-hover-color
:border-radius 3px
.prev-button
:position absolute
:left 3px
:top 3px
:padding 7px 10px 7px 5px
.next-button
:position absolute
:right 3px
:top 3px
:padding 7px 5px 7px 10px
@media print
#breadcrumbs, .header-bar-outer-container
:display none
.pages.show.revisions
.show-revisions
:display none
.show-content
:margin-right 0
#wiki_show_view_main
:overflow auto

View File

@ -0,0 +1,36 @@
<header>
<h2 class="page-title">{{title}}</h2>
</header>
{{#if locked_for_user}}
<div class="locked-alert alert alert-warning">
{{#if lock_info.context_module.prerequisites}}
{{#if lock_info.unlock_at}}
{{#t 'page_locked_by_modules_until'}}This page will be available on {{lock_info.unlock_at}} if you have completed these modules:{{/t}}
{{else}}
{{#t 'page_locked_by_modules'}}This page will be available once you have completed these modules:{{/t}}
{{/if}}
<ul>
{{#each lock_info.context_module.prerequisites}}{{#ifEqual this.type "context_module"}}
{{#if this.name}}
<li>
{{#ifAll ../../../../modules_path this.id}}
<a href="{{../../../../modules_path}}/{{this.id}}">{{this.name}}</a>
{{else}}
{{this.name}}
{{/ifAll}}
</li>
{{/if}}
{{/ifEqual}}{{/each}}
</ul>
{{else}}
{{#if lock_info.unlock_at}}
{{#t 'page_locked_until'}}This page will be available on {{lock_info.unlock_at}}{{/t}}
{{else}}
{{#t 'page_locked'}}This page is locked{{/t}}
{{/if}}
{{/if}}
</div>
{{else}}
{{{body}}}
{{/if}}

View File

@ -0,0 +1,23 @@
<div class="revision-content">
<div class="revision-details">
{{#if edited_by}}
{{#t 'revision_summary'}}<strong>{{updated_at}}</strong> by {{edited_by}}{{/t}}
{{else}}
<strong>{{updated_at}}</strong>
{{/if}}
</div>
{{#if IS.LATEST}}
<div class="revision-actions">{{#t 'latest_revision'}}Latest Revision{{/t}}</div>
{{else}}
{{#ifAll IS.SELECTED IS.LOADED}}
<div class="revision-actions">
{{#if IS.SAME_AS_LATEST}}
{{#t 'same_as_latest'}}Same as <strong>Latest</strong>{{/t}}
{{else}}
<a class="restore-link">{{#t 'restore_revision'}}Restore this revision{{/t}}</a>
{{/if}}
</div>
{{/ifAll}}
{{/if}}
</div>

View File

@ -0,0 +1,18 @@
<aside>
<div class="revision-history">
{{#t 'revision_history'}}Revision History{{/t}}
<a href="#" class="close-button"><i class="icon-x"></i></a>
</div>
<ul class="collectionViewItems revisions-list">
</ul>
{{#ifAny CAN.FETCH_PREV CAN.FETCH_NEXT}}
<div class="revision-nav-buttons">
{{#if CAN.FETCH_PREV}}
<a class="prev-button"><i class="icon-mini-arrow-left"></i> Previous</a>
{{/if}}
{{#if CAN.FETCH_NEXT}}
<a class="next-button">Next <i class="icon-mini-arrow-right"></i></a>
{{/if}}
</div>
{{/ifAny}}
</aside>

View File

@ -0,0 +1,6 @@
<%
content_for :page_title, t('page_revisions_title', '%{title} revisions : %{context_name}', :title => @page.title, :context_name => @context.name)
js_bundle :wiki_page_revisions
%>
<div id="wiki_page_revisions" class="clearfix">
</div>

View File

@ -155,6 +155,7 @@ FakeRails3Routes.draw do
get 'pages' => 'wiki_pages#pages_index'
get 'pages/:wiki_page_id' => 'wiki_pages#show_page', :wiki_page_id => /[^\/]+/, :as => :named_page
get 'pages/:wiki_page_id/edit' => 'wiki_pages#edit_page', :wiki_page_id => /[^\/]+/, :as => :edit_named_page
get 'pages/:wiki_page_id/revisions' => 'wiki_pages#page_revisions', :wiki_page_id => /[^\/]+/, :as => :named_page_revisions
resources :wiki_pages, :path => :wiki do
match 'revisions/latest' => 'wiki_page_revisions#latest_version_number', :as => :latest_version_number

View File

@ -0,0 +1,23 @@
define [
'compiled/models/WikiPage'
'compiled/collections/WikiPageRevisionsCollection'
], (WikiPage, WikiPageRevisionsCollection) ->
module 'WikiPageRevisionsCollection'
test 'parentModel accepted in constructor', ->
parentModel = new WikiPage
collection = new WikiPageRevisionsCollection([], parentModel: parentModel)
strictEqual collection.parentModel, parentModel, 'parentModel accepted in constructor'
test 'url based on parentModel', ->
parentModel = new WikiPage {url: 'a-page'}, contextAssetString: 'course_73'
collection = new WikiPageRevisionsCollection([], parentModel: parentModel)
equal collection.url(), '/api/v1/courses/73/pages/a-page/revisions', 'url built properly'
test 'child models inherit parent url propertly', ->
parentModel = new WikiPage {url: 'a-page'}, contextAssetString: 'course_73'
collection = new WikiPageRevisionsCollection([], parentModel: parentModel)
collection.add(revision_id: 37)
equal collection.models.length, 1, 'child model added'
equal collection.models[0].url(), '/api/v1/courses/73/pages/a-page/revisions/37', 'child url built properly'

View File

@ -1,12 +1,16 @@
define [
'jquery'
'underscore'
'compiled/models/WikiPage'
'compiled/models/WikiPageRevision'
], (_, WikiPageRevision) ->
], ($, _, WikiPage, WikiPageRevision) ->
module 'WikiPageRevision::urls'
test 'captures contextAssetString, pageUrl, latest, and summary as constructor options', ->
revision = new WikiPageRevision {}, contextAssetString: 'course_73', pageUrl: 'page-url', latest: true, summary: true
test 'captures contextAssetString, page, pageUrl, latest, and summary as constructor options', ->
page = new WikiPage
revision = new WikiPageRevision {}, contextAssetString: 'course_73', page: page, pageUrl: 'page-url', latest: true, summary: true
strictEqual revision.contextAssetString, 'course_73', 'contextAssetString'
strictEqual revision.page, page, 'page'
strictEqual revision.pageUrl, 'page-url', 'pageUrl'
strictEqual revision.latest, true, 'latest'
strictEqual revision.summary, true, 'summary'
@ -19,24 +23,29 @@ define [
revision = new WikiPageRevision {}, contextAssetString: 'course_73', pageUrl: 'page-url'
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions', 'base url'
test 'url is affected by the latest flag', ->
revision = new WikiPageRevision {id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url', latest: true
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/latest', 'latest'
test 'url is affected by the revision_id attribute', ->
revision = new WikiPageRevision {revision_id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url'
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/42', 'revision 42'
test 'url is affected by the id', ->
revision = new WikiPageRevision {id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url'
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/42', 'id'
test 'url is affected by the latest flag', ->
revision = new WikiPageRevision {revision_id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url', latest: true
strictEqual revision.url(), '/api/v1/courses/73/pages/page-url/revisions/latest', 'latest'
module 'WikiPageRevision::parse'
test 'parse sets the id to the url', ->
revision = new WikiPageRevision {url: 'url'}
strictEqual revision.get('id'), 'url', 'url set through constructor'
revision = new WikiPageRevision
strictEqual revision.parse({url: 'bob'}).id, 'bob', 'url set through parse'
test 'toJSON omits the id', ->
revision = new WikiPageRevision {url: 'url'}
strictEqual revision.toJSON().id, undefined, 'id omitted'
test 'restore POSTs to the revision', ->
revision = new WikiPageRevision {revision_id: 42}, contextAssetString: 'course_73', pageUrl: 'page-url'
mock = @mock($)
mock.expects('ajaxJSON').atLeast(1).withArgs('/api/v1/courses/73/pages/page-url/revisions/42', 'POST').returns($.Deferred())
revision.restore()
module 'WikiPageRevision::fetch'
test 'the summary flag is passed to the server', ->

View File

@ -29,6 +29,10 @@ define [
wikiPage = new WikiPage {url: 'url'}, revision: 42
equal wikiPage.latestRevision().get('revision_id'), 42, 'revision passed to latestRevision'
test 'wiki page passed to latestRevision', ->
wikiPage = new WikiPage {url: 'url'}
equal wikiPage.latestRevision().page, wikiPage, 'wiki page passed to latestRevision'
test 'latestRevision should be marked as latest', ->
wikiPage = new WikiPage {url: 'url'}
equal wikiPage.latestRevision().latest, true, 'marked as latest'

View File

@ -0,0 +1,31 @@
define [
'compiled/models/WikiPage'
'compiled/views/wiki/WikiPageContentView'
], (WikiPage, WikiPageContentView) ->
module 'WikiPageContentView'
test 'setModel causes a re-render', ->
wikiPage = new WikiPage
contentView = new WikiPageContentView
@mock(contentView).expects('render').atLeast(1)
contentView.setModel(wikiPage)
test 'setModel binds to the model change:title trigger', ->
wikiPage = new WikiPage
contentView = new WikiPageContentView
contentView.setModel(wikiPage)
@mock(contentView).expects('render').atLeast(1)
wikiPage.set('title', 'A New Title')
test 'setModel binds to the model change:title trigger', ->
wikiPage = new WikiPage
contentView = new WikiPageContentView
contentView.setModel(wikiPage)
@mock(contentView).expects('render').atLeast(1)
wikiPage.set('body', 'A New Body')
test 'render publishes a "userContent/change" (to enhance user content)', ->
contentView = new WikiPageContentView
$.subscribe('userContent/change', @mock().atLeast(1))
contentView.render()

View File

@ -0,0 +1,37 @@
define [
'compiled/models/WikiPageRevision'
'compiled/collections/WikiPageRevisionsCollection'
'compiled/views/wiki/WikiPageRevisionView'
], (WikiPageRevision, WikiPageRevisionsCollection, WikiPageRevisionView) ->
module 'WikiPageRevisionView'
test 'binds to model change triggers', ->
revision = new WikiPageRevision
view = new WikiPageRevisionView model: revision
@mock(view).expects('render').atLeast(1)
revision.set('body', 'A New Body')
test 'restore delegates to model.restore', ->
revision = new WikiPageRevision
view = new WikiPageRevisionView model: revision
@mock(revision).expects('restore').atLeast(1).returns($.Deferred())
view.restore()
test 'toJSON serializes expected values', ->
attributes =
latest: true
selected: true
title: 'Title'
body: 'Body'
revision = new WikiPageRevision attributes
collection = new WikiPageRevisionsCollection [revision]
collection.latest = new WikiPageRevision attributes
view = new WikiPageRevisionView model: revision
json = view.toJSON()
strictEqual json.IS?.LATEST, true, 'IS.LATEST'
strictEqual json.IS?.SELECTED, true, 'IS.SELECTED'
strictEqual json.IS?.LOADED, true, 'IS.LOADED'
strictEqual json.IS?.SAME_AS_LATEST, true, 'IS.SAME_AS_LATEST'

View File

@ -0,0 +1,54 @@
define [
'compiled/collections/WikiPageRevisionsCollection'
'compiled/views/wiki/WikiPageRevisionsView'
], (WikiPageRevisionsCollection, WikiPageRevisionsView) ->
module 'WikiPageRevisionsView'
test 'selecting a model/view sets the selected attribute on the model', ->
fixture = $('<div id="main"><div id="content"></div></div>').appendTo('#fixtures')
collection = new WikiPageRevisionsCollection
view = new WikiPageRevisionsView collection: collection
view.$el.appendTo('#content')
view.render()
collection.add(revision_id: 21)
collection.add(revision_id: 37)
strictEqual collection.models.length, 2, 'models added to collection'
view.setSelectedModelAndView(collection.models[0], collection.models[0].view)
strictEqual collection.models[0].get('selected'), true, 'selected attribute set'
strictEqual collection.models[1].get('selected'), false, 'selected attribute not set'
view.setSelectedModelAndView(collection.models[1], collection.models[1].view)
strictEqual collection.models[0].get('selected'), false, 'selected attribute not set'
strictEqual collection.models[1].get('selected'), true, 'selected attribute set'
fixture.remove()
test 'prevPage fetches previous page from collection', ->
collection = new WikiPageRevisionsCollection
@mock(collection).expects('fetch').atLeast(1).withArgs(page: 'prev', reset: true).returns($.Deferred())
view = new WikiPageRevisionsView collection: collection
view.prevPage()
test 'nextPage fetches next page from collection', ->
collection = new WikiPageRevisionsCollection
@mock(collection).expects('fetch').atLeast(1).withArgs(page: 'next', reset: true).returns($.Deferred())
view = new WikiPageRevisionsView collection: collection
view.nextPage()
test 'toJSON - CAN.FETCH_PREV', ->
collection = new WikiPageRevisionsCollection
view = new WikiPageRevisionsView collection: collection
@stub(collection, 'canFetch', (arg) -> arg == 'prev')
strictEqual view.toJSON().CAN?.FETCH_PREV, true, 'can fetch previous'
test 'toJSON - CAN.FETCH_NEXT', ->
collection = new WikiPageRevisionsCollection
view = new WikiPageRevisionsView collection: collection
@stub(collection, 'canFetch', (arg) -> arg == 'next')
strictEqual view.toJSON().CAN?.FETCH_NEXT, true, 'can fetch next'

View File

@ -129,6 +129,18 @@ describe WikiPagesController do
response.code.should == '302'
response.redirected_to.should =~ %r{/pages/a-page\z}
end
it "should forward /wiki/name/revisions to /pages/name/revisions" do
get @base_url + "wiki/a-page/revisions"
response.code.should == '302'
response.redirected_to.should =~ %r{/pages/a-page/revisions\z}
end
it "should forward /wiki/name/revisions/revision to /pages/name/revisions" do
get @base_url + "wiki/a-page/revisions/42"
response.code.should == '302'
response.redirected_to.should =~ %r{/pages/a-page/revisions\z}
end
end
context "draft state disabled" do
@ -149,6 +161,12 @@ describe WikiPagesController do
response.code.should == '302'
response.redirected_to.should =~ %r{/wiki/a-page#edit\z}
end
it "should forward /pages/name/revisions to /wiki/name/revisions" do
get @base_url + "pages/a-page/revisions"
response.code.should == '302'
response.redirected_to.should =~ %r{/wiki/a-page/revisions\z}
end
end
end