このコミットが含まれているのは:
@@ -24,6 +24,18 @@ FactoryBot.define do
|
||||
|
||||
after(:create) do |tag, evaluator|
|
||||
evaluator.primary_tag_name.update!(tag:, primary_flg: true)
|
||||
|
||||
Locale.where(deprecated_at: nil).order(:language_code, :created_at, :script_code).to_a
|
||||
.uniq(&:language_code).each do |locale|
|
||||
next if tag.tag_names.exists?(language_code: locale.language_code,
|
||||
primary_flg: true)
|
||||
|
||||
create(:tag_name,
|
||||
tag:, language_code: locale.language_code,
|
||||
script_code: locale.script_code,
|
||||
name: TagName.generate_name(locale, tag, evaluator.primary_tag_name),
|
||||
primary_flg: true, auto_generated: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Language, type: :model do
|
||||
it 'accepts supported language codes' do
|
||||
expect(described_class.new(code: 'en', name: 'English')).to be_valid
|
||||
end
|
||||
|
||||
it 'rejects an unsupported language code' do
|
||||
expect {
|
||||
described_class.create!(code: 'xx', name: 'Unsupported')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not treat French as a supported language' do
|
||||
expect {
|
||||
described_class.create!(code: 'fr', name: 'French')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,15 +1,15 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Locale, type: :model do
|
||||
def prepare_french_reference!
|
||||
Language.find_or_create_by!(code: 'fr') { _1.name = 'French' }
|
||||
def prepare_english_reference!
|
||||
Language.find_or_create_by!(code: 'en') { _1.name = 'English' }
|
||||
Script.find_or_create_by!(code: 'Latn') { _1.name = 'Latin' }
|
||||
end
|
||||
|
||||
def create_french_locale!
|
||||
prepare_french_reference!
|
||||
described_class.create!(code: 'fr', language_code: 'fr',
|
||||
script_code: 'Latn', name: 'French')
|
||||
def create_english_locale!
|
||||
prepare_english_reference!
|
||||
described_class.create!(code: 'en', language_code: 'en',
|
||||
script_code: 'Latn', name: 'English')
|
||||
end
|
||||
|
||||
it 'generates a primary name for every existing tag using the name generator' do
|
||||
@@ -20,55 +20,80 @@ RSpec.describe Locale, type: :model do
|
||||
.with(kind_of(described_class), first, primary_tag_name_for(first, 'ja'))
|
||||
.and_return('name_from_generator')
|
||||
|
||||
locale = create_french_locale!
|
||||
locale = create_english_locale!
|
||||
|
||||
expect(TagName).to have_received(:generate_name)
|
||||
.with(locale, first, primary_tag_name_for(first, 'ja'))
|
||||
expect(TagName).to have_received(:generate_name)
|
||||
.with(locale, second, primary_tag_name_for(second, 'ja'))
|
||||
expect(first.tag_names.find_by!(language_code: 'fr', primary_flg: true))
|
||||
expect(first.tag_names.find_by!(language_code: 'en', primary_flg: true))
|
||||
.to have_attributes(name: 'name_from_generator', tag_id: first.id,
|
||||
language_code: 'fr', script_code: 'Latn',
|
||||
language_code: 'en', script_code: 'Latn',
|
||||
primary_flg: true, auto_generated: true)
|
||||
expect(second.tag_names.find_by!(language_code: 'fr', primary_flg: true))
|
||||
.to have_attributes(tag_id: second.id, language_code: 'fr',
|
||||
expect(second.tag_names.find_by!(language_code: 'en', primary_flg: true))
|
||||
.to have_attributes(tag_id: second.id, language_code: 'en',
|
||||
script_code: 'Latn', primary_flg: true,
|
||||
auto_generated: true)
|
||||
expect(TagName.where(language_code: 'fr', primary_flg: true,
|
||||
expect(TagName.where(language_code: 'en', primary_flg: true,
|
||||
tag_id: [first.id, second.id]).count).to eq(2)
|
||||
end
|
||||
|
||||
it 'does not duplicate an existing primary name in the new language' do
|
||||
prepare_french_reference!
|
||||
prepare_english_reference!
|
||||
tag = create(:tag, primary_name: 'already_named')
|
||||
existing = create(:tag_name, name: 'nom_existant', tag:,
|
||||
language_code: 'fr', script_code: 'Latn')
|
||||
existing = create(:tag_name, name: 'existing_english', tag:,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
create_french_locale!
|
||||
create_english_locale!
|
||||
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'fr', primary_flg: true))
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(existing)
|
||||
expect(existing.reload).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'fr', script_code: 'Latn',
|
||||
tag_id: tag.id, language_code: 'en', script_code: 'Latn',
|
||||
primary_flg: true, auto_generated: false)
|
||||
end
|
||||
|
||||
it 'creates a primary name when the language has only an alias' do
|
||||
prepare_french_reference!
|
||||
prepare_english_reference!
|
||||
tag = create(:tag, primary_name: 'alias_only_tag')
|
||||
alias_name = create(:tag_name, :alias, name: 'alias_fr',
|
||||
alias_name = create(:tag_name, :alias, name: 'alias_en',
|
||||
tag:,
|
||||
language_code: 'fr', script_code: 'Latn')
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
create_french_locale!
|
||||
create_english_locale!
|
||||
|
||||
expect(alias_name.reload).to have_attributes(
|
||||
tag_id: tag.id,
|
||||
language_code: 'fr', script_code: 'Latn', primary_flg: false)
|
||||
generated = TagName.find_by!(tag_id: tag.id, language_code: 'fr', primary_flg: true)
|
||||
expect(generated).to have_attributes(tag_id: tag.id, language_code: 'fr',
|
||||
language_code: 'en', script_code: 'Latn', primary_flg: false)
|
||||
generated = TagName.find_by!(tag_id: tag.id, language_code: 'en', primary_flg: true)
|
||||
expect(generated).to have_attributes(tag_id: tag.id, language_code: 'en',
|
||||
script_code: 'Latn', primary_flg: true,
|
||||
auto_generated: true)
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'fr', primary_flg: true).count).to eq(1)
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'en', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
|
||||
it 'rolls back the locale and all generated names when backfill fails' do
|
||||
prepare_english_reference!
|
||||
first = create(:tag, primary_name: 'atomic_first')
|
||||
second = create(:tag, primary_name: 'atomic_second')
|
||||
allow(TagName).to receive(:generate_name).and_call_original
|
||||
allow(TagName).to receive(:generate_name)
|
||||
.with(kind_of(described_class), second, primary_tag_name_for(second, 'ja'))
|
||||
.and_raise('backfill failed')
|
||||
|
||||
expect { create_english_locale! }.to raise_error('backfill failed')
|
||||
|
||||
expect(described_class.exists?(code: 'en')).to be(false)
|
||||
expect(TagName.where(language_code: 'en', tag_id: [first.id, second.id])).to be_empty
|
||||
end
|
||||
|
||||
it 'accepts a stable locale code outside the historical enum values' do
|
||||
prepare_english_reference!
|
||||
|
||||
locale = described_class.create!(
|
||||
code: 'en-x-rspec', language_code: 'en',
|
||||
script_code: 'Latn', name: 'English')
|
||||
|
||||
expect(locale.reload).to have_attributes(code: 'en-x-rspec', language_code: 'en')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Script, type: :model do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'selects locales by script code, not language code' do
|
||||
expect(described_class.find('Latn').locales).to include(locale)
|
||||
expect(described_class.find('Jpan').locales).to include(Locale.nipponese)
|
||||
expect(described_class.find('Jpan').locales).not_to include(locale)
|
||||
end
|
||||
|
||||
it 'accepts supported script codes and rejects unsupported ones' do
|
||||
expect(described_class.new(code: 'Latn', name: 'Latin')).to be_valid
|
||||
expect {
|
||||
described_class.create!(code: 'Zzzz', name: 'Unsupported')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -39,6 +39,18 @@ RSpec.describe TagName, type: :model do
|
||||
expect(tag.reload.tag_name_id).to eq(name.id)
|
||||
end
|
||||
|
||||
it 'creates a complete ordinary tag factory under every active language' do
|
||||
tag = create(:tag, primary_name: 'factory_multilingual')
|
||||
|
||||
Locale.where(deprecated_at: nil).distinct.pluck(:language_code).each do |language_code|
|
||||
expect(TagName.where(tag:, language_code:, primary_flg: true).count).to eq(1)
|
||||
end
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
|
||||
.to contain_exactly(have_attributes(auto_generated: false))
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(have_attributes(auto_generated: true))
|
||||
end
|
||||
|
||||
it 'builds a primary name without persisting the tag or its name' do
|
||||
tag = nil
|
||||
|
||||
@@ -74,10 +86,18 @@ RSpec.describe TagName, type: :model do
|
||||
.to eq(['detached_alias'])
|
||||
end
|
||||
|
||||
it 'does not resolve a detached alias through another detached primary' do
|
||||
create(:tag_name, :alias, name: 'detached_alias')
|
||||
create(:tag_name, name: 'unrelated_detached_primary')
|
||||
|
||||
expect(described_class.canonicalise(Locale.nipponese, ['detached_alias']))
|
||||
.to eq(['detached_alias'])
|
||||
end
|
||||
|
||||
it 'resolves the primary name of the alias language on the same tag' do
|
||||
tag = create(:tag, primary_name: '日本語正本')
|
||||
english_primary = create(:tag_name, tag:, name: 'english_primary',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english_primary = primary_tag_name_for(tag, 'en')
|
||||
english_primary.update!(name: 'english_primary', auto_generated: false)
|
||||
english_alias = create(:tag_name, :alias, tag:, name: 'english_alias',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
representative_id = tag.tag_name_id
|
||||
@@ -115,4 +135,16 @@ RSpec.describe TagName, type: :model do
|
||||
.to eq(['nipponese_canonical'])
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects the generated-name prefix in human input' do
|
||||
human_name = build(:tag_name, name: 'Tag_#999', auto_generated: false)
|
||||
|
||||
expect(human_name).to be_invalid
|
||||
end
|
||||
|
||||
it 'allows the generated-name prefix for system-created names' do
|
||||
generated_name = build(:tag_name, name: 'Tag_#1000', auto_generated: true)
|
||||
|
||||
expect { generated_name.save! }.to change(described_class, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,7 +32,8 @@ RSpec.describe Tag, type: :model do
|
||||
english_name = create(:tag_name, name: 'english_canonical',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = create(:tag, primary_tag_name: english_name)
|
||||
create(:tag_name, tag: english, name: 'english_in_nipponese')
|
||||
primary_tag_name_for(english, 'ja').update!(
|
||||
name: 'english_in_nipponese', auto_generated: false)
|
||||
english_locale = Locale.create!(
|
||||
code: 'en', language_code: 'en', script_code: 'Latn', name: 'English')
|
||||
nipponese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
@@ -252,6 +253,63 @@ RSpec.describe Tag, type: :model do
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
|
||||
it 'creates one primary per language when multiple locales use English' do
|
||||
described_class.find_or_create_by_tag_name!(
|
||||
locale, 'before_second_english_locale', category: :general)
|
||||
Locale.create!(code: 'en-x-rspec', language_code: 'en',
|
||||
script_code: 'Latn', name: 'English')
|
||||
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
locale, 'one_english_primary', category: :general)
|
||||
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true).count).to eq(1)
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
|
||||
it 'rolls back a new tag and requested name if name generation fails' do
|
||||
original_tag_count = described_class.count
|
||||
allow(TagName).to receive(:generate_name).and_raise('generation failed')
|
||||
|
||||
expect {
|
||||
described_class.find_or_create_by_tag_name!(
|
||||
locale, 'atomic_english_name', category: :general)
|
||||
}.to raise_error('generation failed')
|
||||
|
||||
expect(described_class.count).to eq(original_tag_count)
|
||||
expect(TagName.where(language_code: 'en', name: 'atomic_english_name')).to be_empty
|
||||
expect(described_class.joins(:tag_names)
|
||||
.where(tag_names: { name: 'atomic_english_name' })).to be_empty
|
||||
end
|
||||
|
||||
it 'keeps a detached requested name detached if generation fails' do
|
||||
detached = create(:tag_name, :alias, name: 'atomic_detached',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
original_tag_count = described_class.count
|
||||
allow(TagName).to receive(:generate_name).and_raise('generation failed')
|
||||
|
||||
expect {
|
||||
described_class.find_or_create_by_tag_name!(
|
||||
locale, detached.name, category: :general)
|
||||
}.to raise_error('generation failed')
|
||||
|
||||
expect(described_class.count).to eq(original_tag_count)
|
||||
expect(detached.reload).to have_attributes(tag_id: nil, primary_flg: false)
|
||||
end
|
||||
|
||||
it 'promotes a detached alias into the requested primary name' do
|
||||
detached = create(:tag_name, :alias, name: 'detached_english',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
locale, detached.name, category: :general)
|
||||
|
||||
expect(detached.reload).to have_attributes(
|
||||
tag_id: tag.id, primary_flg: true, canonical_id: nil,
|
||||
auto_generated: false)
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(detached)
|
||||
end
|
||||
|
||||
it 'creates separate tag identities for the same name in different languages' do
|
||||
nipponese = described_class.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'same_name', category: :general)
|
||||
@@ -272,8 +330,8 @@ RSpec.describe Tag, type: :model do
|
||||
it 'preserves the V1 representative when looking up another primary language' do
|
||||
tag = create(:tag, primary_name: '日本語代表名')
|
||||
representative_id = tag.tag_name_id
|
||||
english = create(:tag_name, name: 'english_primary', tag:,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = primary_tag_name_for(tag, 'en')
|
||||
english.update!(name: 'english_primary', auto_generated: false)
|
||||
|
||||
found = described_class.find_or_create_by_tag_name!(
|
||||
locale, english.name, category: :general)
|
||||
|
||||
@@ -182,6 +182,51 @@ RSpec.describe 'Locale propagation on write paths', type: :request do
|
||||
end
|
||||
let!(:tag) { create(:tag, primary_tag_name: english_name) }
|
||||
|
||||
it 'marks a human-renamed generated primary as no longer auto-generated' do
|
||||
generated_tag = create(:tag, primary_name: 'rename_generated_source')
|
||||
generated_name = primary_tag_name_for(generated_tag, 'en')
|
||||
expect(generated_name.auto_generated).to be(true)
|
||||
|
||||
patch "/tags/#{ generated_tag.id }", params: {
|
||||
locale: 'en', name: 'human_english_name' }
|
||||
|
||||
expect(response).to have_http_status(:ok), response.body
|
||||
expect(generated_name.reload).to have_attributes(
|
||||
name: 'human_english_name', auto_generated: false)
|
||||
end
|
||||
|
||||
it 'does not steal another tag primary for an alias' do
|
||||
other_tag = create(:tag, primary_name: 'other_nipponese')
|
||||
other_primary = primary_tag_name_for(other_tag, 'en')
|
||||
other_primary.update!(name: 'other_english_primary', auto_generated: false)
|
||||
create(:tag_name, :alias, tag:, name: 'existing_english_alias',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
primary_name_before = primary_tag_name_for(tag, 'en').name
|
||||
aliases_before =
|
||||
tag.tag_names.where(language_code: 'en', primary_flg: false)
|
||||
.order(:name).pluck(:name)
|
||||
category_before = tag.category
|
||||
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
locale: 'en', name: 'renamed_before_alias_collision',
|
||||
category: 'character', deprecated: false,
|
||||
aliases: 'other_english_primary', parent_tags: '' }
|
||||
|
||||
aggregate_failures do
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(other_primary.reload).to have_attributes(
|
||||
tag_id: other_tag.id, language_code: 'en', primary_flg: true)
|
||||
expect(TagName.where(tag: other_tag, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(other_primary)
|
||||
expect(primary_tag_name_for(tag.reload, 'en').name).to eq(primary_name_before)
|
||||
expect(tag.category).to eq(category_before)
|
||||
aliases_after =
|
||||
tag.tag_names.where(language_code: 'en', primary_flg: false)
|
||||
.order(:name).pluck(:name)
|
||||
expect(aliases_after).to eq(aliases_before)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows a rename to a name already used in Nipponese' do
|
||||
nipponese = create(:tag, primary_name: 'rename_target')
|
||||
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする