diff options
Diffstat (limited to 'spec/scripts/lib/glfm')
-rw-r--r-- | spec/scripts/lib/glfm/shared_spec.rb | 36 | ||||
-rw-r--r-- | spec/scripts/lib/glfm/update_example_snapshots_spec.rb | 316 | ||||
-rw-r--r-- | spec/scripts/lib/glfm/update_specification_spec.rb | 196 |
3 files changed, 548 insertions, 0 deletions
diff --git a/spec/scripts/lib/glfm/shared_spec.rb b/spec/scripts/lib/glfm/shared_spec.rb new file mode 100644 index 00000000000..f6792b93718 --- /dev/null +++ b/spec/scripts/lib/glfm/shared_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +require 'fast_spec_helper' +require_relative '../../../../scripts/lib/glfm/shared' + +RSpec.describe Glfm::Shared do + let(:instance) do + Class.new do + include Glfm::Shared + end.new + end + + describe '#run_external_cmd' do + it 'works' do + expect(instance.run_external_cmd('echo "hello"')).to eq("hello\n") + end + + context 'when command fails' do + it 'raises error' do + invalid_cmd = 'ls nonexistent_file' + expect(instance).to receive(:warn).with(/Error running command `#{invalid_cmd}`/) + expect(instance).to receive(:warn).with(/nonexistent_file.*no such file/i) + expect { instance.run_external_cmd(invalid_cmd) }.to raise_error(RuntimeError) + end + end + end + + describe '#output' do + # NOTE: The #output method is normally always mocked, to prevent output while the specs are + # running. However, in order to provide code coverage for the method, we have to invoke + # it at least once. + it 'has code coverage' do + allow(instance).to receive(:puts) + instance.output('') + end + end +end diff --git a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb new file mode 100644 index 00000000000..169f5d1c5a6 --- /dev/null +++ b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb @@ -0,0 +1,316 @@ +# frozen_string_literal: true +require 'fast_spec_helper' +require_relative '../../../../scripts/lib/glfm/update_example_snapshots' + +RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do + subject { described_class.new } + + # GLFM input files + let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH } + let(:glfm_spec_txt_local_io) { StringIO.new(glfm_spec_txt_contents) } + let(:glfm_example_status_yml_path) { described_class::GLFM_EXAMPLE_STATUS_YML_PATH } + let(:glfm_example_status_yml_io) { StringIO.new(glfm_example_status_yml_contents) } + + # Example Snapshot (ES) output files + let(:es_examples_index_yml_path) { described_class::ES_EXAMPLES_INDEX_YML_PATH } + let(:es_examples_index_yml_io) { StringIO.new } + let(:es_markdown_yml_path) { described_class::ES_MARKDOWN_YML_PATH } + let(:es_markdown_yml_io) { StringIO.new } + let(:es_html_yml_path) { described_class::ES_HTML_YML_PATH } + let(:es_html_yml_io) { StringIO.new } + let(:es_prosemirror_json_yml_path) { described_class::ES_PROSEMIRROR_JSON_YML_PATH } + let(:es_prosemirror_json_yml_io) { StringIO.new } + + # Internal tempfiles + let(:static_html_tempfile_path) { Tempfile.new.path } + + let(:glfm_spec_txt_contents) do + <<~GLFM_SPEC_TXT_CONTENTS + --- + title: GitLab Flavored Markdown Spec + ... + + # Introduction + + GLFM intro text... + + # Inlines + + ## Strong + + ```````````````````````````````` example + __bold__ + . + <p><strong>bold</strong></p> + ```````````````````````````````` + + ```````````````````````````````` example strikethrough + __bold with more text__ + . + <p><strong>bold with more text</strong></p> + ```````````````````````````````` + + <div class="extension"> + + ## Strikethrough (extension) + + GFM enables the `strikethrough` extension. + + ```````````````````````````````` example strikethrough + ~~Hi~~ Hello, world! + . + <p><del>Hi</del> Hello, world!</p> + ```````````````````````````````` + + </div> + + End of last GitHub examples section. + + # First GitLab-Specific Section with Examples + + ## Strong but with two asterisks + + ```````````````````````````````` example gitlab strong + **bold** + . + <p><strong>bold</strong></p> + ```````````````````````````````` + + # Second GitLab-Specific Section with Examples + + ## Strong but with HTML + + ```````````````````````````````` example gitlab strong + <strong> + bold + </strong> + . + <p><strong> + bold + </strong></p> + ```````````````````````````````` + + <!-- END TESTS --> + + # Appendix + + Appendix text. + GLFM_SPEC_TXT_CONTENTS + end + + let(:glfm_example_status_yml_contents) do + <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS + --- + - 07_01_first_gitlab_specific_section_with_examples_strong_but_with_two_asterisks: + skip_update_example_snapshots: false + skip_running_snapshot_static_html_tests: false + skip_running_snapshot_wysiwyg_html_tests: false + skip_running_snapshot_prosemirror_json_tests: false + skip_running_conformance_static_tests: false + skip_running_conformance_wysiwyg_tests: false + - 07_02_first_gitlab_specific_section_with_examples_strong_but_with_html: + skip_update_example_snapshots: false + skip_running_snapshot_static_html_tests: false + skip_running_snapshot_wysiwyg_html_tests: false + skip_running_snapshot_prosemirror_json_tests: false + skip_running_conformance_static_tests: false + skip_running_conformance_wysiwyg_tests: false + GLFM_EXAMPLE_STATUS_YML_CONTENTS + end + + before do + # We mock out the URI and local file IO objects with real StringIO, instead of just mock + # objects. This gives better and more realistic coverage, while still avoiding + # actual network and filesystem I/O during the spec run. + + # input files + allow(File).to receive(:open).with(glfm_spec_txt_path) { glfm_spec_txt_local_io } + allow(File).to receive(:open).with(glfm_example_status_yml_path) { glfm_example_status_yml_io } + + # output files + allow(File).to receive(:open).with(es_examples_index_yml_path, 'w') { es_examples_index_yml_io } + allow(File).to receive(:open).with(es_html_yml_path, 'w') { es_html_yml_io } + allow(File).to receive(:open).with(es_prosemirror_json_yml_path, 'w') { es_prosemirror_json_yml_io } + + # output files which are also input files + allow(File).to receive(:open).with(es_markdown_yml_path, 'w') { es_markdown_yml_io } + allow(File).to receive(:open).with(es_markdown_yml_path) { es_markdown_yml_io } + + # Allow normal opening of Tempfile files created during script execution. + tempfile_basenames = [ + described_class::MARKDOWN_TEMPFILE_BASENAME[0], + described_class::STATIC_HTML_TEMPFILE_BASENAME[0], + described_class::WYSIWYG_HTML_AND_JSON_TEMPFILE_BASENAME[0] + ].join('|') + # NOTE: This approach with a single regex seems to be the only way this can work. If you + # attempt to have multiple `allow...and_call_original` with `any_args`, the mocked + # parameter matching will fail to match the second one. + tempfiles_regex = /(#{tempfile_basenames})/ + allow(File).to receive(:open).with(tempfiles_regex, any_args).and_call_original + + # Prevent console output when running tests + allow(subject).to receive(:output) + end + + describe 'writing examples_index.yml' do + let(:es_examples_index_yml_contents) { reread_io(es_examples_index_yml_io) } + + it 'writes the correct content' do + subject.process(skip_static_and_wysiwyg: true) + + expected = + <<~ES_EXAMPLES_INDEX_YML_CONTENTS + --- + 02_01__inlines__strong__01: + spec_txt_example_position: 1 + source_specification: commonmark + 02_01__inlines__strong__02: + spec_txt_example_position: 2 + source_specification: github + 02_02__inlines__strikethrough_extension__01: + spec_txt_example_position: 3 + source_specification: github + 03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01: + spec_txt_example_position: 4 + source_specification: gitlab + 04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__01: + spec_txt_example_position: 5 + source_specification: gitlab + ES_EXAMPLES_INDEX_YML_CONTENTS + expect(es_examples_index_yml_contents).to eq(expected) + end + end + + describe 'writing markdown.yml' do + let(:es_markdown_yml_contents) { reread_io(es_markdown_yml_io) } + + it 'writes the correct content' do + subject.process(skip_static_and_wysiwyg: true) + + expected = + <<~ES_MARKDOWN_YML_CONTENTS + --- + 02_01__inlines__strong__01: | + __bold__ + 02_01__inlines__strong__02: | + __bold with more text__ + 02_02__inlines__strikethrough_extension__01: | + ~~Hi~~ Hello, world! + 03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01: | + **bold** + 04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__01: | + <strong> + bold + </strong> + ES_MARKDOWN_YML_CONTENTS + + expect(es_markdown_yml_contents).to eq(expected) + end + end + + describe 'writing html.yml and prosemirror_json.yml' do + let(:es_html_yml_contents) { reread_io(es_html_yml_io) } + let(:es_prosemirror_json_yml_contents) { reread_io(es_prosemirror_json_yml_io) } + + let(:glfm_example_status_yml_contents) do + <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS + --- + - 02_01_gitlab_specific_section_with_examples_strong_but_with_two_asterisks: + skip_update_example_snapshots: false + skip_running_snapshot_static_html_tests: false + skip_running_snapshot_wysiwyg_html_tests: false + skip_running_snapshot_prosemirror_json_tests: false + skip_running_conformance_static_tests: false + skip_running_conformance_wysiwyg_tests: false + GLFM_EXAMPLE_STATUS_YML_CONTENTS + end + + let(:glfm_spec_txt_contents) do + <<~GLFM_SPEC_TXT_CONTENTS + --- + title: GitLab Flavored Markdown Spec + ... + + # Introduction + + # GitLab-Specific Section with Examples + + ## Strong but with two asterisks + + ```````````````````````````````` example gitlab strong + **bold** + . + <p><strong>bold</strong></p> + ```````````````````````````````` + <!-- END TESTS --> + + # Appendix + + Appendix text. + GLFM_SPEC_TXT_CONTENTS + end + + before do + # NOTE: This is a necessary to avoid an `error Couldn't find an integrity file` error + # when invoking `yarn jest ...` on CI from within an RSpec job. It could be solved by + # adding `.yarn-install` to be included in the RSpec CI job, but that would be a performance + # hit to all RSpec jobs. We could also make a dedicate job just for this spec. However, + # since this is just a single script, those options may not be justified. + described_class.new.run_external_cmd('yarn install') if ENV['CI'] + end + + # NOTE: Both `html.yml` and `prosemirror_json.yml` generation are tested in a single example, to + # avoid slower tests, because generating the static HTML is slow due to the need to invoke + # the rails environment. We could have separate sections, but this would require an extra flag + # to the `process` method to independently skip static vs. WYSIWYG, which is not worth the effort. + it 'writes the correct content' do + subject.process + + expected_html = + <<~ES_HTML_YML_CONTENTS + --- + 02_01__gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01: + canonical: | + <p><strong>bold</strong></p> + static: |- + <p data-sourcepos="1:1-1:8" dir="auto"><strong>bold</strong></p> + wysiwyg: |- + <p><strong>bold</strong></p> + ES_HTML_YML_CONTENTS + + expected_prosemirror_json = + <<~ES_PROSEMIRROR_JSON_YML_CONTENTS + --- + 02_01__gitlab_specific_section_with_examples__strong_but_with_two_asterisks__01: |- + { + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "bold" + } + ] + } + ] + } + ES_PROSEMIRROR_JSON_YML_CONTENTS + + expect(es_html_yml_contents).to eq(expected_html) + expect(es_prosemirror_json_yml_contents).to eq(expected_prosemirror_json) + end + end + + def reread_io(io) + # Reset the io StringIO to the beginning position of the buffer + io.seek(0) + io.read + end +end diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb new file mode 100644 index 00000000000..e8d34b13efa --- /dev/null +++ b/spec/scripts/lib/glfm/update_specification_spec.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true +require 'fast_spec_helper' +require_relative '../../../../scripts/lib/glfm/update_specification' + +RSpec.describe Glfm::UpdateSpecification, '#process' do + subject { described_class.new } + + let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI } + let(:ghfm_spec_txt_uri_io) { StringIO.new(ghfm_spec_txt_contents) } + let(:ghfm_spec_txt_path) { described_class::GHFM_SPEC_TXT_PATH } + let(:ghfm_spec_txt_local_io) { StringIO.new(ghfm_spec_txt_contents) } + + let(:glfm_intro_txt_path) { described_class::GLFM_INTRO_TXT_PATH } + let(:glfm_intro_txt_io) { StringIO.new(glfm_intro_txt_contents) } + let(:glfm_examples_txt_path) { described_class::GLFM_EXAMPLES_TXT_PATH } + let(:glfm_examples_txt_io) { StringIO.new(glfm_examples_txt_contents) } + let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH } + let(:glfm_spec_txt_io) { StringIO.new } + + let(:ghfm_spec_txt_contents) do + <<~GHFM_SPEC_TXT_CONTENTS + --- + title: GitHub Flavored Markdown Spec + version: 0.29 + date: '2019-04-06' + license: '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)' + ... + + # Introduction + + ## What is GitHub Flavored Markdown? + + It's like GLFM, but with an H. + + # Section with Examples + + ## Strong + + ```````````````````````````````` example + __bold__ + . + <p><strong>bold</strong></p> + ```````````````````````````````` + + End of last GitHub examples section. + + <!-- END TESTS --> + + # Appendix + + Appendix text. + GHFM_SPEC_TXT_CONTENTS + end + + let(:glfm_intro_txt_contents) do + <<~GLFM_INTRO_TXT_CONTENTS + # Introduction + + ## What is GitLab Flavored Markdown? + + Intro text about GitLab Flavored Markdown. + GLFM_INTRO_TXT_CONTENTS + end + + let(:glfm_examples_txt_contents) do + <<~GLFM_EXAMPLES_TXT_CONTENTS + # GitLab-Specific Section with Examples + + Some examples. + GLFM_EXAMPLES_TXT_CONTENTS + end + + before do + # Mock default ENV var values + allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return(nil) + allow(ENV).to receive(:[]).and_call_original + + # We mock out the URI and local file IO objects with real StringIO, instead of just mock + # objects. This gives better and more realistic coverage, while still avoiding + # actual network and filesystem I/O during the spec run. + allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io } + allow(File).to receive(:open).with(ghfm_spec_txt_path) { ghfm_spec_txt_local_io } + allow(File).to receive(:open).with(glfm_intro_txt_path) { glfm_intro_txt_io } + allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io } + allow(File).to receive(:open).with(glfm_spec_txt_path, 'w') { glfm_spec_txt_io } + + # Prevent console output when running tests + allow(subject).to receive(:output) + end + + describe 'retrieving latest GHFM spec.txt' do + context 'when UPDATE_GHFM_SPEC_TXT is not true (default)' do + it 'does not download' do + expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri) + + subject.process + + expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents) + end + end + + context 'when UPDATE_GHFM_SPEC_TXT is true' do + let(:ghfm_spec_txt_local_io) { StringIO.new } + + before do + allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return('true') + allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io } + end + + context 'with success' do + it 'downloads and saves' do + subject.process + + expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents) + end + end + + context 'with error handling' do + context 'with a version mismatch' do + let(:ghfm_spec_txt_contents) do + <<~GHFM_SPEC_TXT_CONTENTS + --- + title: GitHub Flavored Markdown Spec + version: 0.30 + ... + GHFM_SPEC_TXT_CONTENTS + end + + it 'raises an error' do + expect { subject.process }.to raise_error /version mismatch.*expected.*29.*got.*30/i + end + end + + context 'with a failed read of file lines' do + let(:ghfm_spec_txt_contents) { '' } + + it 'raises an error if lines cannot be read' do + expect { subject.process }.to raise_error /unable to read lines/i + end + end + + context 'with a failed re-read of file string' do + before do + allow(ghfm_spec_txt_uri_io).to receive(:read).and_return(nil) + end + + it 'raises an error if file is blank' do + expect { subject.process }.to raise_error /unable to read string/i + end + end + end + end + end + + describe 'writing GLFM spec.txt' do + let(:glfm_contents) { reread_io(glfm_spec_txt_io) } + + before do + subject.process + end + + it 'replaces the header text with the GitLab version' do + expect(glfm_contents).not_to match(/GitHub Flavored Markdown Spec/m) + expect(glfm_contents).not_to match(/^version: \d\.\d/m) + expect(glfm_contents).not_to match(/^date: /m) + expect(glfm_contents).not_to match(/^license: /m) + expect(glfm_contents).to match(/#{Regexp.escape(described_class::GLFM_SPEC_TXT_HEADER)}\n/mo) + end + + it 'replaces the intro section with the GitLab version' do + expect(glfm_contents).not_to match(/What is GitHub Flavored Markdown/m) + expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_txt_contents)}/m) + end + + it 'inserts the GitLab examples sections before the appendix section' do + expected = <<~GHFM_SPEC_TXT_CONTENTS + End of last GitHub examples section. + + # GitLab-Specific Section with Examples + + Some examples. + + <!-- END TESTS --> + + # Appendix + GHFM_SPEC_TXT_CONTENTS + expect(glfm_contents).to match(/#{Regexp.escape(expected)}/m) + end + end + + def reread_io(io) + # Reset the io StringIO to the beginning position of the buffer + io.seek(0) + io.read + end +end |