subconv 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,311 @@
1
+ require 'spec_helper'
2
+
3
+ module Subconv
4
+ # Often used codes:
5
+ # 9420: Resume caption loading
6
+ # 91d0: Preamble address code for row 0 column 0
7
+ # 942f: End of caption
8
+ describe Scc::Reader do
9
+ include TestHelpers
10
+
11
+ let(:backspace) { '94a1 94a1' }
12
+ let(:test) { '54e5 73f4' }
13
+ let(:a) { 'c180' }
14
+
15
+ before(:each) do
16
+ @reader = Scc::Reader.new
17
+ end
18
+
19
+ it 'should reject garbage' do
20
+ expect {
21
+ @reader.read(StringIO.new('safjewofjpoajfljg'), default_fps)
22
+ }.to raise_error(Scc::Reader::InvalidFormatError)
23
+ end
24
+
25
+ # Helper function to create a string that represents an SCC file with
26
+ # a single caption at zero time
27
+ # data must be given as fully formatted SCC string
28
+ def caption_at_zero(data)
29
+ "Scenarist_SCC V1.0\n\n00:00:00:00\t#{data}"
30
+ end
31
+
32
+ # Parse the input string with Scc::Reader and return the resulting captions
33
+ def get_captions(input, fps = default_fps, check_parity = true)
34
+ @reader.read(StringIO.new(input), fps, check_parity)
35
+ @reader.captions
36
+ end
37
+
38
+ it 'should reject data with wrong parity' do
39
+ expect {
40
+ get_captions(caption_at_zero("9420 11d0 #{test} 942f"))
41
+ }.to raise_error(Scc::Reader::ParityError)
42
+ end
43
+
44
+ it 'should decode simple text' do
45
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'Test')
46
+
47
+ expect(get_captions(caption_at_zero("9420 91d0 #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
48
+ end
49
+
50
+ it 'should not reject data with wrong parity when checking is not requested' do
51
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'Test')
52
+
53
+ expect(get_captions(caption_at_zero("9420 11d0 #{test} 942f"), default_fps, false)).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
54
+ end
55
+
56
+ expected_rows = {
57
+ '9140' => 0,
58
+ '91E0' => 1,
59
+ '9240' => 2,
60
+ '92E0' => 3,
61
+ '1540' => 4,
62
+ '15E0' => 5,
63
+ '1640' => 6,
64
+ '16E0' => 7,
65
+ '9740' => 8,
66
+ '97E0' => 9,
67
+ '1040' => 10,
68
+ '1340' => 11,
69
+ '13E0' => 12,
70
+ '9440' => 13,
71
+ '94E0' => 14
72
+ }
73
+
74
+ expected_rows.each_pair do |pac, row|
75
+ it "should decode the preamble address code #{pac} to row #{row}" do
76
+ expected_grid = Scc::Grid.new.insert_text(row, 0, 'Test')
77
+ expect(get_captions(caption_at_zero("9420 #{pac} #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
78
+ end
79
+ end
80
+
81
+ expected_column = {
82
+ '91d0' => 0,
83
+ '9152' => 4,
84
+ '9154' => 8,
85
+ '91D6' => 12,
86
+ '9158' => 16,
87
+ '91da' => 20,
88
+ '91dc' => 24,
89
+ '915e' => 28
90
+ }
91
+
92
+ expected_column.each_pair do |pac, column|
93
+ it "should decode the preamble address code #{pac} to column #{column}" do
94
+ expected_grid = Scc::Grid.new.insert_text(0, column, 'Test')
95
+ expect(get_captions(caption_at_zero("9420 #{pac} #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
96
+ end
97
+ end
98
+
99
+ it 'should handle backspace' do
100
+ expected_grid = Scc::Grid.new.insert_text(1, 0, 'Test')
101
+ # Write AA, then backspace three times, go to next row, write Tesu, backspace one time, then write t
102
+ # -> Should result in the first line being empty and the second line reading "Test"
103
+ expect(get_captions(caption_at_zero('9420 91d0 c1c1 ' + (backspace + ' ') * 3 + "91e0 54e5 7375 #{backspace} f480 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(15, default_fps), grid: expected_grid)])
104
+ end
105
+
106
+ it 'should handle overflowing lines' do
107
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A' * 31 + 'B')
108
+ # Write 40 As and then tw Bs
109
+ # -> Should result in 31 As and one B since after overflow the last column is overwritten the whole time
110
+ expect(get_captions(caption_at_zero('9420 91d0 ' + ('c1c1 ' * 20) + 'c2c2 942f'))).to eq([Scc::Caption.new(timecode: Timecode.new(23, default_fps), grid: expected_grid)])
111
+ end
112
+
113
+ it 'should handle italics' do
114
+ style = Scc::CharacterStyle.default
115
+ style.italics = true
116
+ # Italics is a mid-row spacing code, so expect a space before the text
117
+ expected_grid = Scc::Grid.new.insert_text(0, 0, ' ').insert_text(0, 1, 'Test', style)
118
+ expect(get_captions(caption_at_zero("9420 91d0 91ae #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(5, default_fps), grid: expected_grid)])
119
+ end
120
+
121
+ it 'should turn off flash on italics mid-row codes' do
122
+ style = Scc::CharacterStyle.default
123
+ flash_style = Scc::CharacterStyle.default
124
+ style.italics = true
125
+ flash_style.flash = true
126
+ # Two spaces before the text
127
+ expected_grid = Scc::Grid.new.insert_text(0, 0, ' ').insert_text(0, 1, ' ', flash_style).insert_text(0, 2, 'Test', style)
128
+ # "<flash on><italics>Test"
129
+ expect(get_captions(caption_at_zero("9420 91d0 94a8 91ae #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(6, default_fps), grid: expected_grid)])
130
+ end
131
+
132
+ it 'should handle italics preamble address code' do
133
+ style = Scc::CharacterStyle.default
134
+ style.italics = true
135
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'Test', style)
136
+ expect(get_captions(caption_at_zero("9420 91ce #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
137
+ end
138
+
139
+ it 'should handle italics and underline preamble address code' do
140
+ style = Scc::CharacterStyle.default
141
+ style.italics = true
142
+ style.underline = true
143
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'Test', style)
144
+ expect(get_captions(caption_at_zero("9420 914f #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
145
+ end
146
+
147
+ it 'should handle underline' do
148
+ style = Scc::CharacterStyle.default
149
+ style.underline = true
150
+ # Underline is a mid-row spacing code, so expect a space before the text
151
+ expected_grid = Scc::Grid.new.insert_text(0, 0, ' ').insert_text(0, 1, 'Test', style)
152
+ expect(get_captions(caption_at_zero("9420 91d0 91a1 #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(5, default_fps), grid: expected_grid)])
153
+ end
154
+
155
+ it 'should handle italics and underline' do
156
+ style = Scc::CharacterStyle.default
157
+ style.italics = true
158
+ style.underline = true
159
+ # Italics/underline is a mid-row spacing code, so expect a space before the text
160
+ expected_grid = Scc::Grid.new.insert_text(0, 0, ' ').insert_text(0, 1, 'Test', style)
161
+ expect(get_captions(caption_at_zero("9420 91d0 912f #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(5, default_fps), grid: expected_grid)])
162
+ end
163
+
164
+ color_code_map = {
165
+ Scc::Color::WHITE => '9120',
166
+ Scc::Color::GREEN => '91a2',
167
+ Scc::Color::BLUE => '91a4',
168
+ Scc::Color::CYAN => '9126',
169
+ Scc::Color::RED => '91a8',
170
+ Scc::Color::YELLOW => '912a',
171
+ Scc::Color::MAGENTA => '912c'
172
+ }
173
+
174
+ color_code_map.each_pair do |color, color_code|
175
+ it "should handle the color #{color}" do
176
+ style = Scc::CharacterStyle.default
177
+ style.color = color
178
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A', style)
179
+ expect(get_captions(caption_at_zero("9420 91d0 #{color_code} #{backspace} #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(6, default_fps), grid: expected_grid)])
180
+ end
181
+ end
182
+
183
+ it 'should turn off italics on color' do
184
+ style = Scc::CharacterStyle.default
185
+ italics_style = Scc::CharacterStyle.default
186
+ style.color = Scc::Color::RED
187
+ italics_style.italics = true
188
+ # Two spaces before the text
189
+ expected_grid = Scc::Grid.new.insert_text(0, 0, ' ').insert_text(0, 1, ' ', italics_style).insert_text(0, 2, 'Test', style)
190
+ # "<italics><red>Test"
191
+ expect(get_captions(caption_at_zero("9420 91d0 91ae 91a8 #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(6, default_fps), grid: expected_grid)])
192
+ end
193
+
194
+ it 'should handle all available attributes combined' do
195
+ style = Scc::CharacterStyle.default
196
+ style.color = Scc::Color::RED
197
+ # Space generated by italics/underline
198
+ expected_grid = Scc::Grid.new.insert_text(0, 0, ' ', style.dup)
199
+ style.italics = true
200
+ style.underline = true
201
+ # Space generated by flash
202
+ expected_grid.insert_text(0, 1, ' ', style.dup)
203
+ style.flash = true
204
+ expected_grid.insert_text(0, 2, 'Test', style.dup)
205
+ # Set color via preamble address code to test that too, then set italics/underline via mid-row code, then assign flash via "flash on" miscellaneous control code
206
+ expect(get_captions(caption_at_zero("9420 91c8 912f 94a8 #{test} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(6, default_fps), grid: expected_grid)])
207
+ end
208
+
209
+ it 'should handle transparent space' do
210
+ # Column 1 should be empty
211
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A').insert_text(0, 2, 'A')
212
+ # "A<transparent space>A"
213
+ expect(get_captions(caption_at_zero("9420 91d0 #{a} 91b9 #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(5, default_fps), grid: expected_grid)])
214
+ end
215
+
216
+ it 'should delete characters behind transparent space' do
217
+ # Column 1 should be empty
218
+ expected_grid = Scc::Grid.new.insert_text(0, 1, 'est')
219
+ # "Test<PAC><transparent space>"
220
+ expect(get_captions(caption_at_zero("9420 91d0 #{test} 91d0 91b9 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(6, default_fps), grid: expected_grid)])
221
+ end
222
+
223
+ it 'should handle standard space' do
224
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A A')
225
+ # "A A"
226
+ expect(get_captions(caption_at_zero('9420 91d0 c120 c180 942f'))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid)])
227
+ end
228
+
229
+ it 'should handle tab offset' do
230
+ # Space between the As should be empty
231
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A').insert_text(0, 2, 'A').insert_text(0, 5, 'A').insert_text(0, 9, 'A')
232
+ # "A<tab offset 1>A<tab offset 2>A<tab offset 3>A"
233
+ expect(get_captions(caption_at_zero("9420 91d0 #{a} 97a1 #{a} 97a2 #{a} 9723 #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(9, default_fps), grid: expected_grid)])
234
+ end
235
+
236
+ it 'should not delete characters on tab offset' do
237
+ # Space between the As should be empty
238
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'TAst')
239
+ # "Test<PAC><tab offset 1>A"
240
+ expect(get_captions(caption_at_zero("9420 91d0 #{test} 91d0 97a1 #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(7, default_fps), grid: expected_grid)])
241
+ end
242
+
243
+ it 'should ignore repeated commands' do
244
+ # There should be only one, not two spaces between the As
245
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A').insert_text(0, 2, 'A')
246
+ # "A<transparent space><transparent space>A"
247
+ expect(get_captions(caption_at_zero("9420 91d0 #{a} 91b9 91b9 #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(6, default_fps), grid: expected_grid)])
248
+ end
249
+
250
+ it 'should not ignore multiply repeated commands' do
251
+ # Now there should be not, not four or one, spaces between the As
252
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A').insert_text(0, 3, 'A')
253
+ # "A<transparent space><transparent space<transparent space><transparent space>>A"
254
+ expect(get_captions(caption_at_zero("9420 91d0 #{a} 91b9 91b9 91b9 91b9 #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(8, default_fps), grid: expected_grid)])
255
+ end
256
+
257
+ it 'should handle delete to end of row' do
258
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'AAAA')
259
+ # "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<PAC indent 4><delete to end of row>"
260
+ expect(get_captions(caption_at_zero('9420 91d0 ' + (a + ' ') * 32 + '9152 94a4 942f'))).to eq([Scc::Caption.new(timecode: Timecode.new(36, default_fps), grid: expected_grid)])
261
+ end
262
+
263
+ it 'should handle erase displayed memory' do
264
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'Test')
265
+ # Display "Test", wait 2 frames, erase displayed memory
266
+ # -> should result in an empty caption 3 frames later
267
+ expect(get_captions(caption_at_zero("9420 91d0 #{test} 942f 8080 8080 942c"))).to eq([Scc::Caption.new(timecode: Timecode.new(4, default_fps), grid: expected_grid), Scc::Caption.new(timecode: Timecode.new(7, default_fps))])
268
+ end
269
+
270
+ it 'should handle erase non-displayed memory' do
271
+ expected_grid = Scc::Grid.new.insert_text(0, 0, 'A')
272
+ # Insert "Test", delete the non-displayed memory, insert "A" and then flip captions
273
+ expect(get_captions(caption_at_zero("9420 91d0 #{test} 94ae 91d0 #{a} 942f"))).to eq([Scc::Caption.new(timecode: Timecode.new(7, default_fps), grid: expected_grid)])
274
+ end
275
+
276
+ it 'should handle special characters' do
277
+ expected_grid = Scc::Grid.new.insert_text(0, 0, '♪')
278
+ expect(get_captions(caption_at_zero('9420 91d0 9137 942f'))).to eq([Scc::Caption.new(timecode: Timecode.new(3, default_fps), grid: expected_grid)])
279
+ end
280
+
281
+ it 'should handle multiple timecodes and captions' do
282
+ expected_grid_test = Scc::Grid.new.insert_text(0, 0, 'Test')
283
+ expected_grid_a = Scc::Grid.new.insert_text(0, 0, 'A')
284
+ expected_grid_b = Scc::Grid.new.insert_text(0, 0, 'B')
285
+ caption_text = <<"END"
286
+ Scenarist_SCC V1.0
287
+
288
+ 00:00:01:00\t9420 91d0 #{test} 942f 94ae
289
+
290
+ 00:00:02:00\t9420 91d0 #{test} 942f 94ae
291
+
292
+ 00:00:03:00\t942c
293
+
294
+ 00:00:04:00\t9420 91d0 c180 942f
295
+
296
+ 00:00:05:00\t9420 91d0 c280 942f
297
+ END
298
+ expect(get_captions(caption_text)).to eq([
299
+ # First caption: Test
300
+ Scc::Caption.new(timecode: Timecode.parse('00:00:01:04', default_fps), grid: expected_grid_test),
301
+ # Caption at 00:00:02:00 should be identical to the first one and thus not get put out
302
+ # At 00:00:03:00: erase displayed caption
303
+ Scc::Caption.new(timecode: Timecode.parse('00:00:03:00', default_fps)),
304
+ # At 00:00:04:00: Display "A"
305
+ Scc::Caption.new(timecode: Timecode.parse('00:00:04:03', default_fps), grid: expected_grid_a),
306
+ # At 00:00:05:00: Display "B" without erasing A beforehand
307
+ Scc::Caption.new(timecode: Timecode.parse('00:00:05:03', default_fps), grid: expected_grid_b)
308
+ ])
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+
3
+ module Subconv
4
+ describe Scc::Transformer do
5
+ include TestHelpers
6
+ include Subconv
7
+
8
+ before(:each) do
9
+ @transformer = Scc::Transformer.new
10
+ end
11
+
12
+ it 'should handle empty captions' do
13
+ expect(@transformer.transform([])).to eq([])
14
+ end
15
+
16
+ it 'should convert a simple caption' do
17
+ transformed = @transformer.transform(single_scc_caption_with_grid(test_grid))
18
+ expect(transformed).to eq([Caption.new(timespan: t1_2, position: left_top, content: test_content)])
19
+ end
20
+
21
+ it 'should auto-close dangling captions' do
22
+ transformed = @transformer.transform([Scc::Caption.new(timecode: t1, grid: test_grid)])
23
+ # Caption should be closed after 5 seconds
24
+ t_end = t1 + Timecode.from_seconds(5.0, default_fps)
25
+ expect(transformed).to eq([Caption.new(timespan: Utility::Timespan.new(t1, t_end), position: left_top, content: test_content)])
26
+ end
27
+
28
+ it 'should auto-close a previous caption on caption change' do
29
+ grid = Scc::Grid.new.insert_text(0, 0, 'AAAA')
30
+ transformed = @transformer.transform([Scc::Caption.new(timecode: t1, grid: test_grid), Scc::Caption.new(timecode: t2, grid: grid), Scc::Caption.new(timecode: t3)])
31
+ expect(transformed).to eq([Caption.new(timespan: t1_2, position: left_top, content: test_content), Caption.new(timespan: t2_3, position: left_top, content: root_with_text('AAAA'))])
32
+ end
33
+
34
+ it 'should position nodes at the edges at the correct positions' do
35
+ grid = Scc::Grid.new.insert_text(0, 0, 'a').insert_text(0, Scc::GRID_COLUMNS - 1, 'b').insert_text(Scc::GRID_ROWS - 1, 0, 'c').insert_text(Scc::GRID_ROWS - 1, Scc::GRID_COLUMNS - 1, 'd')
36
+ transformed = @transformer.transform(single_scc_caption_with_grid(grid))
37
+ # The exact positions are subject to change when comparison with real SCC data and rendering is done
38
+ expected = [['a', [0.2, 0.1]], ['b', [0.78125, 0.1]], ['c', [0.2, 0.8467]], ['d', [0.78125, 0.8467]]].map { |group|
39
+ Caption.new(timespan: t1_2, position: Position.new(group[1][0], group[1][1]), content: root_with_text(group[0]))
40
+ }
41
+ expect(transformed).to eq(expected)
42
+ end
43
+
44
+ def transform_test_style(style)
45
+ @transformer.transform(single_scc_caption_with_grid(Scc::Grid.new.insert_text(0, 0, 'Test', style)))
46
+ end
47
+
48
+ it 'should handle italics' do
49
+ style = Scc::CharacterStyle.default
50
+ style.italics = true
51
+ expect(transform_test_style(style)).to eq(single_caption_with_content(ItalicsNode.new([TextNode.new('Test')])))
52
+ end
53
+
54
+ it 'should handle underline' do
55
+ style = Scc::CharacterStyle.default
56
+ style.underline = true
57
+ expect(transform_test_style(style)).to eq(single_caption_with_content(UnderlineNode.new([TextNode.new('Test')])))
58
+ end
59
+
60
+ it 'should handle flash' do
61
+ style = Scc::CharacterStyle.default
62
+ style.flash = true
63
+ expect(transform_test_style(style)).to eq(single_caption_with_content(FlashNode.new([TextNode.new('Test')])))
64
+ end
65
+
66
+ it 'should handle color' do
67
+ style = Scc::CharacterStyle.default
68
+ style.color = Scc::Color::RED
69
+ expect(transform_test_style(style)).to eq(single_caption_with_content(ColorNode.new(:red, [TextNode.new('Test')])))
70
+ end
71
+
72
+ it 'should handle all styles combined' do
73
+ style = Scc::CharacterStyle.default
74
+ style.underline = true
75
+ style.italics = true
76
+ style.flash = true
77
+ style.color = Scc::Color::MAGENTA
78
+ # This operates under the assumption that the nodes are sorted with these exact priorities
79
+ # (which may change in the future)
80
+ # Perhaps it would be better to compare independent of the exact order of the nodes
81
+ expect(transform_test_style(style)).to eq(single_caption_with_content(
82
+ ColorNode.new(:magenta, [
83
+ UnderlineNode.new([
84
+ ItalicsNode.new([
85
+ FlashNode.new([
86
+ TextNode.new('Test')
87
+ ])
88
+ ])
89
+ ])
90
+ ])
91
+ ))
92
+ end
93
+
94
+ it 'should handle this complicated combination of styles and text' do
95
+ style = Scc::CharacterStyle.default
96
+ grid = Scc::Grid.new
97
+ grid.insert_text(0, 0, 'a')
98
+ style.color = Scc::Color::BLUE
99
+ style.italics = true
100
+ style.flash = true
101
+ grid.insert_text(0, 1, 'b', style.dup)
102
+ style.underline = true
103
+ grid.insert_text(0, 2, 'cde', style.dup)
104
+ style.color = Scc::Color::CYAN
105
+ style.flash = false
106
+ grid.insert_text(0, 5, 'f', style.dup)
107
+ transformed = @transformer.transform(single_scc_caption_with_grid(grid))
108
+ expect(transformed).to eq(single_caption_with_content(
109
+ [
110
+ TextNode.new('a'),
111
+ ItalicsNode.new([
112
+ ColorNode.new(:blue, [
113
+ FlashNode.new([
114
+ TextNode.new('b'),
115
+ UnderlineNode.new([
116
+ TextNode.new('cde')
117
+ ])
118
+ ])
119
+ ]),
120
+ ColorNode.new(:cyan, [
121
+ UnderlineNode.new([
122
+ TextNode.new('f')
123
+ ])
124
+ ])
125
+ ])
126
+ ]
127
+ )
128
+ )
129
+ end
130
+
131
+ it 'should handle this other complicated combination of styles and text' do
132
+ style = Scc::CharacterStyle.default
133
+ grid = Scc::Grid.new
134
+ style.color = Scc::Color::RED
135
+ style.italics = true
136
+ grid.insert_text(0, 0, 'xyz', style.dup)
137
+ style.underline = true
138
+ style.flash = true
139
+ grid.insert_text(0, 3, 'abc', style.dup)
140
+ style.color = Scc::Color::YELLOW
141
+ style.flash = false
142
+ style.italics = false
143
+ grid.insert_text(0, 6, 'jkl', style.dup)
144
+ transformed = @transformer.transform(single_scc_caption_with_grid(grid))
145
+ expect(transformed).to eq(single_caption_with_content(
146
+ [
147
+ ColorNode.new(:red, [
148
+ ItalicsNode.new([
149
+ TextNode.new('xyz'),
150
+ UnderlineNode.new([
151
+ FlashNode.new([
152
+ TextNode.new('abc')
153
+ ])
154
+ ])
155
+ ])
156
+ ]),
157
+ ColorNode.new(:yellow, [
158
+ UnderlineNode.new([
159
+ TextNode.new('jkl')
160
+ ])
161
+ ])
162
+ ]
163
+ )
164
+ )
165
+ end
166
+ end
167
+ end