Multi-version support

Ref #25.
pull/308/merge
Thibaut Courouble 9 years ago
parent bd6e27eca2
commit b2d2066d96

@ -75,6 +75,7 @@
docs = @settings.getDocs() docs = @settings.getDocs()
for doc in @DOCS for doc in @DOCS
(if docs.indexOf(doc.slug) >= 0 then @docs else @disabledDocs).add(doc) (if docs.indexOf(doc.slug) >= 0 then @docs else @disabledDocs).add(doc)
@migrateDocs()
@docs.sort() @docs.sort()
@disabledDocs.sort() @disabledDocs.sort()
@docs.load @start.bind(@), @onBootError.bind(@), readCache: true, writeCache: true @docs.load @start.bind(@), @onBootError.bind(@), readCache: true, writeCache: true
@ -99,6 +100,15 @@
@entries.add doc.entries.all() @entries.add doc.entries.all()
return return
migrateDocs: ->
for slug in @settings.getDocs() when not @docs.findBy('slug', slug)
needsSaving = true
if doc = @disabledDocs.findBy('slug_without_version', slug)
@disabledDocs.remove(doc)
@docs.add(doc)
@saveDocs() if needsSaving
enableDoc: (doc, _onSuccess, onError) -> enableDoc: (doc, _onSuccess, onError) ->
return if @docs.contains(doc) return if @docs.contains(doc)
onSuccess = => onSuccess = =>
@ -106,14 +116,17 @@
@docs.add(doc) @docs.add(doc)
@docs.sort() @docs.sort()
@initDoc(doc) @initDoc(doc)
@settings.setDocs(doc.slug for doc in @docs.all()) @saveDocs()
_onSuccess() _onSuccess()
@appCache?.updateInBackground()
return return
doc.load onSuccess, onError, writeCache: true doc.load onSuccess, onError, writeCache: true
return return
saveDocs: ->
@settings.setDocs(doc.slug for doc in @docs.all())
@appCache?.updateInBackground()
welcomeBack: -> welcomeBack: ->
visitCount = @settings.get('count') visitCount = @settings.get('count')
@settings.set 'count', ++visitCount @settings.set 'count', ++visitCount

@ -39,7 +39,7 @@ class app.Router
return return
doc: (context, next) -> doc: (context, next) ->
if doc = app.docs.findBy('slug', context.params.doc) or app.disabledDocs.findBy('slug', context.params.doc) if doc = app.docs.findBySlug(context.params.doc) or app.disabledDocs.findBySlug(context.params.doc)
context.doc = doc context.doc = doc
context.entry = doc.toEntry() context.entry = doc.toEntry()
@triggerRoute 'entry' @triggerRoute 'entry'
@ -48,7 +48,7 @@ class app.Router
return return
type: (context, next) -> type: (context, next) ->
doc = app.docs.findBy 'slug', context.params.doc doc = app.docs.findBySlug(context.params.doc)
if type = doc?.types.findBy 'slug', context.params.type if type = doc?.types.findBy 'slug', context.params.type
context.doc = doc context.doc = doc
@ -59,7 +59,7 @@ class app.Router
return return
entry: (context, next) -> entry: (context, next) ->
doc = app.docs.findBy 'slug', context.params.doc doc = app.docs.findBySlug(context.params.doc)
if entry = doc?.findEntryByPathAndHash(context.params.path, context.hash) if entry = doc?.findEntryByPathAndHash(context.params.path, context.hash)
context.doc = doc context.doc = doc

@ -1,6 +1,9 @@
class app.collections.Docs extends app.Collection class app.collections.Docs extends app.Collection
@model: 'Doc' @model: 'Doc'
findBySlug: (slug) ->
@findBy('slug', slug) or @findBy('slug_without_version', slug)
sort: -> sort: ->
@models.sort (a, b) -> @models.sort (a, b) ->
a = a.name.toLowerCase() a = a.name.toLowerCase()

@ -4,6 +4,8 @@ class app.models.Doc extends app.Model
constructor: -> constructor: ->
super super
@reset @ @reset @
[@slug_without_version, @version] = @slug.split('~v')
@icon = @slug_without_version
@text = @toEntry().text @text = @toEntry().text
reset: (data) -> reset: (data) ->

@ -51,10 +51,11 @@ canICloseTheTab = ->
app.templates.offlineDoc = (doc, status) -> app.templates.offlineDoc = (doc, status) ->
outdated = doc.isOutdated(status) outdated = doc.isOutdated(status)
version = if doc.version then " (#{doc.version})" else ''
html = """ html = """
<tr data-slug="#{doc.slug}"#{if outdated then ' class="_highlight"' else ''}> <tr data-slug="#{doc.slug}"#{if outdated then ' class="_highlight"' else ''}>
<td class="_docs-name _icon-#{doc.slug}">#{doc.name}</td> <td class="_docs-name _icon-#{doc.icon}">#{doc.name}#{version}</td>
<td class="_docs-size">#{Math.ceil(doc.db_size / 100000) / 10} MB</td> <td class="_docs-size">#{Math.ceil(doc.db_size / 100000) / 10} MB</td>
""" """

@ -1,5 +1,5 @@
app.templates.path = (doc, type, entry) -> app.templates.path = (doc, type, entry) ->
html = """<a href="#{doc.fullPath()}" class="_path-item _icon-#{doc.slug}">#{doc.name}</a>""" html = """<a href="#{doc.fullPath()}" class="_path-item _icon-#{doc.icon}">#{doc.name}</a>"""
html += """<a href="#{type.fullPath()}" class="_path-item">#{type.name}</a>""" if type html += """<a href="#{type.fullPath()}" class="_path-item">#{type.name}</a>""" if type
html += """<span class="_path-item">#{$.escape entry.name}</span>""" if entry html += """<span class="_path-item">#{$.escape entry.name}</span>""" if entry
html html

@ -1,7 +1,7 @@
templates = app.templates templates = app.templates
templates.sidebarDoc = (doc, options = {}) -> templates.sidebarDoc = (doc, options = {}) ->
link = """<a href="#{doc.fullPath()}" class="_list-item _icon-#{doc.slug} """ link = """<a href="#{doc.fullPath()}" class="_list-item _icon-#{doc.icon} """
link += if options.disabled then '_list-disabled' else '_list-dir' link += if options.disabled then '_list-disabled' else '_list-dir'
link += """" data-slug="#{doc.slug}" title="#{doc.name}">""" link += """" data-slug="#{doc.slug}" title="#{doc.name}">"""
if options.disabled if options.disabled
@ -22,7 +22,7 @@ templates.sidebarResult = (entry) ->
"""<span class="_list-enable" data-enable="#{entry.doc.slug}">Enable</span>""" """<span class="_list-enable" data-enable="#{entry.doc.slug}">Enable</span>"""
else else
"""<span class="_list-reveal" data-reset-list title="Reveal in list"></span>""" """<span class="_list-reveal" data-reset-list title="Reveal in list"></span>"""
"""<a href="#{entry.fullPath()}" class="_list-item _list-hover _list-result _icon-#{entry.doc.slug}">#{addon}#{$.escape entry.name}</a>""" """<a href="#{entry.fullPath()}" class="_list-item _list-hover _list-result _icon-#{entry.doc.icon}">#{addon}#{$.escape entry.name}</a>"""
templates.sidebarNoResults = -> templates.sidebarNoResults = ->
html = """ <div class="_list-note">No results.</div> """ html = """ <div class="_list-note">No results.</div> """
@ -35,11 +35,13 @@ templates.sidebarPageLink = (count) ->
"""<span class="_list-item _list-pagelink">Show more\u2026 (#{count})</span>""" """<span class="_list-item _list-pagelink">Show more\u2026 (#{count})</span>"""
templates.sidebarLabel = (doc, options = {}) -> templates.sidebarLabel = (doc, options = {}) ->
label = """<label class="_list-item _list-label _icon-#{doc.slug}""" label = """<label class="_list-item _list-label _icon-#{doc.icon}"""
label += ' _list-label-off' unless options.checked label += ' _list-label-off' unless options.checked
label += """"><input type="checkbox" name="#{doc.slug}" class="_list-checkbox" """ label += """"><input type="checkbox" name="#{doc.slug}" class="_list-checkbox" """
label += 'checked' if options.checked label += 'checked' if options.checked
label + ">#{doc.name}</label>" label += ">#{doc.name}"
label += " (#{doc.version})" if doc.version
label + "</label>"
templates.sidebarDisabledList = (options) -> templates.sidebarDisabledList = (options) ->
"""<div class="_disabled-list">#{templates.render 'sidebarDoc', options.docs, disabled: true}</div>""" """<div class="_disabled-list">#{templates.render 'sidebarDoc', options.docs, disabled: true}</div>"""

@ -115,6 +115,23 @@ class App < Sinatra::Application
end end
end end
def find_doc(slug)
settings.docs[slug] || begin
slug = "#{slug}~v"
settings.docs.each do |_slug, _doc|
return _doc if _slug.start_with?(slug)
end
nil
end
end
def user_has_docs?(slug)
docs.include?(slug) || begin
slug = "#{slug}~v"
docs.any? { |_slug| _slug.start_with?(slug) }
end
end
def doc_index_urls def doc_index_urls
docs.each_with_object [] do |slug, result| docs.each_with_object [] do |slug, result|
if doc = settings.docs[slug] if doc = settings.docs[slug]
@ -247,14 +264,14 @@ class App < Sinatra::Application
settings.news_feed settings.news_feed
end end
get %r{\A/(\w+)(\-[\w\-]+)?(/.*)?\z} do |doc, type, rest| get %r{\A/([\w~\.]+)(\-[\w\-]+)?(/.*)?\z} do |doc, type, rest|
return 404 unless @doc = settings.docs[doc] return 404 unless @doc = find_doc(doc)
if rest.nil? if rest.nil?
redirect "/#{doc}#{type}/#{query_string_for_redirection}" redirect "/#{doc}#{type}/#{query_string_for_redirection}"
elsif rest.length > 1 && rest.end_with?('/') elsif rest.length > 1 && rest.end_with?('/')
redirect "/#{doc}#{type}#{rest[0...-1]}#{query_string_for_redirection}" redirect "/#{doc}#{type}#{rest[0...-1]}#{query_string_for_redirection}"
elsif docs.include?(doc) && supports_js_redirection? elsif user_has_docs?(doc) && supports_js_redirection?
redirect_via_js(request.path) redirect_via_js(request.path)
else else
erb :other erb :other

@ -31,31 +31,44 @@ module Docs
Dir["#{root_path}/docs/scrapers/**/*.rb"]. Dir["#{root_path}/docs/scrapers/**/*.rb"].
map { |file| File.basename(file, '.rb') }. map { |file| File.basename(file, '.rb') }.
sort!. sort!.
map(&method(:find)). map { |name| const_get(name.camelize) }.
reject(&:abstract) reject(&:abstract)
end end
def self.find(name) def self.all_versions
all.flat_map(&:versions)
end
def self.find(name, version)
const = name.camelize const = name.camelize
const_get(const) doc = const_get(const)
if version.present?
doc = doc.versions.find { |klass| klass.version == version }
raise DocNotFound.new(%(could not find version "#{version}" for doc "#{name}"), name) unless doc
else
doc = doc.versions.first
end
doc
rescue NameError => error rescue NameError => error
if error.name.to_s == const if error.name.to_s == const
raise DocNotFound.new("failed to locate doc class '#{name}'", name) raise DocNotFound.new(%(could not find doc "#{name}"), name)
else else
raise error raise error
end end
end end
def self.generate_page(name, page_id) def self.generate_page(name, version, page_id)
find(name).store_page(store, page_id) find(name, version).store_page(store, page_id)
end end
def self.generate(name) def self.generate(name, version)
find(name).store_pages(store) find(name, version).store_pages(store)
end end
def self.generate_manifest def self.generate_manifest
Manifest.new(store, all).store Manifest.new(store, all_versions).store
end end
def self.store def self.store

@ -12,12 +12,39 @@ module Docs
subclass.type = type subclass.type = type
end end
def version(version = nil, &block)
return @version if version.nil?
klass = Class.new(self)
klass.class_exec(&block)
klass.name = name
klass.slug = slug
klass.version = version
klass.links = links
@versions ||= []
@versions << klass
klass
end
def version=(value)
@version = value.to_s
end
def versions
@versions.presence || [self]
end
def version?
version.present?
end
def name def name
@name || super.try(:demodulize) @name || super.try(:demodulize)
end end
def slug def slug
@slug || name.try(:downcase) slug = @slug || name.try(:downcase)
version? ? "#{slug}~v#{version}" : slug
end end
def path def path

@ -1,6 +1,6 @@
module Docs module Docs
class Python2 class Python
class EntriesFilter < Docs::EntriesFilter class EntriesV2Filter < Docs::EntriesFilter
REPLACE_TYPES = { REPLACE_TYPES = {
'compiler package' => 'Compiler', 'compiler package' => 'Compiler',
'Cryptographic' => 'Cryptography', 'Cryptographic' => 'Cryptography',

@ -1,6 +1,6 @@
module Docs module Docs
class Python class Python
class EntriesFilter < Docs::EntriesFilter class EntriesV3Filter < Docs::EntriesFilter
REPLACE_TYPES = { REPLACE_TYPES = {
'Cryptographic' => 'Cryptography', 'Cryptographic' => 'Cryptography',
'Custom Interpreters' => 'Interpreters', 'Custom Interpreters' => 'Interpreters',

@ -1,13 +1,8 @@
module Docs module Docs
class Python < FileScraper class Python < FileScraper
self.release = '3.5.1'
self.type = 'sphinx' self.type = 'sphinx'
self.dir = '/Users/Thibaut/DevDocs/Docs/Python' # downloaded from docs.python.org/3/download.html
self.base_url = 'http://docs.python.org/3/'
self.root_path = 'library/index.html' self.root_path = 'library/index.html'
html_filters.push 'python/entries', 'python/clean_html'
options[:only_patterns] = [/\Alibrary\//] options[:only_patterns] = [/\Alibrary\//]
options[:skip] = %w( options[:skip] = %w(
@ -23,5 +18,21 @@ module Docs
&copy; 1990&ndash;2015 Python Software Foundation<br> &copy; 1990&ndash;2015 Python Software Foundation<br>
Licensed under the PSF License. Licensed under the PSF License.
HTML HTML
version '3.5' do
self.release = '3.5.1'
self.dir = '/Users/Thibaut/DevDocs/Docs/Python35' # docs.python.org/3.5/download.html
self.base_url = 'https://docs.python.org/3.5/'
html_filters.push 'python/entries_v3', 'python/clean_html'
end
version '2.7' do
self.release = '2.7.10'
self.dir = '/Users/Thibaut/DevDocs/Docs/Python27' # docs.python.org/2.7/download.html
self.base_url = 'https://docs.python.org/2.7/'
html_filters.push 'python/entries_v2', 'python/clean_html'
end
end end
end end

@ -1,29 +0,0 @@
module Docs
class Python2 < FileScraper
self.name = 'Python 2'
self.slug = 'python2'
self.release = '2.7.10'
self.type = 'sphinx'
self.dir = '/Users/Thibaut/DevDocs/Docs/Python2' # downloaded from docs.python.org/2.7/download.html
self.base_url = 'http://docs.python.org/2.7/'
self.root_path = 'library/index.html'
html_filters.push 'python2/entries', 'python/clean_html'
options[:only_patterns] = [/\Alibrary\//]
options[:skip] = %w(
library/2to3.html
library/formatter.html
library/index.html
library/intro.html
library/undoc.html
library/unittest.mock-examples.html
library/sunau.html)
options[:attribution] = <<-HTML
&copy; 1990&ndash;2015 Python Software Foundation<br>
Licensed under the PSF License.
HTML
end
end

@ -16,11 +16,13 @@ class DocsCLI < Thor
max_length = 0 max_length = 0
Docs.all. Docs.all.
map { |doc| [doc.to_s.demodulize.underscore, doc] }. map { |doc| [doc.to_s.demodulize.underscore, doc] }.
each { |pair| max_length = pair.first.length if pair.first.length > max_length }. to_h.
each { |pair| puts "#{pair.first.rjust max_length + 1}: #{pair.second.base_url.remove %r{\Ahttps?://}}" } each { |name, doc| max_length = name.length if name.length > max_length }.
each { |name, doc| puts "#{name.rjust max_length + 1}: #{doc.versions.map { |v| v.release || '-' }.join(', ')}" }
end end
desc 'page <doc> [path] [--verbose] [--debug]', 'Generate a page (no indexing)' desc 'page <doc> [path] [--version] [--verbose] [--debug]', 'Generate a page (no indexing)'
option :version, type: :string
option :verbose, type: :boolean option :verbose, type: :boolean
option :debug, type: :boolean option :debug, type: :boolean
def page(name, path = '') def page(name, path = '')
@ -34,16 +36,17 @@ class DocsCLI < Thor
Docs.install_report :filter, :request Docs.install_report :filter, :request
end end
if Docs.generate_page(name, path) if Docs.generate_page(name, options[:version], path)
puts 'Done' puts 'Done'
else else
puts "Failed!#{' (try running with --debug for more information)' unless options[:debug]}" puts "Failed!#{' (try running with --debug for more information)' unless options[:debug]}"
end end
rescue Docs::DocNotFound rescue Docs::DocNotFound => error
invalid_doc(name) handle_doc_not_found_error(error)
end end
desc 'generate <doc> [--verbose] [--debug] [--force] [--package]', 'Generate a documentation' desc 'generate <doc> [--version] [--verbose] [--debug] [--force] [--package]', 'Generate a documentation'
option :version, type: :string
option :verbose, type: :boolean option :verbose, type: :boolean
option :debug, type: :boolean option :debug, type: :boolean
option :force, type: :boolean option :force, type: :boolean
@ -66,18 +69,18 @@ class DocsCLI < Thor
return unless yes? 'Proceed? (y/n)' return unless yes? 'Proceed? (y/n)'
end end
if Docs.generate(name) if Docs.generate(name, options[:version])
generate_manifest generate_manifest
if options[:package] if options[:package]
require 'unix_utils' require 'unix_utils'
package_doc(Docs.find(name)) package_doc(Docs.find(name, options[:version]))
end end
puts 'Done' puts 'Done'
else else
puts "Failed!#{' (try running with --debug for more information)' unless options[:debug]}" puts "Failed!#{' (try running with --debug for more information)' unless options[:debug]}"
end end
rescue Docs::DocNotFound rescue Docs::DocNotFound => error
invalid_doc(name) handle_doc_not_found_error(error)
end end
desc 'manifest', 'Create the manifest' desc 'manifest', 'Create the manifest'
@ -86,7 +89,7 @@ class DocsCLI < Thor
puts 'Done' puts 'Done'
end end
desc 'download (<doc> <doc>... | --all)', 'Download documentations' desc 'download (<doc> <doc@version>... | --all)', 'Download documentations'
option :all, type: :boolean option :all, type: :boolean
def download(*names) def download(*names)
require 'unix_utils' require 'unix_utils'
@ -96,10 +99,10 @@ class DocsCLI < Thor
generate_manifest generate_manifest
puts 'Done' puts 'Done'
rescue Docs::DocNotFound => error rescue Docs::DocNotFound => error
invalid_doc(error.name) handle_doc_not_found_error(error)
end end
desc 'package (<doc> <doc>... | --all)', 'Package documentations' desc 'package (<doc> <doc@version>... | --all)', 'Package documentations'
option :all, type: :boolean option :all, type: :boolean
def package(*names) def package(*names)
require 'unix_utils' require 'unix_utils'
@ -108,7 +111,7 @@ class DocsCLI < Thor
docs.each(&method(:package_doc)) docs.each(&method(:package_doc))
puts 'Done' puts 'Done'
rescue Docs::DocNotFound => error rescue Docs::DocNotFound => error
invalid_doc(error.name) handle_doc_not_found_error(error)
end end
desc 'clean', 'Delete documentation packages' desc 'clean', 'Delete documentation packages'
@ -121,7 +124,8 @@ class DocsCLI < Thor
def find_docs(names) def find_docs(names)
names.map do |name| names.map do |name|
Docs.find(name) name, version = name.split('@')
Docs.find(name, version)
end end
end end
@ -133,9 +137,9 @@ class DocsCLI < Thor
end end
end end
def invalid_doc(name) def handle_doc_not_found_error(error)
puts %(ERROR: invalid doc "#{name}".) puts %(ERROR: #{error}.)
puts 'Run "thor docs:list" to see the list of docs.' puts 'Run "thor docs:list" to see the list of docs and versions.'
end end
def download_docs(docs) def download_docs(docs)

@ -80,11 +80,18 @@ class AppTest < MiniTest::Spec
end end
it "works with cookie" do it "works with cookie" do
set_cookie('docs=css/html') set_cookie('docs=css/html~v5')
get '/manifest.appcache' get '/manifest.appcache'
assert last_response.ok? assert last_response.ok?
assert_includes last_response.body, '/css/index.json' assert_includes last_response.body, '/css/index.json?1420139788'
assert_includes last_response.body, '/html/index.json' assert_includes last_response.body, '/html~v5/index.json?1420139791'
end
it "ignores invalid docs in the cookie" do
set_cookie('docs=foo')
get '/manifest.appcache'
assert last_response.ok?
refute_includes last_response.body, 'foo'
end end
it "has the word 'default' when no 'dark' cookie is set" do it "has the word 'default' when no 'dark' cookie is set" do
@ -120,13 +127,26 @@ class AppTest < MiniTest::Spec
describe "/[doc]" do describe "/[doc]" do
it "renders when the doc exists and isn't enabled" do it "renders when the doc exists and isn't enabled" do
set_cookie('docs=css') set_cookie('docs=html~v5')
get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER get '/html~v4/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
assert last_response.ok? assert last_response.ok?
end end
it "redirects via JS cookie when the doc exists and is enabled" do it "redirects via JS cookie when the doc exists and is enabled" do
set_cookie('docs=html') set_cookie('docs=html~v5')
get '/html~v5/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
assert last_response.redirect?
assert_equal 'http://example.org/', last_response['Location']
assert last_response['Set-Cookie'].start_with?("initial_path=%2Fhtml%7Ev5%2F; path=/; expires=")
end
it "renders when the doc exists, has no version in the path, and isn't enabled" do
get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
assert last_response.ok?
end
it "redirects via JS cookie when the doc exists, has no version in the path, and a version is enabled" do
set_cookie('docs=html~v5')
get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER get '/html/', {}, 'HTTP_USER_AGENT' => MODERN_BROWSER
assert last_response.redirect? assert last_response.redirect?
assert_equal 'http://example.org/', last_response['Location'] assert_equal 'http://example.org/', last_response['Location']
@ -140,7 +160,7 @@ class AppTest < MiniTest::Spec
end end
it "returns 404 when the doc doesn't exist" do it "returns 404 when the doc doesn't exist" do
get '/foo/' get '/html~v6/'
assert last_response.not_found? assert last_response.not_found?
end end
@ -157,42 +177,49 @@ class AppTest < MiniTest::Spec
describe "/[doc]-[type]" do describe "/[doc]-[type]" do
it "works when the doc exists" do it "works when the doc exists" do
get '/html~v4-foo-bar_42/'
assert last_response.ok?
assert_includes last_response.body, 'app.DOC = {"name":"HTML","slug":"html~v4"'
end
it "works when the doc has no version in the path and a version exists" do
get '/html-foo-bar_42/' get '/html-foo-bar_42/'
assert last_response.ok? assert last_response.ok?
assert_includes last_response.body, 'app.DOC = {"name":"HTML","slug":"html~v5"'
end end
it "returns 404 when the type is blank" do it "returns 404 when the type is blank" do
get '/html-/' get '/css-/'
assert last_response.not_found? assert last_response.not_found?
end end
it "returns 404 when the type is not alpha-numeric" do it "returns 404 when the type is not alpha-numeric" do
get '/html-foo:bar/' get '/css-foo:bar/'
assert last_response.not_found? assert last_response.not_found?
end end
it "returns 404 when the doc doesn't exist" do it "returns 404 when the doc doesn't exist" do
get '/foo-bar/' get '/html~v6-bar/'
assert last_response.not_found? assert last_response.not_found?
end end
it "redirects with trailing slash" do it "redirects with trailing slash" do
get '/html-foo' get '/css-foo'
assert last_response.redirect? assert last_response.redirect?
assert_equal 'http://example.org/html-foo/', last_response['Location'] assert_equal 'http://example.org/css-foo/', last_response['Location']
get '/html-foo', bar: 'baz' get '/css-foo', bar: 'baz'
assert last_response.redirect? assert last_response.redirect?
assert_equal 'http://example.org/html-foo/?bar=baz', last_response['Location'] assert_equal 'http://example.org/css-foo/?bar=baz', last_response['Location']
end end
end end
describe "/[doc+type]/[path]" do describe "/[doc+type]/[path]" do
it "works when the doc exists" do it "works when the doc exists" do
get '/html/foo' get '/css/foo'
assert last_response.ok? assert last_response.ok?
get '/html-bar/foo' get '/css-bar/foo'
assert last_response.ok? assert last_response.ok?
end end
@ -202,13 +229,13 @@ class AppTest < MiniTest::Spec
end end
it "redirects without trailing slash" do it "redirects without trailing slash" do
get '/html/foo/' get '/css/foo/'
assert last_response.redirect? assert last_response.redirect?
assert_equal 'http://example.org/html/foo', last_response['Location'] assert_equal 'http://example.org/css/foo', last_response['Location']
get '/html/foo/', bar: 'baz' get '/css/foo/', bar: 'baz'
assert last_response.redirect? assert last_response.redirect?
assert_equal 'http://example.org/html/foo?bar=baz', last_response['Location'] assert_equal 'http://example.org/css/foo?bar=baz', last_response['Location']
end end
end end

@ -1 +1 @@
[{"name":"CSS","slug":"css","type":"mdn","release":null,"mtime":1420139788,"db_size":3460507},{"name":"DOM","slug":"dom","type":"mdn","release":null,"mtime":1420139789,"db_size":11399128},{"name":"DOM Events","slug":"dom_events","type":"mdn","release":null,"mtime":1420139790,"db_size":889020},{"name":"HTML","slug":"html","type":"mdn","release":null,"mtime":1420139790,"db_size":1835646},{"name":"HTTP","slug":"http","type":"rfc","release":null,"mtime":1420139790,"db_size":183083},{"name":"JavaScript","slug":"javascript","type":"mdn","release":null,"mtime":1420139791,"db_size":4125477}] [{"name":"CSS","slug":"css","type":"mdn","release":null,"mtime":1420139788,"db_size":3460507},{"name":"DOM","slug":"dom","type":"mdn","release":null,"mtime":1420139789,"db_size":11399128},{"name":"DOM Events","slug":"dom_events","type":"mdn","release":null,"mtime":1420139790,"db_size":889020},{"name":"HTML","slug":"html~v5","type":"mdn","version":"5","mtime":1420139791,"db_size":1835647},{"name":"HTML","slug":"html~v4","type":"mdn","version":"4","mtime":1420139790,"db_size":1835646},{"name":"HTTP","slug":"http","type":"rfc","release":null,"mtime":1420139790,"db_size":183083},{"name":"JavaScript","slug":"javascript","type":"mdn","release":null,"mtime":1420139791,"db_size":4125477}]

@ -44,6 +44,17 @@ class DocsDocTest < MiniTest::Spec
it "returns 'doc' when the class is Docs::Doc" do it "returns 'doc' when the class is Docs::Doc" do
assert_equal 'doc', Docs::Doc.slug assert_equal 'doc', Docs::Doc.slug
end end
it "returns 'doc~v42' when the class is Docs::Doc and its #version is '42'" do
stub(Docs::Doc).version { '42' }
assert_equal 'doc~v42', Docs::Doc.slug
end
it "returns 'foo~v42' when #slug has been set to 'foo' and #version to '42'" do
doc.slug = 'foo'
doc.version = '42'
assert_equal 'foo~v42', doc.slug
end
end end
describe ".slug=" do describe ".slug=" do
@ -53,6 +64,13 @@ class DocsDocTest < MiniTest::Spec
end end
end end
describe ".version=" do
it "stores .version as a string" do
doc.version = 4815162342
assert_equal '4815162342', doc.version
end
end
describe ".release=" do describe ".release=" do
it "stores .release" do it "stores .release" do
doc.release = '1' doc.release = '1'
@ -297,4 +315,37 @@ class DocsDocTest < MiniTest::Spec
end end
end end
end end
describe ".versions" do
it "returns [self] if no versions have been created" do
assert_equal [doc], doc.versions
end
end
describe ".version" do
context "with no args" do
it "returns @version by default" do
doc.version = 'v'
assert_equal 'v', doc.version
end
end
context "with args" do
it "creates a version subclass" do
version = doc.version('4') { self.release = '8'}
assert_equal [version], doc.versions
assert_nil doc.version
assert_nil doc.release
refute doc.version?
assert version.version?
assert_equal '4', version.version
assert_equal '8', version.release
assert_equal 'name', version.name
assert_equal 'type', version.type
end
end
end
end end

@ -21,7 +21,7 @@
<div class="_list"> <div class="_list">
<% unless @doc %> <% unless @doc %>
<% App.docs.each do |slug, doc| %> <% App.docs.each do |slug, doc| %>
<a href="/<%= slug %>/" class="_list-item _icon-<%= slug %> _list-dir"><span class="_list-arrow"></span><%= doc['name'] %></a> <a href="/<%= slug %>/" class="_list-item"><span class="_list-arrow"></span><%= doc['name'] %></a>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

Loading…
Cancel
Save