From 26c046cdfa1d62151c4fe271590b37f97037ec0a Mon Sep 17 00:00:00 2001 From: Rick Okin Date: Tue, 9 Aug 2005 02:20:28 +0000 Subject: [PATCH] move to AR --- Rakefile | 202 +++++++++++++++ app/controllers/admin_controller.rb | 2 +- app/controllers/application.rb | 6 +- app/controllers/file_controller.rb | 2 +- app/controllers/wiki_controller.rb | 7 +- app/models/page.rb | 125 ++++++++- app/models/revision.rb | 121 ++++++++- app/models/system.rb | 4 + app/models/web.rb | 174 ++++++++++++- app/views/wiki/list.rhtml | 2 +- app/views/wiki/page.rhtml | 2 +- config/database.yml | 81 ++++-- config/environment.rb | 2 + config/environments/development.rb | 1 - config/environments/test.rb | 13 +- db/development_structure.sql | 40 +++ db/pages.erbsql | 7 +- db/revisions.erbsql | 15 +- db/schema.postgre.sql | 43 ++++ db/system.erbsql | 4 + db/webs.erbsql | 14 +- lib/author.rb | 18 ++ lib/chunks/test.rb | 2 +- lib/db_structure.rb | 14 + lib/file_yard.rb | 59 +++++ lib/page_set.rb | 89 +++++++ lib/wiki.rb | 96 +++++++ lib/wiki_content.rb | 206 +++++++++++++++ lib/wiki_words.rb | 23 ++ rakefile.rb | 134 ---------- script/console | 23 ++ script/create_db | 2 +- script/server | 100 ++------ test/fixtures/pages.yml | 56 +++- test/fixtures/revisions.yml | 84 +++++- test/fixtures/system.yml | 2 + test/fixtures/webs.yml | 13 +- test/functional/admin_controller_test.rb | 113 ++++---- test/functional/application_test.rb | 23 +- test/functional/file_controller_test.rb | 39 +-- test/functional/routes_test.rb | 2 +- test/functional/wiki_controller_test.rb | 242 ++++++++---------- test/test_helper.rb | 90 ++++++- test/unit/diff_test.rb | 4 +- test/unit/file_yard_test.rb | 4 +- test/unit/page_test.rb | 77 +++++- test/unit/redcloth_for_tex_test.rb | 4 +- test/unit/revision_test.rb | 312 ++++++++++++++++++++++- test/unit/uri_test.rb | 2 +- test/unit/web_test.rb | 157 +++++++++++- test/unit/wiki_words_test.rb | 4 +- 51 files changed, 2345 insertions(+), 516 deletions(-) create mode 100644 Rakefile create mode 100644 app/models/system.rb create mode 100644 db/development_structure.sql create mode 100644 db/schema.postgre.sql create mode 100644 db/system.erbsql create mode 100644 lib/author.rb create mode 100644 lib/file_yard.rb create mode 100644 lib/page_set.rb create mode 100644 lib/wiki.rb create mode 100644 lib/wiki_content.rb create mode 100644 lib/wiki_words.rb create mode 100644 script/console create mode 100644 test/fixtures/system.yml diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..5407e140 --- /dev/null +++ b/Rakefile @@ -0,0 +1,202 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +$VERBOSE = nil +TEST_CHANGES_SINCE = Time.now - 600 + +desc "Run all the tests on a fresh test database" +task :default => [ :test_units, :test_functional ] + + +desc 'Require application environment.' +task :environment do + unless defined? RAILS_ROOT + require File.dirname(__FILE__) + '/config/environment' + end +end + +desc "Generate API documentation, show coding stats" +task :doc => [ :appdoc, :stats ] + + +# Look up tests for recently modified sources. +def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) + FileList[source_pattern].map do |path| + if File.mtime(path) > touched_since + test = "#{test_path}/#{File.basename(path, '.rb')}_test.rb" + test if File.exists?(test) + end + end.compact +end + +desc 'Test recent changes.' +Rake::TestTask.new(:recent => [ :clone_structure_to_test ]) do |t| + since = TEST_CHANGES_SINCE + touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + + recent_tests('app/models/*.rb', 'test/unit', since) + + recent_tests('app/controllers/*.rb', 'test/functional', since) + + t.libs << 'test' + t.verbose = true + t.test_files = touched.uniq +end +task :test_recent => [ :clone_structure_to_test ] + +desc "Run the unit tests in test/unit" +Rake::TestTask.new("test_units") { |t| + t.libs << "test" + t.pattern = 'test/unit/**/*_test.rb' + t.verbose = true +} +task :test_units => [ :clone_structure_to_test ] + +desc "Run the functional tests in test/functional" +Rake::TestTask.new("test_functional") { |t| + t.libs << "test" + t.pattern = 'test/functional/**/*_test.rb' + t.verbose = true +} +task :test_functional => [ :clone_structure_to_test ] + +desc "Generate documentation for the application" +Rake::RDocTask.new("appdoc") { |rdoc| + rdoc.rdoc_dir = 'doc/app' + rdoc.title = "Rails Application Documentation" + rdoc.options << '--line-numbers --inline-source' + rdoc.rdoc_files.include('doc/README_FOR_APP') + rdoc.rdoc_files.include('app/**/*.rb') +} + +desc "Generate documentation for the Rails framework" +Rake::RDocTask.new("apidoc") { |rdoc| + rdoc.rdoc_dir = 'doc/api' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.title = "Rails Framework Documentation" + rdoc.options << '--line-numbers --inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') + rdoc.rdoc_files.include('vendor/rails/activerecord/README') + rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') + rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') + rdoc.rdoc_files.include('vendor/rails/actionpack/README') + rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionmailer/README') + rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb') + rdoc.rdoc_files.include('vendor/rails/activesupport/README') + rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') +} + +desc "Report code statistics (KLOCs, etc) from the application" +task :stats => [ :environment ] do + require 'code_statistics' + CodeStatistics.new( + ["Helpers", "app/helpers"], + ["Controllers", "app/controllers"], + ["APIs", "app/apis"], + ["Components", "components"], + ["Functionals", "test/functional"], + ["Models", "app/models"], + ["Units", "test/unit"] + ).to_s +end + +desc "Recreate the test databases from the development structure" +task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') + IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| + ActiveRecord::Base.connection.execute(table) + end + when "postgresql" + ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] + ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] + ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] + `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` + when "sqlite", "sqlite3" + `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + else + raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" + end +end + +desc "Dump the database structure to a SQL file" +task :db_structure_dump => :environment do + abcs = ActiveRecord::Base.configurations + case abcs[RAILS_ENV]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) + File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } + when "postgresql" + ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] + ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] + ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] + `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}` + when "sqlite", "sqlite3" + `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` + else + raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" + end +end + +desc "Empty the test database" +task :purge_test_database => :environment do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"]) + when "postgresql" + ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] + ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] + ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] + `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` + `createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` + when "sqlite","sqlite3" + File.delete(abcs["test"]["dbfile"]) if File.exist?(abcs["test"]["dbfile"]) + when "sqlserver" + dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + else + raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" + end +end + +desc "Clears all *.log files in log/" +task :clear_logs => :environment do + FileList["log/*.log"].each do |log_file| + f = File.open(log_file, "w") + f.close + end +end + +desc "Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x" +task :migrate => :environment do + ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil) +end \ No newline at end of file diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 6bca0f15..a52b2005 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -46,7 +46,6 @@ class AdminController < ApplicationController end def edit_web - system_password = @params['system_password'] if system_password # form submitted @@ -67,6 +66,7 @@ class AdminController < ApplicationController flash[:info] = "Web '#{@params['address']}' was successfully updated" redirect_home(@params['address']) rescue Instiki::ValidationError => e + logger.warn e.message @error = e.message # and re-render the same template again end diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 1e2af4e6..7f608e49 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -10,12 +10,12 @@ class ApplicationController < ActionController::Base # a global variable is used here because Rails reloads controller and model classes in the # development environment; therefore, storing it as a class variable does not work # class variable is, anyway, not much different from a global variable - $instiki_wiki_service = the_wiki + #$instiki_wiki_service = the_wiki logger.debug("Wiki service: #{the_wiki.to_s}") end def self.wiki - $instiki_wiki_service + Wiki.new end protected @@ -146,7 +146,7 @@ class ApplicationController < ActionController::Base end def wiki - $instiki_wiki_service + self.class.wiki end def needs_authorization?(action) diff --git a/app/controllers/file_controller.rb b/app/controllers/file_controller.rb index bf0ee964..e67ee3a2 100644 --- a/app/controllers/file_controller.rb +++ b/app/controllers/file_controller.rb @@ -78,7 +78,7 @@ class FileController < ApplicationController return false end - unless @web.allow_uploads + unless @web.allow_uploads? render_text 'File uploads are blocked by the webmaster', '403 Forbidden' return false end diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 43d55b2e..c274e209 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -1,4 +1,3 @@ -require 'application' require 'fileutils' require 'redcloth_for_tex' require 'parsedate' @@ -156,7 +155,7 @@ class WikiController < ApplicationController end def published - if @web.published + if @web.published? @page = wiki.read_page(@web_name, @page_name || 'HomePage') else redirect_home @@ -270,7 +269,7 @@ class WikiController < ApplicationController end def export_web_to_tex(file_path) - @tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web) + @tex_content = table_of_contents(@web.page('HomePage').content, render_tex_web) File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) } end @@ -342,7 +341,7 @@ class WikiController < ApplicationController end def rss_with_content_allowed? - @web.password.nil? or @web.published + @web.password.nil? or @web.published? end def truncate(text, length = 30, truncate_string = '...') diff --git a/app/models/page.rb b/app/models/page.rb index 5f85c8ab..6baa5802 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -1,4 +1,125 @@ class Page < ActiveRecord::Base belongs_to :web - has_many :pages -end \ No newline at end of file + has_many :revisions, :order => 'number' + has_one :current_revision, :class_name => 'Revision', :order => 'number DESC' + + def revise(content, created_at, author) + revisions_size = new_record? ? 0 : revisions.size + if (revisions_size > 0) and content == current_revision.content + raise Instiki::ValidationError.new( + "You have tried to save page '#{name}' without changing its content") + end + + author = Author.new(author.to_s) unless author.is_a?(Author) + + # Try to render content to make sure that markup engine can take it, + # before addin a revision to the page + Revision.new(:page => self, :content => content, :created_at => created_at, :author => author).force_rendering + + # A user may change a page, look at it and make some more changes - several times. + # Not to record every such iteration as a new revision, if the previous revision was done + # by the same author, not more than 30 minutes ago, then update the last revision instead of + # creating a new one + if (revisions_size > 0) && continous_revision?(created_at, author) + current_revision.update_attributes(:created_at => created_at, :content => content) + else + Revision.create(:page => self, :content => content, :created_at => created_at, :author => author) + end + + self.created_at = created_at + save + web.refresh_pages_with_references(name) if revisions_size == 0 + + self + end + + def rollback(revision_number, created_at, author_ip = nil) + roll_back_revision = Revision.find(:first, :conditions => ['page_id = ? AND number = ?', id, revision_number]) + revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip)) + end + + def revisions? + revisions.size > 1 + end + + def revised_on + created_on + end + + def in_category?(cat) + cat.nil? || cat.empty? || categories.include?(cat) + end + + def categories + display_content.find_chunks(Category).map { |cat| cat.list }.flatten + end + + def authors + revisions.collect { |rev| rev.author } + end + + def references + web.select.pages_that_reference(name) + end + + def linked_from + web.select.pages_that_link_to(name) + end + + def included_from + web.select.pages_that_include(name) + end + + # Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page". + def plain_name + web.brackets_only? ? name : WikiWords.separate(name) + end + + # used to build chunk ids. + #def id + # @id ||= name.unpack('H*').first + #end + + def link(options = {}) + web.make_link(name, nil, options) + end + + def author_link(options = {}) + web.make_link(author, nil, options) + end + + LOCKING_PERIOD = 30.minutes + + def lock(time, locked_by) + update_attributes(:locked_at => time, :locked_by => locked_by) + end + + def lock_duration(time) + ((time - locked_at) / 60).to_i unless locked_at.nil? + end + + def unlock + update_attribute(:locked_at, nil) + end + + def locked?(comparison_time) + locked_at + LOCKING_PERIOD > comparison_time unless locked_at.nil? + end + + private + + def continous_revision?(created_at, author) + current_revision.author == author && current_revision.created_at + 30.minutes > created_at + end + + # Forward method calls to the current revision, so the page responds to all revision calls + def method_missing(method_id, *args, &block) + method_name = method_id.to_s + # Perform a hand-off to AR::Base#method_missing + if @attributes.include?(method_name) or md = /(=|\?|_before_type_cast)$/.match(method_name) + super(method_id, *args, &block) + else + current_revision.send(method_id) + end + end +end diff --git a/app/models/revision.rb b/app/models/revision.rb index 017b3543..ca886245 100644 --- a/app/models/revision.rb +++ b/app/models/revision.rb @@ -1,3 +1,122 @@ +require 'diff' class Revision < ActiveRecord::Base belongs_to :page -end \ No newline at end of file + composed_of :author, :mapping => [ %w(author name), %w(ip ip) ] + + def created_on + created_at.to_date + end + + def pretty_created_at + # Must use DateTime because Time doesn't support %e on at least some platforms + DateTime.new( + created_at.year, created_at.mon, created_at.day, created_at.hour, created_at.min + ).strftime "%B %e, %Y %H:%M" + end + + # todo: drop next_revision, previuous_revision and number from here - unused code + def next_revision + Revision.find_by_number_and_page_id(number+1, page_id) + end + + def previous_revision + @previous_revions ||= number > 0 ? Revision.find_by_number_and_page_id(number-1, page_id) : nil + end + + # Returns an array of all the WikiIncludes present in the content of this revision. + def wiki_includes + unless @wiki_includes_cache + chunks = display_content.find_chunks(Include) + @wiki_includes_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq + end + @wiki_includes_cache + end + + # Returns an array of all the WikiReferences present in the content of this revision. + def wiki_references + unless @wiki_references_cache + chunks = display_content.find_chunks(WikiChunk::WikiReference) + @wiki_references_cache = chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq + end + @wiki_references_cache + end + + # Returns an array of all the WikiWords present in the content of this revision. + def wiki_words + unless @wiki_words_cache + wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink) + @wiki_words_cache = wiki_chunks.map { |c| ( c.escaped? ? nil : c.page_name ) }.compact.uniq + end + @wiki_words_cache + end + + # Returns an array of all the WikiWords present in the content of this revision. + # that already exists as a page in the web. + def existing_pages + wiki_words.select { |wiki_word| page.web.page(wiki_word) } + end + + # Returns an array of all the WikiWords present in the content of this revision + # that *doesn't* already exists as a page in the web. + def unexisting_pages + wiki_words - existing_pages + end + + # Explicit check for new type of display cache with chunks_by_type method. + # Ensures new version works with older snapshots. + def display_content + unless @display_cache && @display_cache.respond_to?(:chunks_by_type) + @display_cache = WikiContent.new(self) + @display_cache.render! + end + @display_cache + end + + def display_diff + previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content + end + + def clear_display_cache + @wiki_words_cache = @published_cache = @display_cache = @wiki_includes_cache = + @wiki_references_cache = nil + end + + def display_published + unless @published_cache && @published_cache.respond_to?(:chunks_by_type) + @published_cache = WikiContent.new(self, {:mode => :publish}) + @published_cache.render! + end + @published_cache + end + + def display_content_for_export + WikiContent.new(self, {:mode => :export} ).render! + end + + def force_rendering + begin + display_content.render! + rescue => e + logger.error "Failed rendering page #{@name}" + logger.error e + message = e.message + # substitute content with an error message + self.content = <<-EOL +

Markup engine has failed to render this page, raising the following error:

+

#{message}

+
#{self.content}
+ EOL + clear_display_cache + raise e + end + end + + protected + before_create :set_revision_number + after_create :force_rendering + after_save :clear_display_cache + + def set_revision_number + self.number = self.class.count(['page_id = ?', page_id]) + 1 + end +end diff --git a/app/models/system.rb b/app/models/system.rb new file mode 100644 index 00000000..7ac1ad08 --- /dev/null +++ b/app/models/system.rb @@ -0,0 +1,4 @@ +class System < ActiveRecord::Base + set_table_name 'system' + validates_presence_of :password +end \ No newline at end of file diff --git a/app/models/web.rb b/app/models/web.rb index 2c31bf8f..5ca077d5 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -1,3 +1,173 @@ +require 'cgi' + class Web < ActiveRecord::Base - has_many :pages -end \ No newline at end of file + has_many :pages#, :include => [:current_revision, :web] + + def wiki + Wiki.new + end + + def file_yard + @file_yard ||= FileYard.new("#{Wiki.storage_path}/#{address}", max_upload_size) + end + + def settings_changed?(markup, safe_mode, brackets_only) + self.markup != markup || + self.safe_mode != safe_mode || + self.brackets_only != brackets_only + end + + def add_page(name, content, created_at, author) + page = page(name) || Page.new(:web => self, :name => name) + page.revise(content, created_at, author) + end + + def authors + select.authors + end + + def categories + select.map { |page| page.categories }.flatten.uniq.sort + end + + def page(name) + pages.find(:first, :conditions => ['name = ?', name]) + end + + def has_page?(name) + Page.count(['web_id = ? AND name = ?', id, name]) > 0 + end + + def has_file?(name) + wiki.file_yard(self).has_file?(name) + end + + def markup + read_attribute('markup').to_sym + end + + def make_file_link(mode, name, text, base_url) + link = CGI.escape(name) + case mode + when :export + if has_file?(name) then "#{text}" + else "#{text}" end + when :publish + if has_file?(name) then "#{text}" + else "#{text}" end + else + if has_file?(name) + "#{text}" + else + "#{text}?" + end + end + end + + # Create a link for the given page name and link text based + # on the render mode in options and whether the page exists + # in the this web. + # The links a relative, and will work only if displayed on another WikiPage. + # It should not be used in menus, templates and such - instead, use link_to_page helper + def make_link(name, text = nil, options = {}) + text = CGI.escapeHTML(text || WikiWords.separate(name)) + mode = options[:mode] || :show + base_url = options[:base_url] || '..' + link_type = options[:link_type] || :show + case link_type.to_sym + when :show + make_page_link(mode, name, text, base_url) + when :file + make_file_link(mode, name, text, base_url) + when :pic + make_pic_link(mode, name, text, base_url) + else + raise "Unknown link type: #{link_type}" + end + end + + def make_page_link(mode, name, text, base_url) + link = CGI.escape(name) + case mode.to_sym + when :export + if has_page?(name) then %{#{text}} + else %{#{text}} end + when :publish + if has_page?(name) then %{#{text}} + else %{#{text}} end + else + if has_page?(name) + %{#{text}} + else + %{#{text}?} + end + end + end + + def make_pic_link(mode, name, text, base_url) + link = CGI.escape(name) + case mode.to_sym + when :export + if has_file?(name) then %{#{text}} + else %{#{text}} end + when :publish + if has_file?(name) then %{#{text}} + else %{#{text}} end + else + if has_file?(name) then %{#{text}} + else %{#{text}?} end + end + end + + # Clears the display cache for all the pages with references to + def refresh_pages_with_references(page_name) + #select.pages_that_reference(page_name).each { |page| + # page.revisions.each { |revision| revision.clear_display_cache } + #} + end + + def refresh_revisions + select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } } + end + + def remove_pages(pages_to_be_removed) + pages_to_be_removed.each { |p| p.destroy } + end + + def revised_on + select.most_recent_revision + end + + def select(&condition) + PageSet.new(self, pages, condition) + end + + private + + # Returns an array of all the wiki words in any current revision + def wiki_words + pages.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq + end + + # Returns an array of all the page names on this web + def page_names + pages.map { |p| p.name } + end + + protected + before_save :sanitize_markup + before_validation :validate_address + validates_uniqueness_of :address + validates_length_of :color, :in => 3..6 + + def sanitize_markup + self.markup = markup.to_s + end + + def validate_address + unless address == CGI.escape(address) + self.errors.add(:address, 'should contain only valid URI characters') + raise Instiki::ValidationError.new("#{self.class.human_attribute_name('address')} #{errors.on(:address)}") + end + end +end diff --git a/app/views/wiki/list.rhtml b/app/views/wiki/list.rhtml index 34a93798..d891f5a2 100644 --- a/app/views/wiki/list.rhtml +++ b/app/views/wiki/list.rhtml @@ -17,7 +17,7 @@ <% end %> -<% if @web.count_pages %> +<% if @web.count_pages? %> <% total_chars = @pages_in_category.characters %>

All content: <%= total_chars %> chars / <%= sprintf("%-.1f", (total_chars / 2275 )) %> pages

