integtest/spec/single_book_spec.rb (854 lines of code) (raw):

# frozen_string_literal: true require 'fileutils' require 'net/http' require_relative 'spec_helper' RSpec.describe 'building a single book' do HEADER = <<~ASCIIDOC = Title [[chapter]] == Chapter ASCIIDOC let(:emdash) { '&#8212;' } let(:ellipsis) { '&#8230;' } let(:zero_width_space) { '&#8203;' } context 'for a minimal book' do shared_context 'expected' do |file_name| convert_single_before_context do |src| src.write file_name, <<~ASCIIDOC #{HEADER} This is a minimal viable asciidoc file for use with build_docs. The actual contents of this paragraph aren't important but having a paragraph here is required. ASCIIDOC end page_context 'index.html' do it 'has the right title' do expect(title).to eq('Title') end it 'has a link to the css' do expect(head).to include(<<~HTML) <link rel="stylesheet" type="text/css" href="/guide/static/styles-v1.css" /> HTML end it 'has a link to the js' do expect(contents).to include(<<~HTML) <script type="text/javascript" src="/guide/static/docs-v1.js"></script> HTML end it 'has the right language' do expect(language).to eq('en') end it 'has an empty initial js state' do expect(contents).to initial_js_state(be_empty) end end page_context 'toc.html' do it "doesn't have the initial js state" do # because we didn't apply the template expect(contents).to initial_js_state(be_nil) end end page_context 'raw/toc.html' page_context 'raw/index.html' do it "doesn't have the xml prolog" do expect(contents).not_to include('?xml') end it 'has the html5 doctype' do expect(contents).to include("<!DOCTYPE html>\n") end it "doesn't have any xmlns declarations" do expect(contents).not_to include('xmlns=') end it "doesn't have a meta generator" do expect(contents).not_to include('<meta name="generator"') end it "doesn't have a meta description" do expect(contents).not_to include('<meta name="description"') end it "doesn't have any xml:lang tags" do expect(contents).not_to include('xml:lang=') end it 'has a trailing newline' do expect(contents).to end_with("\n") end it 'has the right title in head' do expect(head_title).to match(/Title\s+\|\s+Elastic/m) end it 'has the right title' do expect(title).to eq('Title') end it "doesn't have the initial js state" do # because we don't apply the template which is how that gets in there expect(contents).to initial_js_state(be_nil) end end page_context 'chapter.html' do it 'has the right title in head' do expect(head_title).to match(/Chapter\s+\|\s+Title\s+\|\s+Elastic/m) end it 'has the right title' do expect(title).to eq('Chapter') end end page_context 'raw/chapter.html' do it 'has the right title in head' do expect(head_title).to match(/Chapter\s+\|\s+Title\s+\|\s+Elastic/m) end it 'has the right title' do expect(title).to eq('Chapter') end end end context 'when the file ends in .asciidoc' do include_context 'expected', 'minimal.asciidoc' end context 'when the file ends in .adoc' do include_context 'expected', 'minimal.adoc' end end context "when there isn't an elastic remote" do convert_single_before_context do |src| src.add_elastic_remote = false src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} This is a minimal viable asciidoc file for use with build_docs. The actual contents of this paragraph aren't important but having a paragraph here is required. ASCIIDOC end let(:repo) { src.repo 'src' } context 'the logs' do it 'say they are using the first remote intead' do expect(outputs[0]).to include(<<~LOG) Couldn't find an Elastic remote for #{repo.root}. Generating edit links targeting the first remote instead. LOG end end page_context 'chapter.html' do it 'has an "unknown" edit url' do expect(body).to include(<<~HTML.strip) <a class="edit_me" rel="nofollow" title="Edit this page on GitHub" href="unknown/edit/master/index.asciidoc">edit</a> HTML end end end context 'when one file includes another' do convert_single_before_context do |src| src.write 'included.asciidoc', 'I am tiny.' src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} I include "included" between here include::included.asciidoc[] and here. ASCIIDOC end page_context 'chapter.html' do it 'contains the index text' do expect(body).to include('I include "included"') end it 'contains the included text' do expect(body).to include('I am tiny.') end end end context 'for "interesting" inputs' do context 'for a book that contains an em dash' do convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} I have an em dash between some--words. ASCIIDOC end page_context 'chapter.html' do it 'the emdash is followed by a zero width space' do expect(body).to include("some#{emdash}#{zero_width_space}words") end end end context 'for a book that contains an ellipsis' do convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} I have an ellipsis between some...words. ASCIIDOC end page_context 'chapter.html' do it 'the ellipsis is followed by a zero width space' do expect(body).to include("some#{ellipsis}#{zero_width_space}words") end end end context 'for a book that contains an empty table cell' do convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} |=== | Title | Other Title | | Empty cell before this one |=== ASCIIDOC end let(:empty_cell) do <<~HTML.strip <td align="left" valign="top"><p></p></td> HTML end let(:non_empty_cell) do <<~HTML.strip <td align="left" valign="top"><p>Empty cell before this one</p></td> HTML end page_context 'chapter.html' do it "the empty cell doesn't contain any other tags" do # We match on the empty cell followed by the non-empty cell so we # can be sure we're matching the right part of the table. expect(body).to include <<~HTML <tr> #{empty_cell} #{non_empty_cell} </tr> HTML end end end context 'for a book that has a reference to a floating title' do convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} <<floater>> [float] [[floater]] == Floater ASCIIDOC end page_context 'chapter.html' do it "there isn't an edit me link in the link to the section" do expect(body).to include(<<~HTML.strip) <a class="xref" href="chapter.html#floater" title="Floater">Floater</a> HTML end end end end shared_context 'care admonition' do page_context 'chapter.html' do it 'includes the warning admonition' do expect(body).to include( '<div class="warning admon">' ) end end end context 'when the book contains beta[]' do include_context 'care admonition' convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} beta[] Words ASCIIDOC end page_context 'chapter.html' do it 'includes the beta text' do expect(body).to include( 'The design and code is less mature than official GA features' ) end end end context 'when the book contains experimental[]' do include_context 'care admonition' convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC #{HEADER} experimental[] Words ASCIIDOC end page_context 'chapter.html' do it 'includes the experimental text' do expect(body).to include( 'This functionality is in technical preview and may be changed or '\ 'removed in a future release. Elastic will work to fix '\ 'any issues, but features in technical preview are not subject to '\ 'the support SLA of official GA features.' ) end end end context 'when there is a link to elastic.co' do convert_single_before_context do |src| src.write 'index.asciidoc', <<~ASCIIDOC = Title [[chapter]] == Chapter https://www.elastic.co/cloud/[link] ASCIIDOC end page_context 'chapter.html' do it 'contains an absolute link to www.elatic.co' do expect(body).to include(<<~HTML.strip) <a href="https://www.elastic.co/cloud/" class="ulink" target="_top">link</a> HTML end end end context 'for README.asciidoc' do convert_single_before_context do |src| root = File.expand_path('../../', __dir__) images = ['cat.jpg', 'chunking-toc.png', 'example.svg', 'screenshot.png'] images.each do |img| src.cp "#{root}/resources/readme/#{img}", "resources/readme/#{img}" end src.copy_shared_conf txt = File.open("#{root}/README.asciidoc", 'r:UTF-8', &:read) src.write 'index.asciidoc', txt end page_context 'index.html' do it 'has the right title' do expect(title).to eq('Docs HOWTO') end end page_context '_conditions_of_use.html' do it 'has the right title' do expect(title).to eq('Conditions of use') end end page_context 'setup.html' do it 'has the right title' do expect(title).to eq('Getting started') end end page_context 'build.html' do it 'has the right title' do expect(title).to eq('Building documentation') end end page_context 'asciidoc-guide.html' do it 'has the right title' do expect(title).to eq('Asciidoc Guide') end end page_context 'images.html' do it 'has the right title' do expect(title).to eq('Images') end it 'has the cat image with a title' do expect(body).to include <<~HTML <div id="cat" class="imageblock"> <div class="content"> <img src="resources/readme/cat.jpg" alt="Alt text"> </div> <div class="title">Figure 1. A scaredy cat</div> </div> HTML end it 'has the cat image with specified width and without a title' do expect(body).to include <<~HTML <div id="cat" class="imageblock"> <div class="content"> <img src="resources/readme/cat.jpg" alt="Alt text"> </div> HTML end it 'has the screenshot' do expect(body).to include <<~HTML <div class="imageblock screenshot"> <div class="content"> <img src="resources/readme/screenshot.png" alt="A screenshot example"> </div> </div> HTML end end page_context 'chunking.html' do it 'has the right title' do expect(title).to eq('Controlling chunking') end it 'has the chunking image' do expect(body).to include <<~HTML <div class="imageblock"> <div class="content"> <img src="resources/readme/chunking-toc.png" alt="TOC screenshot"> </div> </div> HTML end end # NOTE: There are lots more pages but it probably isn't worth asserting # on them too. file_context 'snippets/1.console' do let(:expected) do <<~CONSOLE GET /_search { "query": "foo bar" } CONSOLE end it 'has the right content' do expect(contents).to eq(expected) end end file_context 'resources/readme/cat.jpg' file_context 'resources/readme/chunking-toc.png' file_context 'resources/readme/screenshot.png' end context 'for a book with console alternatives' do def self.index <<~ASCIIDOC = Title [[chapter]] == Chapter #{ConsoleExamples::README_LIKE} ASCIIDOC end convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.asciidoc', index repo.commit 'commit outstanding' # Points java to a directory without any examples so we can report that. convert = dest.prepare_convert_single(from, '.') .alternatives( 'console', 'js', "#{__dir__}/../readme_examples/js" ) .alternatives( 'console', 'csharp', "#{__dir__}/../readme_examples/csharp" ) .alternatives('console', 'java', "#{__dir__}/helper") convert.convert end include_examples 'README-like console alternatives', 'raw', '.' end context 'for a book with an -extra-title-page.html file' do INDEX_BODY = <<~ASCIIDOC = Title [[section]] == Section ASCIIDOC context 'single page' do def self.setup(index_name) convert_before do |src, dest| repo = src.repo 'src' from = repo.write index_name, INDEX_BODY repo.write 'index-extra-title-page.html', '<p>extra!</p>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').single.convert end end shared_examples 'has the extra' do file_context 'raw/index.html' do it 'contains the extra title page' do expect(contents).to include("<div>\n<p>extra!</p>\n</div>") end end end context 'when the index is .adoc' do setup 'index.adoc' include_examples 'has the extra' end context 'when the index is .asciidoc' do setup 'index.asciidoc' include_examples 'has the extra' end context 'when the index is .x.asciidoc' do setup 'index.x.asciidoc' include_examples 'has the extra' end end context 'multipage' do convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.adoc', INDEX_BODY repo.write 'index-extra-title-page.html', '<p>extra!</p>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').convert end file_context 'raw/index.html' do it 'contains the extra title page' do expect(contents).to include("<div>\n<p>extra!</p>\n</div>") end it 'still contains the TOC' do expect(contents).to include('START_TOC') expect(contents).to include('<div class="toc">') end end file_context 'raw/section.html' do it "doesn't contain the extra title page" do expect(contents).not_to include("<div>\n<p>extra!</p>\n</div>") end end end end context 'for a book with a -custom-title-page.html file' do INDEX_BODY = <<~ASCIIDOC = Title [[section]] == Section ASCIIDOC context 'built as a single page' do convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.asciidoc', INDEX_BODY repo.write 'index-custom-title-page.html', '<h1>My Custom Header</h1>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.') .single.convert(expect_failure: true) end it 'prints an error message about being incompatible' do expect(outputs[0]).to include(<<~LOG.strip) Using a custom title page is incompatible with --single LOG end end context 'multipage' do convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.adoc', INDEX_BODY repo.write 'index-custom-title-page.html', '<h1>My Custom Header</h1>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').convert end file_context 'raw/index.html' do it 'contains the custom header' do expect(contents).to include('<h1>My Custom Header</h1>') end it 'does not contain the table of contents' do expect(contents).not_to include('START_TOC') expect(contents).not_to include('<div class="toc">') end end file_context 'raw/section.html' do it 'does not contain the custom header' do expect(contents).not_to include('My Custom Header') end end end context 'and a -extra-title-page.html file' do convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.adoc', INDEX_BODY repo.write 'index-custom-title-page.html', '<h1>My Custom Header</h1>' repo.write 'index-extra-title-page.html', '<h1>My Extra Header</h1>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').convert(expect_failure: true) end it 'prints an error about both files existing' do expect(outputs[0]).to include(<<~LOG.strip) Cannot have both custom and extra title pages for the same source file LOG end end end context 'for a book with page-header.html' do context 'single page' do convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.adoc', <<~ASCIIDOC = Title [[section]] == Section Words. ASCIIDOC repo.write 'page_header.html', '<p>header</p>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').single.convert end file_context 'raw/index.html' do it 'should contain the header' do expect(contents).to include '<p>header</p>' end end end context 'multipage' do convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.adoc', <<~ASCIIDOC = Title [[section]] == Section Words. ASCIIDOC repo.write 'page_header.html', '<p>header</p>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').convert end file_context 'raw/index.html' do it 'should contain the header' do expect(contents).to include '<p>header</p>' end end file_context 'raw/section.html' do it 'should contain the header' do expect(contents).to include '<p>header</p>' end end end context 'with chinese text' do # We've failed in the past on Chinese text with encoding issues. convert_before do |src, dest| repo = src.repo 'src' from = repo.write 'index.adoc', <<~ASCIIDOC = Title [[section]] == Section Words. ASCIIDOC repo.write 'page_header.html', '<p>请注意</p>' repo.commit 'commit outstanding' dest.prepare_convert_single(from, '.').convert end file_context 'raw/index.html' do it 'should contain the header' do expect(contents).to include '<p>请注意</p>' end end file_context 'raw/section.html' do it 'should contain the header' do expect(contents).to include '<p>请注意</p>' end end end end context 'for a book that uses {source_branch}' do INDEX = <<~ASCIIDOC = Title [[chapter]] == Chapter The branch is {source_branch}. ASCIIDOC def self.convert_with_source_branch_before_context(branch) convert_single_before_context do |src| unless branch == 'master' src.write 'dummy', 'needed so git is ok with switching branches' src.commit 'dummy' src.switch_to_new_branch branch end src.write 'index.asciidoc', INDEX end end shared_examples 'contains branch' do |branch| page_context 'chapter.html' do it 'contains the branch name' do expect(body).to include("The branch is #{branch}.") end end end context 'when the branch is master' do convert_with_source_branch_before_context 'master' include_examples 'contains branch', 'master' end context 'when the branch is 7.x' do convert_with_source_branch_before_context '7.x' include_examples 'contains branch', '7.x' end context 'when the branch is 1.5' do convert_with_source_branch_before_context '1.5' include_examples 'contains branch', '1.5' end context 'when the branch is 18.5' do convert_with_source_branch_before_context '18.5' include_examples 'contains branch', '18.5' end context 'when the branch is some_crazy_thing' do convert_with_source_branch_before_context 'some_crazy_thing' include_examples 'contains branch', 'master' end context 'when the branch is some_crazy_thing_7.x' do convert_with_source_branch_before_context 'some_crazy_thing_7.x' include_examples 'contains branch', '7.x' end context 'when the branch is some_crazy_thing_7_x' do convert_with_source_branch_before_context 'some_crazy_thing_7_x' include_examples 'contains branch', '7_x' end end context 'when run with --open' do include_context 'source and dest' before(:context) do repo = @src.repo_with_index 'repo', 'Words' @opened_docs = @dest.prepare_convert_single("#{repo.root}/index.asciidoc", '.').open end after(:context) do @opened_docs.exit end let(:root) { 'http://localhost:8000/guide' } let(:static) { "#{root}/static" } let(:index) { Net::HTTP.get_response(URI("#{root}/index.html")) } let(:air_gapped_index) do uri = URI("#{root}/index.html") req = Net::HTTP::Get.new(uri) req['Host'] = 'gapped.localhost' Net::HTTP.start(uri.hostname, uri.port, read_timeout: 20) do |http| http.request(req) end end let(:toc) { Net::HTTP.get_response(URI("#{root}/toc.html")) } let(:js) do Net::HTTP.get_response(URI("#{static}/docs-v1.js")) end let(:jquery) do Net::HTTP.get_response(URI("#{static}/jquery.js")) end let(:css) do Net::HTTP.get_response(URI("#{static}/styles-v1.css")) end include_examples 'the root' include_examples 'the favicon' context 'the index' do context 'when not air gapped' do it 'contains the gtag js' do expect(index).to serve(include(<<~HTML.strip)) https://www.googletagmanager.com/gtag/js HTML end it 'serves the chapter header' do expect(index).to serve(doc_body(include(<<~HTML.strip))) <a href="chapter.html">Chapter HTML end end context 'when air gapped' do it "doesn't contain the gtag js" do expect(air_gapped_index).not_to serve(include(<<~HTML.strip)) https://www.googletagmanager.com/gtag/js HTML end it 'serves the chapter header' do expect(air_gapped_index).to serve(doc_body(include(<<~HTML.strip))) <a href="chapter.html">Chapter HTML end end end context 'the table of contents' do it "isn't templated" do expect(toc).to serve(start_with('<div class="toc">')) end end context 'the js' do it 'is unminified' do expect(js).to serve(include(<<~JS)) // Test comment used to detect unminifed JS in tests JS end it 'include hot module replacement for the css' do expect(js).to serve(include(<<~JS)) // Setup hot module replacement for css if we're in dev mode. JS end it 'includes a source map' do expect(js).to serve(include('sourceMappingURL=')) end end context 'jquery' do it 'is unminified' do # This comment is a little brittle to detect but I don't expect us to # rely on jquery forever. expect(jquery).to serve(include(<<~JS)) Includes Sizzle.js JS end it "doesn't include a source map" do expect(jquery).not_to serve(include('sourceMappingURL=')) end end context 'the css' do it 'is unminified' do expect(css).to serve(include(<<~CSS)) /* test comment used to detect unminified source */ CSS end it 'includes a source map' do expect(css).to serve(include('sourceMappingURL=')) end end end ## # When you point `build_docs` to a worktree it doesn't properly share the # worktree's parent into the docker container. This test simulates *that*. context 'when building a book in a worktree without its parent' do convert_before do |src, dest| repo = src.repo_with_index 'src', <<~ASCIIDOC I am in a worktree. ASCIIDOC worktree = src.path 'worktree' repo.create_worktree worktree, 'HEAD' FileUtils.rm_rf repo.root dest.prepare_convert_single("#{worktree}/index.asciidoc", '.') .convert end page_context 'chapter.html' do it 'complains about not being able to find the repo toplevel' do expect(outputs[0]).to include("Couldn't find repo toplevel for /tmp/") end it 'has the worktree text' do expect(body).to include('I am in a worktree.') end end end context 'when a book contains migration warnings' do shared_context 'convert with migration warnings' do |suppress| convert_before do |src, dest| repo = src.repo_with_index 'src', <<~ASCIIDOC -------- CODE HERE ---- ASCIIDOC c = dest.prepare_convert_single("#{repo.root}/index.asciidoc", '.') c.suppress_migration_warnings if suppress c.convert(expect_failure: !suppress) end end context 'and they are not suppressed' do include_context 'convert with migration warnings', false it 'fails with an appropriate error status' do expect(statuses[0]).to eq(255) end it 'complains about the MIGRATION warning' do expect(outputs[0]).to include(<<~LOG) asciidoctor: WARNING: index.asciidoc: line 7: MIGRATION: code block end doesn't match start LOG end end context 'and they are suppressed' do include_context 'convert with migration warnings', true it "doesn't complain about the MIGRATION warning" do expect(outputs[0]).not_to include(<<~LOG) asciidoctor: WARNING: index.asciidoc: line 7: MIGRATION: code block end doesn't match start LOG end page_context 'chapter.html' do it 'contains the snippet' do expect(body).to include('CODE HERE') end end end end context 'when an included file is missing' do convert_before do |src, dest| repo = src.repo_with_index 'src', <<~ASCIIDOC include::missing.asciidoc[] ASCIIDOC dest.prepare_convert_single("#{repo.root}/index.asciidoc", '.') .convert(expect_failure: true) end it 'fails with an appropriate error status' do expect(statuses[0]).to eq(255) end it 'logs the missing file' do expect(outputs[0]).to include(<<~LOG.strip) ERROR: index.asciidoc: line 5: include file not found: #{@src.repo('src').root}/missing.asciidoc LOG end end context 'when a referenced id is missing' do convert_before do |src, dest| repo = src.repo_with_index 'src', <<~ASCIIDOC <<missing-ref>> ASCIIDOC dest.prepare_convert_single("#{repo.root}/index.asciidoc", '.') .convert(expect_failure: true) end it 'fails with an appropriate error status' do expect(statuses[0]).to eq(255) end it 'logs the file that contains the missing include' do expect(outputs[0]).to include(<<~LOG.strip) asciidoctor: WARNING: invalid reference: missing-ref LOG end end end