Welcome to mirror list, hosted at ThFree Co, Russian Federation.

update_example_snapshots_spec.rb « glfm « lib « scripts « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: fe815aa6f1ea084cea227c0738e99862c8e9b697 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../../scripts/lib/glfm/update_example_snapshots'

# IMPORTANT NOTE: See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/
# for details on the implementation and usage of the `update_example_snapshots` script being tested.
# This developers guide contains diagrams and documentation of the script,
# including explanations and examples of all files it reads and writes.
#
# Note that this test is not structured in a traditional way, with multiple examples
# to cover all different scenarios. Instead, the content of the stubbed test fixture
# files are crafted to cover multiple scenarios with in a single example run.
#
# This is because the invocation of the full script is slow, because it executes
# two subshells for processing, one which runs a full Rails environment, and one
# which runs a jest test environment. This results in each full run of the script
# taking between 30-60 seconds. The majority of this is spent loading the Rails environment.
#
# However, only the `writing html.yml and prosemirror_json.yml` context is used
# to test these slow sub-processes, and it only contains a single example.
#
# All other tests currently in the file pass the `skip_static_and_wysiwyg: true`
# flag to `#process`, which skips the slow sub-processes. All of these tests
# should run in sub-second time when the Spring pre-loader is used. This allows
# logic which is not directly related to the slow sub-processes to be TDD'd with a
# very rapid feedback cycle.
#
# Also, the textual content of the individual fixture file entries is also crafted to help
# indicate which scenarios which they are covering.
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_existing) { StringIO.new(es_html_yml_io_existing_contents) }
  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_existing) { StringIO.new(es_prosemirror_json_yml_io_existing_contents) }
  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

      This example doesn't have an extension after the `example` keyword, so its
      `source_specification` will be `commonmark`.

      ```````````````````````````````` example
      __bold__
      .
      <p><strong>bold</strong></p>
      ````````````````````````````````

      This example has an extension after the `example` keyword, so its
      `source_specification` will be `github`.

      ```````````````````````````````` example some_extension_name
      __bold with more text__
      .
      <p><strong>bold with more text</strong></p>
      ````````````````````````````````

      <div class="extension">

      ### Motivation

      This is a third-level heading with no examples, as exists in the actual GHFM
      specification. It exists to drive a fix for a bug where this caused the
      indexing and ordering to in examples_index.yml to be incorrect.

      ### Another H3

      This is a second consecutive third-level heading. It exists to drive full code coverage
      for this scenario, although it doesn't (yet) exist in the actual spec.txt.

      ## An H2 with all disabled examples

      In the GHFM specification, the 'Task list items (extension)' contains only "disabled"
      examples, which are ignored by the GitHub fork of `spec_test.py`, and thus not part of the
      Markdown conformance tests, but are part of the HTML-rendered version of the specification.
      We also exclude them from our GLFM specification for consistency, but we may add
      GitLab-specific examples for the behavior instead.

      ```````````````````````````````` example disabled
      this example is disabled during conformance testing
      .
      <p>this example is disabled during conformance testing</p>
      ````````````````````````````````

      ## 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

      This example has the `gitlab` keyword after the `example` keyword, so its
      `source_specification` will be `gitlab`.


      ```````````````````````````````` example gitlab strong
      <strong>
      bold
      </strong>
      .
      <p><strong>
      bold
      </strong></p>
      ````````````````````````````````

      # Third GitLab-Specific Section with skipped Examples

      ## Strong but skipped

      ```````````````````````````````` example gitlab strong
      **this example will be skipped**
      .
      <p><strong>this example will be skipped</strong></p>
      ````````````````````````````````

      ## Strong but manually modified and skipped

      ```````````````````````````````` example gitlab strong
      **This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved**
      .
      <p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p>
      ````````````````````````````````

      <!-- END TESTS -->

      # Appendix

      Appendix text.
    GLFM_SPEC_TXT_CONTENTS
  end

  let(:glfm_example_status_yml_contents) do
    # language=YAML
    <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS
      ---
      02_01__inlines__strong__001:
        # The skip_update_example_snapshots key is present, but false, so this example is not skipped
        skip_update_example_snapshots: false
      02_01__inlines__strong__002:
        # It is OK to have an empty (nil) value for an example statuses entry, it means they will all be false.
      05_01__third_gitlab_specific_section_with_skipped_examples__strong_but_skipped__001:
        # Always skip this example
        skip_update_example_snapshots: 'skipping this example because it is very bad'
      05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
        # Always skip this example, but preserve the existing manual modifications
        skip_update_example_snapshots: 'skipping this example because we have manually modified it'
    GLFM_EXAMPLE_STATUS_YML_CONTENTS
  end

  let(:es_html_yml_io_existing_contents) do
    # language=YAML
    <<~ES_HTML_YML_IO_EXISTING_CONTENTS
      ---
      00_00__obsolete_entry_to_be_deleted__001:
        canonical: |
          This entry is no longer exists in the spec.txt, and is not skipped, so it will be deleted.
        static: |-
          This entry is no longer exists in the spec.txt, and is not skipped, so it will be deleted.
        wysiwyg: |-
          This entry is no longer exists in the spec.txt, and is not skipped, so it will be deleted.
      02_01__inlines__strong__001:
        canonical: |
          This entry is existing, but not skipped, so it will be overwritten.
        static: |-
          This entry is existing, but not skipped, so it will be overwritten.
        wysiwyg: |-
          This entry is existing, but not skipped, so it will be overwritten.
      05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
        canonical: |
          <p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p>
        static: |-
          <p>This is the manually modified static HTML which will be preserved</p>
        wysiwyg: |-
          <p>This is the manually modified WYSIWYG HTML which will be preserved</p>
    ES_HTML_YML_IO_EXISTING_CONTENTS
  end

  let(:es_prosemirror_json_yml_io_existing_contents) do
    # language=YAML
    <<~ES_PROSEMIRROR_JSON_YML_IO_EXISTING_CONTENTS
      ---
      00_00__obsolete_entry_to_be_deleted__001:
        {
          "obsolete": "This entry is no longer exists in the spec.txt, and is not skipped, so it will be deleted."
        }
      02_01__inlines__strong__001: |-
        {
          "existing": "This entry is existing, but not skipped, so it will be overwritten."
        }
      # 02_01__inlines__strong__002: is omitted from the existing file and skipped, to test that scenario.
      02_03__inlines__strikethrough_extension__001: |-
        {
          "type": "doc",
          "content": [
            {
              "type": "paragraph",
              "content": [
                {
                  "type": "text",
                  "text": "~~Hi~~ Hello, world!"
                }
              ]
            }
          ]
        }
      04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |-
        {
          "existing": "This entry is manually modified and preserved because skip_update_example_snapshot_prosemirror_json will be truthy"
        }
      05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001: |-
        {
          "existing": "This entry is manually modified and preserved because skip_update_example_snapshots will be truthy"
        }
    ES_PROSEMIRROR_JSON_YML_IO_EXISTING_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 }

    # 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(File).to receive(:open).with(es_html_yml_path, 'w') { es_html_yml_io }
    allow(File).to receive(:open).with(es_html_yml_path) { es_html_yml_io_existing }
    allow(File).to receive(:open).with(es_prosemirror_json_yml_path, 'w') { es_prosemirror_json_yml_io }
    allow(File).to receive(:open).with(es_prosemirror_json_yml_path) { es_prosemirror_json_yml_io_existing }

    # 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 'when skip_update_example_snapshots is truthy' do
    let(:es_examples_index_yml_contents) { reread_io(es_examples_index_yml_io) }
    let(:es_markdown_yml_contents) { reread_io(es_markdown_yml_io) }
    let(:expected_unskipped_example) do
      /05_01__third_gitlab_specific_section_with_skipped_examples__strong_but_skipped__001/
    end

    it 'still writes the example to examples_index.yml' do
      subject.process(skip_static_and_wysiwyg: true)

      expect(es_examples_index_yml_contents).to match(expected_unskipped_example)
    end

    it 'still writes the example to markdown.yml' do
      subject.process(skip_static_and_wysiwyg: true)

      expect(es_markdown_yml_contents).to match(expected_unskipped_example)
    end

    describe 'when any other skip_update_example_* is also truthy' do
      let(:glfm_example_status_yml_contents) do
        # language=YAML
        <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS
          ---
          02_01__inlines__strong__001:
            skip_update_example_snapshots: 'if the skip_update_example_snapshots key is truthy...'
            skip_update_example_snapshot_html_static: '...then no other skip_update_example_* keys can be truthy'
        GLFM_EXAMPLE_STATUS_YML_CONTENTS
      end

      it 'raises an error' do
        expected_msg = "Error: '02_01__inlines__strong__001' must not have any 'skip_update_example_snapshot_*' " \
          "values specified if 'skip_update_example_snapshots' is truthy"
        expect { subject.process }.to raise_error(/#{Regexp.escape(expected_msg)}/)
      end
    end
  end

  describe 'writing examples_index.yml' do
    let(:es_examples_index_yml_contents) { reread_io(es_examples_index_yml_io) }
    let(:expected_examples_index_yml_contents) do
      # language=YAML
      <<~ES_EXAMPLES_INDEX_YML_CONTENTS
        ---
        02_01__inlines__strong__001:
          spec_txt_example_position: 1
          source_specification: commonmark
        02_01__inlines__strong__002:
          spec_txt_example_position: 2
          source_specification: github
        02_03__inlines__strikethrough_extension__001:
          spec_txt_example_position: 4
          source_specification: github
        03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001:
          spec_txt_example_position: 5
          source_specification: gitlab
        04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001:
          spec_txt_example_position: 6
          source_specification: gitlab
        05_01__third_gitlab_specific_section_with_skipped_examples__strong_but_skipped__001:
          spec_txt_example_position: 7
          source_specification: gitlab
        05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
          spec_txt_example_position: 8
          source_specification: gitlab
      ES_EXAMPLES_INDEX_YML_CONTENTS
    end

    it 'writes the correct content' do
      subject.process(skip_static_and_wysiwyg: true)

      expect(es_examples_index_yml_contents).to eq(expected_examples_index_yml_contents)
    end
  end

  describe 'writing markdown.yml' do
    let(:es_markdown_yml_contents) { reread_io(es_markdown_yml_io) }
    let(:expected_markdown_yml_contents) do
      # language=YAML
      <<~ES_MARKDOWN_YML_CONTENTS
        ---
        02_01__inlines__strong__001: |
          __bold__
        02_01__inlines__strong__002: |
          __bold with more text__
        02_03__inlines__strikethrough_extension__001: |
          ~~Hi~~ Hello, world!
        03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001: |
          **bold**
        04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |
          <strong>
          bold
          </strong>
        05_01__third_gitlab_specific_section_with_skipped_examples__strong_but_skipped__001: |
          **this example will be skipped**
        05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001: |
          **This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved**
      ES_MARKDOWN_YML_CONTENTS
    end

    it 'writes the correct content' do
      subject.process(skip_static_and_wysiwyg: true)

      expect(es_markdown_yml_contents).to eq(expected_markdown_yml_contents)
    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) }

    # NOTE: This example_status.yml is crafted in conjunction with expected_html_yml_contents
    # to test the behavior of the `skip_update_*` flags
    let(:glfm_example_status_yml_contents) do
      # language=YAML
      <<~GLFM_EXAMPLE_STATUS_YML_CONTENTS
        ---
        02_01__inlines__strong__002:
          skip_update_example_snapshot_prosemirror_json: "skipping because JSON isn't cool enough"
        03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001:
          skip_update_example_snapshot_html_static: "skipping because there's too much static"
        04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001:
          skip_update_example_snapshot_html_wysiwyg: 'skipping because what you see is NOT what you get'
          skip_update_example_snapshot_prosemirror_json: "skipping because JSON still isn't cool enough"
        05_01__third_gitlab_specific_section_with_skipped_examples__strong_but_skipped__001:
          skip_update_example_snapshots: 'skipping this example because it is very bad'
        05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
          skip_update_example_snapshots: 'skipping this example because we have manually modified it'
      GLFM_EXAMPLE_STATUS_YML_CONTENTS
    end

    let(:expected_html_yml_contents) do
      # language=YAML
      <<~ES_HTML_YML_CONTENTS
        ---
        02_01__inlines__strong__001:
          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>
        02_01__inlines__strong__002:
          canonical: |
            <p><strong>bold with more text</strong></p>
          static: |-
            <p data-sourcepos="1:1-1:23" dir="auto"><strong>bold with more text</strong></p>
          wysiwyg: |-
            <p><strong>bold with more text</strong></p>
        02_03__inlines__strikethrough_extension__001:
          canonical: |
            <p><del>Hi</del> Hello, world!</p>
          static: |-
            <p data-sourcepos="1:1-1:20" dir="auto"><del>Hi</del> Hello, world!</p>
          wysiwyg: |-
            <p><s>Hi</s> Hello, world!</p>
        03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001:
          canonical: |
            <p><strong>bold</strong></p>
          wysiwyg: |-
            <p><strong>bold</strong></p>
        04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001:
          canonical: |
            <p><strong>
            bold
            </strong></p>
          static: |-
            <strong>
            bold
            </strong>
        05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
          canonical: |
            <p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p>
          static: |-
            <p>This is the manually modified static HTML which will be preserved</p>
          wysiwyg: |-
            <p>This is the manually modified WYSIWYG HTML which will be preserved</p>
      ES_HTML_YML_CONTENTS
    end

    let(:expected_prosemirror_json_contents) do
      # language=YAML
      <<~ES_PROSEMIRROR_JSON_YML_CONTENTS
        ---
        02_01__inlines__strong__001: |-
          {
            "type": "doc",
            "content": [
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "marks": [
                      {
                        "type": "bold"
                      }
                    ],
                    "text": "bold"
                  }
                ]
              }
            ]
          }
        02_03__inlines__strikethrough_extension__001: |-
          {
            "type": "doc",
            "content": [
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "marks": [
                      {
                        "type": "strike"
                      }
                    ],
                    "text": "Hi"
                  },
                  {
                    "type": "text",
                    "text": " Hello, world!"
                  }
                ]
              }
            ]
          }
        03_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001: |-
          {
            "type": "doc",
            "content": [
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "marks": [
                      {
                        "type": "bold"
                      }
                    ],
                    "text": "bold"
                  }
                ]
              }
            ]
          }
        04_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |-
          {
            "existing": "This entry is manually modified and preserved because skip_update_example_snapshot_prosemirror_json will be truthy"
          }
        05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001: |-
          {
            "existing": "This entry is manually modified and preserved because skip_update_example_snapshots will be truthy"
          }
      ES_PROSEMIRROR_JSON_YML_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', :unlimited_max_formatted_output_length do
      # expectation that skipping message is only output once per example
      expect(subject).to receive(:output).once.with(/reason.*skipping this example because it is very bad/i)

      subject.process

      expect(es_html_yml_contents).to eq(expected_html_yml_contents)
      expect(es_prosemirror_json_yml_contents).to eq(expected_prosemirror_json_contents)
    end
  end

  def reread_io(io)
    # Reset the io StringIO to the beginning position of the buffer
    io.seek(0)
    io.read
  end
end