This commit is contained in:
2025-11-07 13:34:32 -08:00
commit 7578ff95dd
437 changed files with 11000 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
include SystemTestHelper
driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
end

View File

@@ -0,0 +1,19 @@
require "test_helper"
module ApplicationCable
class ConnectionTest < ActionCable::Connection::TestCase
test "connects with cookies" do
users(:kevin).sessions.start!(user_agent: "test", ip_address: "10.0.0.1").tap do |session|
cookies.signed[:session_token] = session.token
end
connect
assert_equal users(:kevin), connection.current_user
end
test "rejects connection without cookies" do
assert_reject_connection { connect }
end
end
end

View File

@@ -0,0 +1,31 @@
require "test_helper"
class ActionText::Markdown::UploadsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "attach a file" do
assert_changes -> { ActiveStorage::Attachment.count }, 1 do
post action_text_markdown_uploads_url, params: {
record_gid: pages(:welcome).to_signed_global_id.to_s,
attribute_name: "body",
file: fixture_file_upload("reading.webp", "image/webp")
}, as: :xhr
end
assert_response :success
end
test "view attached file" do
markdown = pages(:welcome).body.tap(&:save!)
markdown.uploads.attach fixture_file_upload("reading.webp", "image/webp")
attachment = pages(:welcome).body.uploads.last
get action_text_markdown_upload_url(slug: attachment.slug)
assert_response :redirect
assert_match /\/rails\/active_storage\/.*\/reading\.webp/, @response.redirect_url
end
end

View File

@@ -0,0 +1,33 @@
require "test_helper"
class Books::BookmarksControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "show includes a link to read the last read leaf" do
cookies["reading_progress_#{books(:handbook).id}"] = "#{leaves(:welcome_page).id}/3"
get book_bookmark_url(books(:handbook))
assert_response :success
assert_select "a", /Resume reading/
end
test "show includes a link to start reading if the last read leaf has been trashed" do
leaves(:welcome_page).trashed!
cookies["reading_progress_#{books(:handbook).id}"] = "#{leaves(:welcome_page).id}/3"
get book_bookmark_url(books(:handbook))
assert_response :success
assert_select "a", /Start reading/
end
test "show includes a link to start reading if no reading progress has been recorded" do
get book_bookmark_url(books(:handbook))
assert_response :success
assert_select "a", /Start reading/
end
end

View File

@@ -0,0 +1,25 @@
require "test_helper"
class Books::Leaves::MovesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "moving a single item" do
assert_equal [ leaves(:welcome_section), leaves(:welcome_page), leaves(:summary_page), leaves(:reading_picture) ], books(:handbook).leaves.positioned
post book_leaves_moves_url(books(:handbook), id: leaves(:welcome_page).id, position: 0)
assert_response :no_content
assert_equal [ leaves(:welcome_page), leaves(:welcome_section), leaves(:summary_page), leaves(:reading_picture) ], books(:handbook).leaves.positioned
end
test "moving multiple items" do
assert_equal [ leaves(:welcome_section), leaves(:welcome_page), leaves(:summary_page), leaves(:reading_picture) ], books(:handbook).leaves.positioned
post book_leaves_moves_url(books(:handbook), id: leaves(:summary_page, :reading_picture).map(&:id), position: 1)
assert_response :no_content
assert_equal [ leaves(:welcome_section), leaves(:summary_page), leaves(:reading_picture), leaves(:welcome_page) ], books(:handbook).leaves.positioned
end
end

View File

@@ -0,0 +1,32 @@
require "test_helper"
class Books::PublicationsTest < ActionDispatch::IntegrationTest
setup do
@book = books(:manual)
sign_in :david
end
test "publish a book" do
assert_changes -> { @book.reload.published? }, from: false, to: true do
patch book_publication_url(@book), params: { book: { published: "1" } }
end
@book.reload
assert_redirected_to book_slug_url(@book)
assert_equal "manual", @book.slug
end
test "edit book slug" do
@book.update! published: true
get edit_book_publication_url(@book)
assert_response :success
patch book_publication_url(@book), params: { book: { slug: "new-slug" } }
@book.reload
assert_redirected_to book_slug_url(@book)
assert_equal "new-slug", @book.slug
end
end

View File

