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

gettext_extractor_spec.rb « tooling « lib « tooling « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3c0f91342c2ce7496355c139abe47603bf20b7a0 (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
# frozen_string_literal: true

require 'rspec/parameterized'

require_relative '../../../../tooling/lib/tooling/gettext_extractor'
require_relative '../../../support/helpers/stub_env'
require_relative '../../../support/tmpdir'

RSpec.describe Tooling::GettextExtractor, feature_category: :tooling do
  include StubENV
  include TmpdirHelper

  let(:base_dir) { mktmpdir }
  let(:instance) { described_class.new(backend_glob: '*.{rb,haml,erb}', glob_base: base_dir) }
  let(:frontend_status) { true }

  let(:files) do
    {
      rb_file: File.join(base_dir, 'ruby.rb'),
      haml_file: File.join(base_dir, 'template.haml'),
      erb_file: File.join(base_dir, 'template.erb')
    }
  end

  before do
    # Disable parallelism in specs in order to suppress some confusing stack traces
    stub_env(
      'PARALLEL_PROCESSOR_COUNT' => 0
    )
    # Mock Backend files
    File.write(files[:rb_file], '[_("RB"), _("All"), n_("Apple", "Apples", size), s_("Context|A"), N_("All2") ]')
    File.write(
      files[:erb_file],
      '<h1><%= _("ERB") + _("All") + n_("Pear", "Pears", size) + s_("Context|B") + N_("All2") %></h1>'
    )
    File.write(
      files[:haml_file],
      '%h1= _("HAML") + _("All") + n_("Cabbage", "Cabbages", size) + s_("Context|C") + N_("All2")'
    )
    # Stub out Frontend file parsing
    status = {}
    allow(status).to receive(:success?).and_return(frontend_status)
    allow(Open3).to receive(:capture2)
                       .with("node scripts/frontend/extract_gettext_all.js --all")
                       .and_return([
                         '{"example.js": [ ["JS"], ["All"], ["Mango\u0000Mangoes"], ["Context|D"], ["All2"] ] }',
                         status
                       ])
  end

  describe '::HamlParser' do
    # Testing with a non-externalized string, as the functionality
    # is properly tested later on
    it '#parse_source' do
      expect(described_class::HamlParser.new(files[:haml_file]).parse_source('%h1= "Test"')).to match_array([])
    end
  end

  describe '#parse' do
    it 'collects and merges translatable strings from frontend and backend' do
      expect(instance.parse([]).to_h { |entry| [entry.msgid, entry.msgid_plural] }).to eq({
        'All' => nil,
        'All2' => nil,
        'Context|A' => nil,
        'Context|B' => nil,
        'Context|C' => nil,
        'Context|D' => nil,
        'ERB' => nil,
        'HAML' => nil,
        'JS' => nil,
        'RB' => nil,
        'Apple' => 'Apples',
        'Cabbage' => 'Cabbages',
        'Mango' => 'Mangoes',
        'Pear' => 'Pears'
      })
    end

    it 're-raises error from backend extraction' do
      allow(instance).to receive(:parse_backend_file).and_raise(StandardError)

      expect { instance.parse([]) }.to raise_error(StandardError)
    end

    context 'when frontend extraction raises an error' do
      let(:frontend_status) { false }

      it 'is re-raised' do
        expect { instance.parse([]) }.to raise_error(StandardError, 'Could not parse frontend files')
      end
    end
  end

  describe '#generate_pot' do
    subject { instance.generate_pot }

    it 'produces pot without date headers' do
      expect(subject).not_to include('POT-Creation-Date:')
      expect(subject).not_to include('PO-Revision-Date:')
    end

    it 'produces pot file with all translated strings, sorted by msg id' do
      expect(subject).to eql <<~POT_FILE
        # SOME DESCRIPTIVE TITLE.
        # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
        # This file is distributed under the same license as the gitlab package.
        # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
        #
        #, fuzzy
        msgid ""
        msgstr ""
        "Project-Id-Version: gitlab 1.0.0\\n"
        "Report-Msgid-Bugs-To: \\n"
        "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
        "Language-Team: LANGUAGE <LL@li.org>\\n"
        "Language: \\n"
        "MIME-Version: 1.0\\n"
        "Content-Type: text/plain; charset=UTF-8\\n"
        "Content-Transfer-Encoding: 8bit\\n"
        "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n"

        msgid "All"
        msgstr ""

        msgid "All2"
        msgstr ""

        msgid "Apple"
        msgid_plural "Apples"
        msgstr[0] ""
        msgstr[1] ""

        msgid "Cabbage"
        msgid_plural "Cabbages"
        msgstr[0] ""
        msgstr[1] ""

        msgid "Context|A"
        msgstr ""

        msgid "Context|B"
        msgstr ""

        msgid "Context|C"
        msgstr ""

        msgid "Context|D"
        msgstr ""

        msgid "ERB"
        msgstr ""

        msgid "HAML"
        msgstr ""

        msgid "JS"
        msgstr ""

        msgid "Mango"
        msgid_plural "Mangoes"
        msgstr[0] ""
        msgstr[1] ""

        msgid "Pear"
        msgid_plural "Pears"
        msgstr[0] ""
        msgstr[1] ""

        msgid "RB"
        msgstr ""
      POT_FILE
    end
  end

  # This private methods is tested directly, because unfortunately it is called
  # with the "Parallel" gem. As the parallel gem executes this function in a different
  # thread, our coverage reporting is confused
  #
  # On the other hand, the tests are also more readable, so maybe a win-win
  describe '#parse_backend_file' do
    subject { instance.send(:parse_backend_file, curr_file) }

    where do
      {
        'with ruby file' => {
          invalid_syntax: 'x = {id: _("RB")',
          file: :rb_file,
          result: {
            'All' => nil,
            'All2' => nil,
            'Context|A' => nil,
            'RB' => nil, 'Apple' => 'Apples'
          },
          parser: GetText::RubyParser
        },
        'with haml file' => {
          invalid_syntax: "  %a\n- content = _('HAML')",
          file: :haml_file,
          result: {
            'All' => nil,
            'All2' => nil,
            'Context|C' => nil,
            'HAML' => nil,
            'Cabbage' => 'Cabbages'
          },
          parser: described_class::HamlParser
        },
        'with erb file' => {
          invalid_syntax: "<% x = {id: _('ERB') %>",
          file: :erb_file,
          result: {
            'All' => nil,
            'All2' => nil,
            'Context|B' => nil,
            'ERB' => nil,
            'Pear' => 'Pears'
          },
          parser: GetText::ErbParser
        }
      }
    end

    with_them do
      let(:curr_file) { files[file] }

      context 'when file has valid syntax' do
        before do
          allow(parser).to receive(:new).and_call_original
        end

        it 'parses file and returns extracted strings as POEntries' do
          expect(subject.map(&:class).uniq).to match_array([GetText::POEntry])
          expect(subject.to_h { |entry| [entry.msgid, entry.msgid_plural] }).to eq(result)
          expect(parser).to have_received(:new)
        end
      end

      # We do not worry about syntax errors in these file types, as it is _not_ the job of
      # gettext extractor to ensure correctness of the files. These errors should raise
      # in other places
      context 'when file has invalid syntax' do
        before do
          File.write(curr_file, invalid_syntax)
        end

        it 'does not raise error' do
          expect { subject }.not_to raise_error
        end
      end

      context 'when file does not contain "_("' do
        before do
          allow(parser).to receive(:new).and_call_original
          File.write(curr_file, '"abcdef"')
        end

        it 'never parses the file and returns empty array' do
          expect(subject).to match_array([])
          expect(parser).not_to have_received(:new)
        end
      end
    end

    context 'with unsupported file containing "_("' do
      let(:curr_file) { File.join(base_dir, 'foo.unsupported') }

      before do
        File.write(curr_file, '_("Test")')
      end

      it 'raises error' do
        expect { subject }.to raise_error(NotImplementedError)
      end
    end
  end
end