<% end %> diff --git a/app/views/wiki/page.rhtml b/app/views/wiki/page.rhtml index 8e9bc474..28763b78 100644 --- a/app/views/wiki/page.rhtml +++ b/app/views/wiki/page.rhtml @@ -25,7 +25,7 @@ <%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %> by <%= @page.author_link %> <%= "(#{@page.author.ip})" if @page.author.respond_to?(:ip) %> - <% if @web.count_pages %> + <% if @web.count_pages? %> <% total_chars = @page.content.length %> (<%= total_chars %> characters / <%= sprintf("%-.1f", (total_chars / 2275 rescue 0)) %> pages) <% end %> diff --git a/config/database.yml b/config/database.yml index 1233b7e3..3dd03157 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,30 +1,65 @@ -# SQLite is enabled by default. Remember to change the dbfile path. production: adapter: sqlite3 - dbfile: /tmp/instiki_prod.db - -# Uncomment this section for MySQL: -# production: -# adapter: mysql -# host: localhost -# database: instiki -# username: instiki -# password: pass - -# Uncomment this section for PostgreSQL: -# production: -# adapter: postgresql -# host: localhost -# database: instiki -# username: instiki -# password: pass - -# The following settings are only used for testing and development. + dbfile: db/prod.db development: adapter: sqlite3 - dbfile: /tmp/instiki_dev.db - + dbfile: db/dev.db test: adapter: sqlite3 - dbfile: /tmp/instiki_test.db + dbfile: db/test.db + +# Sample Sqlite 2 Config +#production: +# adapter: sqlite +# dbfile: db/prod.db +#development: +# adapter: sqlite +# dbfile: db/dev.db +#test: +# adapter: sqlite +# dbfile: db/test.db + +# Sample Postgresql Config +#production: +# adapter: postgresql +# host: localhost +# database: instiki_prod +# username: user +# password: pass +# +#development: +# adapter: postgresql +# host: localhost +# database: instiki_dev +# username: user +# password: pass +# +#test: +# adapter: postgresql +# host: localhost +# database: instiki_test +# username: user +# password: pass + +# Sample MySQL Config +#production: +# adapter: mysql +# host: localhost +# database: instiki_prod +# username: user +# password: pass +# +#development: +# adapter: mysql +# host: localhost +# database: instiki_dev +# username: user +# password: pass +# +#test: +# adapter: mysql +# host: localhost +# database: instiki_test +# username: user +# password: pass diff --git a/config/environment.rb b/config/environment.rb index 589025c8..509f4ac0 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -60,6 +60,8 @@ require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails") require 'active_support' require 'active_record' require 'action_controller' +require 'action_mailer' +require 'action_web_service' # Environment-specific configuration. require_dependency "environments/#{RAILS_ENV}" diff --git a/config/environments/development.rb b/config/environments/development.rb index 61ce9fec..3f0868e6 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -2,4 +2,3 @@ Dependencies.mechanism = :require ActionController::Base.consider_all_requests_local = true ActionController::Base.perform_caching = false BREAKPOINT_SERVER_PORT = 42531 -$instiki_debug_logging = true diff --git a/config/environments/test.rb b/config/environments/test.rb index fac509a1..a5813f5d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,15 +1,4 @@ Dependencies.mechanism = :require ActionController::Base.consider_all_requests_local = true ActionController::Base.perform_caching = false - -require 'fileutils' -FileUtils.mkdir_p(RAILS_ROOT + "/log") - -unless defined? TEST_LOGGER - timestamp = Time.now.strftime('%Y%m%d%H%M%S') - log_name = RAILS_ROOT + "/log/instiki_test.#{timestamp}.log" - $stderr.puts "To see the Rails log:\n less #{log_name}" - - TEST_LOGGER = ActionController::Base.logger = Logger.new(log_name) - $instiki_debug_logging = true -end +FileYard.restrict_upload_access = false \ No newline at end of file diff --git a/db/development_structure.sql b/db/development_structure.sql new file mode 100644 index 00000000..61d49c31 --- /dev/null +++ b/db/development_structure.sql @@ -0,0 +1,40 @@ +CREATE TABLE pages ( + id INTEGER PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + web_id INTEGER NOT NULL, + locked_by VARCHAR(60), + name VARCHAR(60), + locked_at DATETIME +); +CREATE TABLE revisions ( + id INTEGER PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + page_id INTEGER NOT NULL, + content TEXT NOT NULL, + author VARCHAR(60), + ip VARCHAR(60), + number INTEGER +); +CREATE TABLE system ( + id INTEGER PRIMARY KEY, + 'password' VARCHAR(60) +); +CREATE TABLE webs ( + id INTEGER PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + name VARCHAR(60) NOT NULL, + address VARCHAR(60) NOT NULL, + 'password' VARCHAR(60), + additional_style VARCHAR(255), + allow_uploads INTEGER DEFAULT '1', + published INTEGER DEFAULT '0', + count_pages INTEGER DEFAULT '0', + markup VARCHAR(50) DEFAULT 'textile', + color VARCHAR(6) DEFAULT '008B26', + max_upload_size INTEGER DEFAULT 100, + safe_mode INTEGER DEFAULT '0', + brackets_only INTEGER DEFAULT '0' +); diff --git a/db/pages.erbsql b/db/pages.erbsql index afc57484..5c2dd6d5 100644 --- a/db/pages.erbsql +++ b/db/pages.erbsql @@ -2,5 +2,8 @@ CREATE TABLE pages ( id <%= @pk %>, created_at <%= @datetime %> NOT NULL, updated_at <%= @datetime %> NOT NULL, - web_id INTEGER NOT NULL -) <%= create_options %>; + web_id INTEGER NOT NULL, + locked_by VARCHAR(60), + name VARCHAR(60), + locked_at <%= @datetime %> +) <%= create_options %>; \ No newline at end of file diff --git a/db/revisions.erbsql b/db/revisions.erbsql index 1639cf2e..28034555 100644 --- a/db/revisions.erbsql +++ b/db/revisions.erbsql @@ -1,7 +1,10 @@ CREATE TABLE revisions ( - id <%= @pk %>, - created_at <%= @datetime %> NOT NULL, - updated_at <%= @datetime %> NOT NULL, - page_id INTEGER NOT NULL, - content TEXT NOT NULL -) <%= create_options %>; + id <%= @pk %>, + created_at <%= @datetime %> NOT NULL, + updated_at <%= @datetime %> NOT NULL, + page_id INTEGER NOT NULL, + content TEXT NOT NULL, + author VARCHAR(60), + ip VARCHAR(60), + number INTEGER +) <%= create_options %>; \ No newline at end of file diff --git a/db/schema.postgre.sql b/db/schema.postgre.sql new file mode 100644 index 00000000..5191d9e4 --- /dev/null +++ b/db/schema.postgre.sql @@ -0,0 +1,43 @@ +CREATE TABLE pages ( + id serial primary key, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + web_id integer NOT NULL, + locked_by character varying(60), + name character varying(60), + locked_at timestamp without time zone +); + +CREATE TABLE revisions ( + id serial primary key, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + page_id integer NOT NULL, + content text NOT NULL, + author character varying(60), + ip character varying(60), + number integer +); + +CREATE TABLE system ( + id serial primary key, + "password" character varying(60) +); + +CREATE TABLE webs ( + id serial primary key, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + name character varying(60) NOT NULL, + address character varying(60) NOT NULL, + "password" character varying(60), + additional_style character varying(255), + allow_uploads boolean DEFAULT true, + published boolean DEFAULT false, + count_pages boolean DEFAULT false, + markup character varying(50) DEFAULT 'textile'::character varying, + color character varying(6) DEFAULT '008B26'::character varying, + max_upload_size integer DEFAULT 100, + safe_mode boolean DEFAULT false, + brackets_only boolean DEFAULT false +); diff --git a/db/system.erbsql b/db/system.erbsql new file mode 100644 index 00000000..e0a55a33 --- /dev/null +++ b/db/system.erbsql @@ -0,0 +1,4 @@ +CREATE TABLE system ( + id <%= @pk %>, + <%= db_quote('password') %> VARCHAR(60) +) <%= create_options %>; \ No newline at end of file diff --git a/db/webs.erbsql b/db/webs.erbsql index bbb9758c..c29728cd 100644 --- a/db/webs.erbsql +++ b/db/webs.erbsql @@ -3,5 +3,15 @@ CREATE TABLE webs ( created_at <%= @datetime %> NOT NULL, updated_at <%= @datetime %> NOT NULL, name VARCHAR(60) NOT NULL, - address VARCHAR(60) NOT NULL -) <%= create_options %>; + address VARCHAR(60) NOT NULL, + <%= db_quote('password') %> VARCHAR(60), + additional_style VARCHAR(255), + allow_uploads <%= @boolean %> DEFAULT '1', + published <%= @boolean %> DEFAULT '0', + count_pages <%= @boolean %> DEFAULT '0', + markup VARCHAR(50) DEFAULT 'textile', + color VARCHAR(6) DEFAULT '008B26', + max_upload_size INTEGER DEFAULT 100, + safe_mode <%= @boolean %> DEFAULT '0', + brackets_only <%= @boolean %> DEFAULT '0' +) <%= create_options %>; \ No newline at end of file diff --git a/lib/author.rb b/lib/author.rb new file mode 100644 index 00000000..be8a5cf7 --- /dev/null +++ b/lib/author.rb @@ -0,0 +1,18 @@ +class Author < String + attr_accessor :ip + attr_reader :name + def initialize(name, ip = nil) + @ip = ip + super(name) + end + + def name=(value) + self.gsub!(/.+/, value) + end + + alias_method :name, :to_s + + def <=>(other) + name <=> other.to_s + end +end \ No newline at end of file diff --git a/lib/chunks/test.rb b/lib/chunks/test.rb index edf77d14..73af8142 100644 --- a/lib/chunks/test.rb +++ b/lib/chunks/test.rb @@ -4,7 +4,7 @@ class ChunkTest < Test::Unit::TestCase # Asserts a number of tests for the given type and text. def match(type, test_text, expected) - pattern = type.pattern + pattern = type.pattern assert_match(pattern, test_text) pattern =~ test_text # Previous assertion guarantees match chunk = type.new($~) diff --git a/lib/db_structure.rb b/lib/db_structure.rb index c7b58c33..09c212da 100644 --- a/lib/db_structure.rb +++ b/lib/db_structure.rb @@ -6,6 +6,17 @@ def create_options end end +def db_quote(column) + case @db + when 'postgresql' + return "\"#{column}\"" + when 'sqlite', 'sqlite3' + return "'#{column}'" + when 'mysql' + return "`#{column}`" + end +end + def db_structure(db) db.downcase! @db = db @@ -13,12 +24,15 @@ def db_structure(db) when 'postgresql' @pk = 'SERIAL PRIMARY KEY' @datetime = 'TIMESTAMP' + @boolean = "BOOLEAN" when 'sqlite', 'sqlite3' @pk = 'INTEGER PRIMARY KEY' @datetime = 'DATETIME' + @boolean = "INTEGER" when 'mysql' @pk = 'INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY' @datetime = 'DATETIME' + @boolean = "TINYINT" @mysql_engine = 'InnoDB' else raise "Unknown db type #{db}" diff --git a/lib/file_yard.rb b/lib/file_yard.rb new file mode 100644 index 00000000..f9402c17 --- /dev/null +++ b/lib/file_yard.rb @@ -0,0 +1,59 @@ +require 'fileutils' +require 'instiki_errors' + +class FileYard + cattr_accessor :restrict_upload_access + restrict_upload_access = true + attr_reader :files_path + + def initialize(files_path, max_upload_size) + @files_path, @max_upload_size = files_path, max_upload_size + FileUtils.mkdir_p(@files_path) unless File.exist?(@files_path) + @files = Dir["#{@files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact + end + + def upload_file(name, io) + sanitize_file_name(name) + if io.kind_of?(Tempfile) + io.close + check_upload_size(io.size) + File.chmod(600, file_path(name)) if File.exists? file_path(name) + FileUtils.mv(io.path, file_path(name)) + else + content = io.read + check_upload_size(content.length) + File.open(file_path(name), 'wb') { |f| f.write(content) } + end + # just in case, restrict read access and prohibit write access to the uploaded file + FileUtils.chmod(0440, file_path(name)) if restrict_upload_access + end + + def files + Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact + end + + def has_file?(name) + files.include?(name) + end + + def file_path(name) + "#{files_path}/#{name}" + end + + SANE_FILE_NAME = /[a-zA-Z0-9\-_\. ]{1,255}/ + + def sanitize_file_name(name) + unless name =~ SANE_FILE_NAME or name == '.' or name == '..' + raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" + + "Only latin characters, digits, dots, underscores, dashes and spaces are accepted.") + end + end + + def check_upload_size(actual_upload_size) + if actual_upload_size > @max_upload_size.kilobytes + raise Instiki::ValidationError.new("Uploaded file size (#{actual_upload_size / 1024} " + + "kbytes) exceeds the maximum (#{@max_upload_size} kbytes) set for this wiki") + end + end + +end diff --git a/lib/page_set.rb b/lib/page_set.rb new file mode 100644 index 00000000..c50abe80 --- /dev/null +++ b/lib/page_set.rb @@ -0,0 +1,89 @@ +# Container for a set of pages with methods for manipulation. + +class PageSet < Array + attr_reader :web + + def initialize(web, pages = nil, condition = nil) + @web = web + # if pages is not specified, make a list of all pages in the web + if pages.nil? + super(web.pages) + # otherwise use specified pages and condition to produce a set of pages + elsif condition.nil? + super(pages) + else + super(pages.select { |page| condition[page] }) + end + end + + def most_recent_revision + self.map { |page| page.created_at }.max || Time.at(0) + end + + + def by_name + PageSet.new(@web, sort_by { |page| page.name }) + end + + alias :sort :by_name + + def by_revision + PageSet.new(@web, sort_by { |page| page.created_at }).reverse + end + + def pages_that_reference(page_name) + self.select { |page| page.wiki_references.include?(page_name) } + end + + def pages_that_link_to(page_name) + self.select { |page| page.wiki_words.include?(page_name) } + end + + def pages_that_include(page_name) + self.select { |page| page.wiki_includes.include?(page_name) } + end + + def pages_authored_by(author) + self.select { |page| page.authors.include?(author) } + end + + def characters + self.inject(0) { |chars,page| chars += page.content.size } + end + + # Returns all the orphaned pages in this page set. That is, + # pages in this set for which there is no reference in the web. + # The HomePage and author pages are always assumed to have + # references and so cannot be orphans + # Pages that refer to themselves and have no links from outside are oprphans. + def orphaned_pages + never_orphans = web.select.authors + ['HomePage'] + self.select { |page| + if never_orphans.include? page.name + false + else + references = pages_that_reference(page.name) + references.empty? or references == [page] + end + } + end + + # Returns all the wiki words in this page set for which + # there are no pages in this page set's web + def wanted_pages + wiki_words - web.select.names + end + + def names + self.map { |page| page.name } + end + + def wiki_words + self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq + end + + def authors + self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort + end + +end diff --git a/lib/wiki.rb b/lib/wiki.rb new file mode 100644 index 00000000..50093193 --- /dev/null +++ b/lib/wiki.rb @@ -0,0 +1,96 @@ +class Wiki + cattr_accessor :storage_path, :logger + self.storage_path = "#{RAILS_ROOT}/storage/" + self.logger = RAILS_DEFAULT_LOGGER + + def authenticate(password) + password == (system.password || 'instiki') + end + + def create_web(name, address, password = nil) + @webs = nil + Web.create(:name => name, :address => address, :password => password) + end + + def delete_web(address) + web = Web.find_by_address(address) + unless web.nil? + web.destroy + @webs = nil + end + end + + def file_yard(web) + web.file_yard + end + + def edit_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, + password = nil, published = false, brackets_only = false, count_pages = false, + allow_uploads = true, max_upload_size = nil) + + if not (web = Web.find_by_address(old_address)) + raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist") + end + + web.refresh_revisions if web.settings_changed?(markup, safe_mode, brackets_only) + web.update_attributes(:address => new_address, :name => name, :markup => markup, :color => color, + :additional_style => additional_style, :safe_mode => safe_mode, :password => password, :published => published, + :brackets_only => brackets_only, :count_pages => count_pages, :allow_uploads => allow_uploads, :max_upload_size => max_upload_size) + @webs = nil + raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'") unless web.errors.on(:address).nil? + web + end + + def read_page(web_address, page_name) + self.class.logger.debug "Reading page '#{page_name}' from web '#{web_address}'" + web = Web.find_by_address(web_address) + if web.nil? + self.class.logger.debug "Web '#{web_address}' not found" + return nil + else + page = web.pages.find(:first, :conditions => ['name = ?', page_name]) + self.class.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found" + return page + end + end + + def remove_orphaned_pages(web_address) + web = Web.find_by_address(web_address) + web.remove_pages(web.select.orphaned_pages) + end + + def revise_page(web_address, page_name, content, revised_on, author) + page = read_page(web_address, page_name) + page.revise(content, revised_on, author) + end + + def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil) + page = read_page(web_address, page_name) + page.rollback(revision_number, created_at, author_id) + end + + def setup(password, web_name, web_address) + system.update_attribute(:password, password) + create_web(web_name, web_address) + end + + def system + @system ||= (System.find(:first) || System.create) + end + + def setup? + Web.count > 0 + end + + def webs + @webs ||= Web.find(:all).inject({}) { |webs, web| webs.merge(web.address => web) } + end + + def storage_path + self.class.storage_path + end + + def write_page(web_address, page_name, content, written_on, author) + Web.find_by_address(web_address).add_page(page_name, content, written_on, author) + end +end \ No newline at end of file diff --git a/lib/wiki_content.rb b/lib/wiki_content.rb new file mode 100644 index 00000000..dcc89ef9 --- /dev/null +++ b/lib/wiki_content.rb @@ -0,0 +1,206 @@ +require 'cgi' +require 'chunks/engines' +require 'chunks/category' +require 'chunks/include' +require 'chunks/wiki' +require 'chunks/literal' +require 'chunks/uri' +require 'chunks/nowiki' + +# Wiki content is just a string that can process itself with a chain of +# actions. The actions can modify wiki content so that certain parts of +# it are protected from being rendered by later actions. +# +# When wiki content is rendered, it can be interrogated to find out +# which chunks were rendered. This means things like categories, wiki +# links, can be determined. +# +# Exactly how wiki content is rendered is determined by a number of +# settings that are optionally passed in to a constructor. The current +# options are: +# * :engine +# => The structural markup engine to use (Textile, Markdown, RDoc) +# * :engine_opts +# => A list of options to pass to the markup engines (safe modes, etc) +# * :pre_engine_actions +# => A list of render actions or chunks to be processed before the +# markup engine is applied. By default this is: +# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word +# * :post_engine_actions +# => A list of render actions or chunks to apply after the markup +# engine. By default these are: +# Literal::Pre, Literal::Tags +# * :mode +# => How should the content be rendered? For normal display (show), +# publishing (:publish) or export (:export)? + +module ChunkManager + attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id + + ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, URIChunk, LocalURIChunk, + WikiChunk::Word ] + + HIDE_CHUNKS = [ Literal::Pre, Literal::Tags ] + + MASK_RE = { + ACTIVE_CHUNKS => Chunk::Abstract.mask_re(ACTIVE_CHUNKS), + HIDE_CHUNKS => Chunk::Abstract.mask_re(HIDE_CHUNKS) + } + + def init_chunk_manager + @chunks_by_type = Hash.new + Chunk::Abstract::derivatives.each{|chunk_type| + @chunks_by_type[chunk_type] = Array.new + } + @chunks_by_id = Hash.new + @chunks = [] + @chunk_id = 0 + end + + def add_chunk(c) + @chunks_by_type[c.class] << c + @chunks_by_id[c.id] = c + @chunks << c + @chunk_id += 1 + end + + def delete_chunk(c) + @chunks_by_type[c.class].delete(c) + @chunks_by_id.delete(c.id) + @chunks.delete(c) + end + + def merge_chunks(other) + other.chunks.each{|c| add_chunk(c)} + end + + def scan_chunkid(text) + text.scan(MASK_RE[ACTIVE_CHUNKS]){|a| yield a[0] } + end + + def find_chunks(chunk_type) + @chunks.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? } + end + + # for testing and WikiContentStub; we need a page_id even if we have no page + def page_id + 0 + end + +end + +# A simplified version of WikiContent. Useful to avoid recursion problems in +# WikiContent.new +class WikiContentStub < String + attr_reader :options + include ChunkManager + def initialize(content, options) + super(content) + @options = options + init_chunk_manager + end + + # Detects the mask strings contained in the text of chunks of type chunk_types + # and yields the corresponding chunk ids + # example: content = "chunk123categorychunk
chunk456categorychunk
" + # inside_chunks(Literal::Pre) ==> yield 456 + def inside_chunks(chunk_types) + chunk_types.each{|chunk_type| chunk_type.apply_to(self) } + + chunk_types.each{|chunk_type| @chunks_by_type[chunk_type].each{|hide_chunk| + scan_chunkid(hide_chunk.text){|id| yield id } + } + } + end +end + +class WikiContent < String + + include ChunkManager + + DEFAULT_OPTS = { + :active_chunks => ACTIVE_CHUNKS, + :engine => Engines::Textile, + :engine_opts => [], + :mode => :show + }.freeze + + attr_reader :web, :options, :revision, :not_rendered, :pre_rendered + + # Create a new wiki content string from the given one. + # The options are explained at the top of this file. + def initialize(revision, options = {}) + @revision = revision + @web = @revision.page.web + + @options = DEFAULT_OPTS.dup.merge(options) + @options[:engine] = Engines::MAP[@web.markup] + @options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode? + @options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only? + + @not_rendered = @pre_rendered = nil + + super(@revision.content) + init_chunk_manager + build_chunks + @not_rendered = String.new(self) + end + + # Call @web.page_link using current options. + def page_link(name, text, link_type) + @options[:link_type] = (link_type || :show) + @web.make_link(name, text, @options) + end + + def build_chunks + # create and mask Includes and "active_chunks" chunks + Include.apply_to(self) + @options[:active_chunks].each{|chunk_type| chunk_type.apply_to(self)} + + # Handle hiding contexts like "pre" and "code" etc.. + # The markup (textile, rdoc etc) can produce such contexts with its own syntax. + # To reveal them, we work on a copy of the content. + # The copy is rendered and used to detect the chunks that are inside protecting context + # These chunks are reverted on the original content string. + + copy = WikiContentStub.new(self, @options) + @options[:engine].apply_to(copy) + + copy.inside_chunks(HIDE_CHUNKS) do |id| + @chunks_by_id[id].revert + end + end + + def pre_render! + unless @pre_rendered + @chunks_by_type[Include].each{|chunk| chunk.unmask } + @pre_rendered = String.new(self) + end + @pre_rendered + end + + def render! + pre_render! + @options[:engine].apply_to(self) + # unmask in one go. $~[1] is the chunk id + gsub!(MASK_RE[ACTIVE_CHUNKS]){ + if chunk = @chunks_by_id[$~[1]] + chunk.unmask_text + # if we match a chunkmask that existed in the original content string + # just keep it as it is + else + $~[0] + end} + self + end + + def page_name + @revision.page.name + end + + def page_id + @revision.page.id + end + +end + diff --git a/lib/wiki_words.rb b/lib/wiki_words.rb new file mode 100644 index 00000000..8f2b154f --- /dev/null +++ b/lib/wiki_words.rb @@ -0,0 +1,23 @@ +# Contains all the methods for finding and replacing wiki words +module WikiWords + # In order of appearance: Latin, greek, cyrillian, armenian + I18N_HIGHER_CASE_LETTERS = + "ÀÃ?ÂÃÄÅĀĄĂÆÇĆČĈĊĎÄ?ÈÉÊËĒĘĚĔĖĜĞĠĢĤĦÌÃ?ÃŽÃ?ĪĨĬĮİIJĴĶÅ?ĽĹĻĿÑŃŇŅŊÒÓÔÕÖØŌÅ?ŎŒŔŘŖŚŠŞŜȘŤŢŦȚÙÚÛÜŪŮŰŬŨŲŴÃ?ŶŸŹŽŻ" + + "ΑΒΓΔΕΖΗΘΙΚΛΜÎ?ΞΟΠΡΣΤΥΦΧΨΩ" + + "ΆΈΉΊΌΎÎ?ѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎÒ?ҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾÓ?ÓƒÓ…Ó‡Ó‰Ó‹Ó?Ó?ӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӸЖ" + + "Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€Õ?Õ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕ?Õ?Õ?Õ‘Õ’Õ“Õ”Õ•Õ–" + + I18N_LOWER_CASE_LETTERS = + "àáâãäåÄ?ąăæçćÄ?ĉċÄ?đèéêëēęěĕėƒÄ?ğġģĥħìíîïīĩĭįıijĵķĸłľĺļŀñńňņʼnŋòóôõöøÅ?Å‘Å?œŕřŗśšşÅ?șťţŧțùúûüūůűŭũųŵýÿŷžżźÞþßſÃ?ð" + + "άέήίΰαβγδεζηθικλμνξοπÏ?ςστυφχψωϊϋόÏ?ÏŽÎ?" + + "абвгдежзийклмнопрÑ?туфхцчшщъыьÑ?ÑŽÑ?Ñ?ёђѓєѕіїјљћќÑ?ўџѡѣѥѧѩѫѭѯѱѳѵѷѹѻѽѿÒ?Ò‹Ò?Ò?Ò‘Ò“Ò•Ò—Ò™Ò›Ò?ҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӀӂӄӆӈӊӌӎӑӓӕӗәӛÓ?ÓŸÓ¡Ó£Ó¥Ó§Ó©Ó«Ó­Ó¯Ó±Ó³ÓµÓ¹" + + "Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€Ö?Ö‚ÖƒÖ„Ö…Ö†Ö‡" + + WIKI_WORD_PATTERN = '[A-Z' + I18N_HIGHER_CASE_LETTERS + '][a-z' + I18N_LOWER_CASE_LETTERS + ']+[A-Z' + I18N_HIGHER_CASE_LETTERS + ']\w+' + CAMEL_CASED_WORD_BORDER = /([a-z#{I18N_LOWER_CASE_LETTERS}])([A-Z#{I18N_HIGHER_CASE_LETTERS}])/u + + def self.separate(wiki_word) + wiki_word.gsub(CAMEL_CASED_WORD_BORDER, '\1 \2') + end + +end diff --git a/rakefile.rb b/rakefile.rb index 1495f84e..e69de29b 100755 --- a/rakefile.rb +++ b/rakefile.rb @@ -1,134 +0,0 @@ -require 'rake' -require 'rake/clean' -require 'rake/testtask' -require 'rake/rdoctask' -require 'rake/packagetask' - -$VERBOSE = nil - -# Standard Rails tasks - -desc 'Run all tests' -task :default => [:test_units, :test_functional] - -desc 'Require application environment.' -task :environment do - unless defined? RAILS_ROOT - require File.dirname(__FILE__) + '/config/environment' - end -end - -desc 'Generate API documentatio, show coding stats' -task :doc => [ :appdoc, :stats ] - -desc 'Run the unit tests in test/unit' -Rake::TestTask.new('test_units') { |t| - t.libs << 'test' - t.pattern = 'test/unit/**/*_test.rb' - t.verbose = true -} - -desc 'Run the functional tests in test/functional' -Rake::TestTask.new('test_functional') { |t| - t.libs << 'test' - t.pattern = 'test/functional/**/*_test.rb' - t.verbose = true -} - -desc 'Generate documentation for the application' -Rake::RDocTask.new('appdoc') { |rdoc| - rdoc.rdoc_dir = 'doc/app' - rdoc.title = 'Rails Application Documentation' - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('doc/README_FOR_APP') - rdoc.rdoc_files.include('app/**/*.rb') -} - -desc 'Generate documentation for the Rails framework' -Rake::RDocTask.new("apidoc") { |rdoc| - rdoc.rdoc_dir = 'doc/api' - rdoc.title = 'Rails Framework Documentation' - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') - rdoc.rdoc_files.include('vendor/rails/activerecord/README') - rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') - rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') - rdoc.rdoc_files.include('vendor/rails/actionpack/README') - rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionmailer/README') - rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/ChangeLog') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/activesupport/README') - rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') -} - -desc 'Report code statistics (KLOCs, etc) from the application' -task :stats => [ :environment ] do - require 'code_statistics' - CodeStatistics.new( - ['Helpers', 'app/helpers'], - ['Controllers', 'app/controllers'], - ['Functionals', 'test/functional'], - ['Models', 'app/models'], - ['Units', 'test/unit'], - ['Miscellaneous (lib)', 'lib'] - ).to_s -end - -# Additional tasks (not standard Rails) - -CLEAN << 'pkg' << 'storage' << 'doc' << 'html' - -begin - require 'rubygems' - require 'rake/gempackagetask' -rescue Exception => e - nil -end - -if defined? Rake::GemPackageTask - gemspec = eval(File.read('instiki.gemspec')) - Rake::GemPackageTask.new(gemspec) do |p| - p.gem_spec = gemspec - p.need_tar = true - p.need_zip = true - end - - Rake::PackageTask.new('instiki', gemspec.version) do |p| - p.need_tar = true - p.need_zip = true - # the list of glob expressions for files comes from instiki.gemspec - p.package_files.include($__instiki_source_patterns) - end - - # Create a task to build the RDOC documentation tree. - rd = Rake::RDocTask.new("rdoc") { |rdoc| - rdoc.rdoc_dir = 'html' - rdoc.title = 'Instiki -- The Wiki' - rdoc.options << '--line-numbers --inline-source --main README' - rdoc.rdoc_files.include(gemspec.files) - rdoc.main = 'README' - } -else - puts 'Warning: without Rubygems packaging tasks are not available' -end - -# Shorthand aliases -desc 'Shorthand for test_units' -task :tu => :test_units -desc 'Shorthand for test_units' -task :ut => :test_units - -desc 'Shorthand for test_functional' -task :tf => :test_functional -desc 'Shorthand for test_functional' -task :ft => :test_functional diff --git a/script/console b/script/console new file mode 100644 index 00000000..eece24a9 --- /dev/null +++ b/script/console @@ -0,0 +1,23 @@ +#!/usr/local/bin/ruby +irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' + +require 'optparse' +options = { :sandbox => false, :irb => irb } +OptionParser.new do |opt| + opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| } + opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |options[:irb]| } + opt.parse!(ARGV) +end + +libs = " -r irb/completion" +libs << " -r #{File.dirname(__FILE__)}/../config/environment" +libs << " -r console_sandbox" if options[:sandbox] + +ENV['RAILS_ENV'] = ARGV.first || 'development' +if options[:sandbox] + puts "Loading #{ENV['RAILS_ENV']} environment in sandbox." + puts "Any modifications you make will be rolled back on exit." +else + puts "Loading #{ENV['RAILS_ENV']} environment." +end +exec "#{options[:irb]} #{libs} --prompt-mode simple" diff --git a/script/create_db b/script/create_db index b421fb8c..642b0d03 100644 --- a/script/create_db +++ b/script/create_db @@ -12,7 +12,7 @@ config = ActiveRecord::Base.configurations ENV['RAILS_ENV'] = target load APP_ROOT + 'config/environment.rb' puts "Creating tables for #{target}..." - + db_structure(config[target]['adapter']).split(/\s*;\s*/).each do |sql| ActiveRecord::Base.connection.execute(sql) end diff --git a/script/server b/script/server index 0da05085..487d1fd9 100755 --- a/script/server +++ b/script/server @@ -1,93 +1,49 @@ -#!/usr/bin/ruby +#!/usr/local/bin/ruby require 'webrick' require 'optparse' -require 'fileutils' - -pwd = File.expand_path(File.dirname(__FILE__) + "/..") OPTIONS = { - # Overridable options - :port => 2500, - :ip => '0.0.0.0', - :environment => 'production', - :server_root => File.expand_path(File.dirname(__FILE__) + '/../public/'), - :server_type => WEBrick::SimpleServer, - :storage => "#{File.expand_path(FileUtils.pwd)}/storage", + :port => 3000, + :ip => "0.0.0.0", + :environment => "development", + :server_root => File.expand_path(File.dirname(__FILE__) + "/../public/"), + :server_type => WEBrick::SimpleServer } ARGV.options do |opts| script_name = File.basename($0) opts.banner = "Usage: ruby #{script_name} [options]" - opts.separator '' + opts.separator "" - opts.on('-p', '--port=port', Integer, - 'Runs Instiki on the specified port.', - 'Default: 2500') { |OPTIONS[:port]| } - opts.on('-b', '--binding=ip', String, - 'Binds Rails to the specified ip.', - 'Default: 0.0.0.0') { |OPTIONS[:ip]| } - opts.on('-e', '--environment=name', String, - 'Specifies the environment to run this server under (test/development/production).', - 'Default: production') { |OPTIONS[:environment]| } - opts.on('-d', '--daemon', - 'Make Instiki run as a Daemon (only works if fork is available -- meaning on *nix).' + opts.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", + "Default: 3000") { |OPTIONS[:port]| } + opts.on("-b", "--binding=ip", String, + "Binds Rails to the specified ip.", + "Default: 0.0.0.0") { |OPTIONS[:ip]| } + opts.on("-e", "--environment=name", String, + "Specifies the environment to run this server under (test/development/production).", + "Default: development") { |OPTIONS[:environment]| } + opts.on("-d", "--daemon", + "Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)." ) { OPTIONS[:server_type] = WEBrick::Daemon } - opts.on('-s', '--simple', '--simple-server', - '[deprecated] Forces Instiki not to run as a Daemon if fork is available.', - 'Since version 0.10.0 this option is ignored.' - ) { puts "Warning: -s (--simple) option is deprecated. See instiki --help for details." } - opts.on('-t', '--storage=storage', String, - 'Makes Instiki use the specified directory for storage.', - 'Default: ./storage/[port]') { |OPTIONS[:storage]| } - opts.on('-x', '--notex', - 'Blocks wiki exports to TeX and PDF, even when pdflatex is available.' - ) { |OPTIONS[:notex]| } - opts.on('-v', '--verbose', - 'Enables debug-level logging' - ) { OPTIONS[:verbose] = true } - opts.separator '' + opts.separator "" - opts.on('-h', '--help', - 'Show this help message.') { puts opts; exit } + opts.on("-h", "--help", + "Show this help message.") { puts opts; exit } opts.parse! end -if OPTIONS[:environment] == 'production' - storage_path = "#{OPTIONS[:storage]}/#{OPTIONS[:port]}" -else - storage_path = "#{OPTIONS[:storage]}/#{OPTIONS[:environment]}/#{OPTIONS[:port]}" -end -FileUtils.mkdir_p(storage_path) +ENV["RAILS_ENV"] = OPTIONS[:environment] +require File.dirname(__FILE__) + "/../config/environment" +require 'webrick_server' -ENV['RAILS_ENV'] = OPTIONS[:environment] -$instiki_debug_logging = OPTIONS[:verbose] -require File.expand_path(File.dirname(__FILE__) + '/../config/environment') -WikiService.storage_path = storage_path +OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT) -if OPTIONS[:notex] - OPTIONS[:pdflatex] = false -else - begin - OPTIONS[:pdflatex] = system "pdflatex -version" - rescue Errno::ENOENT - OPTIONS[:pdflatex] = false - end -end - -if defined? INSTIKI_BATCH_JOB - require 'application' -else - puts "=> Starting Instiki on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" - puts "=> Data files are stored in #{storage_path}" - - require 'webrick_server' - require_dependency 'application' - - OPTIONS[:index_controller] = 'wiki' - ApplicationController.wiki = WikiService.instance - DispatchServlet.dispatch(OPTIONS) -end +puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" +puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer +DispatchServlet.dispatch(OPTIONS) diff --git a/test/fixtures/pages.yml b/test/fixtures/pages.yml index 2c62113c..f7add425 100644 --- a/test/fixtures/pages.yml +++ b/test/fixtures/pages.yml @@ -1,5 +1,55 @@ home_page: id: 1 - created_at: 2004-08-01 - updated_at: 2005-08-01 - web_id: 1 \ No newline at end of file + created_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + web_id: 1 + name: HomePage + +my_way: + id: 2 + created_at: <%= 9.days.ago.to_formatted_s(:db) %> + updated_at: <%= 9.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: MyWay + +smart_engine: + id: 3 + created_at: <%= 8.days.ago.to_formatted_s(:db) %> + updated_at: <%= 8.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: SmartEngine + +that_way: + id: 4 + created_at: <%= 7.days.ago.to_formatted_s(:db) %> + updated_at: <%= 7.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: ThatWay + +no_wiki_word: + id: 5 + created_at: <%= 6.days.ago.to_formatted_s(:db) %> + updated_at: <%= 6.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: NoWikiWord + +first_page: + id: 6 + created_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + web_id: 1 + name: FirstPage + +oak: + id: 7 + created_at: <%= 5.days.ago.to_formatted_s(:db) %> + updated_at: <%= 5.days.ago.to_formatted_s(:db) %> + web_id: 1 + name: Oak + +elephant: + id: 8 + created_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + updated_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + web_id: 1 + name: Elephant \ No newline at end of file diff --git a/test/fixtures/revisions.yml b/test/fixtures/revisions.yml index 9d43eb26..eb5eff5b 100644 --- a/test/fixtures/revisions.yml +++ b/test/fixtures/revisions.yml @@ -1,6 +1,84 @@ home_page_first_revision: id: 1 - created_at: 2004-08-01 - updated_at: 2005-08-01 + created_at: <%= Time.local(2004, 4, 4, 15, 50).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 15, 50).to_formatted_s(:db) %> page_id: 1 - content: some text \ No newline at end of file + number: 1 + content: First revision of the HomePage end + author: AnAuthor + ip: 127.0.0.1 + +my_way_first_revision: + id: 2 + created_at: <%= 9.days.ago.to_formatted_s(:db) %> + updated_at: <%= 9.days.ago.to_formatted_s(:db) %> + page_id: 2 + number: 1 + content: MyWay + author: Me + +smart_engine_first_revision: + id: 3 + created_at: <%= 8.days.ago.to_formatted_s(:db) %> + updated_at: <%= 8.days.ago.to_formatted_s(:db) %> + page_id: 3 + number: 1 + content: SmartEngine + author: Me + +that_way_first_revision: + id: 4 + created_at: <%= 7.days.ago.to_formatted_s(:db) %> + updated_at: <%= 7.days.ago.to_formatted_s(:db) %> + page_id: 4 + number: 1 + content: ThatWay + author: Me + +no_wiki_word_first_revision: + id: 5 + created_at: <%= 6.days.ago.to_formatted_s(:db) %> + updated_at: <%= 6.days.ago.to_formatted_s(:db) %> + page_id: 5 + number: 1 + content: hey you + author: Me + +home_page_second_revision: + id: 6 + created_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 50).to_formatted_s(:db) %> + page_id: 1 + number: 2 + content: HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in that SmartEngineGUI + author: DavidHeinemeierHansson + +first_page_first_revision: + id: 7 + created_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + updated_at: <%= Time.local(2004, 4, 4, 16, 55).to_formatted_s(:db) %> + page_id: 6 + number: 1 + content: HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that SmartEngineGUI + author: DavidHeinemeierHansson + +oak_first_revision: + id: 8 + created_at: <%= 5.days.ago.to_formatted_s(:db) %> + updated_at: <%= 5.days.ago.to_formatted_s(:db) %> + page_id: 7 + number: 1 + content: "All about oak.\ncategory: trees" + author: TreeHugger + ip: 127.0.0.2 + +elephant_first_revision: + id: 9 + created_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + updated_at: <%= 10.minutes.ago.to_formatted_s(:db) %> + page_id: 8 + number: 1 + content: "All about elephants.\ncategory: animals" + author: Guest + ip: 127.0.0.2 + diff --git a/test/fixtures/system.yml b/test/fixtures/system.yml new file mode 100644 index 00000000..1b17f2fc --- /dev/null +++ b/test/fixtures/system.yml @@ -0,0 +1,2 @@ +system: + password: test_password diff --git a/test/fixtures/webs.yml b/test/fixtures/webs.yml index 7276bb65..29c89566 100644 --- a/test/fixtures/webs.yml +++ b/test/fixtures/webs.yml @@ -2,5 +2,14 @@ test_wiki: id: 1 created_at: 2004-08-01 updated_at: 2005-08-01 - name: wiki - address: wiki \ No newline at end of file + name: wiki1 + address: wiki1 + markup: textile + +instiki: + id: 2 + created_at: 2004-08-01 + updated_at: 2005-08-01 + name: Instiki + address: instiki + markup: textile \ No newline at end of file diff --git a/test/functional/admin_controller_test.rb b/test/functional/admin_controller_test.rb index 7aed5c70..a24cf8c0 100644 --- a/test/functional/admin_controller_test.rb +++ b/test/functional/admin_controller_test.rb @@ -1,70 +1,73 @@ -#!/bin/env ruby -w +#!/bin/env ruby -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'admin_controller' # Raise errors beyond the default web-based presentation class AdminController; def rescue_action(e) logger.error(e); raise e end; end class AdminControllerTest < Test::Unit::TestCase + fixtures :webs, :pages, :revisions, :system def setup - setup_test_wiki - setup_controller_test + @controller = AdminController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new + @oak = pages(:oak) + @elephant = pages(:elephant) + @web = webs(:test_wiki) + @home = @page = pages(:home_page) end - def tear_down - tear_down_wiki - end - - def test_create_system_form_displayed - ApplicationController.wiki = WikiServiceWithNoPersistence.new + use_blank_wiki process('create_system') - assert_success + assert_response :success end def test_create_system_form_submitted - ApplicationController.wiki = WikiServiceWithNoPersistence.new - assert !ApplicationController.wiki.setup? + use_blank_wiki + assert !@wiki.setup? process('create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki') assert_redirected_to :web => 'my_wiki', :controller => 'wiki', :action => 'new', :id => 'HomePage' - assert ApplicationController.wiki.setup? - assert_equal 'a_password', ApplicationController.wiki.system[:password] - assert_equal 1, ApplicationController.wiki.webs.size - new_web = ApplicationController.wiki.webs['my_wiki'] + assert @wiki.setup? + assert_equal 'a_password', @wiki.system[:password] + assert_equal 1, @wiki.webs.size + new_web = @wiki.webs['my_wiki'] assert_equal 'My Wiki', new_web.name assert_equal 'my_wiki', new_web.address end def test_create_system_form_submitted_and_wiki_already_initialized - wiki_before = ApplicationController.wiki - assert ApplicationController.wiki.setup? + wiki_before = @wiki + old_size = @wiki.webs.size + assert @wiki.setup? process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki', 'web_address' => 'my_wiki' - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' - assert_equal wiki_before, ApplicationController.wiki + assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage' + assert_equal wiki_before, @wiki # and no new web should be created either - assert_equal 1, ApplicationController.wiki.webs.size + assert_equal old_size, @wiki.webs.size assert_flash_has :error end def test_create_system_no_form_and_wiki_already_initialized assert @wiki.setup? process('create_system') - assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' + assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage' assert_flash_has :error end def test_create_web - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2' @@ -76,7 +79,7 @@ class AdminControllerTest < Test::Unit::TestCase end def test_create_web_default_password - @wiki.system[:password] = nil + @wiki.system.update_attribute(:password, nil) process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2' @@ -84,7 +87,7 @@ class AdminControllerTest < Test::Unit::TestCase end def test_create_web_failed_authentication - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2' @@ -93,20 +96,20 @@ class AdminControllerTest < Test::Unit::TestCase end def test_create_web_no_form_submitted - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process 'create_web' - assert_success + assert_response :success end def test_edit_web_no_form process 'edit_web', 'web' => 'wiki1' # this action simply renders a form - assert_success + assert_response :success end def test_edit_web_form_submitted - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process('edit_web', 'system_password' => 'pswd', 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', @@ -116,21 +119,22 @@ class AdminControllerTest < Test::Unit::TestCase 'max_upload_size' => '300') assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' + @web = Web.find(@web.id) assert_equal 'renamed_wiki1', @web.address assert_equal 'Renamed Wiki1', @web.name assert_equal :markdown, @web.markup assert_equal 'blue', @web.color - assert @web.safe_mode + assert @web.safe_mode? assert_equal 'new_password', @web.password - assert @web.published - assert @web.brackets_only - assert @web.count_pages - assert @web.allow_uploads + assert @web.published? + assert @web.brackets_only? + assert @web.count_pages? + assert @web.allow_uploads? assert_equal 300, @web.max_upload_size end def test_edit_web_opposite_values - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') process('edit_web', 'system_password' => 'pswd', 'web' => 'wiki1', 'address' => 'renamed_wiki1', 'name' => 'Renamed Wiki1', @@ -140,11 +144,12 @@ class AdminControllerTest < Test::Unit::TestCase # and should become false assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage' - assert !@web.safe_mode - assert !@web.published - assert !@web.brackets_only - assert !@web.count_pages - assert !@web.allow_uploads + @web = Web.find(@web.id) + assert !@web.safe_mode? + assert !@web.published? + assert !@web.brackets_only? + assert !@web.count_pages? + assert !@web.allow_uploads? end def test_edit_web_wrong_password @@ -154,12 +159,12 @@ class AdminControllerTest < Test::Unit::TestCase 'password' => 'new_password') #returns to the same form - assert_success + assert_response :success assert @response.has_template_object?('error') end def test_edit_web_rename_to_already_existing_web_name - @wiki.system[:password] = 'pswd' + @wiki.system.update_attribute(:password, 'pswd') @wiki.create_web('Another', 'another') process('edit_web', 'system_password' => 'pswd', @@ -168,7 +173,7 @@ class AdminControllerTest < Test::Unit::TestCase 'password' => 'new_password') #returns to the same form - assert_success + assert_response :success assert @response.has_template_object?('error') end @@ -179,15 +184,15 @@ class AdminControllerTest < Test::Unit::TestCase 'password' => 'new_password') #returns to the same form - assert_success + assert_response :success assert @response.has_template_object?('error') end def test_remove_orphaned_pages - setup_wiki_with_three_pages - @wiki.system[:password] = 'pswd' - orhan_page_linking_to_oak = @wiki.write_page('wiki1', 'Pine', + @wiki.system.update_attribute(:password, 'pswd') + page_order = [@home, pages(:my_way), @oak, pages(:smart_engine), pages(:that_way)] + orphan_page_linking_to_oak = @wiki.write_page('wiki1', 'Pine', "Refers to [[Oak]].\n" + "category: trees", Time.now, Author.new('TreeHugger', '127.0.0.2')) @@ -195,25 +200,28 @@ class AdminControllerTest < Test::Unit::TestCase r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list' - assert_equal [@home, @oak], @web.select.sort, + @web.pages(true) + assert_equal page_order, @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" # Oak is now orphan, second pass should remove it r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list' - assert_equal [@home], @web.select.sort, + @web.pages(true) + page_order.delete(@oak) + assert_equal page_order, @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" # third pass does not destroy HomePage r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd') assert_redirected_to :action => 'list' - assert_equal [@home], @web.select.sort, + @web.pages(true) + assert_equal page_order, @web.select.sort, "Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}" end def test_remove_orphaned_pages_empty_or_wrong_password - setup_wiki_with_three_pages @wiki.system[:password] = 'pswd' process('remove_orphaned_pages', 'web' => 'wiki1') @@ -224,5 +232,4 @@ class AdminControllerTest < Test::Unit::TestCase assert_redirected_to(:controller => 'admin', :action => 'edit_web', :web => 'wiki1') assert @response.flash[:error] end - end diff --git a/test/functional/application_test.rb b/test/functional/application_test.rb index 3a33df74..c32f8b23 100755 --- a/test/functional/application_test.rb +++ b/test/functional/application_test.rb @@ -8,24 +8,23 @@ require 'rexml/document' class WikiController; def rescue_action(e) logger.error(e); raise e end; end class ApplicationTest < Test::Unit::TestCase - + fixtures :webs, :pages, :revisions, :system + def setup - setup_test_wiki - setup_controller_test(WikiController) + @controller = WikiController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new end - - def tear_down - tear_down_wiki - end - + def test_utf8_header - r = process('show', 'web' => 'wiki1', 'id' => 'HomePage') - assert_equal 'text/html; charset=UTF-8', r.headers['Content-Type'] + get :show, :web => 'wiki1', :id => 'HomePage' + assert_equal 'text/html; charset=UTF-8', @response.headers['Content-Type'] end def test_connect_to_model_unknown_wiki - r = process('show', 'web' => 'unknown_wiki', 'id' => 'HomePage') - assert_equal 404, r.response_code + get :show, :web => 'unknown_wiki', :id => 'HomePage' + assert_response :missing end end diff --git a/test/functional/file_controller_test.rb b/test/functional/file_controller_test.rb index 23f18c42..cea3aa81 100755 --- a/test/functional/file_controller_test.rb +++ b/test/functional/file_controller_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'file_controller' @@ -8,24 +8,26 @@ require 'fileutils' class FileController; def rescue_action(e) logger.error(e); raise e end; end class FileControllerTest < Test::Unit::TestCase + fixtures :webs, :pages, :revisions, :system - FILE_AREA = RAILS_ROOT + '/storage/test/wiki1' + Wiki.storage_path += "test/" + FILE_AREA = Wiki.storage_path + 'wiki1' FileUtils.mkdir_p(FILE_AREA) unless File.directory?(FILE_AREA) FileUtils.rm(Dir["#{FILE_AREA}/*"]) def setup - setup_test_wiki - setup_controller_test - end - - def tear_down - tear_down_wiki + @controller = FileController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new + @web = webs(:test_wiki) + @home = @page = pages(:home_page) end def test_file process 'file', 'web' => 'wiki1', 'id' => 'foo.tgz' - assert_success + assert_response :success assert_rendered_file 'file/file' end @@ -34,7 +36,7 @@ class FileControllerTest < Test::Unit::TestCase r = process 'file', 'web' => 'wiki1', 'id' => 'foo.txt' - assert_success + assert_response :success assert_equal "aaa\nbbb\n", r.binary_content assert_equal 'text/plain', r.headers['Content-Type'] end @@ -44,7 +46,7 @@ class FileControllerTest < Test::Unit::TestCase r = process 'file', 'web' => 'wiki1', 'id' => 'foo.pdf' - assert_success + assert_response :success assert_equal "aaa\nbbb\n", r.binary_content assert_equal 'application/pdf', r.headers['Content-Type'] end @@ -54,14 +56,14 @@ class FileControllerTest < Test::Unit::TestCase r = process 'pic', 'web' => 'wiki1', 'id' => 'rails.gif' - assert_success + assert_response :success assert_equal File.size("#{FILE_AREA}/rails.gif"), r.binary_content.size end def test_pic_unknown_pic r = process 'pic', 'web' => 'wiki1', 'id' => 'non-existant.gif' - assert_success + assert_response :success assert_rendered_file 'file/file' end @@ -74,7 +76,7 @@ class FileControllerTest < Test::Unit::TestCase # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form r = process 'pic', 'web' => 'wiki1', 'id' => 'rails-e2e.gif' - assert_success + assert_response :success assert_rendered_file 'file/file' # User uploads the picture @@ -98,7 +100,7 @@ class FileControllerTest < Test::Unit::TestCase # rails-e2e.gif is unknown to the system, so pic action goes to the file [upload] form r = process 'file', 'web' => 'wiki1', 'id' => 'instiki-e2e.txt' - assert_success + assert_response :success assert_rendered_file 'file/file' # User uploads the picture @@ -109,17 +111,18 @@ class FileControllerTest < Test::Unit::TestCase assert_equal(file, File.read("#{RAILS_ROOT}/storage/test/wiki1/instiki-e2e.txt")) # this should refresh the page display content (cached) + @home = Page.find(@home.id) assert_equal "

" + "instiki-e2e.txt

", @home.display_content end def test_uploads_blocking - @web.allow_uploads = true + set_web_property :allow_uploads, true r = process 'file', 'web' => 'wiki1', 'id' => 'filename' - assert_success + assert_response :success - @web.allow_uploads = false + set_web_property :allow_uploads, false r = process 'file', 'web' => 'wiki1', 'id' => 'filename' assert_equal '403 Forbidden', r.headers['Status'] end diff --git a/test/functional/routes_test.rb b/test/functional/routes_test.rb index 2e338bd0..c2334ef7 100644 --- a/test/functional/routes_test.rb +++ b/test/functional/routes_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index bc79a8bf..b799ad3b 100755 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,10 +1,10 @@ -#!/bin/env ruby -w +#!/bin/env ruby # Uncomment the line below to enable pdflatex tests; don't forget to comment them again # commiting to SVN # $INSTIKI_TEST_PDFLATEX = true -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'wiki_controller' require 'rexml/document' require 'tempfile' @@ -14,63 +14,60 @@ require 'zip/zipfilesystem' class WikiController; def rescue_action(e) logger.error(e); raise e end; end class WikiControllerTest < Test::Unit::TestCase - + fixtures :webs, :pages, :revisions, :system + def setup - setup_test_wiki - setup_controller_test + @controller = WikiController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @wiki = Wiki.new + @web = webs(:test_wiki) + @home = @page = pages(:home_page) + @oak = pages(:oak) + @elephant = pages(:elephant) end - def tear_down - tear_down_wiki - end - - def test_authenticate - @web.password = 'pswd' + set_web_property :password, 'pswd' - r = process('authenticate', 'web' => 'wiki1', 'password' => 'pswd') + get :authenticate, :web => 'wiki1', :password => 'pswd' assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' - assert_equal ['pswd'], r.cookies['web_address'] + assert_equal ['pswd'], @response.cookies['web_address'] end def test_authenticate_wrong_password - @web.password = 'pswd' + set_web_property :password, 'pswd' r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password') assert_redirected_to :action => 'login', :web => 'wiki1' assert_nil r.cookies['web_address'] end - def test_authors - setup_wiki_with_three_pages @wiki.write_page('wiki1', 'BreakSortingOrder', "This page breaks the accidentally correct sorting order of authors", Time.now, Author.new('BreakingTheOrder', '127.0.0.2')) r = process('authors', 'web' => 'wiki1') - assert_success - assert_equal ['AnAuthor', 'BreakingTheOrder', 'Guest', 'TreeHugger'], + assert_response :success + assert_equal %w(AnAuthor BreakingTheOrder DavidHeinemeierHansson Guest Me TreeHugger), r.template_objects['authors'] end - def test_cancel_edit - setup_wiki_with_three_pages @oak.lock(Time.now, 'Locky') assert @oak.locked?(Time.now) r = process('cancel_edit', 'web' => 'wiki1', 'id' => 'Oak') assert_redirected_to :action => 'show', :id => 'Oak' - assert !@oak.locked?(Time.now) + assert !Page.find(@oak.id).locked?(Time.now) end - def test_edit r = process 'edit', 'web' => 'wiki1', 'id' => 'HomePage' - assert_success + assert_response :success assert_equal @wiki.read_page('wiki1', 'HomePage'), r.template_objects['page'] end @@ -83,7 +80,8 @@ class WikiControllerTest < Test::Unit::TestCase def test_edit_page_break_lock @home.lock(Time.now, 'Locky') process 'edit', 'web' => 'wiki1', 'id' => 'HomePage', 'break_lock' => 'y' - assert_success + assert_response :success + @home = Page.find(@home.id) assert @home.locked?(Time.now) end @@ -99,19 +97,17 @@ class WikiControllerTest < Test::Unit::TestCase Time.now, Author.new('Special', '127.0.0.3')) r = process 'edit', 'web' => 'wiki1', 'id' => 'With : Special /> symbols' - assert_success + assert_response :success xml = REXML::Document.new(r.body) form = REXML::XPath.first(xml, '//form') assert_equal '/wiki1/save/With+%3A+Special+%2F%3E+symbols', form.attributes['action'] end - def test_export_html - setup_wiki_with_three_pages - + @home.rollback(1, Time.now, 'Rick') # much simpler regex statement to match r = process 'export_html', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/zip', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] @@ -124,7 +120,7 @@ class WikiControllerTest < Test::Unit::TestCase begin File.open(@tempfile_path, 'wb') { |f| f.write(content); @exported_file = f.path } Zip::ZipFile.open(@exported_file) do |zip| - assert_equal %w(Elephant.html HomePage.html Oak.html index.html), zip.dir.entries('.').sort + assert_equal %w(Elephant.html FirstPage.html HomePage.html MyWay.html NoWikiWord.html Oak.html SmartEngine.html ThatWay.html index.html), zip.dir.entries('.').sort assert_match /.*/, zip.file.read('Elephant.html').gsub(/\s+/, ' ') assert_match /.*/, @@ -138,12 +134,10 @@ class WikiControllerTest < Test::Unit::TestCase end end - def test_export_html_no_layout - setup_wiki_with_three_pages - + def test_export_html_no_layout r = process 'export_html', 'web' => 'wiki1', 'layout' => 'no' - assert_success + assert_response :success assert_equal 'application/zip', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-html-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] @@ -155,7 +149,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_export_markup r = process 'export_markup', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/zip', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-textile-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.zip"/, r.headers['Content-Disposition'] @@ -168,7 +162,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_export_pdf r = process 'export_pdf', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/pdf', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.pdf"/, r.headers['Content-Disposition'] @@ -183,13 +177,10 @@ class WikiControllerTest < Test::Unit::TestCase puts ' $INSTIKI_TEST_PDFLATEX to enable them.' end - - def test_export_tex - setup_wiki_with_three_pages - + def test_export_tex r = process 'export_tex', 'web' => 'wiki1' - assert_success + assert_response :success assert_equal 'application/octet-stream', r.headers['Content-Type'] assert_match /attachment; filename="wiki1-tex-\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d.tex"/, r.headers['Content-Disposition'] @@ -202,6 +193,8 @@ class WikiControllerTest < Test::Unit::TestCase end def test_index + # delete extra web fixture + webs(:instiki).destroy process('index') assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' end @@ -219,41 +212,39 @@ class WikiControllerTest < Test::Unit::TestCase end def test_index_wiki_not_initialized - ApplicationController.wiki = WikiServiceWithNoPersistence.new + use_blank_wiki process('index') assert_redirected_to :controller => 'admin', :action => 'create_system' end def test_list - setup_wiki_with_three_pages - r = process('list', 'web' => 'wiki1') assert_equal ['animals', 'trees'], r.template_objects['categories'] assert_nil r.template_objects['category'] - assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category'] + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'] end def test_locked @home.lock(Time.now, 'Locky') r = process('locked', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] end def test_login r = process 'login', 'web' => 'wiki1' - assert_success + assert_response :success # this action goes straight to the templates end def test_new r = process('new', 'id' => 'NewPage', 'web' => 'wiki1') - assert_success + assert_response :success assert_equal 'AnonymousCoward', r.template_objects['author'] assert_equal 'NewPage', r.template_objects['page_name'] end @@ -264,7 +255,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_pdf assert RedClothForTex.available?, 'Cannot do test_pdf when pdflatex is not available' r = process('pdf', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success content = r.binary_content @@ -282,23 +273,23 @@ class WikiControllerTest < Test::Unit::TestCase def test_print r = process('print', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal :show, r.template_objects['link_mode'] end def test_published - @web.published = true + set_web_property :published, true r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] end def test_published_web_not_published - @web.published = false + set_web_property :published, false r = process('published', 'web' => 'wiki1', 'id' => 'HomePage') @@ -308,11 +299,11 @@ class WikiControllerTest < Test::Unit::TestCase def test_recently_revised r = process('recently_revised', 'web' => 'wiki1') - assert_success + assert_response :success - assert_equal [], r.template_objects['categories'] + assert_equal %w(animals trees), r.template_objects['categories'] assert_nil r.template_objects['category'] - assert_equal [@home], r.template_objects['pages_in_category'] + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'] assert_equal 'the web', r.template_objects['set_name'] end @@ -323,37 +314,33 @@ class WikiControllerTest < Test::Unit::TestCase Time.now, Author.new('AnotherAuthor', '127.0.0.2')) r = process('recently_revised', 'web' => 'wiki1') - assert_success + assert_response :success - assert_equal ['categorized'], r.template_objects['categories'] + assert_equal %w(animals categorized trees), r.template_objects['categories'] # no category is specified in params assert_nil r.template_objects['category'] - assert_equal [@home, page2], r.template_objects['pages_in_category'], + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, page2, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'], "Pages are not as expected: " + r.template_objects['pages_in_category'].map {|p| p.name}.inspect assert_equal 'the web', r.template_objects['set_name'] end def test_recently_revised_with_categorized_page_multiple_categories - setup_wiki_with_three_pages - r = process('recently_revised', 'web' => 'wiki1') - assert_success + assert_response :success assert_equal ['animals', 'trees'], r.template_objects['categories'] # no category is specified in params assert_nil r.template_objects['category'] - assert_equal [@elephant, @home, @oak], r.template_objects['pages_in_category'], + assert_equal [@elephant, pages(:first_page), @home, pages(:my_way), pages(:no_wiki_word), @oak, pages(:smart_engine), pages(:that_way)], r.template_objects['pages_in_category'], "Pages are not as expected: " + r.template_objects['pages_in_category'].map {|p| p.name}.inspect assert_equal 'the web', r.template_objects['set_name'] end def test_recently_revised_with_specified_category - setup_wiki_with_three_pages - r = process('recently_revised', 'web' => 'wiki1', 'category' => 'animals') - assert_success + assert_response :success assert_equal ['animals', 'trees'], r.template_objects['categories'] # no category is specified in params @@ -366,7 +353,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_revision r = process 'revision', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] assert_equal @home.revisions[0], r.template_objects['revision'] end @@ -377,27 +364,24 @@ class WikiControllerTest < Test::Unit::TestCase # its assigns the same as or revision r = process 'rollback', 'web' => 'wiki1', 'id' => 'HomePage', 'rev' => '0' - assert_success + assert_response :success assert_equal @home, r.template_objects['page'] assert_equal @home.revisions[0], r.template_objects['revision'] end def test_rss_with_content - setup_wiki_with_three_pages - r = process 'rss_with_content', 'web' => 'wiki1' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal [@home, @oak, @elephant], pages, + assert_equal [@elephant, @oak, pages(:no_wiki_word), pages(:that_way), pages(:smart_engine), pages(:my_way), pages(:first_page), @home], pages, "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" assert !r.template_objects['hide_description'] end def test_rss_with_content_when_blocked - setup_wiki_with_three_pages - @web.password = 'aaa' - @web.published = false + @web.update_attributes(:password => 'aaa', :published => false) + @web = Web.find(@web.id) r = process 'rss_with_content', 'web' => 'wiki1' @@ -406,7 +390,6 @@ class WikiControllerTest < Test::Unit::TestCase def test_rss_with_headlines - setup_wiki_with_three_pages @title_with_spaces = @wiki.write_page('wiki1', 'Title With Spaces', 'About spaces', 1.hour.ago, Author.new('TreeHugger', '127.0.0.2')) @@ -415,19 +398,24 @@ class WikiControllerTest < Test::Unit::TestCase r = process 'rss_with_headlines', 'web' => 'wiki1' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal [@home, @oak, @elephant, @title_with_spaces], pages, - "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" + assert_equal [@elephant, @title_with_spaces, @oak, pages(:no_wiki_word), pages(:that_way), pages(:smart_engine), pages(:my_way), pages(:first_page), @home], pages, "Pages are not as expected: #{pages.map {|p| p.name}.inspect}" assert r.template_objects['hide_description'] xml = REXML::Document.new(r.body) expected_page_links = - ['http://localhost:8080/wiki1/show/HomePage', + ['http://localhost:8080/wiki1/show/Elephant', + 'http://localhost:8080/wiki1/show/Title+With+Spaces', 'http://localhost:8080/wiki1/show/Oak', - 'http://localhost:8080/wiki1/show/Elephant', - 'http://localhost:8080/wiki1/show/Title+With+Spaces'] + 'http://localhost:8080/wiki1/show/NoWikiWord', + 'http://localhost:8080/wiki1/show/ThatWay', + 'http://localhost:8080/wiki1/show/SmartEngine', + 'http://localhost:8080/wiki1/show/MyWay', + 'http://localhost:8080/wiki1/show/FirstPage', + 'http://localhost:8080/wiki1/show/HomePage', + ] assert_template_xpath_match '/rss/channel/link', 'http://localhost:8080/wiki1/show/HomePage' @@ -436,22 +424,26 @@ class WikiControllerTest < Test::Unit::TestCase end def test_rss_switch_links_to_published - setup_wiki_with_three_pages - @web.password = 'aaa' - @web.published = true + @web.update_attributes(:password => 'aaa', :published => true) + @web = Web.find(@web.id) @request.host = 'foo.bar.info' @request.port = 80 r = process 'rss_with_headlines', 'web' => 'wiki1' - assert_success + assert_response :success xml = REXML::Document.new(r.body) expected_page_links = - ['http://foo.bar.info/wiki1/published/HomePage', + ['http://foo.bar.info/wiki1/published/Elephant', 'http://foo.bar.info/wiki1/published/Oak', - 'http://foo.bar.info/wiki1/published/Elephant'] + 'http://foo.bar.info/wiki1/published/NoWikiWord', + 'http://foo.bar.info/wiki1/published/ThatWay', + 'http://foo.bar.info/wiki1/published/SmartEngine', + 'http://foo.bar.info/wiki1/published/MyWay', + 'http://foo.bar.info/wiki1/published/FirstPage', + 'http://foo.bar.info/wiki1/published/HomePage'] assert_template_xpath_match '/rss/channel/link', 'http://foo.bar.info/wiki1/published/HomePage' @@ -463,45 +455,43 @@ class WikiControllerTest < Test::Unit::TestCase setup_wiki_with_30_pages r = process 'rss_with_headlines', 'web' => 'wiki1' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 15, pages.size, 15 r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => '5' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 5, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => '25' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 25, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'limit' => 'all' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal 31, pages.size + assert_equal 38, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'start' => '1976-10-16' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] - assert_equal 16, pages.size + assert_equal 23, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'end' => '1976-10-16' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 15, pages.size r = process 'rss_with_headlines', 'web' => 'wiki1', 'start' => '1976-10-01', 'end' => '1976-10-06' - assert_success + assert_response :success pages = r.template_objects['pages_by_revision'] assert_equal 5, pages.size end def test_rss_title_with_ampersand - # was ticket:143 - setup_wiki_with_three_pages - + # was ticket:143 @wiki.write_page('wiki1', 'Title&With&Ampersands', 'About spaces', 1.hour.ago, Author.new('NitPicker', '127.0.0.3')) @@ -511,15 +501,12 @@ class WikiControllerTest < Test::Unit::TestCase assert r.body.include?('Title&With&Ampersands') end - def test_rss_timestamp - setup_wiki_with_three_pages - + def test_rss_timestamp new_page = @wiki.write_page('wiki1', 'PageCreatedAtTheBeginningOfCtime', 'Created on 1 Jan 1970 at 0:00:00 Z', Time.at(0), Author.new('NitPicker', '127.0.0.3')) r = process 'rss_with_headlines', 'web' => 'wiki1' - - assert_template_xpath_match '/rss/channel/item/pubDate[4]', "Thu, 01 Jan 1970 00:00:00 Z" + assert_template_xpath_match '/rss/channel/item/pubDate[9]', "Thu, 01 Jan 1970 00:00:00 Z" end def test_save @@ -535,6 +522,7 @@ class WikiControllerTest < Test::Unit::TestCase def test_save_new_revision_of_existing_page @home.lock(Time.now, 'Batman') + current_revisions = @home.revisions.size r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage', 'author' => 'Batman' @@ -542,8 +530,7 @@ class WikiControllerTest < Test::Unit::TestCase assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage' assert_equal ['Batman'], r.cookies['author'].value home_page = @wiki.read_page('wiki1', 'HomePage') - assert_equal [home_page], @web.pages.values - assert_equal 2, home_page.revisions.size + assert_equal current_revisions+1, home_page.revisions.size assert_equal 'Revised HomePage', home_page.content assert_equal 'Batman', home_page.author assert !home_page.locked?(Time.now) @@ -563,57 +550,47 @@ class WikiControllerTest < Test::Unit::TestCase revisions_after = @home.revisions.size assert_equal revisions_before, revisions_after + @home = Page.find(@home.id) assert !@home.locked?(Time.now), 'HomePage should be unlocked if an edit was unsuccessful' end def test_search - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak' assert_redirected_to :action => 'show', :id => 'Oak' end def test_search_multiple_results - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => 'All about' - assert_success + assert_response :success assert_equal 'All about', r.template_objects['query'] assert_equal [@elephant, @oak], r.template_objects['results'] assert_equal [], r.template_objects['title_results'] end def test_search_by_content_and_title - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => '(Oak|Elephant)' - assert_success + assert_response :success assert_equal '(Oak|Elephant)', r.template_objects['query'] assert_equal [@elephant, @oak], r.template_objects['results'] assert_equal [@elephant, @oak], r.template_objects['title_results'] end def test_search_zero_results - setup_wiki_with_three_pages - r = process 'search', 'web' => 'wiki1', 'query' => 'non-existant text' - assert_success + assert_response :success assert_equal [], r.template_objects['results'] assert_equal [], r.template_objects['title_results'] end - - - def test_show_page - r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') - assert_success - assert_match /First revision of the end/, r.body + r = process('show', 'id' => 'Oak', 'web' => 'wiki1') + assert_response :success + assert_tag :content => /All about oak/ end def test_show_page_with_multiple_revisions @@ -622,7 +599,7 @@ class WikiControllerTest < Test::Unit::TestCase r = process('show', 'id' => 'HomePage', 'web' => 'wiki1') - assert_success + assert_response :success assert_match /Second revision of the end/, r.body end @@ -633,24 +610,25 @@ class WikiControllerTest < Test::Unit::TestCase def test_show_no_page r = process('show', 'id' => '', 'web' => 'wiki1') - assert_equal 404, r.response_code + assert_response :missing r = process('show', 'web' => 'wiki1') - assert_equal 404, r.response_code + assert_response :missing end def test_tex r = process('tex', 'web' => 'wiki1', 'id' => 'HomePage') - assert_success + assert_response :success assert_equal "\\documentclass[12pt,titlepage]{article}\n\n\\usepackage[danish]{babel} " + "%danske tekster\n\\usepackage[OT1]{fontenc} %rigtige danske bogstaver...\n" + "\\usepackage{a4}\n\\usepackage{graphicx}\n\\usepackage{ucs}\n\\usepackage[utf8x]" + "{inputenc}\n\\input epsf \n\n%----------------------------------------------------" + "---------------\n\n\\begin{document}\n\n\\sloppy\n\n%-----------------------------" + - "--------------------------------------\n\n\\section*{HomePage}\n\nFirst revision of " + - "the HomePage end\n\n\\end{document}", r.body + "--------------------------------------\n\n\\section*{HomePage}\n\nHisWay would be " + + "MyWay in kinda ThatWay in HisWay though MyWay \\OverThere -- see SmartEngine in that " + + "SmartEngineGUI\n\n\\end{document}", r.body end @@ -659,8 +637,8 @@ class WikiControllerTest < Test::Unit::TestCase r = process('web_list') - assert_success - assert_equal [another_wiki, @web], r.template_objects['webs'] + assert_response :success + assert_equal [another_wiki, webs(:instiki), @web], r.template_objects['webs'] end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 19219ce7..0b29f757 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,17 +10,97 @@ require 'active_record/fixtures' require 'action_controller/test_process' require 'action_web_service/test_invoke' require 'breakpoint' +require 'wiki_content' +# Uncomment these and hang on, because the tests will be FAST +#Test::Unit::TestCase.pre_loaded_fixtures = false +#Test::Unit::TestCase.use_transactional_fixtures = true + +Test::Unit::TestCase.use_instantiated_fixtures = false Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" class Test::Unit::TestCase - # Turn these on to use transactional fixtures with table_name(:fixture_name) instantiation of fixtures - # self.use_transactional_fixtures = true - # self.use_instantiated_fixtures = false - def create_fixtures(*table_names) Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_names) end # Add more helper methods to be used by all tests here... -end \ No newline at end of file + def set_web_property(property, value) + @web.update_attribute(property, value) + @page = Page.find(@page.id) + @wiki.webs[@web.name] = @web + end + + def setup_wiki_with_30_pages + ActiveRecord::Base.silence do + (1..30).each do |i| + @wiki.write_page('wiki1', "page#{i}", "Test page #{i}\ncategory: test", + Time.local(1976, 10, i, 12, 00, 00), Author.new('Dema', '127.0.0.2')) + end + end + @web = Web.find(@web.id) + end + + def use_blank_wiki + Revision.destroy_all + Page.destroy_all + Web.destroy_all + end +end + +# This module is to be included in unit tests that involve matching chunks. +# It provides a easy way to test whether a chunk matches a particular string +# and any the values of any fields that should be set after a match. +class ContentStub < String + include ChunkManager + def initialize(str) + super + init_chunk_manager + end + def page_link(*); end +end + +module ChunkMatch + + # Asserts a number of tests for the given type and text. + def match(chunk_type, test_text, expected_chunk_state) + if chunk_type.respond_to? :pattern + assert_match(chunk_type.pattern, test_text) + end + + content = ContentStub.new(test_text) + chunk_type.apply_to(content) + + # Test if requested parts are correct. + expected_chunk_state.each_pair do |a_method, expected_value| + assert content.chunks.last.kind_of?(chunk_type) + assert_respond_to(content.chunks.last, a_method) + assert_equal(expected_value, content.chunks.last.send(a_method.to_sym), + "Wrong #{a_method} value") + end + end + + # Asserts that test_text doesn't match the chunk_type + def no_match(chunk_type, test_text) + if chunk_type.respond_to? :pattern + assert_no_match(chunk_type.pattern, test_text) + end + end +end + +if defined? $validate_xml_in_assert_success and $validate_xml_in_assert_success == true + module Test + module Unit + module Assertions + unless method_defined? :__assert_success_before_override_by_instiki + alias :__assert_success_before_override_by_instiki :assert_success + end + def assert_success + __assert_success_before_override_by_instiki + if @response.body.kind_of?(Proc) then # it's a file download, not an HTML content + else assert_nothing_raised(@response.body) { REXML::Document.new(@response.body) } end + end + end + end + end +end diff --git a/test/unit/diff_test.rb b/test/unit/diff_test.rb index 3a0f35a5..90d0e79c 100755 --- a/test/unit/diff_test.rb +++ b/test/unit/diff_test.rb @@ -1,6 +1,6 @@ -#!/bin/env ruby -w +#!/bin/env ruby -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'diff' include Diff diff --git a/test/unit/file_yard_test.rb b/test/unit/file_yard_test.rb index f40c0588..9d76bd7c 100755 --- a/test/unit/file_yard_test.rb +++ b/test/unit/file_yard_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'fileutils' @@ -67,4 +67,4 @@ class FileYardTest < Test::Unit::TestCase "#{RAILS_ROOT}/storage/test/instiki" end -end \ No newline at end of file +end diff --git a/test/unit/page_test.rb b/test/unit/page_test.rb index 8af94246..02380175 100644 --- a/test/unit/page_test.rb +++ b/test/unit/page_test.rb @@ -1,7 +1,78 @@ -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class PageTest < Test::Unit::TestCase - - fixtures 'webs', 'pages', 'revisions' + fixtures :webs, :pages, :revisions, :system + def setup + @page = pages(:first_page) + end + + def test_lock + assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) + + @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") + + assert @page.locked?(Time.local(2004, 4, 4, 16, 50)) + assert !@page.locked?(Time.local(2004, 4, 4, 17, 1)) + + @page.unlock + + assert !@page.locked?(Time.local(2004, 4, 4, 16, 50)) + end + + def test_lock_duration + @page.lock(Time.local(2004, 4, 4, 16, 30), "DavidHeinemeierHansson") + + assert_equal 15, @page.lock_duration(Time.local(2004, 4, 4, 16, 45)) + end + + def test_plain_name + assert_equal "First Page", @page.plain_name + end + + def test_revise + @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler') + assert_equal 2, @page.revisions(true).length, 'Should have two revisions' + assert_equal 'MarianneSyhler', @page.current_revision(true).author.to_s, 'Mary should be the author now' + assert_equal 'DavidHeinemeierHansson', @page.revisions.first.author.to_s, 'David was the first author' + end + + def test_revise_continous_revision + @page.revise('HisWay would be MyWay in kinda lame', Time.local(2004, 4, 4, 16, 55), 'MarianneSyhler') + assert_equal 2, @page.revisions(true).length + + @page.current_revision(true) + @page.revise('HisWay would be MyWay in kinda update', Time.local(2004, 4, 4, 16, 57), 'MarianneSyhler') + assert_equal 2, @page.revisions(true).length + assert_equal 'HisWay would be MyWay in kinda update', @page.revisions.last.content + assert_equal Time.local(2004, 4, 4, 16, 57), @page.revisions.last.created_at + + @page.revise('HisWay would be MyWay in the house', Time.local(2004, 4, 4, 16, 58), 'DavidHeinemeierHansson') + assert_equal 3, @page.revisions(true).length + assert_equal 'HisWay would be MyWay in the house', @page.revisions.last.content + + @page.revise('HisWay would be MyWay in my way', Time.local(2004, 4, 4, 17, 30), 'DavidHeinemeierHansson') + assert_equal 4, @page.revisions(true).length + end + + def test_revise_content_unchanged + last_revision_before = @page.current_revision + revisions_number_before = @page.revisions.size + + assert_raises(Instiki::ValidationError) { + @page.revise(@page.current_revision.content, Time.now, 'AlexeyVerkhovsky') + } + + assert_equal last_revision_before, @page.current_revision(true) + assert_equal revisions_number_before, @page.revisions.size + end + + def test_rollback + @page.revise("spot two", Time.now, "David") + @page.revise("spot three", Time.now + 2000, "David") + assert_equal 3, @page.revisions(true).length, "Should have three revisions" + @page.current_revision(true) + @page.rollback(1, Time.now) + assert_equal "HisWay would be MyWay in kinda ThatWay in HisWay though MyWay \\\\OverThere -- see SmartEngine in that SmartEngineGUI", @page.current_revision(true).content + end end diff --git a/test/unit/redcloth_for_tex_test.rb b/test/unit/redcloth_for_tex_test.rb index 82270303..d15071a0 100755 --- a/test/unit/redcloth_for_tex_test.rb +++ b/test/unit/redcloth_for_tex_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'redcloth_for_tex' @@ -66,4 +66,4 @@ EOL def test_subsection_depth assert_equal "\\subsubsection*{Hello}", RedClothForTex.new("h4. Hello").to_tex end -end \ No newline at end of file +end diff --git a/test/unit/revision_test.rb b/test/unit/revision_test.rb index 0024c6b3..c3f588fa 100644 --- a/test/unit/revision_test.rb +++ b/test/unit/revision_test.rb @@ -1,7 +1,315 @@ -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class RevisionTest < Test::Unit::TestCase + fixtures :webs, :pages, :revisions, :system - fixtures 'webs', 'pages', 'revisions' + def setup + @wiki = Wiki.new + @web = webs(:test_wiki) + @page = pages(:home_page) + @revision = revisions(:home_page_second_revision) + end + + def test_wiki_words + assert_equal %w( HisWay MyWay SmartEngine SmartEngineGUI ThatWay ), @revision.wiki_words.sort + + @wiki.write_page('wiki1', 'NoWikiWord', 'hey you!', Time.now, 'Me') + assert_equal [], @wiki.read_page('wiki1', 'NoWikiWord').wiki_words + end + def test_existing_pages + assert_equal %w( MyWay SmartEngine ThatWay ), @revision.existing_pages.sort + end + + def test_unexisting_pages + assert_equal %w( HisWay SmartEngineGUI ), @revision.unexisting_pages.sort + end + + def test_content_with_wiki_links + assert_equal '

His Way? ' + + 'would be My Way in kinda ' + + 'That Way in ' + + 'His Way? ' + + 'though My Way OverThere—see ' + + 'Smart Engine in that ' + + 'Smart Engine GUI' + + '?

', + @revision.display_content + end + + def test_markdown + set_web_property :markup, :markdown + + assert_markup_parsed_as( + %{

My Headline

\n\n

that } + + %{Smart Engine GUI?

}, + "My Headline\n===========\n\nthat SmartEngineGUI") + + code_block = [ + 'This is a code block:', + '', + ' def a_method(arg)', + ' return ThatWay', + '', + 'Nice!' + ].join("\n") + + assert_markup_parsed_as( + %{

This is a code block:

\n\n
def a_method(arg)\n} +
+        %{return ThatWay\n
\n\n

Nice!

}, + code_block) + end + + def test_markdown_hyperlink_with_slash + # in response to a bug, see http://dev.instiki.org/attachment/ticket/177 + set_web_property :markup, :markdown + + assert_markup_parsed_as( + '

text

', + '[text](http://example/with/slash)') + end + + def test_mixed_formatting + textile_and_markdown = [ + 'Markdown heading', + '================', + '', + 'h2. Textile heading', + '', + '*some* **text** _with_ -styles-', + '', + '* list 1', + '* list 2' + ].join("\n") + + set_web_property :markup, :markdown + assert_markup_parsed_as( + "

Markdown heading

\n\n" + + "

h2. Textile heading

\n\n" + + "

some text with -styles-

\n\n" + + "
    \n
  • list 1
  • \n
  • list 2
  • \n
", + textile_and_markdown) + + set_web_property :markup, :textile + assert_markup_parsed_as( + "

Markdown heading
================

\n\n\n\t

Textile heading

" + + "\n\n\n\t

some text with styles

" + + "\n\n\n\t
    \n\t
  • list 1
  • \n\t\t
  • list 2
  • \n\t
", + textile_and_markdown) + + set_web_property :markup, :mixed + assert_markup_parsed_as( + "

Markdown heading

\n\n\n\t

Textile heading

\n\n\n\t" + + "

some text with styles

\n\n\n\t" + + "
    \n\t
  • list 1
  • \n\t\t
  • list 2
  • \n\t
", + textile_and_markdown) + end + + def test_rdoc + set_web_property :markup, :rdoc + + @revision = Revision.new(:page => @page, :content => '+hello+ that SmartEngineGUI', + :author => Author.new('DavidHeinemeierHansson')) + + assert_equal "hello that Smart Engine GUI" + + "?\n\n", @revision.display_content + end + + def test_content_with_auto_links + assert_markup_parsed_as( + '

http://www.loudthinking.com/ ' + + 'points to That Way from ' + + 'david@loudthinking.com

', + 'http://www.loudthinking.com/ points to ThatWay from david@loudthinking.com') + + end + + def test_content_with_aliased_links + assert_markup_parsed_as( + '

Would a clever motor' + + ' go by any other name?

', + 'Would a [[SmartEngine|clever motor]] go by any other name?') + end + + def test_content_with_wikiword_in_em + assert_markup_parsed_as( + '

should we go ' + + 'That Way or This Way?' + + '

', + '_should we go ThatWay or ThisWay _') + end + + def test_content_with_wikiword_in_tag + assert_markup_parsed_as( + '

That is some Stylish Emphasis

', + 'That is some Stylish Emphasis') + end + + def test_content_with_escaped_wikiword + # there should be no wiki link + assert_markup_parsed_as('

WikiWord

', '\WikiWord') + end + + def test_content_with_pre_blocks + assert_markup_parsed_as( + '

A class SmartEngine end would not mark up

CodeBlocks

', + 'A class SmartEngine end would not mark up
CodeBlocks
') + end + + def test_content_with_autolink_in_parentheses + assert_markup_parsed_as( + '

The W3C body (' + + 'http://www.w3c.org) sets web standards

', + 'The W3C body (http://www.w3c.org) sets web standards') + end + + def test_content_with_link_in_parentheses + assert_markup_parsed_as( + '

(What is a wiki?)

', + '("What is a wiki?":http://wiki.org/wiki.cgi?WhatIsWiki)') + end + + def test_content_with_image_link + assert_markup_parsed_as( + '

This is a Textile image link.

', + 'This !http://hobix.com/sample.jpg! is a Textile image link.') + end + + def test_content_with_inlined_img_tag + assert_markup_parsed_as( + '

This is an inline image link.

', + 'This is an inline image link.') + + assert_markup_parsed_as( + '

This is an inline image link.

', + 'This is an inline image link.') + end + + def test_nowiki_tag + assert_markup_parsed_as( + '

Do not mark up [[this text]] or http://www.thislink.com.

', + 'Do not mark up [[this text]] ' + + 'or http://www.thislink.com.') + end + + def test_multiline_nowiki_tag + assert_markup_parsed_as( + "

Do not mark \n up [[this text]] \nand http://this.url.com but markup " + + 'this?

', + "Do not mark \n up [[this text]] \n" + + "and http://this.url.com but markup [[this]]") + end + + def test_content_with_bracketted_wiki_word + set_web_property :brackets_only, true + assert_markup_parsed_as( + '

This is a WikiWord and a tricky name ' + + 'Sperberg-McQueen?.

', + 'This is a WikiWord and a tricky name [[Sperberg-McQueen]].') + end + + def test_content_for_export + assert_equal '

His Way would be ' + + 'My Way in kinda ' + + 'That Way in ' + + 'His Way though ' + + 'My Way OverThere—see ' + + 'Smart Engine in that ' + + 'Smart Engine GUI

', + @revision.display_content_for_export + end + + def test_double_replacing + @revision.content = "VersionHistory\r\n\r\ncry VersionHistory" + assert_equal '

Version History' + + "?

\n\n\n\t

cry " + + 'Version History?' + + '

', + @revision.display_content + + @revision.clear_display_cache + + @revision.content = "f\r\nVersionHistory\r\n\r\ncry VersionHistory" + assert_equal "

f
Version History" + + "?

\n\n\n\t

cry " + + "Version History?" + + "

", + @revision.display_content + end + + def test_difficult_wiki_words + @revision.content = "[[It's just awesome GUI!]]" + assert_equal "

It's just awesome GUI!" + + "?

", + @revision.display_content + end + + def test_revisions_diff + Revision.create(:page => @page, :content => 'What a blue and lovely morning', :author => Author.new('DavidHeinemeierHansson')) + Revision.create(:page => @page, :content => 'What a red and lovely morning today', :author => Author.new('DavidHeinemeierHansson')) + + assert_equal "

What a blue red " + + "and lovely morningmorning " + + "today

", @page.revisions.last.display_diff + end + + def test_link_to_file + assert_markup_parsed_as( + '

doc.pdf?

', + '[[doc.pdf:file]]') + end + + def test_link_to_pic + FileUtils.mkdir_p "#{RAILS_ROOT}/storage/test/wiki1" + FileUtils.rm(Dir["#{RAILS_ROOT}/storage/test/wiki1/*"]) + @wiki.file_yard(@web).upload_file('square.jpg', StringIO.new('')) + assert_markup_parsed_as( + '

Square

', + '[[square.jpg|Square:pic]]') + assert_markup_parsed_as( + '

square.jpg

', + '[[square.jpg:pic]]') + end + + def test_link_to_non_existant_pic + assert_markup_parsed_as( + '

NonExistant?' + + '

', + '[[NonExistant.jpg|NonExistant:pic]]') + assert_markup_parsed_as( + '

NonExistant.jpg?' + + '

', + '[[NonExistant.jpg:pic]]') + end + + def test_wiki_link_with_colon + assert_markup_parsed_as( + '

With:Colon?

', + '[[With:Colon]]') + end + + # TODO Remove the leading underscores from this test when upgrading to RedCloth 3.0.1; + # also add a test for the "Unhappy Face" problem (another interesting RedCloth bug) + def test_list_with_tildas + list_with_tildas = <<-EOL + * "a":~b + * c~ d + EOL + + assert_markup_parsed_as( + "
    \n\t
  • a
  • \n\t\t
  • c~ d
  • \n\t
", + list_with_tildas) + end + + def test_textile_image_in_mixed_wiki + set_web_property :markup, :mixed + assert_markup_parsed_as( + "

\"\"\nss

", + "!http://google.com!\r\nss") + end + + def assert_markup_parsed_as(expected_output, input) + revision = Revision.new(:page => @page, :content => input, :author => Author.new('AnAuthor')) + assert_equal expected_output, revision.display_content, 'Textile output not as expected' + end end diff --git a/test/unit/uri_test.rb b/test/unit/uri_test.rb index 9cef4a23..4affbd60 100755 --- a/test/unit/uri_test.rb +++ b/test/unit/uri_test.rb @@ -1,4 +1,4 @@ -#!/bin/env ruby -w +#!/bin/env ruby require File.dirname(__FILE__) + '/../test_helper' require 'chunks/uri' diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index 2e504e0c..433b579f 100644 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,7 +1,158 @@ -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class WebTest < Test::Unit::TestCase - - fixtures 'webs', 'pages', 'revisions' + fixtures :webs, :pages, :revisions, :system + def setup + @web = webs(:instiki) + end + + def test_wiki_word_linking + @web.add_page('SecondPage', 'Yo, yo. Have you EverBeenHated', + Time.now, 'DavidHeinemeierHansson') + + assert_equal('

Yo, yo. Have you Ever Been Hated' + + '?

', + @web.page("SecondPage").display_content) + + @web.add_page('EverBeenHated', 'Yo, yo. Have you EverBeenHated', Time.now, + 'DavidHeinemeierHansson') + assert_equal('

Yo, yo. Have you Ever Been Hated

', + @web.page("SecondPage").display_content) + end + + def test_pages_by_revision + add_sample_pages + assert_equal 'EverBeenHated', @web.select.by_revision.first.name + end + + def test_pages_by_match + add_sample_pages + assert_equal 2, @web.select { |page| page.content =~ /me/i }.length + assert_equal 1, @web.select { |page| page.content =~ /Who/i }.length + assert_equal 0, @web.select { |page| page.content =~ /none/i }.length + end + + def test_references + add_sample_pages + assert_equal 1, @web.select.pages_that_reference('EverBeenHated').length + assert_equal 0, @web.select.pages_that_reference('EverBeenInLove').length + end + + def test_delete + add_sample_pages + assert_equal 2, @web.pages.length + @web.remove_pages([ @web.page('EverBeenInLove') ]) + assert_equal 1, @web.pages(true).length + end + + def test_make_link + add_sample_pages + + existing_page_wiki_url = + 'Ever Been In Love' + existing_page_published_url = + 'Ever Been In Love' + existing_page_static_url = + 'Ever Been In Love' + new_page_wiki_url = + 'Unknown Word?' + new_page_published_url = + new_page_static_url = + 'Unknown Word' + + # no options + assert_equal existing_page_wiki_url, @web.make_link('EverBeenInLove') + + # :mode => :export + assert_equal existing_page_static_url, @web.make_link('EverBeenInLove', nil, :mode => :export) + + # :mode => :publish + assert_equal existing_page_published_url, + @web.make_link('EverBeenInLove', nil, :mode => :publish) + + # new page, no options + assert_equal new_page_wiki_url, @web.make_link('UnknownWord') + + # new page, :mode => :export + assert_equal new_page_static_url, @web.make_link('UnknownWord', nil, :mode => :export) + + # new page, :mode => :publish + assert_equal new_page_published_url, @web.make_link('UnknownWord', nil, :mode => :publish) + + # Escaping special characters in the name + assert_equal( + 'Smith & Wesson?', + @web.make_link('Smith & Wesson')) + + # optionally using text as the link text + assert_equal( + existing_page_published_url.sub(/>Ever Been In LoveHaven't you ever been in love?<"), + @web.make_link('EverBeenInLove', "Haven't you ever been in love?", :mode => :publish)) + + end + + def test_initialize + web = Web.new(:name => 'Wiki2', :address => 'wiki2', :password => '123') + + assert_equal 'Wiki2', web.name + assert_equal 'wiki2', web.address + assert_equal '123', web.password + + # new web should be set for maximum features enabled + assert_equal :textile, web.markup + assert_equal '008B26', web.color + assert !web.safe_mode? + assert_equal([], web.pages) + assert web.allow_uploads? + assert_nil web.additional_style + assert !web.published? + assert !web.brackets_only? + assert !web.count_pages? + assert_equal 100, web.max_upload_size + end + + def test_initialize_invalid_name + assert_raises(Instiki::ValidationError) { + Web.create(:name => 'Wiki2', :address => "wiki\234", :password => '123') + } + end + + def test_new_page_linked_from_mother_page + # this was a bug in revision 204 + home = @web.add_page('HomePage', 'This page refers to AnotherPage', + Time.local(2004, 4, 4, 16, 50), 'Alexey Verkhovsky') + @web.add_page('AnotherPage', 'This is \AnotherPage', + Time.local(2004, 4, 4, 16, 51), 'Alexey Verkhovsky') + + @web.pages(true) + assert_equal [home], @web.select.pages_that_link_to('AnotherPage') + end + + def test_orphaned_pages + add_sample_pages + home = @web.add_page('HomePage', + 'This is a home page, it should not be an orphan', + Time.local(2004, 4, 4, 16, 50), 'AlexeyVerkhovsky') + author = @web.add_page('AlexeyVerkhovsky', + 'This is an author page, it should not be an orphan', + Time.local(2004, 4, 4, 16, 50), 'AlexeyVerkhovsky') + self_linked = @web.add_page('SelfLinked', + 'I am me SelfLinked and link to EverBeenInLove', + Time.local(2004, 4, 4, 16, 50), 'AnonymousCoward') + + # page that links to itself, and nobody else links to it must be an orphan + assert_equal ['EverBeenHated', 'SelfLinked'], + @web.select.orphaned_pages.collect{ |page| page.name }.sort + end + + private + + def add_sample_pages + @in_love = @web.add_page('EverBeenInLove', 'Who am I me', + Time.local(2004, 4, 4, 16, 50), 'DavidHeinemeierHansson') + @hated = @web.add_page('EverBeenHated', 'I am me EverBeenHated', + Time.local(2004, 4, 4, 16, 51), 'DavidHeinemeierHansson') + end end diff --git a/test/unit/wiki_words_test.rb b/test/unit/wiki_words_test.rb index a1aa1ff9..93bc5d12 100755 --- a/test/unit/wiki_words_test.rb +++ b/test/unit/wiki_words_test.rb @@ -1,6 +1,6 @@ -#!/bin/env ruby -w +#!/bin/env ruby -require File.dirname(__FILE__) + '/../test_helper' +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') require 'wiki_words' class WikiWordsTest < Test::Unit::TestCase