@@ -0,0 +1,89 @@
require "test_helper"
class BooksControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "index lists the current user's books" do
get root_url
assert_response :success
assert_select "h2", text: "Handbook"
assert_select "h2", text: "Manual", count: 0
end
test "index includes published books, even when the user does not have access" do
books(:manual).update!(published: true)
get root_url
assert_response :success
assert_select "h2", text: "Handbook"
assert_select "h2", text: "Manual"
end
test "index shows published books when not logged in" do
books(:manual).update!(published: true)
sign_out
get root_url
assert_response :success
assert_select "h2", text: "Handbook", count: 0
assert_select "h2", text: "Manual"
end
test "index redirects to login if not signed in and no published books exist" do
sign_out
get root_url
assert_redirected_to new_session_url
end
test "create makes the current user an editor" do
assert_difference -> { Book.count }, +1 do
post books_url, params: { book: { title: "New Book", everyone_access: false } }
end
assert_redirected_to book_slug_url(Book.last)
book = Book.last
assert_equal "New Book", book.title
assert_equal 1, Book.last.accesses.count
assert book.editable?(user: users(:kevin))
end
test "create sets additional accesses" do
sign_in :jason
assert_difference -> { Book.count }, +1 do
post books_url, params: { book: { title: "New Book", everyone_access: false }, "editor_ids[]": users(:jz).id, "reader_ids[]": users(:kevin).id }
end
book = Book.last
assert_equal "New Book", book.title
assert_equal 3, Book.last.accesses.count
assert book.editable?(user: users(:jz))
assert book.accessable?(user: users(:kevin))
assert_not book.editable?(user: users(:kevin))
end
test "show only shows books the current user can access" do
get book_slug_url(books(:manual))
assert_response :not_found
get book_slug_url(books(:handbook))
assert_response :success
end
test "show includes OG metadata for public access" do
get book_slug_url(books(:handbook))
assert_response :success
assert_select "meta[property='og:title'][content='Handbook']"
assert_select "meta[property='og:url'][content='#{book_slug_url(books(:handbook))}']"
end
end

View File

@@ -0,0 +1,29 @@
require "test_helper"
class Accounts::CustomStylesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :david
end
test "edit" do
get edit_account_custom_styles_url
assert_response :ok
end
test "update" do
assert users(:david).administrator?
put account_custom_styles_url, params: { account: { custom_styles: ":root { --color-text: red; }" } }
assert_redirected_to edit_account_custom_styles_url
assert_equal accounts(:signal).custom_styles, ":root { --color-text: red; }"
end
test "non-admins cannot update" do
sign_in :kevin
assert users(:kevin).member?
put account_custom_styles_url, params: { account: { custom_styles: ":root { --color-text: red; }" } }
assert_response :forbidden
end
end

View File

@@ -0,0 +1,48 @@
require "test_helper"
class FirstRunsControllerTest < ActionDispatch::IntegrationTest
setup do
Book.destroy_all
User.destroy_all
end
test "new" do
get first_run_url
assert_response :success
end
test "new is not permitted when users exist" do
create_user
get first_run_url
assert_redirected_to root_url
end
test "create" do
assert_difference -> { User.count }, 1 do
post first_run_url, params: { user: { name: "New Person", email_address: "new@37signals.com", password: "secret123456" } }
end
assert_redirected_to root_url
assert parsed_cookies.signed[:session_token]
end
test "create is not permitted when users exist" do
create_user
assert_no_difference -> { User.count } do
post first_run_url, params: { user: { name: "New Person", email_address: "new@37signals.com", password: "secret123456" } }
end
assert_redirected_to root_url
end
private
def create_user
User.create! \
name: "Existing Person",
email_address: "user@example.com",
password: "secret123456"
end
end

View File

@@ -0,0 +1,20 @@
require "test_helper"
class Accounts::JoinCodesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :david
end
test "create new join code" do
assert_changes -> { accounts(:signal).reload.join_code } do
post account_join_code_url
assert_redirected_to users_url
end
end
test "only administrators can create new join codes" do
sign_in :jz
post account_join_code_url
assert_response :forbidden
end
end

View File

@@ -0,0 +1,54 @@
require "test_helper"
class LeafablesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "show" do
get leafable_slug_path(leaves(:welcome_page))
assert_response :success
assert_select "p", "This is such a great handbook."
end
test "show with public access to a published book" do
sign_out
books(:handbook).update!(published: true)
get leafable_slug_path(leaves(:welcome_page))
assert_response :success
assert_select "p", "This is such a great handbook."
end
test "show does not allow public access to an unpublished book" do
sign_out
get leafable_slug_path(leaves(:welcome_page))
assert_response :not_found
end
test "create" do
assert_changes -> { books(:handbook).leaves.count }, +1 do
post book_pages_path(books(:handbook), format: :turbo_stream), params: {
leaf: { title: "Another page" }, page: { body: "With interesting words." }
}
end
assert_response :success
end
test "create requires editor access" do
books(:handbook).access_for(user: users(:kevin)).update! level: :reader
assert_no_changes -> { books(:handbook).leaves.count } do
post book_pages_path(books(:handbook), format: :turbo_stream), params: {
leaf: { title: "Another page" }, page: { body: "With interesting words." }
}
end
assert_response :forbidden
end
end

View File

@@ -0,0 +1,26 @@
require "test_helper"
class Pages::EditsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "show an edit" do
leaves(:welcome_page).edit leafable_params: { body: "Completely new content" }
get page_edit_url(leaves(:welcome_page), leaves(:welcome_page).edits.last)
assert_response :success
assert_select "p", /such a great handbook/
assert_select "p", /Completely new content/
end
test "show latest edit" do
leaves(:welcome_page).edit leafable_params: { body: "Updated" }
get page_edit_url(leaves(:welcome_page), "latest")
assert_response :success
assert_select "p", /such a great handbook/
end
end

View File

@@ -0,0 +1,86 @@
require "test_helper"
class PagesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "show" do
get leafable_path(sample_page_leaf("## Hello"))
assert_response :ok
assert_select "h2", text: /Hello/
end
test "show sanitizes dangerous content" do
get leafable_path(sample_page_leaf(%(<div id="test"><script>alert("ouch")</script></div>)))
assert_select "#test", html: %(alert("ouch"))
end
test "show with HTML content in the markdown" do
get leafable_path(sample_page_leaf(%(<div id="test"><div style="text-align:center;">Hello</div></div>)))
assert_select "#test", html: %(<div style="text-align:center;">Hello</div>)
end
test "show with iframes" do
get leafable_path(sample_page_leaf(%(<div id="test"><iframe src="http://example.com"></iframe></div>)))
assert_select "#test", html: %(<iframe src="http://example.com"></iframe>)
end
test "show with tables in the markdown" do
get leafable_path(sample_page_leaf(%(| name | food |\n| ---- | ---- |\n| Kevin | Pizza |)))
assert_select "table th", text: "name"
assert_select "table th", text: "food"
assert_select "table td", text: "Kevin"
assert_select "table td", text: "Pizza"
end
test "create" do
post book_pages_path(books(:handbook), format: :turbo_stream), params: { leaf: { title: "Another page" }, page: { body: "With interesting words." } }
assert_response :success
new_page = Page.last
assert_equal "Another page", new_page.title
assert_equal "With interesting words.", new_page.body.content
assert_equal books(:handbook), new_page.leaf.book
end
test "create with default params" do
assert_changes -> { Page.count }, +1 do
post book_pages_path(books(:handbook), format: :turbo_stream)
end
assert_response :success
assert_equal "Untitled", Page.last.title
end
test "create at a specific position" do
assert_changes -> { Page.count }, +1 do
post book_pages_path(books(:handbook), format: :turbo_stream), params: { position: 2 }
end
assert_response :success
assert_equal 2, books(:handbook).leaves.before(Page.last.leaf).count
end
test "update" do
get edit_leafable_path(leaves(:welcome_page))
assert_response :ok
put leafable_path(leaves(:welcome_page)), params: { leaf: { title: "Better welcome" }, page: { body: "With even more interesting words." } }
assert_response :no_content
updated_page = Page.last
assert_equal "Better welcome", updated_page.title
assert_equal "With even more interesting words.", updated_page.body.content
end
private
def sample_page_leaf(markdown)
books(:handbook).press Page.new(body: markdown), title: "Sample"
end
end

View File

@@ -0,0 +1,41 @@
require "test_helper"
class PicturesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "update picture" do
get edit_leafable_path(leaves(:reading_picture))
assert_response :ok
put leafable_path(leaves(:reading_picture)), params: {
leaf: { title: "New picture" },
picture: {
image: fixture_file_upload("white-rabbit.webp", "image/webp")
} }
assert_response :no_content
updated_picture = Picture.last
assert_equal "New picture", updated_picture.title
assert_equal "white-rabbit.webp", updated_picture.image.filename.to_s
end
test "update caption" do
get edit_leafable_path(leaves(:reading_picture))
assert_response :ok
put leafable_path(leaves(:reading_picture)), params: {
picture: {
caption: "New caption"
} }
assert_response :no_content
updated_picture = Picture.last
assert_equal "New caption", updated_picture.caption
assert_equal "reading.webp", updated_picture.image.filename.to_s
end
end

View File

@@ -0,0 +1,37 @@
require "test_helper"
class SectionsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in :kevin
end
test "create" do
post book_sections_path(books(:handbook), format: :turbo_stream)
assert_response :success
new_section = Section.last
assert_equal "Section", new_section.title
assert_equal books(:handbook), new_section.leaf.book
end
test "update" do
put leafable_path(leaves(:welcome_section)), params: {
leaf: { title: "Title" },
section: { body: "Section body" }
}
assert_response :success
section = leaves(:welcome_section).reload.leafable
assert_equal "Title", section.title
assert_equal "Section body", section.body
end
test "update with no body supplied" do
put leafable_path(leaves(:welcome_section)), params: { leaf: { title: "New title" } }
assert_response :success
section = leaves(:welcome_section).reload.leafable
assert_equal "New title", section.title
assert_equal "New title", section.body
end
end

View File

@@ -0,0 +1,18 @@
require "test_helper"
class Sessions::TransfersControllerTest < ActionDispatch::IntegrationTest
test "show renders when not signed in" do
get session_transfer_url("some-token")
assert_response :success
end
test "update establishes a session when the code is valid" do
user = users(:david)
put session_transfer_url(user.transfer_id)
assert_redirected_to root_url
assert parsed_cookies.signed[:session_token]
end
end

View File

@@ -0,0 +1,52 @@
require "test_helper"
class SessionsControllerTest < ActionDispatch::IntegrationTest
ALLOWED_BROWSER = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15"
DISALLOWED_BROWSER = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0"
test "new" do
get new_session_url
assert_response :success
end
test "new redirects to first run when no users exist" do
User.destroy_all
get new_session_url
assert_redirected_to first_run_url
end
test "new denied with incompatible browser" do
get new_session_url, env: { "HTTP_USER_AGENT" => DISALLOWED_BROWSER }
assert_select "svg", message: /Your browser is not supported/
end
test "new allowed with compatible browser" do
get new_session_url, env: { "HTTP_USER_AGENT" => ALLOWED_BROWSER }
assert_select "svg", message: /Your browser is not supported/, count: 0
end
test "create with valid credentials" do
post session_url, params: { email_address: "david@37signals.com", password: "secret123456" }
assert_redirected_to root_url
assert parsed_cookies.signed[:session_token]
end
test "create with invalid credentials" do
post session_url, params: { email_address: "david@37signals.com", password: "wrong" }
assert_response :unauthorized
assert_nil parsed_cookies.signed[:session_token]
end
test "destroy" do
sign_in :david
delete session_url
assert_redirected_to root_url
assert_not cookies[:session_token].present?
end
end

View File

@@ -0,0 +1,41 @@
require "test_helper"
class Users::ProfilesControllerTest < ActionDispatch::IntegrationTest
test "show" do
sign_in :david
get user_profile_url(users(:david))
assert_response :success
get user_profile_url(users(:kevin))
assert_response :success
end
test "edit is accessable to the current user" do
sign_in :david
get edit_user_profile_url(users(:david))
assert_response :success
get edit_user_profile_url(users(:kevin))
assert_response :forbidden
end
test "update modifies profile for current user" do
sign_in :kevin
put user_profile_url(users(:kevin)), params: { user: { name: "Bob" } }
assert_redirected_to users_url
assert_equal "Bob", users(:kevin).reload.name
end
test "update prohibits modifying profile of other users" do
sign_in :david
put user_profile_url(users(:kevin)), params: { user: { name: "Bob" } }
assert_response :forbidden
assert_equal "Kevin", users(:kevin).reload.name
end
end

View File

@@ -0,0 +1,81 @@
require "test_helper"
class UsersControllerTest < ActionDispatch::IntegrationTest
setup do
@join_code = accounts(:signal).join_code
end
test "new" do
get join_url(@join_code)
assert_response :success
end
test "new does not allow a signed in user" do
sign_in :david
get join_url(@join_code)
assert_redirected_to root_url
end
test "new requires a join code" do
get join_url("not")
assert_response :not_found
end
test "create" do
assert_difference -> { User.count }, 1 do
post join_url(@join_code), params: { user: { name: "New Person", email_address: "new@37signals.com", password: "secret123456" } }
end
assert_redirected_to root_url
user = User.last
assert_equal user.id, Session.find_by(token: parsed_cookies.signed[:session_token]).user.id
end
test "creating a new user with an existing email address redirects to login screen" do
assert_no_difference -> { User.count } do
post join_url(@join_code), params: { user: { name: "Another David", email_address: users(:david).email_address, password: "secret123456" } }
end
assert_redirected_to new_session_url(email_address: users(:david).email_address)
end
test "update" do
sign_in :david
assert users(:david).administrator?
put user_url(users(:kevin)), params: { user: { role: "administrator" } }
assert_redirected_to users_url
assert users(:kevin).reload.administrator?
end
test "update does not allow non-admins to change roles" do
sign_in :kevin
assert_not users(:kevin).administrator?
put user_url(users(:kevin)), params: { user: { role: "administrator" } }
assert_response :forbidden
assert_not users(:kevin).reload.administrator?
end
test "destroy" do
sign_in :david
assert_difference -> { User.active.count }, -1 do
delete user_url(users(:kevin))
end
assert_redirected_to users_url
assert_nil User.active.find_by(id: users(:kevin).id)
end
test "destroy is not allowed to non-admins" do
sign_in :kevin
delete user_url(users(:david))
assert_response :forbidden
end
end

34
test/fixtures/accesses.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
david_handbook:
user: david
book: handbook
level: editor
david_manual:
user: david
book: manual
level: editor
jason_handbook:
user: jason
book: handbook
level: editor
jason_manual:
user: jason
book: manual
level: editor
jz_handbook:
user: jz
book: handbook
level: reader
jz_manual:
user: jz
book: manual
level: reader
kevin_handbook:
user: kevin
book: handbook
level: editor

3
test/fixtures/accounts.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
signal:
name: 37signals
join_code: cs3s-enl1-EKC3

View File

@@ -0,0 +1,9 @@
welcome:
record: welcome (Page)
name: body
content: This is _such_ a great handbook.
summary:
record: summary (Page)
name: body
content: Thanks for reading!

View File

@@ -0,0 +1,4 @@
handbook_reading_image:
name: image
record: reading (Picture)
blob: handbook_reading_image_blob

View File

@@ -0,0 +1 @@
handbook_reading_image_blob: <%= ActiveStorage::FixtureSet.blob filename: "reading.webp", service_name: "test" %>

7
test/fixtures/books.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
handbook:
title: Handbook
slug: handbook
manual:
title: Manual
slug: manual

0
test/fixtures/files/.keep vendored Normal file
View File

BIN
test/fixtures/files/reading.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

BIN
test/fixtures/files/white-rabbit.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

27
test/fixtures/leaves.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
welcome_section:
book: handbook
title: The Welcome Section
leafable: welcome (Section)
position_score: 1
status: active
welcome_page:
book: handbook
title: Welcome to The Handbook!
leafable: welcome (Page)
position_score: 2
status: active
summary_page:
book: handbook
title: Summary
leafable: summary (Page)
position_score: 3
status: active
reading_picture:
book: handbook
title: Reading
leafable: reading (Picture)
position_score: 4
status: active

4
test/fixtures/pages.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
welcome: {}
summary: {}

1
test/fixtures/pictures.yml vendored Normal file
View File

@@ -0,0 +1 @@
reading: {}

1
test/fixtures/sections.yml vendored Normal file
View File

@@ -0,0 +1 @@
welcome: {}

25
test/fixtures/users.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
<% password_digest = BCrypt::Password.create("secret123456") %>
david:
name: David
email_address: david@37signals.com
password_digest: <%= password_digest %>
role: administrator
jason:
name: Jason
email_address: jason@37signals.com
password_digest: <%= password_digest %>
role: administrator
jz:
name: JZ
email_address: jz@37signals.com
password_digest: <%= password_digest %>
role: member
kevin:
name: Kevin
email_address: kevin@37signals.com
password_digest: <%= password_digest %>
role: member

0
test/helpers/.keep Normal file
View File

0
test/integration/.keep Normal file
View File

View File

@@ -0,0 +1,11 @@
require "test_helper"
class MarkdownRendererTest < ActiveSupport::TestCase
test "it generates unique IDs for headers" do
markdown = MarkdownRenderer.build
content = markdown.render("# Header 1\n\n## Duplicated Header\n\n### Duplicated header\n\n")
assert_includes content, "id='duplicated-header'"
assert_includes content, "id='duplicated-header-2'"
end
end

