BigW Consortium Gitlab

po_linter_spec.rb 10.4 KB
Newer Older
1
require 'spec_helper'
2
require 'simple_po_parser'
3

4
describe Gitlab::I18n::PoLinter do
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
  let(:linter) { described_class.new(po_path) }
  let(:po_path) { 'spec/fixtures/valid.po' }

  describe '#errors' do
    it 'only calls validation once' do
      expect(linter).to receive(:validate_po).once.and_call_original

      2.times { linter.errors }
    end
  end

  describe '#validate_po' do
    subject(:errors) { linter.validate_po }

    context 'for a fuzzy message' do
      let(:po_path) { 'spec/fixtures/fuzzy.po' }

      it 'has an error' do
        is_expected.to include('PipelineSchedules|Remove variable row' => ['is marked fuzzy'])
      end
    end

    context 'for a translations with newlines' do
      let(:po_path) { 'spec/fixtures/newlines.po' }

30
      it 'has an error for a normal string' do
31
        message_id = "You are going to remove %{group_name}.\\nRemoved groups CANNOT be restored!\\nAre you ABSOLUTELY sure?"
32
        expected_message = "is defined over multiple lines, this breaks some tooling."
33

34 35 36 37 38
        expect(errors[message_id]).to include(expected_message)
      end

      it 'has an error when a translation is defined over multiple lines' do
        message_id = "You are going to remove %{group_name}.\\nRemoved groups CANNOT be restored!\\nAre you ABSOLUTELY sure?"
39
        expected_message = "has translations defined over multiple lines, this breaks some tooling."
40 41 42 43 44 45

        expect(errors[message_id]).to include(expected_message)
      end

      it 'raises an error when a plural translation is defined over multiple lines' do
        message_id = 'With plural'
46
        expected_message = "has translations defined over multiple lines, this breaks some tooling."
47 48

        expect(errors[message_id]).to include(expected_message)
49
      end
50 51 52 53 54 55 56

      it 'raises an error when the plural id is defined over multiple lines' do
        message_id = 'multiline plural id'
        expected_message = "plural is defined over multiple lines, this breaks some tooling."

        expect(errors[message_id]).to include(expected_message)
      end
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
    end

    context 'with an invalid po' do
      let(:po_path) { 'spec/fixtures/invalid.po' }

      it 'returns the error' do
        is_expected.to include('PO-syntax errors' => a_kind_of(Array))
      end

      it 'does not validate entries' do
        expect(linter).not_to receive(:validate_entries)

        linter.validate_po
      end
    end

73 74 75 76 77 78 79 80
    context 'with missing metadata' do
      let(:po_path) { 'spec/fixtures/missing_metadata.po' }

      it 'returns the an error' do
        is_expected.to include('PO-syntax errors' => a_kind_of(Array))
      end
    end

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    context 'with a valid po' do
      it 'parses the file' do
        expect(linter).to receive(:parse_po).and_call_original

        linter.validate_po
      end

      it 'validates the entries' do
        expect(linter).to receive(:validate_entries).and_call_original

        linter.validate_po
      end

      it 'has no errors' do
        is_expected.to be_empty
      end
    end
98 99

    context 'with missing plurals' do
100
      let(:po_path) { 'spec/fixtures/missing_plurals.po' }
101

102
      it 'has errors' do
103
        is_expected.not_to be_empty
104 105 106 107 108 109
      end
    end

    context 'with multiple plurals' do
      let(:po_path) { 'spec/fixtures/multiple_plurals.po' }

110
      it 'has errors' do
111 112 113
        is_expected.not_to be_empty
      end
    end
114 115 116 117 118 119 120 121 122 123 124

    context 'with unescaped chars' do
      let(:po_path) { 'spec/fixtures/unescaped_chars.po' }

      it 'contains an error' do
        message_id = 'You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?'
        expected_error = 'translation contains unescaped `%`, escape it using `%%`'

        expect(errors[message_id]).to include(expected_error)
      end
    end
125 126 127 128 129 130 131
  end

  describe '#parse_po' do
    context 'with a valid po' do
      it 'fills in the entries' do
        linter.parse_po

132 133
        expect(linter.translation_entries).not_to be_empty
        expect(linter.metadata_entry).to be_kind_of(Gitlab::I18n::MetadataEntry)
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
      end

      it 'does not have errors' do
        expect(linter.parse_po).to be_nil
      end
    end

    context 'with an invalid po' do
      let(:po_path) { 'spec/fixtures/invalid.po' }

      it 'contains an error' do
        expect(linter.parse_po).not_to be_nil
      end

      it 'sets the entries to an empty array' do
        linter.parse_po

151
        expect(linter.translation_entries).to eq([])
152 153 154 155 156 157
      end
    end
  end

  describe '#validate_entries' do
    it 'keeps track of errors for entries' do
158
      fake_invalid_entry = Gitlab::I18n::TranslationEntry.new(
159
        { msgid: "Hello %{world}", msgstr: "Bonjour %{monde}" }, 2
160 161
      )
      allow(linter).to receive(:translation_entries) { [fake_invalid_entry] }
162 163 164 165 166 167 168 169 170 171

      expect(linter).to receive(:validate_entry)
                          .with(fake_invalid_entry)
                          .and_call_original

      expect(linter.validate_entries).to include("Hello %{world}" => an_instance_of(Array))
    end
  end

  describe '#validate_entry' do
172
    it 'validates the flags, variable usage, newlines, and unescaped chars' do
173 174 175 176 177
      fake_entry = double

      expect(linter).to receive(:validate_flags).with([], fake_entry)
      expect(linter).to receive(:validate_variables).with([], fake_entry)
      expect(linter).to receive(:validate_newlines).with([], fake_entry)
178
      expect(linter).to receive(:validate_number_of_plurals).with([], fake_entry)
179
      expect(linter).to receive(:validate_unescaped_chars).with([], fake_entry)
180 181 182 183 184

      linter.validate_entry(fake_entry)
    end
  end

185 186 187 188
  describe '#validate_number_of_plurals' do
    it 'validates when there are an incorrect number of translations' do
      fake_metadata = double
      allow(fake_metadata).to receive(:expected_plurals).and_return(2)
189
      allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
190

191
      fake_entry = Gitlab::I18n::TranslationEntry.new(
192 193
        { msgid: 'the singular', msgid_plural: 'the plural', 'msgstr[0]' => 'the singular' },
        2
194 195 196 197 198 199 200 201 202
      )
      errors = []

      linter.validate_number_of_plurals(errors, fake_entry)

      expect(errors).to include('should have 2 translations')
    end
  end

203
  describe '#validate_variables' do
204
    it 'validates both signular and plural in a pluralized string when the entry has a singular' do
205 206 207 208 209 210 211
      pluralized_entry = Gitlab::I18n::TranslationEntry.new(
        { msgid: 'Hello %{world}',
          msgid_plural: 'Hello all %{world}',
          'msgstr[0]' => 'Bonjour %{world}',
          'msgstr[1]' => 'Bonjour tous %{world}' },
        2
      )
212 213 214

      expect(linter).to receive(:validate_variables_in_message)
                          .with([], 'Hello %{world}', 'Bonjour %{world}')
215
                          .and_call_original
216 217
      expect(linter).to receive(:validate_variables_in_message)
                          .with([], 'Hello all %{world}', 'Bonjour tous %{world}')
218
                          .and_call_original
219 220

      linter.validate_variables([], pluralized_entry)
221 222 223
    end

    it 'only validates plural when there is no separate singular' do
224 225 226 227 228 229
      pluralized_entry = Gitlab::I18n::TranslationEntry.new(
        { msgid: 'Hello %{world}',
          msgid_plural: 'Hello all %{world}',
          'msgstr[0]' => 'Bonjour %{world}' },
        1
      )
230 231 232 233 234

      expect(linter).to receive(:validate_variables_in_message)
                          .with([], 'Hello all %{world}', 'Bonjour %{world}')

      linter.validate_variables([], pluralized_entry)
235 236 237
    end

    it 'validates the message variables' do
238
      entry = Gitlab::I18n::TranslationEntry.new(
239 240
        { msgid: 'Hello', msgstr: 'Bonjour' },
        2
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

      expect(linter).to receive(:validate_variables_in_message)
                          .with([], 'Hello', 'Bonjour')

      linter.validate_variables([], entry)
    end
  end

  describe '#validate_variables_in_message' do
    it 'detects when a variables are used incorrectly' do
      errors = []

      expected_errors = ['<hello %{world} %d> is missing: [%{hello}]',
                         '<hello %{world} %d> is using unknown variables: [%{world}]',
                         'is combining multiple unnamed variables']

      linter.validate_variables_in_message(errors, '%{hello} world %d', 'hello %{world} %d')

      expect(errors).to include(*expected_errors)
    end
  end

  describe '#validate_translation' do
    it 'succeeds with valid variables' do
      errors = []

      linter.validate_translation(errors, 'Hello %{world}',  ['%{world}'])

      expect(errors).to be_empty
    end

    it 'adds an error message when translating fails' do
      errors = []

      expect(FastGettext::Translation).to receive(:_) { raise 'broken' }

      linter.validate_translation(errors, 'Hello', [])

      expect(errors).to include('Failure translating to en with []: broken')
    end

    it 'adds an error message when translating fails when translating with context' do
      errors = []

      expect(FastGettext::Translation).to receive(:s_) { raise 'broken' }

      linter.validate_translation(errors, 'Tests|Hello', [])

      expect(errors).to include('Failure translating to en with []: broken')
    end

    it "adds an error when trying to translate with incorrect variables when using unnamed variables" do
      errors = []

      linter.validate_translation(errors, 'Hello %d', ['%s'])

      expect(errors.first).to start_with("Failure translating to en with")
    end

    it "adds an error when trying to translate with named variables when unnamed variables are expected" do
      errors = []

      linter.validate_translation(errors, 'Hello %d', ['%{world}'])

      expect(errors.first).to start_with("Failure translating to en with")
    end

    it 'adds an error when translated with incorrect variables using named variables' do
      errors = []

      linter.validate_translation(errors, 'Hello %{thing}', ['%d'])

      expect(errors.first).to start_with("Failure translating to en with")
    end
  end

  describe '#fill_in_variables' do
    it 'builds an array for %d translations' do
      result = linter.fill_in_variables(['%d'])

      expect(result).to contain_exactly(a_kind_of(Integer))
    end

    it 'builds an array for %s translations' do
      result = linter.fill_in_variables(['%s'])

      expect(result).to contain_exactly(a_kind_of(String))
    end

    it 'builds a hash for named variables' do
      result = linter.fill_in_variables(['%{hello}'])

      expect(result).to be_a(Hash)
      expect(result).to include('hello' => an_instance_of(String))
    end
  end
end