0
test/mailers/.keep Normal file
View File

0
test/models/.keep Normal file
View File

View File

@@ -0,0 +1,36 @@
require "test_helper"
class Book::AccessableTest < ActiveSupport::TestCase
test "update_access always grants read access to everyone when everyone_access is set" do
book = Book.create!(title: "My new book")
book.update_access(editors: [], readers: [])
assert book.everyone_access?
User.all.each do |user|
assert book.accessable?(user: user)
assert_not book.editable?(user: user) unless user.administrator?
end
end
test "update_access updates existing access" do
book = Book.create!(title: "My new book", everyone_access: false)
book.update_access(editors: [ users(:kevin).id ], readers: [])
assert book.editable?(user: users(:kevin))
book.update_access(editors: [], readers: [ users(:kevin).id ])
assert book.accessable?(user: users(:kevin))
assert_not book.editable?(user: users(:kevin))
end
test "update_access removes stale accesses" do
book = Book.create!(title: "My new book", everyone_access: false)
book.update_access(editors: [ users(:kevin).id ], readers: [ users(:jz).id ])
assert_equal 2, book.accesses.size
book.update_access(editors: [ users(:kevin).id ], readers: [])
assert_equal 1, book.accesses.size
end
end

16
test/models/book_test.rb Normal file
View File

@@ -0,0 +1,16 @@
require "test_helper"
class BookTest < ActiveSupport::TestCase
test "slug is generated from title" do
book = Book.create!(title: "Hello, World!")
assert_equal "hello-world", book.slug
end
test "press a leafable" do
leaf = books(:manual).press Page.new(body: "Important words"), title: "Introduction"
assert leaf.page?
assert_equal "Important words", leaf.page.body.content.to_s
assert_equal "Introduction", leaf.title
end
end

View File

@@ -0,0 +1,37 @@
require "test_helper"
class FirstRunTest < ActiveSupport::TestCase
setup do
Book.destroy_all
User.destroy_all
Account.destroy_all
end
test "creating makes first user an administrator" do
user = create_first_run_user
assert user.administrator?
end
test "creates an account" do
assert_changes -> { Account.count }, +1 do
create_first_run_user
end
end
test "creates a demo book" do
assert_changes -> { Book.count }, to: 1 do
create_first_run_user
end
book = Book.first
assert book.editable?(user: User.first)
assert book.cover.attached?
assert book.leaves.any?
end
private
def create_first_run_user
FirstRun.create!({ name: "User", email_address: "user@example.com", password: "secret123456" })
end
end

View File

@@ -0,0 +1,66 @@
require "test_helper"
class Leaf::EditableTest < ActiveSupport::TestCase
test "editing a leafable records the edit" do
leaves(:welcome_page).edit leafable_params: { body: "New body" }
assert_equal "New body", leaves(:welcome_page).page.body.content
assert leaves(:welcome_page).edits.last.revision?
assert_equal "This is _such_ a great handbook.", leaves(:welcome_page).edits.last.page.body.content
end
test "edits that are close together don't create new revisions" do
assert_difference -> { leaves(:welcome_page).edits.count }, +1 do
leaves(:welcome_page).edit leafable_params: { body: "First change" }
end
freeze_time
travel 1.minute
assert_no_difference -> { leaves(:welcome_page).edits.count } do
leaves(:welcome_page).edit leafable_params: { body: "Second change" }
end
assert_equal "Second change", leaves(:welcome_page).page.body.content
assert_equal Time.now, leaves(:welcome_page).edits.last.updated_at
travel 1.hour
assert_difference -> { leaves(:welcome_page).edits.count }, +1 do
leaves(:welcome_page).edit leafable_params: { body: "Third change" }
end
end
test "changing a leaf title doesn't create a revision" do
assert_no_difference -> { Edit.count } do
leaves(:welcome_page).edit leaf_params: { title: "New title" }
end
assert_equal "New title", leaves(:welcome_page).title
end
test "changes that don't affect the leafable don't create a revision" do
assert_no_difference -> { Edit.count } do
leaves(:welcome_page).edit leafable_params: {}
end
end
test "editing a leafable with an attachment includes the attachments in the new version" do
assert leaves(:reading_picture).picture.image.attached?
leaves(:reading_picture).edit leaf_params: { title: "New title" }
assert_equal "New title", leaves(:reading_picture).title
assert leaves(:reading_picture).picture.image.attached?
end
test "trashing a leaf records the edit" do
leaves(:welcome_page).trashed!
assert leaves(:welcome_page).trashed?
assert leaves(:welcome_page).edits.last.trash?
assert_equal "This is _such_ a great handbook.", leaves(:welcome_page).edits.last.page.body.content
end
end

View File

@@ -0,0 +1,96 @@
require "test_helper"
class Leaf::PositionableTest < ActiveSupport::TestCase
setup do
@leaves = books(:handbook).leaves.positioned
end
test "items are sorted in positioned order" do
assert_equal [ leaves(:welcome_section), leaves(:welcome_page), leaves(:summary_page), leaves(:reading_picture) ], @leaves
end
test "items can be moved earlier" do
leaves(:welcome_page).move_to_position(0)
assert_equal [ leaves(:welcome_page), leaves(:welcome_section), leaves(:summary_page), leaves(:reading_picture) ], @leaves.reload
end
test "items can be moved beyond the start, which puts them at the start" do
leaves(:welcome_page).move_to_position(-99)
assert_equal [ leaves(:welcome_page), leaves(:welcome_section), leaves(:summary_page), leaves(:reading_picture) ], @leaves.reload
end
test "items can be moved later" do
leaves(:welcome_section).move_to_position(2)
assert_equal [ leaves(:welcome_page), leaves(:summary_page), leaves(:welcome_section), leaves(:reading_picture) ], @leaves.reload
end
test "items can be moved beyond the end, which puts them at the end" do
leaves(:welcome_section).move_to_position(99)
assert_equal [ leaves(:welcome_page), leaves(:summary_page), leaves(:reading_picture), leaves(:welcome_section) ], @leaves.reload
end
test "items can be moved to their existing position" do
leaves(:welcome_page).move_to_position(1)
assert_equal [ leaves(:welcome_section), leaves(:welcome_page), leaves(:summary_page), leaves(:reading_picture) ], @leaves.reload
end
test "items can be moved in blocks" do
leaves(:welcome_section).move_to_position(1, followed_by: [ leaves(:welcome_page), leaves(:summary_page) ])
assert_equal [ leaves(:reading_picture), leaves(:welcome_section), leaves(:welcome_page), leaves(:summary_page) ], @leaves.reload
end
test "new items are inserted at the end" do
new_page = books(:handbook).press Page.new(body: "New Page"), title: "New Page"
assert_equal new_page, books(:handbook).leaves.positioned.last
end
test "the first item in the collection has the expected score" do
books(:handbook).leaves.destroy_all
new_page = books(:handbook).press Page.new(body: "New Page"), title: "New Page"
assert_equal 1, new_page.position_score
end
test "positioning is rebalanced when necessary" do
leaves(:welcome_section).update!(position_score: 1e-11)
leaves(:welcome_page).update!(position_score: 2e-11)
leaves(:summary_page).move_to_position(1)
assert_equal leaves(:summary_page), @leaves.reload.second
assert_equal [ 1, 2, 3, 4 ], @leaves.pluck(:position_score)
end
test "items know their neighbours" do
assert_equal leaves(:welcome_section), leaves(:welcome_page).previous
assert_equal leaves(:summary_page), leaves(:welcome_page).next
assert_nil leaves(:welcome_section).previous
assert_nil leaves(:reading_picture).next
end
test "only active items are included as neighbours" do
assert_equal leaves(:summary_page), leaves(:welcome_page).next
leaves(:summary_page).trashed!
assert_equal leaves(:reading_picture), leaves(:welcome_page).next
end
test "only active items are counted when determining position" do
leaves(:welcome_page).trashed!
leaves(:welcome_section).move_to_position(1)
assert_equal [ leaves(:summary_page), leaves(:welcome_section), leaves(:reading_picture) ], @leaves.reload.active
leaves(:welcome_section).move_to_position(0)
assert_equal [ leaves(:welcome_section), leaves(:summary_page), leaves(:reading_picture) ], @leaves.reload.active
end
end

13
test/models/leaf_test.rb Normal file
View File

@@ -0,0 +1,13 @@
require "test_helper"
class LeafTest < ActiveSupport::TestCase
test "slug is generated from title" do
leaf = Leaf.new(title: "Hello, World!")
assert_equal "hello-world", leaf.slug
end
test "slug is never completely blank" do
leaf = Leaf.new(title: "")
assert_equal "-", leaf.slug
end
end

10
test/models/page_test.rb Normal file
View File

@@ -0,0 +1,10 @@
require "test_helper"
class PageTest < ActiveSupport::TestCase
test "html preview" do
page = Page.new(body: "# Hello\n\nWorld!")
assert_match /<h1>Hello<\/h1>/, page.html_preview
assert_match /<p>World!<\/p>/, page.html_preview
end
end

View File

@@ -0,0 +1,11 @@
require "test_helper"
class QrCodeLinkTest < ActiveSupport::TestCase
test "links can be signed and verified" do
link = QrCodeLink.new "https://example.com"
signed_link = link.signed
verified = QrCodeLink.from_signed(signed_link)
assert_equal link.url, verified.url
end
end

View File

@@ -0,0 +1,14 @@
require "test_helper"
class User::RoleTest < ActiveSupport::TestCase
test "creating users makes them members by default" do
assert User.create!(name: "User", email_address: "user@example.com", password: "secret123456").member?
end
test "can_administer?" do
assert User.new(role: :administrator).can_administer?
assert_not User.new(role: :member).can_administer?
assert_not User.new.can_administer?
end
end

20
test/models/user_test.rb Normal file
View File

@@ -0,0 +1,20 @@
require "test_helper"
class UserTest < ActiveSupport::TestCase
test "user does not prevent very long passwords" do
users(:david).update(password: "secret" * 50)
assert users(:david).valid?
end
test "new users get access to everyone books" do
everyone_book = Book.create!(title: "My new book", everyone_access: true)
other_book = Book.create!(title: "My secret book", everyone_access: false)
bob = User.create!(email_address: "bob@example.com", name: "Bob", password: "secret123456")
assert everyone_book.accessable?(user: bob)
assert_not everyone_book.editable?(user: bob)
assert_not other_book.accessable?(user: bob)
end
end

View File

@@ -0,0 +1,19 @@
require "application_system_test_case"
class EditPageTest < ApplicationSystemTestCase
setup do
sign_in "kevin@37signals.com"
end
test "edit page" do
visit edit_book_page_url(books(:handbook), leaves(:welcome_page))
assert_selector "house-md"
fill_house_editor "page[body]", with: "Welcome to the handbook! This is the **first** page."
click_button "Save"
assert_selector ".house-md-content", text: "Welcome to the handbook! This is the **first** page."
assert_selector ".house-md-content strong", text: "first"
end
end

View File

@@ -0,0 +1,38 @@
require "application_system_test_case"
class PublishTest < ApplicationSystemTestCase
setup do
sign_in "kevin@37signals.com"
end
test "create and publish a book" do
visit new_book_url
fill_in "Book title", with: "My Book of Jokes"
fill_in "Author", with: "Kevin"
within "footer" do
click_button
end
assert_text "My Book of Jokes"
click_on "Add a new section page"
fill_in "leaf_title", with: "A horse walks into a bar"
click_on "Save"
click_on "Add a new section page"
fill_in "leaf_title", with: "And the barman says 'Why the long face?'"
click_on "Save"
find(class: "switch__btn").click
public_url = find(id: "invite_url").value
using_session "public" do
visit public_url
assert_text "My Book of Jokes"
page.send_keys :arrow_right
assert_text "A horse walks into a bar"
end
end
end

15
test/test_helper.rb Normal file
View File

@@ -0,0 +1,15 @@
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
module ActiveSupport
class TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
include SessionTestHelper
end
end

View File

@@ -0,0 +1,16 @@
module SessionTestHelper
def parsed_cookies
ActionDispatch::Cookies::CookieJar.build(request, cookies.to_hash)
end
def sign_in(user)
user = users(user) unless user.is_a? User
post session_url, params: { email_address: user.email_address, password: "secret123456" }
assert cookies[:session_token].present?
end
def sign_out
delete session_url
assert_not cookies[:session_token].present?
end
end

View File

@@ -0,0 +1,20 @@
module SystemTestHelper
include ActionView::Helpers::JavaScriptHelper
def sign_in(email_address, password = "secret123456")
visit new_session_url
fill_in "email_address", with: email_address
fill_in "password", with: password
click_on "log_in"
assert_selector "h2", text: "Handbook"
end
def fill_house_editor(name, content)
execute_script <<~JS
const editor = document.querySelector("[name='#{name}']")
editor.value = "#{escape_javascript(content)}"
JS
end
end