このコミットが含まれているのは:
@@ -67,20 +67,18 @@ RSpec.describe Locale, type: :model do
|
|||||||
primary_flg: true, auto_generated: false)
|
primary_flg: true, auto_generated: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a primary name when the language has only an alias' do
|
it 'adds the new language primary while preserving existing names and aliases' do
|
||||||
prepare_ainu_reference!
|
tag = create(:tag, primary_name: 'named_tag')
|
||||||
tag = create(:tag, primary_name: 'alias_only_tag')
|
create(:tag_name, :alias, name: 'existing_alias', tag:,
|
||||||
alias_name = create(:tag_name, :alias, name: 'alias_en',
|
language_code: 'ja', script_code: 'Jpan')
|
||||||
tag:,
|
original_names = tag.tag_names.order(:id).map(&:attributes)
|
||||||
language_code: 'ain', script_code: 'Kana')
|
|
||||||
|
|
||||||
create_ainu_locale!
|
create_ainu_locale!
|
||||||
|
|
||||||
expect(alias_name.reload).to have_attributes(
|
expect(tag.tag_names.where(language_code: 'ja').order(:id).map(&:attributes))
|
||||||
tag_id: tag.id,
|
.to eq(original_names)
|
||||||
language_code: 'ain', script_code: 'Kana', primary_flg: false)
|
|
||||||
generated = TagName.find_by!(tag_id: tag.id, language_code: 'ain', primary_flg: true)
|
generated = TagName.find_by!(tag_id: tag.id, language_code: 'ain', primary_flg: true)
|
||||||
expect(generated).to have_attributes(tag_id: tag.id, language_code: 'ain',
|
expect(generated).to have_attributes(name: 'named_tag', tag_id: tag.id, language_code: 'ain',
|
||||||
script_code: 'Kana', primary_flg: true,
|
script_code: 'Kana', primary_flg: true,
|
||||||
auto_generated: true)
|
auto_generated: true)
|
||||||
expect(TagName.where(tag_id: tag.id, language_code: 'ain', primary_flg: true).count).to eq(1)
|
expect(TagName.where(tag_id: tag.id, language_code: 'ain', primary_flg: true).count).to eq(1)
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
|||||||
it 'renames without merging tags or deleting either language identity' do
|
it 'renames without merging tags or deleting either language identity' do
|
||||||
locale
|
locale
|
||||||
nipponese = create(:tag, primary_name: 'foobar')
|
nipponese = create(:tag, primary_name: 'foobar')
|
||||||
|
primary_tag_name_for(nipponese, 'ain').update!(
|
||||||
|
name: 'different_ainu_name', auto_generated: false)
|
||||||
ainu_name = create(:tag_name, name: 'temporary',
|
ainu_name = create(:tag_name, name: 'temporary',
|
||||||
language_code: 'ain', script_code: 'Kana')
|
language_code: 'ain', script_code: 'Kana')
|
||||||
ainu = create(:tag, primary_tag_name: ainu_name)
|
ainu = create(:tag, primary_tag_name: ainu_name)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ RSpec.describe Tag, type: :model do
|
|||||||
it 'canonicalises aliases in the supplied language only' do
|
it 'canonicalises aliases in the supplied language only' do
|
||||||
prepare_ainu_reference!
|
prepare_ainu_reference!
|
||||||
nipponese = create(:tag, primary_name: 'nipponese_canonical')
|
nipponese = create(:tag, primary_name: 'nipponese_canonical')
|
||||||
ainu_name = create(:tag_name, name: 'ainu_canonical',
|
ainu_name = build(:tag_name, tag: nil, name: 'ainu_canonical',
|
||||||
language_code: 'ain', script_code: 'Kana')
|
language_code: 'ain', script_code: 'Kana')
|
||||||
ainu = create(:tag, primary_tag_name: ainu_name)
|
ainu = create(:tag, primary_tag_name: ainu_name)
|
||||||
primary_tag_name_for(ainu, 'ja').update!(
|
primary_tag_name_for(ainu, 'ja').update!(
|
||||||
@@ -265,7 +265,7 @@ RSpec.describe Tag, type: :model do
|
|||||||
expect(tag.category).to eq('character')
|
expect(tag.category).to eq('character')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses a Japanese fallback for an Ainu-origin tag without reverse transcription' do
|
it 'copies an Ainu-origin name to Japanese without reverse transcription' do
|
||||||
locale
|
locale
|
||||||
allow(TagName).to receive(:generate_name).and_call_original
|
allow(TagName).to receive(:generate_name).and_call_original
|
||||||
|
|
||||||
@@ -277,9 +277,9 @@ RSpec.describe Tag, type: :model do
|
|||||||
name: 'ainu_origin', auto_generated: false))
|
name: 'ainu_origin', auto_generated: false))
|
||||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
|
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
|
||||||
.to contain_exactly(have_attributes(
|
.to contain_exactly(have_attributes(
|
||||||
name: "Tag_##{ tag.id }", auto_generated: true))
|
name: 'ainu_origin', auto_generated: true))
|
||||||
expect(TagName).to have_received(:generate_name)
|
expect(TagName).to have_received(:generate_name)
|
||||||
.with(Locale.nipponese, tag, nil)
|
.with(Locale.nipponese, tag, tag.tag_name('ain'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'gives a new tag one primary name for every active language' do
|
it 'gives a new tag one primary name for every active language' do
|
||||||
|
|||||||
@@ -153,11 +153,14 @@ RSpec.describe 'Locale propagation on write paths', type: :request do
|
|||||||
let(:ainu_name) do
|
let(:ainu_name) do
|
||||||
create(:tag_name, name: 'shared_name', language_code: 'ain', script_code: 'Kana')
|
create(:tag_name, name: 'shared_name', language_code: 'ain', script_code: 'Kana')
|
||||||
end
|
end
|
||||||
let(:ainu_tag) { create(:tag, primary_tag_name: ainu_name, category: :material) }
|
let(:ainu_tag) do
|
||||||
|
create(:tag, primary_tag_name: ainu_name, category: :material).tap do |tag|
|
||||||
|
primary_tag_name_for(tag, 'ja').update!(name: 'ainu_in_japanese', auto_generated: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
let(:nipponese_tag) do
|
let(:nipponese_tag) do
|
||||||
create(:tag, primary_name: 'temporary_nipponese', category: :general).tap do |tag|
|
create(:tag, primary_name: 'temporary_nipponese', category: :general).tap do |tag|
|
||||||
# Isolate lookup from the separately tested uniqueness validation.
|
primary_tag_name_for(tag, 'ja').update!(name: 'shared_name')
|
||||||
primary_tag_name_for(tag, 'ja').update_columns(name: 'shared_name')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -291,6 +294,8 @@ RSpec.describe 'Locale propagation on write paths', type: :request do
|
|||||||
it 'allows a rename to a name already used in Nipponese' do
|
it 'allows a rename to a name already used in Nipponese' do
|
||||||
locale
|
locale
|
||||||
nipponese = create(:tag, primary_name: 'rename_target')
|
nipponese = create(:tag, primary_name: 'rename_target')
|
||||||
|
primary_tag_name_for(nipponese, 'ain').update!(
|
||||||
|
name: 'different_ainu_target', auto_generated: false)
|
||||||
|
|
||||||
patch "/tags/#{ tag.id }", params: { locale: 'ain-Kana', name: 'rename_target' }
|
patch "/tags/#{ tag.id }", params: { locale: 'ain-Kana', name: 'rename_target' }
|
||||||
|
|
||||||
|
|||||||
@@ -2323,8 +2323,10 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
'prev' => nil
|
'prev' => nil
|
||||||
)
|
)
|
||||||
expect(latest.fetch('tags')).to include(
|
expect(latest.fetch('tags')).to include(
|
||||||
{ 'name' => 'spec_tag_2', 'type' => 'added' },
|
{ 'name' => 'spec_tag_2', 'restore_name' => 'spec_tag_2',
|
||||||
{ 'name' => 'spec_tag', 'type' => 'removed' }
|
'external' => false, 'type' => 'added' },
|
||||||
|
{ 'name' => 'spec_tag', 'restore_name' => 'spec_tag',
|
||||||
|
'external' => false, 'type' => 'removed' }
|
||||||
)
|
)
|
||||||
expect(latest.fetch('original_created_from')).to eq(
|
expect(latest.fetch('original_created_from')).to eq(
|
||||||
'current' => oc_from.iso8601,
|
'current' => oc_from.iso8601,
|
||||||
@@ -2351,7 +2353,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
'prev' => nil
|
'prev' => nil
|
||||||
)
|
)
|
||||||
expect(first.fetch('tags')).to include(
|
expect(first.fetch('tags')).to include(
|
||||||
{ 'name' => 'spec_tag', 'type' => 'added' }
|
{ 'name' => 'spec_tag', 'restore_name' => 'spec_tag',
|
||||||
|
'external' => false, 'type' => 'added' }
|
||||||
)
|
)
|
||||||
expect(first.fetch('created_at')).to eq(t_v1.iso8601)
|
expect(first.fetch('created_at')).to eq(t_v1.iso8601)
|
||||||
end
|
end
|
||||||
@@ -2435,7 +2438,9 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
expect(response).to have_http_status(:ok)
|
expect(response).to have_http_status(:ok)
|
||||||
expect(json.fetch('count')).to eq(3)
|
expect(json.fetch('count')).to eq(3)
|
||||||
expect(json.fetch('versions').first.fetch('tags')).to include(
|
expect(json.fetch('versions').first.fetch('tags')).to include(
|
||||||
'name' => primary_tag_name_for(tag2, 'ja').name, 'type' => 'context')
|
'name' => primary_tag_name_for(tag2, 'ja').name,
|
||||||
|
'restore_name' => primary_tag_name_for(tag2, 'ja').name,
|
||||||
|
'external' => false, 'type' => 'context')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters versions by tag when the current snapshot includes the tag' do
|
it 'filters versions by tag when the current snapshot includes the tag' do
|
||||||
@@ -2449,7 +2454,8 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
expect(versions[0]['post_id']).to eq(post_record.id)
|
expect(versions[0]['post_id']).to eq(post_record.id)
|
||||||
expect(versions[0]['version_no']).to eq(2)
|
expect(versions[0]['version_no']).to eq(2)
|
||||||
expect(versions[0]['tags']).to include(
|
expect(versions[0]['tags']).to include(
|
||||||
{ 'name' => 'spec_tag_2', 'type' => 'added' }
|
{ 'name' => 'spec_tag_2', 'restore_name' => 'spec_tag_2',
|
||||||
|
'external' => false, 'type' => 'added' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2467,10 +2473,12 @@ RSpec.describe 'Posts API', type: :request do
|
|||||||
first = versions[1]
|
first = versions[1]
|
||||||
|
|
||||||
expect(latest['tags']).to include(
|
expect(latest['tags']).to include(
|
||||||
{ 'name' => 'spec_tag', 'type' => 'removed' }
|
{ 'name' => 'spec_tag', 'restore_name' => 'spec_tag',
|
||||||
|
'external' => false, 'type' => 'removed' }
|
||||||
)
|
)
|
||||||
expect(first['tags']).to include(
|
expect(first['tags']).to include(
|
||||||
{ 'name' => 'spec_tag', 'type' => 'added' }
|
{ 'name' => 'spec_tag', 'restore_name' => 'spec_tag',
|
||||||
|
'external' => false, 'type' => 'added' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ RSpec.describe PostCreatePlan do
|
|||||||
ainu_name = create(:tag_name, name: 'shared_name',
|
ainu_name = create(:tag_name, name: 'shared_name',
|
||||||
language_code: 'ain', script_code: 'Kana')
|
language_code: 'ain', script_code: 'Kana')
|
||||||
ainu = create(:tag, primary_tag_name: ainu_name, category: :character)
|
ainu = create(:tag, primary_tag_name: ainu_name, category: :character)
|
||||||
|
primary_tag_name_for(ainu, 'ja').update!(name: 'ainu_in_japanese', auto_generated: false)
|
||||||
nipponese = create(:tag, primary_name: 'temporary_nipponese', category: :general)
|
nipponese = create(:tag, primary_name: 'temporary_nipponese', category: :general)
|
||||||
# The DB permits this identity; validation has its own contract spec.
|
primary_tag_name_for(nipponese, 'ja').update!(name: 'shared_name')
|
||||||
primary_tag_name_for(nipponese, 'ja').update_columns(name: 'shared_name')
|
|
||||||
ainu_parent_name = create(:tag_name, name: 'ainu_parent',
|
ainu_parent_name = create(:tag_name, name: 'ainu_parent',
|
||||||
language_code: 'ain', script_code: 'Kana')
|
language_code: 'ain', script_code: 'Kana')
|
||||||
ainu_parent = create(:tag, primary_tag_name: ainu_parent_name,
|
ainu_parent = create(:tag, primary_tag_name: ainu_parent_name,
|
||||||
|
|||||||
@@ -141,6 +141,40 @@ RSpec.describe 'Shared resource IDs across MySQL connections', type: :model do
|
|||||||
result(first)
|
result(first)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
[false, true].each do |inner_rollback|
|
||||||
|
[false, true].each do |outer_rollback|
|
||||||
|
it "keeps savepoint=#{ inner_rollback } lock through outer rollback=#{ outer_rollback }" do
|
||||||
|
connection = ApplicationRecord.connection
|
||||||
|
database = connection.pool.db_config.database
|
||||||
|
lock_name = "btrc:ids:#{ Digest::SHA256.hexdigest(database)[0, 16] }:tags"
|
||||||
|
quoted_lock = connection.quote(lock_name)
|
||||||
|
owner = connection.select_value('SELECT CONNECTION_ID()').to_i
|
||||||
|
|
||||||
|
connection.transaction do
|
||||||
|
connection.transaction(requires_new: true) do
|
||||||
|
create_resource(:external)
|
||||||
|
raise ActiveRecord::Rollback if inner_rollback
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(connection.select_value("SELECT IS_USED_LOCK(#{ quoted_lock })").to_i)
|
||||||
|
.to eq(owner)
|
||||||
|
contender = worker do |other|
|
||||||
|
other.select_value("SELECT GET_LOCK(#{ quoted_lock }, 0)").to_i
|
||||||
|
end
|
||||||
|
expect(result(contender)).to eq(0)
|
||||||
|
|
||||||
|
SharedResourceIdAllocator.lock_space!(ExternalTag, 'tags')
|
||||||
|
raise ActiveRecord::Rollback if outer_rollback
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(connection.select_value("SELECT IS_FREE_LOCK(#{ quoted_lock })").to_i)
|
||||||
|
.to eq(1)
|
||||||
|
expect(ExternalTag.exists?(name: "#{ @prefix }_external"))
|
||||||
|
.to eq(!inner_rollback && !outer_rollback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'raises on a real lock acquisition failure without inserting a record' do
|
it 'raises on a real lock acquisition failure without inserting a record' do
|
||||||
first, (_id, _connection_id, lock_name) = hold_creation(:external, rollback: true)
|
first, (_id, _connection_id, lock_name) = hold_creation(:external, rollback: true)
|
||||||
stub_const('SharedResourceIdAllocator::LOCK_TIMEOUT', 0)
|
stub_const('SharedResourceIdAllocator::LOCK_TIMEOUT', 0)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
|
|||||||
let(:record) { build(:external_tag) }
|
let(:record) { build(:external_tag) }
|
||||||
let(:connection) { double('connection') }
|
let(:connection) { double('connection') }
|
||||||
let(:transaction) { double('transaction', open?: true) }
|
let(:transaction) { double('transaction', open?: true) }
|
||||||
let(:callbacks) { { } }
|
let(:callbacks) { [] }
|
||||||
let(:sql) { [] }
|
let(:sql) { [] }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@@ -128,19 +128,24 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
allow(ExternalTag).to receive(:connection).and_return(connection)
|
allow(ExternalTag).to receive(:connection).and_return(connection)
|
||||||
allow(ExternalTag).to receive(:current_transaction).and_return(transaction)
|
allow(connection).to receive(:current_transaction).and_return(transaction)
|
||||||
|
allow(connection).to receive(:add_transaction_record) { callbacks << _1 }
|
||||||
|
allow(connection).to receive(:disconnect!)
|
||||||
allow(ExternalTag).to receive(:uncached).and_yield
|
allow(ExternalTag).to receive(:uncached).and_yield
|
||||||
[:after_commit, :after_rollback].each do |event|
|
|
||||||
allow(transaction).to receive(event) { |&callback| callbacks[event] = callback }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
[:after_commit, :after_rollback].each do |event|
|
def finish_transaction event
|
||||||
|
allow(transaction).to receive(:open?).and_return(false)
|
||||||
|
callbacks.shift.public_send(event)
|
||||||
|
allow(transaction).to receive(:open?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
[:committed!, :rolledback!].each do |event|
|
||||||
it "holds the shared lock until #{ event }" do
|
it "holds the shared lock until #{ event }" do
|
||||||
described_class.assign!(record)
|
described_class.assign!(record)
|
||||||
expect(record.id).to eq(9)
|
expect(record.id).to eq(9)
|
||||||
expect(sql.grep(/RELEASE_LOCK/)).to be_empty
|
expect(sql.grep(/RELEASE_LOCK/)).to be_empty
|
||||||
callbacks.fetch(event).call
|
finish_transaction(event)
|
||||||
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
|
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -155,7 +160,7 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
|
|||||||
it 'releases a successfully acquired lock when allocation fails and rolls back' do
|
it 'releases a successfully acquired lock when allocation fails and rolls back' do
|
||||||
allow(connection).to receive(:select_all).and_raise('query failed')
|
allow(connection).to receive(:select_all).and_raise('query failed')
|
||||||
expect { described_class.assign!(record) }.to raise_error(RuntimeError, 'query failed')
|
expect { described_class.assign!(record) }.to raise_error(RuntimeError, 'query failed')
|
||||||
callbacks.fetch(:after_rollback).call
|
finish_transaction(:rolledback!)
|
||||||
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
|
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -164,18 +169,17 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
|
|||||||
described_class.assign!(record)
|
described_class.assign!(record)
|
||||||
expect(record.id).to eq(50)
|
expect(record.id).to eq(50)
|
||||||
expect(sql.grep(/FOR UPDATE/)).to be_empty
|
expect(sql.grep(/FOR UPDATE/)).to be_empty
|
||||||
callbacks.fetch(:after_commit).call
|
finish_transaction(:committed!)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses the same named lock within each space and different locks between spaces' do
|
it 'uses the same named lock within each space and different locks between spaces' do
|
||||||
locks = [Tag, ExternalTag, TagDescription, TagGroup, TagDescriptionVersion, TagGroupVersion].map do |model|
|
locks = [Tag, ExternalTag, TagDescription, TagGroup, TagDescriptionVersion, TagGroupVersion].map do |model|
|
||||||
allow(model).to receive(:connection).and_return(connection)
|
allow(model).to receive(:connection).and_return(connection)
|
||||||
allow(model).to receive(:current_transaction).and_return(transaction)
|
|
||||||
allow(model).to receive(:uncached).and_yield
|
allow(model).to receive(:uncached).and_yield
|
||||||
record = model.new(id: 50)
|
record = model.new(id: 50)
|
||||||
described_class.assign!(record)
|
described_class.assign!(record)
|
||||||
statement = sql.grep(/GET_LOCK/).last
|
statement = sql.grep(/GET_LOCK/).last
|
||||||
callbacks.fetch(:after_commit).call
|
finish_transaction(:committed!)
|
||||||
statement
|
statement
|
||||||
end
|
end
|
||||||
expect(locks[0]).to eq(locks[1])
|
expect(locks[0]).to eq(locks[1])
|
||||||
@@ -187,7 +191,21 @@ RSpec.describe SharedResourceIdAllocator, 'lock lifecycle' do
|
|||||||
it 'reports lock release failures' do
|
it 'reports lock release failures' do
|
||||||
described_class.assign!(record)
|
described_class.assign!(record)
|
||||||
allow(connection).to receive(:select_value).with(/RELEASE_LOCK/).and_return(nil)
|
allow(connection).to receive(:select_value).with(/RELEASE_LOCK/).and_return(nil)
|
||||||
expect { callbacks.fetch(:after_commit).call }
|
expect { finish_transaction(:committed!) }
|
||||||
.to raise_error(ActiveRecord::ActiveRecordError, /lock release failed/)
|
.to raise_error(ActiveRecord::ActiveRecordError, /lock release failed/)
|
||||||
|
expect(connection).to have_received(:disconnect!)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'keeps the lock and transfers cleanup when a savepoint rolls back' do
|
||||||
|
described_class.assign!(record)
|
||||||
|
callbacks.shift.rolledback!
|
||||||
|
|
||||||
|
expect(callbacks.size).to eq(1)
|
||||||
|
expect(sql.grep(/RELEASE_LOCK/)).to be_empty
|
||||||
|
described_class.assign!(record)
|
||||||
|
expect(sql.grep(/GET_LOCK/).size).to eq(1)
|
||||||
|
|
||||||
|
finish_transaction(:committed!)
|
||||||
|
expect(sql.grep(/RELEASE_LOCK/).size).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ RSpec.describe TagNameTranscription do
|
|||||||
'ガギグゲゴダヂヅデド' => 'カキクケコタチチュテト',
|
'ガギグゲゴダヂヅデド' => 'カキクケコタチチュテト',
|
||||||
'バビブベボパピプペポ' => 'パピプペポパピプペポ',
|
'バビブベボパピプペポ' => 'パピプペポパピプペポ',
|
||||||
'ザジズゼゾ' => 'チャチチュチェチョ',
|
'ザジズゼゾ' => 'チャチチュチェチョ',
|
||||||
'ジャジュジョ' => 'チヤチユチヨ',
|
'ジャジュジョ' => 'チャチュチョ',
|
||||||
'アㇰアㇱアッアンアㇺアㇷ゚' => 'アㇰアㇱアッアンアㇺアㇷ゚',
|
'アㇰアㇱアッアンアㇺアㇷ゚' => 'アㇰアㇱアッアンアㇺアㇷ゚',
|
||||||
'アㇻイㇼウㇽエㇾオㇿ' => 'アㇻイㇼウㇽエㇾオㇿ',
|
'アㇻイㇼウㇽエㇾオㇿ' => 'アㇻイㇼウㇽエㇾオㇿ',
|
||||||
'漢Aあ' => 'カンAア' }.each do |source, expected|
|
'漢Aあ' => 'カンAア' }.each do |source, expected|
|
||||||
@@ -26,9 +26,12 @@ RSpec.describe TagNameTranscription do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
['😀', 'ヴ', 'ティ', 'ㇵ', '魑', '𠮷', 'ー', '々'].each do |source|
|
{ '😀' => '', 'ヴ' => '', 'ティ' => 'テ', 'ㇵ' => '',
|
||||||
it "rejects unsupported input #{ source }" do
|
'魑' => '', '𠮷' => '', 'ー' => '', '々' => '',
|
||||||
expect { described_class.ainu(source) }.to raise_error(described_class::Unconvertible)
|
'ア😀😀イ' => 'ア_イ', '😀アイ😀' => 'アイ',
|
||||||
|
'_😀_' => '__', 'ア_😀_イ' => 'ア___イ', '漢魑字' => 'カン_チ' }.each do |source, expected|
|
||||||
|
it "preserves convertible portions of #{ source }" do
|
||||||
|
expect(described_class.ainu(source)).to eq(expected)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ RSpec.describe TagVersionRecorder do
|
|||||||
it 'snapshots future languages together without a request locale argument' do
|
it 'snapshots future languages together without a request locale argument' do
|
||||||
prepare_ainu_names!
|
prepare_ainu_names!
|
||||||
|
|
||||||
version = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
version = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||||
|
|
||||||
expect(version.names).to eq(
|
expect(version.names).to eq(
|
||||||
'ja' => nipponese_snapshot,
|
'ja' => nipponese_snapshot,
|
||||||
@@ -75,12 +75,12 @@ RSpec.describe TagVersionRecorder do
|
|||||||
|
|
||||||
it 'creates a version for an Ainu-only change and preserves prior names' do
|
it 'creates a version for an Ainu-only change and preserves prior names' do
|
||||||
prepare_ainu_names!
|
prepare_ainu_names!
|
||||||
original = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
original = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||||
primary_tag_name_for(tag, 'ain').update!(name: 'Renamed')
|
primary_tag_name_for(tag, 'ain').update!(name: 'Renamed')
|
||||||
|
|
||||||
updated = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
updated = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||||
|
|
||||||
expect(updated.version_no).to eq(2)
|
expect(updated.version_no).to eq(original.version_no + 1)
|
||||||
expect(original.reload.names.fetch('ain')).to include('name' => 'Ainu name')
|
expect(original.reload.names.fetch('ain')).to include('name' => 'Ainu name')
|
||||||
expect(updated.names.fetch('ain')).to include('name' => 'Renamed')
|
expect(updated.names.fetch('ain')).to include('name' => 'Renamed')
|
||||||
expect(updated.names.fetch('ja')).to eq(nipponese_snapshot)
|
expect(updated.names.fetch('ja')).to eq(nipponese_snapshot)
|
||||||
@@ -88,11 +88,11 @@ RSpec.describe TagVersionRecorder do
|
|||||||
|
|
||||||
it 'does not create a version for an unchanged multilingual snapshot' do
|
it 'does not create a version for an unchanged multilingual snapshot' do
|
||||||
prepare_ainu_names!
|
prepare_ainu_names!
|
||||||
described_class.record!(tag:, event_type: :create, created_by_user: user)
|
original = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||||
|
|
||||||
unchanged = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
unchanged = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||||
|
|
||||||
expect(unchanged.version_no).to eq(1)
|
expect(unchanged.version_no).to eq(original.version_no)
|
||||||
expect(tag.reload.tag_versions.count).to eq(1)
|
expect(tag.reload.tag_versions.count).to eq(original.version_no)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import { HelmetProvider } from 'react-helmet-async'
|
import { HelmetProvider } from 'react-helmet-async'
|
||||||
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import ErrorScreen from '@/components/ErrorScreen'
|
import ErrorScreen from '@/components/ErrorScreen'
|
||||||
@@ -12,9 +13,9 @@ describe ('ErrorScreen', () => {
|
|||||||
[503, '鯖死んでるよ(泣)'],
|
[503, '鯖死んでるよ(泣)'],
|
||||||
]) ('renders status %s', (status, message) => {
|
]) ('renders status %s', (status, message) => {
|
||||||
render (
|
render (
|
||||||
<HelmetProvider>
|
<MemoryRouter><HelmetProvider>
|
||||||
<ErrorScreen status={status}/>
|
<ErrorScreen status={status}/>
|
||||||
</HelmetProvider>,
|
</HelmetProvider></MemoryRouter>,
|
||||||
)
|
)
|
||||||
|
|
||||||
expect (screen.getByText (String (status))).toBeInTheDocument ()
|
expect (screen.getByText (String (status))).toBeInTheDocument ()
|
||||||
@@ -24,9 +25,9 @@ describe ('ErrorScreen', () => {
|
|||||||
|
|
||||||
it ('throws for unsupported statuses', () => {
|
it ('throws for unsupported statuses', () => {
|
||||||
expect (() => render (
|
expect (() => render (
|
||||||
<HelmetProvider>
|
<MemoryRouter><HelmetProvider>
|
||||||
<ErrorScreen status={418}/>
|
<ErrorScreen status={418}/>
|
||||||
</HelmetProvider>,
|
</HelmetProvider></MemoryRouter>,
|
||||||
)).toThrow ()
|
)).toThrow ()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import PostEditForm from '@/components/PostEditForm'
|
import PostEditForm from '@/components/PostEditForm'
|
||||||
import { buildPost, buildTag } from '@/test/factories'
|
import { buildPost, buildTag } from '@/test/factories'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
const postsApi = vi.hoisted (() => ({
|
const postsApi = vi.hoisted (() => ({
|
||||||
updatePost: vi.fn (),
|
updatePost: vi.fn (),
|
||||||
@@ -19,7 +21,8 @@ const toastApi = vi.hoisted (() => ({
|
|||||||
vi.mock ('@/lib/posts', () => postsApi)
|
vi.mock ('@/lib/posts', () => postsApi)
|
||||||
vi.mock ('@/lib/api', () => api)
|
vi.mock ('@/lib/api', () => api)
|
||||||
vi.mock ('@/components/ui/use-toast', () => toastApi)
|
vi.mock ('@/components/ui/use-toast', () => toastApi)
|
||||||
vi.mock ('@/lib/dialogues/useDialogue', () => ({
|
vi.mock ('@/lib/dialogues/useDialogue', async importOriginal => ({
|
||||||
|
...await importOriginal<typeof import ('@/lib/dialogues/useDialogue')> (),
|
||||||
default: () => ({
|
default: () => ({
|
||||||
choice: vi.fn (),
|
choice: vi.fn (),
|
||||||
}),
|
}),
|
||||||
@@ -60,11 +63,12 @@ describe ('PostEditForm', () => {
|
|||||||
parentPostIds: '3 4',
|
parentPostIds: '3 4',
|
||||||
tags: 'general-tag',
|
tags: 'general-tag',
|
||||||
}),
|
}),
|
||||||
{ baseVersionNo: 4 },
|
{ baseVersionNo: 4 }, 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (onSave).toHaveBeenCalledWith (expect.objectContaining ({ versionNo: 5 }))
|
expect (onSave).toHaveBeenCalledWith (expect.objectContaining ({ versionNo: 5 }))
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ description: '更新しました.' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].description)).toBe ('更新しました.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('preserves duration while the video tag is temporarily removed', () => {
|
it ('preserves duration while the video tag is temporarily removed', () => {
|
||||||
@@ -78,7 +82,7 @@ describe ('PostEditForm', () => {
|
|||||||
|
|
||||||
render (<PostEditForm post={post} onSave={vi.fn ()}/>)
|
render (<PostEditForm post={post} onSave={vi.fn ()}/>)
|
||||||
|
|
||||||
expect (screen.getByText ('動画時間').parentElement?.querySelector ('input'))
|
expect (screen.getByDisplayValue ('180.5'))
|
||||||
.toHaveValue ('180.5')
|
.toHaveValue ('180.5')
|
||||||
|
|
||||||
const tags = screen.getAllByRole ('textbox')[2]
|
const tags = screen.getAllByRole ('textbox')[2]
|
||||||
@@ -88,7 +92,7 @@ describe ('PostEditForm', () => {
|
|||||||
fireEvent.change (tags, {
|
fireEvent.change (tags, {
|
||||||
target: { value: '動画 general-tag' },
|
target: { value: '動画 general-tag' },
|
||||||
})
|
})
|
||||||
expect (screen.getByText ('動画時間').parentElement?.querySelector ('input'))
|
expect (screen.getByDisplayValue ('180.5'))
|
||||||
.toHaveValue ('180.5')
|
.toHaveValue ('180.5')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import PostEmbed from '@/components/PostEmbed'
|
import PostEmbed from '@/components/PostEmbed'
|
||||||
import { buildPost } from '@/test/factories'
|
import { buildPost } from '@/test/factories'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
const dialogue = vi.hoisted (() => ({
|
const dialogue = vi.hoisted (() => ({
|
||||||
confirm: vi.fn (),
|
confirm: vi.fn (),
|
||||||
@@ -12,8 +13,9 @@ const nicoViewer = vi.hoisted (() => ({
|
|||||||
props: vi.fn (),
|
props: vi.fn (),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock ('@/components/dialogues/DialogueProvider', () => ({
|
vi.mock ('@/lib/dialogues/useDialogue', async importOriginal => ({
|
||||||
useDialogue: () => dialogue,
|
...await importOriginal<typeof import ('@/lib/dialogues/useDialogue')> (),
|
||||||
|
default: () => dialogue,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock ('@/components/NicoViewer', () => ({
|
vi.mock ('@/components/NicoViewer', () => ({
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ describe ('PostFormTagsArea', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith (
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
'/tags/autocomplete',
|
'/tags/autocomplete',
|
||||||
{ params: { q: '虹', nico: '0' } },
|
{ locale: 'ja', params: { q: '虹', nico: '0' },
|
||||||
|
signal: expect.any (AbortSignal) },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (setTags).toHaveBeenCalledWith ('虹夏')
|
expect (setTags).toHaveBeenCalledWith ('虹夏')
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ describe ('PostList', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: 'First' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: 'First' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts/1',
|
'/ja/posts/1',
|
||||||
)
|
)
|
||||||
expect (
|
expect (
|
||||||
screen.getByRole ('link', { name: 'https://example.com/second' }),
|
screen.getByRole ('link', { name: 'https://example.com/second' }),
|
||||||
).toHaveAttribute ('href', '/posts/2')
|
).toHaveAttribute ('href', '/ja/posts/2')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('calls the optional click handler', () => {
|
it ('calls the optional click handler', () => {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, screen } from '@testing-library/react'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField'
|
import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
describe ('PostOriginalCreatedTimeField', () => {
|
describe ('PostOriginalCreatedTimeField', () => {
|
||||||
it ('updates from and before values', () => {
|
it ('updates from and before values', () => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe ('SortHeader', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: 'タイトル ▲' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: 'タイトル ▲' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts?tags=x&page=1&order=title%3Adesc',
|
'/ja/posts?tags=x&page=1&order=title%3Adesc',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ describe ('SortHeader', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: '更新' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: '更新' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts?page=1&order=updated_at%3Adesc',
|
'/ja/posts?page=1&order=updated_at%3Adesc',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe ('TagLink', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: '虹 夏' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: '虹 夏' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts?tags=%E8%99%B9+%E5%A4%8F',
|
'/ja/posts?tags=%E8%99%B9+%E5%A4%8F',
|
||||||
)
|
)
|
||||||
expect (screen.getByText ('4')).toBeInTheDocument ()
|
expect (screen.getByText ('4')).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
@@ -40,7 +40,7 @@ describe ('TagLink', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: '?' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: '?' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/wiki/a%2Fb',
|
'/ja/wiki/a%2Fb',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe ('TopNavUser', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: '山田' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: '山田' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/users/settings',
|
'/ja/users/settings',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ describe ('Pagination', () => {
|
|||||||
|
|
||||||
expect (screen.getByLabelText ('前のページ')).toHaveAttribute (
|
expect (screen.getByLabelText ('前のページ')).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts?tags=abc&page=2',
|
'/ja/posts?tags=abc&page=2',
|
||||||
)
|
)
|
||||||
expect (screen.getByLabelText ('次のページ')).toHaveAttribute (
|
expect (screen.getByLabelText ('次のページ')).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts?tags=abc&page=4',
|
'/ja/posts?tags=abc&page=4',
|
||||||
)
|
)
|
||||||
expect (screen.getByText ('3')).toHaveAttribute ('aria-current', 'page')
|
expect (screen.getByText ('3')).toHaveAttribute ('aria-current', 'page')
|
||||||
})
|
})
|
||||||
@@ -33,6 +33,6 @@ describe ('Pagination', () => {
|
|||||||
|
|
||||||
rerender (<Pagination page={1} totalPages={2}/>)
|
rerender (<Pagination page={1} totalPages={2}/>)
|
||||||
|
|
||||||
expect (screen.getByLabelText ('次のページ')).toHaveAttribute ('href', '/tags?page=2')
|
expect (screen.getByLabelText ('次のページ')).toHaveAttribute ('href', '/ja/tags?page=2')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import TagInput from '@/components/common/TagInput'
|
import TagInput from '@/components/common/TagInput'
|
||||||
import { buildTag } from '@/test/factories'
|
import { buildTag } from '@/test/factories'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
const api = vi.hoisted (() => ({
|
const api = vi.hoisted (() => ({
|
||||||
apiGet: vi.fn (),
|
apiGet: vi.fn (),
|
||||||
@@ -26,7 +27,8 @@ describe ('TagInput', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith (
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
'/tags/autocomplete',
|
'/tags/autocomplete',
|
||||||
{ params: { q: '虹夏', nico: '0' } },
|
{ locale: 'ja', params: { q: '虹夏', nico: '0' },
|
||||||
|
signal: expect.any (AbortSignal) },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (setValue).toHaveBeenCalledWith ('ぼっち 虹夏')
|
expect (setValue).toHaveBeenCalledWith ('ぼっち 虹夏')
|
||||||
@@ -53,7 +55,8 @@ describe ('TagInput', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith (
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
'/tags/autocomplete',
|
'/tags/autocomplete',
|
||||||
{ params: { q: '虹夏', nico: '1' } },
|
{ locale: 'ja', params: { q: '虹夏', nico: '1' },
|
||||||
|
signal: expect.any (AbortSignal) },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect } from 'react'
|
||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
|
|
||||||
import DialogueProvider from '@/components/dialogues/DialogueProvider'
|
import DialogueProvider from '@/components/dialogues/DialogueProvider'
|
||||||
import useDialogue from '@/lib/dialogues/useDialogue'
|
import useDialogue from '@/lib/dialogues/useDialogue'
|
||||||
@@ -49,7 +50,7 @@ describe ('DialogueProvider', () => {
|
|||||||
</button>)
|
</button>)
|
||||||
}
|
}
|
||||||
|
|
||||||
render (<DialogueProvider><Launcher/></DialogueProvider>)
|
render (<MemoryRouter><DialogueProvider><Launcher/></DialogueProvider></MemoryRouter>)
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '開く' }))
|
fireEvent.click (screen.getByRole ('button', { name: '開く' }))
|
||||||
|
|
||||||
expect (screen.getByText ('一件目')).toBeInTheDocument ()
|
expect (screen.getByText ('一件目')).toBeInTheDocument ()
|
||||||
@@ -73,7 +74,7 @@ describe ('DialogueProvider', () => {
|
|||||||
</button>)
|
</button>)
|
||||||
}
|
}
|
||||||
|
|
||||||
render (<DialogueProvider><Launcher/></DialogueProvider>)
|
render (<MemoryRouter><DialogueProvider><Launcher/></DialogueProvider></MemoryRouter>)
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '開く' }))
|
fireEvent.click (screen.getByRole ('button', { name: '開く' }))
|
||||||
|
|
||||||
const dialogue = screen.getByRole ('dialog')
|
const dialogue = screen.getByRole ('dialog')
|
||||||
@@ -101,7 +102,7 @@ describe ('DialogueProvider', () => {
|
|||||||
</button>)
|
</button>)
|
||||||
}
|
}
|
||||||
|
|
||||||
render (<DialogueProvider><Launcher/></DialogueProvider>)
|
render (<MemoryRouter><DialogueProvider><Launcher/></DialogueProvider></MemoryRouter>)
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '開く' }))
|
fireEvent.click (screen.getByRole ('button', { name: '開く' }))
|
||||||
const action = await screen.findByRole ('button', { name: '左操作' })
|
const action = await screen.findByRole ('button', { name: '左操作' })
|
||||||
fireEvent.click (action)
|
fireEvent.click (action)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { HelmetProvider } from 'react-helmet-async'
|
import { HelmetProvider } from 'react-helmet-async'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import SidebarComponent from '@/components/layout/SidebarComponent'
|
import SidebarComponent from '@/components/layout/SidebarComponent'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
|
|
||||||
const sidebarWidth = (element: Element | null): string =>
|
const sidebarWidth = (element: Element | null): string =>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { screen } from '@testing-library/react'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { buildPostImportRow } from '@/test/postImportFactories'
|
import { buildPostImportRow } from '@/test/postImportFactories'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
import type { DialogueFormControls } from '@/lib/dialogues/useDialogue'
|
import type { DialogueFormControls } from '@/lib/dialogues/useDialogue'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, screen } from '@testing-library/react'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
|
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
describe ('PostThumbnailPreview', () => {
|
describe ('PostThumbnailPreview', () => {
|
||||||
it ('keeps an existing blob preview URL unchanged for normal post forms', () => {
|
it ('keeps an existing blob preview URL unchanged for normal post forms', () => {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
import { act, screen, waitFor } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import PostImportRowForm from '@/components/posts/import/PostImportRowForm'
|
import PostImportRowForm from '@/components/posts/import/PostImportRowForm'
|
||||||
import { buildPostImportRow } from '@/test/postImportFactories'
|
import { buildPostImportRow } from '@/test/postImportFactories'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
|
|
||||||
import type { DialogueFormAction, DialogueFormControls } from '@/lib/dialogues/useDialogue'
|
import type { DialogueFormAction, DialogueFormControls } from '@/lib/dialogues/useDialogue'
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ describe ('PostImportRowForm', () => {
|
|||||||
const titleInput = screen.getByDisplayValue ('manual title')
|
const titleInput = screen.getByDisplayValue ('manual title')
|
||||||
|
|
||||||
await waitFor (() => expect (actions.at (-1)?.length).toBe (2))
|
await waitFor (() => expect (actions.at (-1)?.length).toBe (2))
|
||||||
const reset = actions.at (-1)?.find (action => action.label === '変更をリセット')
|
const reset = actions.at (-1)?.find (action => textContentOf (action.label) === '変更をリセット')
|
||||||
expect (reset).toMatchObject ({ placement: 'start', variant: 'danger', disabled: false })
|
expect (reset).toMatchObject ({ placement: 'start', variant: 'danger', disabled: false })
|
||||||
await act (async () => {
|
await act (async () => {
|
||||||
await reset?.onSelect ()
|
await reset?.onSelect ()
|
||||||
@@ -47,7 +49,7 @@ describe ('PostImportRowForm', () => {
|
|||||||
expect (titleInput).toHaveValue ('')
|
expect (titleInput).toHaveValue ('')
|
||||||
expect (onSave).not.toHaveBeenCalled ()
|
expect (onSave).not.toHaveBeenCalled ()
|
||||||
|
|
||||||
const save = actions.at (-1)?.find (action => action.label === '編輯内容を保存')
|
const save = actions.at (-1)?.find (action => textContentOf (action.label) === '編輯内容を保存')
|
||||||
await act (async () => {
|
await act (async () => {
|
||||||
await save?.onSelect ()
|
await save?.onSelect ()
|
||||||
})
|
})
|
||||||
@@ -76,7 +78,7 @@ describe ('PostImportRowForm', () => {
|
|||||||
await waitFor (() => expect (actions.length).toBe (2))
|
await waitFor (() => expect (actions.length).toBe (2))
|
||||||
|
|
||||||
await act (async () => {
|
await act (async () => {
|
||||||
await actions.find (action => action.label === '変更をリセット')?.onSelect ()
|
await actions.find (action => textContentOf (action.label) === '変更をリセット')?.onSelect ()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect (screen.getByDisplayValue ('manual title')).toBeInTheDocument ()
|
expect (screen.getByDisplayValue ('manual title')).toBeInTheDocument ()
|
||||||
@@ -124,7 +126,7 @@ describe ('PostImportRowForm', () => {
|
|||||||
await waitFor (() => expect (actions.length).toBe (2))
|
await waitFor (() => expect (actions.length).toBe (2))
|
||||||
|
|
||||||
await act (async () => {
|
await act (async () => {
|
||||||
void actions.find (action => action.label === '編輯内容を保存')?.onSelect ()
|
void actions.find (action => textContentOf (action.label) === '編輯内容を保存')?.onSelect ()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect (onSave).toHaveBeenCalledWith ({
|
expect (onSave).toHaveBeenCalledWith ({
|
||||||
@@ -171,7 +173,7 @@ describe ('PostImportRowForm', () => {
|
|||||||
expect (screen.getByDisplayValue ('tag1')).toBeInTheDocument ()
|
expect (screen.getByDisplayValue ('tag1')).toBeInTheDocument ()
|
||||||
|
|
||||||
await act (async () => {
|
await act (async () => {
|
||||||
await actions.find (action => action.label === '編輯内容を保存')?.onSelect ()
|
await actions.find (action => textContentOf (action.label) === '編輯内容を保存')?.onSelect ()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect (onSave).toHaveBeenCalledWith ({
|
expect (onSave).toHaveBeenCalledWith ({
|
||||||
@@ -218,7 +220,8 @@ describe ('PostImportRowForm', () => {
|
|||||||
onSave={vi.fn ()}/>)
|
onSave={vi.fn ()}/>)
|
||||||
await waitFor (() => expect (actions.length).toBe (2))
|
await waitFor (() => expect (actions.length).toBe (2))
|
||||||
|
|
||||||
expect (actions.find (action => action.label === '変更をリセット')?.disabled).toBe (false)
|
const reset = actions.find (action => textContentOf (action.label) === '変更をリセット')
|
||||||
|
expect (reset?.disabled).toBe (false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it (
|
it (
|
||||||
@@ -249,7 +252,8 @@ describe ('PostImportRowForm', () => {
|
|||||||
|
|
||||||
let savePromise: Promise<boolean | void> | undefined
|
let savePromise: Promise<boolean | void> | undefined
|
||||||
await act (async () => {
|
await act (async () => {
|
||||||
savePromise = actions.find (action => action.label === '編輯内容を保存')?.onSelect ()
|
const save = actions.find (action => textContentOf (action.label) === '編輯内容を保存')
|
||||||
|
savePromise = save?.onSelect ()
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, screen } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import PostImportThumbnailPreview from '@/components/posts/import/PostImportThumbnailPreview'
|
import PostImportThumbnailPreview from '@/components/posts/import/PostImportThumbnailPreview'
|
||||||
|
import { renderWithProviders as render } from '@/test/render'
|
||||||
|
|
||||||
describe ('PostImportThumbnailPreview', () => {
|
describe ('PostImportThumbnailPreview', () => {
|
||||||
beforeEach (() => {
|
beforeEach (() => {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe('Gekanator API functions', () => {
|
|||||||
mockedApiGet.mockResolvedValueOnce({ posts })
|
mockedApiGet.mockResolvedValueOnce({ posts })
|
||||||
|
|
||||||
await expect(fetchGekanatorPosts()).resolves.toEqual(posts)
|
await expect(fetchGekanatorPosts()).resolves.toEqual(posts)
|
||||||
expect(mockedApiGet).toHaveBeenCalledWith('/gekanator/posts')
|
expect(mockedApiGet).toHaveBeenCalledWith('/gekanator/posts', { locale: 'ja' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns questions from the Gekanator questions endpoint', async () => {
|
it('returns questions from the Gekanator questions endpoint', async () => {
|
||||||
@@ -60,7 +60,7 @@ describe('Gekanator API functions', () => {
|
|||||||
mockedApiGet.mockResolvedValueOnce({ questions })
|
mockedApiGet.mockResolvedValueOnce({ questions })
|
||||||
|
|
||||||
await expect(fetchGekanatorQuestions()).resolves.toEqual(questions)
|
await expect(fetchGekanatorQuestions()).resolves.toEqual(questions)
|
||||||
expect(mockedApiGet).toHaveBeenCalledWith('/gekanator/questions')
|
expect(mockedApiGet).toHaveBeenCalledWith('/gekanator/questions', { locale: 'ja' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -458,7 +458,7 @@ describe('Gekanator API writers', () => {
|
|||||||
original_answer: 'partial',
|
original_answer: 'partial',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
}, { locale: 'ja' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sends question suggestions using snake_case request keys', async () => {
|
it('sends question suggestions using snake_case request keys', async () => {
|
||||||
@@ -482,7 +482,7 @@ describe('Gekanator API writers', () => {
|
|||||||
gekanator_game_id: 100,
|
gekanator_game_id: 100,
|
||||||
question_text: '喜多ちゃんが泣いてる?',
|
question_text: '喜多ちゃんが泣いてる?',
|
||||||
answer: 'yes',
|
answer: 'yes',
|
||||||
})
|
}, { locale: 'ja' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sends extra question answers using snake_case request keys', async () => {
|
it('sends extra question answers using snake_case request keys', async () => {
|
||||||
@@ -517,7 +517,7 @@ describe('Gekanator API writers', () => {
|
|||||||
answer: 'probably_no',
|
answer: 'probably_no',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
}, { locale: 'ja' },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe ('Help prefetch integration', () => {
|
|||||||
<PrefetchLink to="/help/guide">Help</PrefetchLink>
|
<PrefetchLink to="/help/guide">Help</PrefetchLink>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={null}/>
|
<Route path="/" element={null}/>
|
||||||
<Route path="/help/:identifier" element={<HelpDetailPage/>}/>
|
<Route path="/ja/help/:identifier" element={<HelpDetailPage/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>)
|
</>)
|
||||||
const loader = helpFixture.moduleLoads['../../help/guide/editions/ja/2026-10-02.10.mdx']
|
const loader = helpFixture.moduleLoads['../../help/guide/editions/ja/2026-10-02.10.mdx']
|
||||||
@@ -84,14 +84,14 @@ describe ('Help prefetch integration', () => {
|
|||||||
<PrefetchLink to={path}>diff</PrefetchLink>
|
<PrefetchLink to={path}>diff</PrefetchLink>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={null}/>
|
<Route path="/" element={null}/>
|
||||||
<Route path="/help/:identifier/diff" element={<HelpDiffPage/>}/>
|
<Route path="/ja/help/:identifier/diff" element={<HelpDiffPage/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>)
|
</>)
|
||||||
await Promise.all ([prefetchers.prefetchForURL (queryClient, path),
|
await Promise.all ([prefetchers.prefetchForURL (queryClient, path),
|
||||||
prefetchers.prefetchForURL (queryClient, path)])
|
prefetchers.prefetchForURL (queryClient, path)])
|
||||||
fireEvent.click (screen.getByRole ('link', { name: 'diff' }))
|
fireEvent.click (screen.getByRole ('link', { name: 'diff' }))
|
||||||
expect (await screen.findByRole ('link', { name: '旧ホーム (2026-10-01)' }))
|
expect (await screen.findByRole ('link', { name: '旧ホーム (2026-10-01)' }))
|
||||||
.toHaveAttribute ('href', '/help/guide?edition=2026-10-01')
|
.toHaveAttribute ('href', '/ja/help/guide?edition=2026-10-01')
|
||||||
expect (screen.getByText ('+ # ホーム第十版')).toBeInTheDocument ()
|
expect (screen.getByText ('+ # ホーム第十版')).toBeInTheDocument ()
|
||||||
Object.entries (helpFixture.raw).forEach (([name, load]) => {
|
Object.entries (helpFixture.raw).forEach (([name, load]) => {
|
||||||
const expected = /\/ja\/(2026-10-01|2026-10-02\.10)\.mdx$/.test (name) ? 1 : 0
|
const expected = /\/ja\/(2026-10-01|2026-10-02\.10)\.mdx$/.test (name) ? 1 : 0
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const wikiApi = vi.hoisted (() => ({
|
|||||||
fetchWikiPage: vi.fn (),
|
fetchWikiPage: vi.fn (),
|
||||||
fetchWikiPageByTitle: vi.fn (),
|
fetchWikiPageByTitle: vi.fn (),
|
||||||
fetchWikiPages: vi.fn (),
|
fetchWikiPages: vi.fn (),
|
||||||
|
fetchWikiPageByReference: vi.fn (),
|
||||||
|
fetchWikiAutolinkCandidates: vi.fn (),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock ('@/lib/posts', () => postsApi)
|
vi.mock ('@/lib/posts', () => postsApi)
|
||||||
@@ -45,12 +47,14 @@ describe ('prefetchForURL', () => {
|
|||||||
wikiApi.fetchWikiPages.mockResolvedValue ([])
|
wikiApi.fetchWikiPages.mockResolvedValue ([])
|
||||||
wikiApi.fetchWikiPage.mockResolvedValue ({ id: 1 })
|
wikiApi.fetchWikiPage.mockResolvedValue ({ id: 1 })
|
||||||
wikiApi.fetchWikiPageByTitle.mockResolvedValue (null)
|
wikiApi.fetchWikiPageByTitle.mockResolvedValue (null)
|
||||||
|
wikiApi.fetchWikiPageByReference.mockResolvedValue (null)
|
||||||
|
wikiApi.fetchWikiAutolinkCandidates.mockResolvedValue ([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('prefetches post indexes from query parameters', async () => {
|
it ('prefetches post indexes from query parameters', async () => {
|
||||||
await prefetchForURL (
|
await prefetchForURL (
|
||||||
qc (),
|
qc (),
|
||||||
'http://localhost/posts?tags=a+b&match=any&page=2&limit=5&order=title%3Aasc',
|
'/ja/posts?tags=a+b&match=any&page=2&limit=5&order=title%3Aasc',
|
||||||
)
|
)
|
||||||
|
|
||||||
expect (postsApi.fetchPosts).toHaveBeenCalledWith (
|
expect (postsApi.fetchPosts).toHaveBeenCalledWith (
|
||||||
@@ -60,20 +64,20 @@ describe ('prefetchForURL', () => {
|
|||||||
page: 2,
|
page: 2,
|
||||||
limit: 5,
|
limit: 5,
|
||||||
order: 'title:asc',
|
order: 'title:asc',
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('prefetches post detail pages', async () => {
|
it ('prefetches post detail pages', async () => {
|
||||||
await prefetchForURL (qc (), 'http://localhost/posts/12')
|
await prefetchForURL (qc (), '/ja/posts/12')
|
||||||
|
|
||||||
expect (postsApi.fetchPost).toHaveBeenCalledWith ('12')
|
expect (postsApi.fetchPost).toHaveBeenCalledWith ('12', 'ja')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('prefetches tag indexes from query parameters', async () => {
|
it ('prefetches tag indexes from query parameters', async () => {
|
||||||
await prefetchForURL (
|
await prefetchForURL (
|
||||||
qc (),
|
qc (),
|
||||||
'http://localhost/tags?post=9&name=x&category=general&page=4&post_count_lte=10',
|
'/ja/tags?post=9&name=x&category=general&page=4&post_count_lte=10',
|
||||||
)
|
)
|
||||||
|
|
||||||
expect (tagsApi.fetchTags).toHaveBeenCalledWith (
|
expect (tagsApi.fetchTags).toHaveBeenCalledWith (
|
||||||
@@ -83,17 +87,17 @@ describe ('prefetchForURL', () => {
|
|||||||
category: 'general',
|
category: 'general',
|
||||||
page: 4,
|
page: 4,
|
||||||
postCountLTE: 10,
|
postCountLTE: 10,
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('prefetches nico tag indexes and their alias from query parameters', async () => {
|
it ('prefetches nico tag indexes and their alias from query parameters', async () => {
|
||||||
await prefetchForURL (
|
await prefetchForURL (
|
||||||
qc (),
|
qc (),
|
||||||
'http://localhost/tags/nico?name=source&linked_tag=destination'
|
'/ja/tags/nico?name=source&linked_tag=destination'
|
||||||
+ '&link_status=linked&page=3&limit=10',
|
+ '&link_status=linked&page=3&limit=10',
|
||||||
)
|
)
|
||||||
await prefetchForURL (qc (), 'http://localhost/nico/tags?page=2')
|
await prefetchForURL (qc (), '/ja/nico/tags?page=2')
|
||||||
|
|
||||||
expect (tagsApi.fetchNicoTags).toHaveBeenNthCalledWith (1, {
|
expect (tagsApi.fetchNicoTags).toHaveBeenNthCalledWith (1, {
|
||||||
name: 'source',
|
name: 'source',
|
||||||
@@ -102,7 +106,7 @@ describe ('prefetchForURL', () => {
|
|||||||
page: 3,
|
page: 3,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
order: 'updated_at:desc',
|
order: 'updated_at:desc',
|
||||||
})
|
}, 'ja')
|
||||||
expect (tagsApi.fetchNicoTags).toHaveBeenNthCalledWith (2, {
|
expect (tagsApi.fetchNicoTags).toHaveBeenNthCalledWith (2, {
|
||||||
name: '',
|
name: '',
|
||||||
linkedTag: '',
|
linkedTag: '',
|
||||||
@@ -110,28 +114,29 @@ describe ('prefetchForURL', () => {
|
|||||||
page: 2,
|
page: 2,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
order: 'updated_at:desc',
|
order: 'updated_at:desc',
|
||||||
})
|
}, 'ja')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('prefetches wiki show pages and related tag/post data', async () => {
|
it ('prefetches wiki show pages and related tag/post data', async () => {
|
||||||
wikiApi.fetchWikiPageByTitle.mockResolvedValueOnce ({
|
wikiApi.fetchWikiPageByReference.mockResolvedValueOnce ({
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Actual',
|
title: 'Actual',
|
||||||
body: 'body',
|
body: 'body',
|
||||||
})
|
})
|
||||||
|
|
||||||
await prefetchForURL (qc (), 'http://localhost/wiki/Alias')
|
await prefetchForURL (qc (), '/ja/wiki/Alias')
|
||||||
|
|
||||||
expect (wikiApi.fetchWikiPageByTitle).toHaveBeenCalledWith ('Alias', { version: undefined })
|
expect (wikiApi.fetchWikiPageByReference).toHaveBeenCalledWith (
|
||||||
expect (wikiApi.fetchWikiPage).toHaveBeenCalledWith ('3', {})
|
'Alias', { version: undefined }, 'ja')
|
||||||
expect (tagsApi.fetchTagByName).toHaveBeenCalledWith ('Actual')
|
expect (wikiApi.fetchWikiPage).not.toHaveBeenCalled ()
|
||||||
|
expect (tagsApi.fetchTagByName).toHaveBeenCalledWith ('Actual', 'ja')
|
||||||
expect (postsApi.fetchPosts).toHaveBeenCalledWith (
|
expect (postsApi.fetchPosts).toHaveBeenCalledWith (
|
||||||
expect.objectContaining ({ tags: 'Actual', limit: 8 }),
|
expect.objectContaining ({ tags: 'Actual', limit: 8 }), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('ignores routes without a prefetcher', async () => {
|
it ('ignores routes without a prefetcher', async () => {
|
||||||
await prefetchForURL (qc (), 'http://localhost/unknown')
|
await prefetchForURL (qc (), '/ja/unknown')
|
||||||
|
|
||||||
expect (postsApi.fetchPosts).not.toHaveBeenCalled ()
|
expect (postsApi.fetchPosts).not.toHaveBeenCalled ()
|
||||||
expect (tagsApi.fetchTags).not.toHaveBeenCalled ()
|
expect (tagsApi.fetchTags).not.toHaveBeenCalled ()
|
||||||
@@ -141,14 +146,14 @@ describe ('prefetchForURL', () => {
|
|||||||
it ('prefetches external tag post history without treating it as an internal tag', async () => {
|
it ('prefetches external tag post history without treating it as an internal tag', async () => {
|
||||||
await prefetchForURL (
|
await prefetchForURL (
|
||||||
qc (),
|
qc (),
|
||||||
'http://localhost/posts/changes?external_tag=12&page=2&limit=50',
|
'/ja/posts/changes?external_tag=12&page=2&limit=50',
|
||||||
)
|
)
|
||||||
|
|
||||||
expect (postsApi.fetchPostChanges).toHaveBeenCalledWith ({
|
expect (postsApi.fetchPostChanges).toHaveBeenCalledWith ({
|
||||||
externalTag: '12',
|
externalTag: '12',
|
||||||
page: 2,
|
page: 2,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
})
|
}, 'ja')
|
||||||
|
|
||||||
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe ('Help MDX with the application compiler', () => {
|
|||||||
expect (within (table).getAllByRole ('columnheader')).toHaveLength (2)
|
expect (within (table).getAllByRole ('columnheader')).toHaveLength (2)
|
||||||
expect (within (table).getAllByRole ('cell')).toHaveLength (4)
|
expect (within (table).getAllByRole ('cell')).toHaveLength (4)
|
||||||
expect (within (table).getByRole ('link', { name: 'ヘルプ' }))
|
expect (within (table).getByRole ('link', { name: 'ヘルプ' }))
|
||||||
.toHaveAttribute ('href', '/help/guide')
|
.toHaveAttribute ('href', '/ja/help/guide')
|
||||||
expect (table.querySelector ('strong')).toHaveTextContent ('強調')
|
expect (table.querySelector ('strong')).toHaveTextContent ('強調')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,6 +72,6 @@ describe ('Help MDX with the application compiler', () => {
|
|||||||
renderWithProviders (<Content/>)
|
renderWithProviders (<Content/>)
|
||||||
fireEvent.mouseEnter (screen.getByRole ('link', { name: '内部リンク' }))
|
fireEvent.mouseEnter (screen.getByRole ('link', { name: '内部リンク' }))
|
||||||
expect (prefetch).toHaveBeenCalledWith (expect.anything (),
|
expect (prefetch).toHaveBeenCalledWith (expect.anything (),
|
||||||
new URL ('/help/guide', window.location.origin).href)
|
new URL ('/ja/help/guide', window.location.origin).href)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe ('DeerjikistDetailPage', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiPut).toHaveBeenCalledWith (
|
expect (api.apiPut).toHaveBeenCalledWith (
|
||||||
'/tags/7/deerjikists',
|
'/tags/7/deerjikists',
|
||||||
[{ platform: null, code: 'abc' }],
|
[{ platform: null, code: 'abc' }], { locale: 'ja' },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (await screen.findByText ('プラットフォームを入力してください.')).toBeInTheDocument ()
|
expect (await screen.findByText ('プラットフォームを入力してください.')).toBeInTheDocument ()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import App from '@/App'
|
|||||||
import { helpFixture } from '@/test/helpFixtures'
|
import { helpFixture } from '@/test/helpFixtures'
|
||||||
|
|
||||||
vi.hoisted (() => {
|
vi.hoisted (() => {
|
||||||
window.history.replaceState (null, '', '/help/changes')
|
window.history.replaceState (null, '', '/ja/help/changes')
|
||||||
})
|
})
|
||||||
|
|
||||||
vi.mock ('@/lib/help', async () => (await import ('@/test/helpFixtures')).helpFixture.api)
|
vi.mock ('@/lib/help', async () => (await import ('@/test/helpFixtures')).helpFixture.api)
|
||||||
@@ -21,7 +21,10 @@ vi.mock ('@/lib/useClientBehaviourSettings', () => ({
|
|||||||
|
|
||||||
|
|
||||||
describe ('Help global edition history', () => {
|
describe ('Help global edition history', () => {
|
||||||
beforeEach (() => helpFixture.reset ())
|
beforeEach (() => {
|
||||||
|
helpFixture.reset ()
|
||||||
|
vi.mocked (apiGet).mockResolvedValue ({ locales: [{ code: 'ja', name: '日本語' }] })
|
||||||
|
})
|
||||||
|
|
||||||
it ('routes /help/changes to published editions in date and numeric order', async () => {
|
it ('routes /help/changes to published editions in date and numeric order', async () => {
|
||||||
const client = new QueryClient ({ defaultOptions: { queries: { retry: false } } })
|
const client = new QueryClient ({ defaultOptions: { queries: { retry: false } } })
|
||||||
@@ -31,12 +34,12 @@ describe ('Help global edition history', () => {
|
|||||||
</QueryClientProvider>)
|
</QueryClientProvider>)
|
||||||
const links = await screen.findAllByRole ('link')
|
const links = await screen.findAllByRole ('link')
|
||||||
const editions = links.map (link => link.getAttribute ('href'))
|
const editions = links.map (link => link.getAttribute ('href'))
|
||||||
.filter (href => href?.startsWith ('/help/') && href.includes ('edition='))
|
.filter (href => href?.startsWith ('/ja/help/') && href.includes ('edition='))
|
||||||
expect (editions).toEqual ([
|
expect (editions).toEqual ([
|
||||||
`/help/${encodeURIComponent ('設定 手順')}?edition=2026-10-03`,
|
`/ja/help/${encodeURIComponent ('設定 手順')}?edition=2026-10-03`,
|
||||||
'/help/guide?edition=2026-10-02.10', '/help/guide?edition=2026-10-02.3',
|
'/ja/help/guide?edition=2026-10-02.10', '/ja/help/guide?edition=2026-10-02.3',
|
||||||
'/help/guide?edition=2026-10-02.2', '/help/guide?edition=2026-10-02',
|
'/ja/help/guide?edition=2026-10-02.2', '/ja/help/guide?edition=2026-10-02',
|
||||||
'/help/guide?edition=2026-10-01'])
|
'/ja/help/guide?edition=2026-10-01'])
|
||||||
expect (container).toHaveTextContent ('旧ホーム')
|
expect (container).toHaveTextContent ('旧ホーム')
|
||||||
expect (screen.getAllByRole ('columnheader').map (cell => cell.textContent))
|
expect (screen.getAllByRole ('columnheader').map (cell => cell.textContent))
|
||||||
.toEqual (['差分', 'タイトル', '有効開始年月日'])
|
.toEqual (['差分', 'タイトル', '有効開始年月日'])
|
||||||
@@ -45,11 +48,11 @@ describe ('Help global edition history', () => {
|
|||||||
expect (container.textContent).not.toMatch (/\d{1,2}:\d{2}/)
|
expect (container.textContent).not.toMatch (/\d{1,2}:\d{2}/)
|
||||||
expect (screen.getAllByRole ('link', { name: '差分' }).map (link =>
|
expect (screen.getAllByRole ('link', { name: '差分' }).map (link =>
|
||||||
link.getAttribute ('href'))).toEqual ([
|
link.getAttribute ('href'))).toEqual ([
|
||||||
'/help/guide/diff?from=2026-10-02.3&to=2026-10-02.10',
|
'/ja/help/guide/diff?from=2026-10-02.3&to=2026-10-02.10',
|
||||||
'/help/guide/diff?from=2026-10-02.2&to=2026-10-02.3',
|
'/ja/help/guide/diff?from=2026-10-02.2&to=2026-10-02.3',
|
||||||
'/help/guide/diff?from=2026-10-02&to=2026-10-02.2',
|
'/ja/help/guide/diff?from=2026-10-02&to=2026-10-02.2',
|
||||||
'/help/guide/diff?from=2026-10-01&to=2026-10-02'])
|
'/ja/help/guide/diff?from=2026-10-01&to=2026-10-02'])
|
||||||
expect (apiGet).not.toHaveBeenCalled ()
|
expect (apiGet).toHaveBeenCalledWith ('/locales')
|
||||||
expect (window.location.pathname).toBe ('/help/changes')
|
expect (window.location.pathname).toBe ('/ja/help/changes')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -17,19 +17,19 @@ const Navigation = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<output data-testid="location">{location.pathname}{location.search}</output>
|
<output data-testid="location">{location.pathname}{location.search}</output>
|
||||||
<button onClick={() => navigate ('/help/設定%20手順')}>switch article</button>
|
<button onClick={() => navigate ('/ja/help/設定%20手順')}>switch article</button>
|
||||||
<button onClick={() => navigate ('/help/guide?edition=2026-10-02.3')}>
|
<button onClick={() => navigate ('/ja/help/guide?edition=2026-10-02.3')}>
|
||||||
switch edition
|
switch edition
|
||||||
</button>
|
</button>
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const renderDetail = (route = '/help/guide') => renderWithProviders (
|
const renderDetail = (route = '/ja/help/guide') => renderWithProviders (
|
||||||
<>
|
<>
|
||||||
<Navigation/>
|
<Navigation/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/help/:identifier" element={<HelpDetailPage/>}/>
|
<Route path="/ja/help/:identifier" element={<HelpDetailPage/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>, { route })
|
</>, { route })
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ describe ('Help details', () => {
|
|||||||
|
|
||||||
it.each (['2026-10-02', '2026-10-02.2', '2026-10-02.3', '2026-10-02.10']) (
|
it.each (['2026-10-02', '2026-10-02.2', '2026-10-02.3', '2026-10-02.10']) (
|
||||||
'displays a date without inventing a time for edition %s', async edition => {
|
'displays a date without inventing a time for edition %s', async edition => {
|
||||||
const { container } = renderDetail (`/help/guide?edition=${ edition }`)
|
const { container } = renderDetail (`/ja/help/guide?edition=${ edition }`)
|
||||||
await screen.findByRole ('heading')
|
await screen.findByRole ('heading')
|
||||||
expect (container).toHaveTextContent ('10月2日')
|
expect (container).toHaveTextContent ('10月2日')
|
||||||
expect (container).not.toHaveTextContent ('Invalid Date')
|
expect (container).not.toHaveTextContent ('Invalid Date')
|
||||||
@@ -61,31 +61,31 @@ describe ('Help details', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it ('uses the historical H1 in both page and document titles', async () => {
|
it ('uses the historical H1 in both page and document titles', async () => {
|
||||||
renderDetail ('/help/guide?edition=2026-10-01')
|
renderDetail ('/ja/help/guide?edition=2026-10-01')
|
||||||
expect (await screen.findByRole ('heading', { name: '旧ホーム' })).toBeInTheDocument ()
|
expect (await screen.findByRole ('heading', { name: '旧ホーム' })).toBeInTheDocument ()
|
||||||
expect (screen.getByText ('過去限定本文')).toBeInTheDocument ()
|
expect (screen.getByText ('過去限定本文')).toBeInTheDocument ()
|
||||||
await waitFor (() => expect (document.title).toContain ('旧ホーム ヘルプ'))
|
await waitFor (() => expect (document.title).toContain ('旧ホーム ヘルプ'))
|
||||||
expect (screen.getByText ('(最古)')).toBeInTheDocument ()
|
expect (screen.getByText ('(最古)')).toBeInTheDocument ()
|
||||||
expect (screen.getByRole ('link', { name: '新 >' }))
|
expect (screen.getByRole ('link', { name: '新 >' }))
|
||||||
.toHaveAttribute ('href', '/help/guide?edition=2026-10-02')
|
.toHaveAttribute ('href', '/ja/help/guide?edition=2026-10-02')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('navigates .2, .3 and .10 in numeric edition order', async () => {
|
it ('navigates .2, .3 and .10 in numeric edition order', async () => {
|
||||||
renderDetail ('/help/guide?edition=2026-10-02.3')
|
renderDetail ('/ja/help/guide?edition=2026-10-02.3')
|
||||||
await screen.findByRole ('heading', { name: 'ホーム第三版' })
|
await screen.findByRole ('heading', { name: 'ホーム第三版' })
|
||||||
expect (screen.getByRole ('link', { name: '< 古' }))
|
expect (screen.getByRole ('link', { name: '< 古' }))
|
||||||
.toHaveAttribute ('href', '/help/guide?edition=2026-10-02.2')
|
.toHaveAttribute ('href', '/ja/help/guide?edition=2026-10-02.2')
|
||||||
const next = screen.getByRole ('link', { name: '新 >' })
|
const next = screen.getByRole ('link', { name: '新 >' })
|
||||||
expect (next).toHaveAttribute ('href', '/help/guide?edition=2026-10-02.10')
|
expect (next).toHaveAttribute ('href', '/ja/help/guide?edition=2026-10-02.10')
|
||||||
fireEvent.click (next)
|
fireEvent.click (next)
|
||||||
await screen.findByRole ('heading', { name: 'ホーム第十版' })
|
await screen.findByRole ('heading', { name: 'ホーム第十版' })
|
||||||
expect (screen.getByText ('(最新)')).toBeInTheDocument ()
|
expect (screen.getByText ('(最新)')).toBeInTheDocument ()
|
||||||
expect (screen.queryByText ('第三版本文')).not.toBeInTheDocument ()
|
expect (screen.queryByText ('第三版本文')).not.toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each (['/help/missing', '/help/guide?edition=missing',
|
it.each (['/ja/help/missing', '/ja/help/guide?edition=missing',
|
||||||
'/help/guide?edition=2026-02-30', '/help/guide?edition=2026-10-02.2.extra',
|
'/ja/help/guide?edition=2026-02-30', '/ja/help/guide?edition=2026-10-02.2.extra',
|
||||||
'/help/guide?edition=2026-10-02.11']) (
|
'/ja/help/guide?edition=2026-10-02.11']) (
|
||||||
'shows NotFound without redirecting %s', async route => {
|
'shows NotFound without redirecting %s', async route => {
|
||||||
renderDetail (route)
|
renderDetail (route)
|
||||||
expect (await screen.findByText ('404')).toBeInTheDocument ()
|
expect (await screen.findByText ('404')).toBeInTheDocument ()
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ describe ('Help search', () => {
|
|||||||
it ('initially lists each current article once with encoded detail links', async () => {
|
it ('initially lists each current article once with encoded detail links', async () => {
|
||||||
renderWithProviders (<HelpListPage/>, { route: '/help' })
|
renderWithProviders (<HelpListPage/>, { route: '/help' })
|
||||||
expect (await screen.findByRole ('link', { name: 'ホーム第十版' }))
|
expect (await screen.findByRole ('link', { name: 'ホーム第十版' }))
|
||||||
.toHaveAttribute ('href', '/help/guide')
|
.toHaveAttribute ('href', '/ja/help/guide')
|
||||||
expect (screen.getByRole ('link', { name: '設定' }))
|
expect (screen.getByRole ('link', { name: '設定' }))
|
||||||
.toHaveAttribute ('href', `/help/${encodeURIComponent ('設定 手順')}`)
|
.toHaveAttribute ('href', `/ja/help/${encodeURIComponent ('設定 手順')}`)
|
||||||
expect (screen.getByText ('2 件')).toBeInTheDocument ()
|
expect (screen.getByText ('2 件')).toBeInTheDocument ()
|
||||||
expect (screen.queryByText ('旧ホーム')).not.toBeInTheDocument ()
|
expect (screen.queryByText ('旧ホーム')).not.toBeInTheDocument ()
|
||||||
expect (screen.queryByText ('未公開ホーム')).not.toBeInTheDocument ()
|
expect (screen.queryByText ('未公開ホーム')).not.toBeInTheDocument ()
|
||||||
@@ -82,7 +82,7 @@ describe ('Help search', () => {
|
|||||||
renderWithProviders (<HelpListPage/>, { route: `/help?${ params }` })
|
renderWithProviders (<HelpListPage/>, { route: `/help?${ params }` })
|
||||||
await screen.findByText (`${ found ? 1 : 0 } 件`)
|
await screen.findByText (`${ found ? 1 : 0 } 件`)
|
||||||
if (found)
|
if (found)
|
||||||
expect (screen.getByRole ('link', { name: title })).toHaveAttribute ('href', '/help/guide')
|
expect (screen.getByRole ('link', { name: title })).toHaveAttribute ('href', '/ja/help/guide')
|
||||||
else
|
else
|
||||||
expect (screen.queryByRole ('link', { name: title })).not.toBeInTheDocument ()
|
expect (screen.queryByRole ('link', { name: title })).not.toBeInTheDocument ()
|
||||||
expect (entry).toEqual ({ identifier: 'guide', edition: '2026-10-02.10',
|
expect (entry).toEqual ({ identifier: 'guide', edition: '2026-10-02.10',
|
||||||
@@ -99,7 +99,7 @@ describe ('Help search', () => {
|
|||||||
route: `/help?order=title:${ direction }` })
|
route: `/help?order=title:${ direction }` })
|
||||||
await screen.findByText ('2 件')
|
await screen.findByText ('2 件')
|
||||||
const expected = direction === 'asc'
|
const expected = direction === 'asc'
|
||||||
? ['/help/beta', '/help/alpha'] : ['/help/alpha', '/help/beta']
|
? ['/ja/help/beta', '/ja/help/alpha'] : ['/ja/help/alpha', '/ja/help/beta']
|
||||||
expect (Array.from (container.querySelectorAll ('tbody a'), link =>
|
expect (Array.from (container.querySelectorAll ('tbody a'), link =>
|
||||||
link.getAttribute ('href'))).toEqual (expected)
|
link.getAttribute ('href'))).toEqual (expected)
|
||||||
})
|
})
|
||||||
@@ -113,7 +113,7 @@ describe ('Help search', () => {
|
|||||||
route: `/help?order=title:${ direction }` })
|
route: `/help?order=title:${ direction }` })
|
||||||
await screen.findByText ('2 件')
|
await screen.findByText ('2 件')
|
||||||
expect (Array.from (container.querySelectorAll ('tbody a'), link =>
|
expect (Array.from (container.querySelectorAll ('tbody a'), link =>
|
||||||
link.getAttribute ('href'))).toEqual (['/help/alpha', '/help/beta'])
|
link.getAttribute ('href'))).toEqual (['/ja/help/alpha', '/ja/help/beta'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it.each (['asc', 'desc']) ('ignores title case before identifier ties in order %s',
|
it.each (['asc', 'desc']) ('ignores title case before identifier ties in order %s',
|
||||||
@@ -125,6 +125,6 @@ describe ('Help search', () => {
|
|||||||
route: `/help?order=title:${ direction }` })
|
route: `/help?order=title:${ direction }` })
|
||||||
await screen.findByText ('2 件')
|
await screen.findByText ('2 件')
|
||||||
expect (Array.from (container.querySelectorAll ('tbody a'), link =>
|
expect (Array.from (container.querySelectorAll ('tbody a'), link =>
|
||||||
link.getAttribute ('href'))).toEqual (['/help/alpha', '/help/beta'])
|
link.getAttribute ('href'))).toEqual (['/ja/help/alpha', '/ja/help/beta'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
|
|||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import MaterialDetailPage from '@/pages/materials/MaterialDetailPage'
|
import MaterialDetailPage from '@/pages/materials/MaterialDetailPage'
|
||||||
import { buildMaterial, buildTag } from '@/test/factories'
|
import { buildMaterial, buildTag } from '@/test/factories'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
@@ -59,7 +60,7 @@ describe ('MaterialDetailPage', () => {
|
|||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith ('/materials/8')
|
expect (api.apiGet).toHaveBeenCalledWith ('/materials/8', { locale: 'ja' })
|
||||||
})
|
})
|
||||||
expect (await screen.findByAltText ('素材タグ')).toHaveAttribute ('src', 'image.png')
|
expect (await screen.findByAltText ('素材タグ')).toHaveAttribute ('src', 'image.png')
|
||||||
})
|
})
|
||||||
@@ -82,13 +83,15 @@ describe ('MaterialDetailPage', () => {
|
|||||||
fireEvent.click (screen.getByRole ('button', { name: '更新' }))
|
fireEvent.click (screen.getByRole ('button', { name: '更新' }))
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiPut).toHaveBeenCalledWith ('/materials/8', expect.any (FormData))
|
expect (api.apiPut).toHaveBeenCalledWith (
|
||||||
|
'/materials/8', expect.any (FormData), { locale: 'ja' })
|
||||||
})
|
})
|
||||||
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
||||||
expect (formData.get ('tag')).toBe ('new')
|
expect (formData.get ('tag')).toBe ('new')
|
||||||
expect (formData.get ('url')).toBe ('https://example.com/ref')
|
expect (formData.get ('url')).toBe ('https://example.com/ref')
|
||||||
expect (formData.get ('export_paths[legacy_drive]')).toBe ('素材/new.png')
|
expect (formData.get ('export_paths[legacy_drive]')).toBe ('素材/new.png')
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ title: '更新成功!' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].title)).toBe ('更新成功!')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('shows not found when material API returns 404', async () => {
|
it ('shows not found when material API returns 404', async () => {
|
||||||
@@ -97,7 +100,7 @@ describe ('MaterialDetailPage', () => {
|
|||||||
|
|
||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
expect (await screen.findByText ('素材が見つかりませんでした.')).toBeInTheDocument ()
|
expect (await screen.findByText ('404')).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('shows an error for non-404 material API failures', async () => {
|
it ('shows an error for non-404 material API failures', async () => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe ('MaterialListPage', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith (
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
'/materials',
|
'/materials',
|
||||||
{ params: {
|
{ locale: 'ja', params: {
|
||||||
tag_state: 'all',
|
tag_state: 'all',
|
||||||
media_kind: 'all',
|
media_kind: 'all',
|
||||||
sort: 'created_at',
|
sort: 'created_at',
|
||||||
@@ -53,8 +53,9 @@ describe ('MaterialListPage', () => {
|
|||||||
renderWithProviders (<MaterialListPage/>, { route: '/materials' })
|
renderWithProviders (<MaterialListPage/>, { route: '/materials' })
|
||||||
|
|
||||||
expect ((await screen.findByText ('素材タグ')).closest ('a'))
|
expect ((await screen.findByText ('素材タグ')).closest ('a'))
|
||||||
.toHaveAttribute ('href', '/materials/8')
|
.toHaveAttribute ('href', '/ja/materials/8')
|
||||||
expect (screen.getByText (/画像 \/ 令和8年1月2日/)).toBeInTheDocument ()
|
expect (screen.getByText ('令和8年1月2日(金) 12:04:05').parentElement)
|
||||||
|
.toHaveTextContent ('画像 / 令和8年1月2日')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('submits list filters as material index params', async () => {
|
it ('submits list filters as material index params', async () => {
|
||||||
@@ -93,7 +94,7 @@ describe ('MaterialListPage', () => {
|
|||||||
expect (await screen.findByRole (
|
expect (await screen.findByRole (
|
||||||
'link',
|
'link',
|
||||||
{ name: '素材検索トップへ戻る' },
|
{ name: '素材検索トップへ戻る' },
|
||||||
)).toHaveAttribute ('href', '/materials?material_filter=any')
|
)).toHaveAttribute ('href', '/ja/materials?material_filter=any')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('uses list view for detailed material rows', async () => {
|
it ('uses list view for detailed material rows', async () => {
|
||||||
@@ -113,7 +114,7 @@ describe ('MaterialListPage', () => {
|
|||||||
|
|
||||||
expect (await screen.findByRole ('link', { name: '詳細素材' })).toHaveAttribute (
|
expect (await screen.findByRole ('link', { name: '詳細素材' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/materials/9',
|
'/ja/materials/9',
|
||||||
)
|
)
|
||||||
expect (screen.getByText ('サイズ:')).toBeInTheDocument ()
|
expect (screen.getByText ('サイズ:')).toBeInTheDocument ()
|
||||||
expect (screen.getByText ('2.0 KB')).toBeInTheDocument ()
|
expect (screen.getByText ('2.0 KB')).toBeInTheDocument ()
|
||||||
@@ -153,11 +154,11 @@ describe ('MaterialListPage', () => {
|
|||||||
|
|
||||||
expect (screen.getByRole ('link', { name: '泣き' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: '泣き' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/materials?tag_id=30&include_descendants=1&group_by=parent_tag&material_filter=present',
|
'/ja/materials?tag_id=30&include_descendants=1'
|
||||||
)
|
+ '&group_by=parent_tag&material_filter=present')
|
||||||
expect (screen.getByRole ('link', { name: 'タグ選択を解除' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: 'タグ選択を解除' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/materials?material_filter=present',
|
'/ja/materials?material_filter=present',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -174,7 +175,7 @@ describe ('MaterialListPage', () => {
|
|||||||
expect (await screen.findByText ('選択中タグが見つかりません.')).toBeInTheDocument ()
|
expect (await screen.findByText ('選択中タグが見つかりません.')).toBeInTheDocument ()
|
||||||
expect (screen.getByRole ('link', { name: 'タグ選択を解除' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: 'タグ選択を解除' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/materials?material_filter=present',
|
'/ja/materials?material_filter=present',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { Route, Routes, useLocation } from 'react-router-dom'
|
import { Route, Routes, useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import MaterialNewPage from '@/pages/materials/MaterialNewPage'
|
import MaterialNewPage from '@/pages/materials/MaterialNewPage'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ describe ('MaterialNewPage', () => {
|
|||||||
it ('initializes tag from query and submits form data', async () => {
|
it ('initializes tag from query and submits form data', async () => {
|
||||||
api.apiPost.mockResolvedValueOnce ({})
|
api.apiPost.mockResolvedValueOnce ({})
|
||||||
|
|
||||||
renderWithProviders (<MaterialNewPage/>, { route: '/materials/new?tag=%E8%99%B9%E5%A4%8F' })
|
renderWithProviders (<MaterialNewPage/>, { route: '/ja/materials/new?tag=%E8%99%B9%E5%A4%8F' })
|
||||||
|
|
||||||
expect (screen.getAllByRole ('textbox')[0]).toHaveValue ('虹夏')
|
expect (screen.getAllByRole ('textbox')[0]).toHaveValue ('虹夏')
|
||||||
fireEvent.change (screen.getAllByRole ('textbox')[1], {
|
fireEvent.change (screen.getAllByRole ('textbox')[1], {
|
||||||
@@ -42,12 +43,14 @@ describe ('MaterialNewPage', () => {
|
|||||||
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiPost).toHaveBeenCalledWith ('/materials', expect.any (FormData))
|
expect (api.apiPost).toHaveBeenCalledWith (
|
||||||
|
'/materials', expect.any (FormData), { locale: 'ja' })
|
||||||
})
|
})
|
||||||
const formData = api.apiPost.mock.calls[0]?.[1] as FormData
|
const formData = api.apiPost.mock.calls[0]?.[1] as FormData
|
||||||
expect (formData.get ('tag')).toBe ('虹夏')
|
expect (formData.get ('tag')).toBe ('虹夏')
|
||||||
expect (formData.get ('url')).toBe ('https://example.com/ref')
|
expect (formData.get ('url')).toBe ('https://example.com/ref')
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ title: '送信成功!' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].title)).toBe ('送信成功!')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('shows validation errors for file and url fields', async () => {
|
it ('shows validation errors for file and url fields', async () => {
|
||||||
@@ -81,12 +84,12 @@ describe ('MaterialNewPage', () => {
|
|||||||
|
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/materials/new" element={<MaterialNewPage/>}/>
|
<Route path="/ja/materials/new" element={<MaterialNewPage/>}/>
|
||||||
<Route path="/materials" element={<LocationView/>}/>
|
<Route path="/ja/materials" element={<LocationView/>}/>
|
||||||
</Routes>,
|
</Routes>,
|
||||||
{
|
{
|
||||||
route:
|
route:
|
||||||
'/materials/new?tag=%E8%99%B9%E5%A4%8F'
|
'/ja/materials/new?tag=%E8%99%B9%E5%A4%8F'
|
||||||
+ '&return_to=%2Fmaterials%3Ftag_id%3D20',
|
+ '&return_to=%2Fmaterials%3Ftag_id%3D20',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -96,7 +99,7 @@ describe ('MaterialNewPage', () => {
|
|||||||
})
|
})
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
||||||
|
|
||||||
expect (await screen.findByText ('/materials?tag_id=20')).toBeInTheDocument ()
|
expect (await screen.findByText ('/ja/materials?tag_id=20')).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('ignores unsafe return_to values after successful creation', async () => {
|
it ('ignores unsafe return_to values after successful creation', async () => {
|
||||||
@@ -104,12 +107,12 @@ describe ('MaterialNewPage', () => {
|
|||||||
|
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/materials/new" element={<MaterialNewPage/>}/>
|
<Route path="/ja/materials/new" element={<MaterialNewPage/>}/>
|
||||||
<Route path="/materials" element={<LocationView/>}/>
|
<Route path="/ja/materials" element={<LocationView/>}/>
|
||||||
</Routes>,
|
</Routes>,
|
||||||
{
|
{
|
||||||
route:
|
route:
|
||||||
'/materials/new?tag=%E8%99%B9%E5%A4%8F'
|
'/ja/materials/new?tag=%E8%99%B9%E5%A4%8F'
|
||||||
+ '&return_to=https%3A%2F%2Fexample.com%2Fevil',
|
+ '&return_to=https%3A%2F%2Fexample.com%2Fevil',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -119,6 +122,6 @@ describe ('MaterialNewPage', () => {
|
|||||||
})
|
})
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
||||||
|
|
||||||
expect (await screen.findByText ('/materials')).toBeInTheDocument ()
|
expect (await screen.findByText ('/ja/materials')).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe ('PostDetailPage', () => {
|
|||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (postsApi.fetchPost).toHaveBeenCalledWith ('9')
|
expect (postsApi.fetchPost).toHaveBeenCalledWith ('9', 'ja')
|
||||||
})
|
})
|
||||||
expect (await screen.findByText ('Embed:https://example.com/9')).toBeInTheDocument ()
|
expect (await screen.findByText ('Embed:https://example.com/9')).toBeInTheDocument ()
|
||||||
expect (screen.getByRole ('button', { name: '未閲覧' })).toBeInTheDocument ()
|
expect (screen.getByRole ('button', { name: '未閲覧' })).toBeInTheDocument ()
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ describe ('PostHistoryPage', () => {
|
|||||||
externalTag: '7',
|
externalTag: '7',
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
})
|
}, 'ja')
|
||||||
})
|
})
|
||||||
|
|
||||||
expect (tagsApi.fetchExternalTag).toHaveBeenCalledWith ('7')
|
expect (tagsApi.fetchExternalTag).toHaveBeenCalledWith ('7', 'ja')
|
||||||
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
||||||
|
|
||||||
expect (
|
expect (
|
||||||
|
|||||||
@@ -130,7 +130,9 @@ describe ('PostImportReviewPage', () => {
|
|||||||
|
|
||||||
renderReviewPage (['https://example.com/post'])
|
renderReviewPage (['https://example.com/post'])
|
||||||
|
|
||||||
fireEvent.click (await screen.findByRole ('button', { name: '追加' }))
|
const submit = await screen.findByRole ('button', { name: '追加' })
|
||||||
|
await waitFor (() => expect (submit).toBeEnabled ())
|
||||||
|
fireEvent.click (submit)
|
||||||
|
|
||||||
expect (await screen.findAllByText ('登録失敗')).toHaveLength (2)
|
expect (await screen.findAllByText ('登録失敗')).toHaveLength (2)
|
||||||
expect (screen.getAllByText ('invalid title')).toHaveLength (2)
|
expect (screen.getAllByText ('invalid title')).toHaveLength (2)
|
||||||
@@ -147,11 +149,14 @@ describe ('PostImportReviewPage', () => {
|
|||||||
|
|
||||||
renderReviewPage (['https://example.com/post'])
|
renderReviewPage (['https://example.com/post'])
|
||||||
|
|
||||||
fireEvent.click (await screen.findByRole ('button', { name: '追加' }))
|
const submit = await screen.findByRole ('button', { name: '追加' })
|
||||||
|
await waitFor (() => expect (submit).toBeEnabled ())
|
||||||
|
fireEvent.click (submit)
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (screen.getByLabelText ('current-location')).toHaveTextContent ('/posts')
|
expect (screen.getByLabelText ('current-location').textContent).toBe ('/ja/posts')
|
||||||
})
|
})
|
||||||
expect (api.apiPost).toHaveBeenCalledWith ('/posts/bulk', expect.any (FormData))
|
expect (api.apiPost).toHaveBeenCalledWith (
|
||||||
|
'/posts/bulk', expect.any (FormData), { locale: 'ja' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ describe ('PostImportSourcePage', () => {
|
|||||||
target: { value: '\nftp://example.com/file\nhttps://example.com/valid' } })
|
target: { value: '\nftp://example.com/file\nhttps://example.com/valid' } })
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '次へ' }))
|
fireEvent.click (screen.getByRole ('button', { name: '次へ' }))
|
||||||
|
|
||||||
expect (screen.getByText (/2 行目: HTTP または HTTPS/)).toBeInTheDocument ()
|
expect (screen.getByText ('ftp://example.com/file').closest ('li'))
|
||||||
|
.toHaveTextContent ('2 行目: HTTP または HTTPS')
|
||||||
expect (screen.getByText ('ftp://example.com/file')).toBeInTheDocument ()
|
expect (screen.getByText ('ftp://example.com/file')).toBeInTheDocument ()
|
||||||
expect (input).toHaveAttribute ('aria-invalid', 'true')
|
expect (input).toHaveAttribute ('aria-invalid', 'true')
|
||||||
expect (router.navigate).not.toHaveBeenCalled ()
|
expect (router.navigate).not.toHaveBeenCalled ()
|
||||||
@@ -51,8 +52,8 @@ describe ('PostImportSourcePage', () => {
|
|||||||
fireEvent.click (screen.getByRole ('button', { name: '次へ' }))
|
fireEvent.click (screen.getByRole ('button', { name: '次へ' }))
|
||||||
|
|
||||||
expect (router.navigate).toHaveBeenCalledWith (
|
expect (router.navigate).toHaveBeenCalledWith (
|
||||||
'/posts/new?urls=https%3A%2F%2Fexample.com%2Fone%2Ba'
|
'/ja/posts/new?urls=https%3A%2F%2Fexample.com%2Fone%2Ba'
|
||||||
+ '+https%3A%2F%2Fexample.com%2Ftwo%3Fvalue%3Db%2Bc')
|
+ '+https%3A%2F%2Fexample.com%2Ftwo%3Fvalue%3Db%2Bc', undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('disables Next when the encoded request target reaches 4096 bytes', () => {
|
it ('disables Next when the encoded request target reaches 4096 bytes', () => {
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ describe ('PostListPage', () => {
|
|||||||
page: 2,
|
page: 2,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
order: 'original_created_at:desc',
|
order: 'original_created_at:desc',
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (await screen.findByRole ('link', { name: '投稿5' })).toHaveAttribute (
|
expect (await screen.findByRole ('link', { name: '投稿5' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts/5',
|
'/ja/posts/5',
|
||||||
)
|
)
|
||||||
expect (screen.getByText ('広場')).toBeInTheDocument ()
|
expect (screen.getByText ('広場')).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
@@ -75,7 +75,7 @@ describe ('PostListPage', () => {
|
|||||||
expect (await screen.findByText ('Wiki body')).toBeInTheDocument ()
|
expect (await screen.findByText ('Wiki body')).toBeInTheDocument ()
|
||||||
expect (screen.getByRole ('link', { name: 'Wiki を見る' })).toHaveAttribute (
|
expect (screen.getByRole ('link', { name: 'Wiki を見る' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/wiki/%E8%99%B9%E5%A4%8F',
|
'/ja/wiki/%E8%99%B9%E5%A4%8F',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ describe ('PostListPage', () => {
|
|||||||
expect.objectContaining ({
|
expect.objectContaining ({
|
||||||
limit: 50,
|
limit: 50,
|
||||||
order: 'original_created_at:desc',
|
order: 'original_created_at:desc',
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -106,7 +106,7 @@ describe ('PostListPage', () => {
|
|||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (postsApi.fetchPosts).toHaveBeenLastCalledWith (
|
expect (postsApi.fetchPosts).toHaveBeenLastCalledWith (
|
||||||
expect.objectContaining ({ page: 1, limit: 100 }),
|
expect.objectContaining ({ page: 1, limit: 100 }), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ describe ('PostSearchPage', () => {
|
|||||||
match: 'any',
|
match: 'any',
|
||||||
page: 2,
|
page: 2,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect ((await screen.findAllByRole ('link', { name: '検索対象' }))[0]).toHaveAttribute (
|
expect ((await screen.findAllByRole ('link', { name: '検索対象' }))[0]).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/posts/4',
|
'/ja/posts/4',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ describe ('PostSearchPage', () => {
|
|||||||
tags: 'tag',
|
tags: 'tag',
|
||||||
match: 'any',
|
match: 'any',
|
||||||
page: 1,
|
page: 1,
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -98,7 +98,7 @@ describe ('PostSearchPage', () => {
|
|||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (postsApi.fetchPosts).toHaveBeenCalledWith (
|
expect (postsApi.fetchPosts).toHaveBeenCalledWith (
|
||||||
expect.objectContaining ({ limit: 50, order: 'created_at:desc' }),
|
expect.objectContaining ({ limit: 50, order: 'created_at:desc' }), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (JSON.parse (localStorage.getItem (CLIENT_SETTINGS_STORAGE_KEY) ?? '{}'))
|
expect (JSON.parse (localStorage.getItem (CLIENT_SETTINGS_STORAGE_KEY) ?? '{}'))
|
||||||
@@ -117,7 +117,7 @@ describe ('PostSearchPage', () => {
|
|||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (postsApi.fetchPosts).toHaveBeenLastCalledWith (
|
expect (postsApi.fetchPosts).toHaveBeenLastCalledWith (
|
||||||
expect.objectContaining ({ page: 1, limit: 100 }),
|
expect.objectContaining ({ page: 1, limit: 100 }), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import NicoTagListPage from '@/pages/tags/NicoTagListPage'
|
|||||||
import { dateString } from '@/lib/utils'
|
import { dateString } from '@/lib/utils'
|
||||||
import { buildTag, buildUser } from '@/test/factories'
|
import { buildTag, buildUser } from '@/test/factories'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
|
|
||||||
import type { NicoTag } from '@/types'
|
import type { NicoTag } from '@/types'
|
||||||
|
|
||||||
@@ -26,10 +27,10 @@ const scrollIntoView = vi.fn ()
|
|||||||
|
|
||||||
vi.mock ('@/lib/api', () => api)
|
vi.mock ('@/lib/api', () => api)
|
||||||
vi.mock ('@/components/ui/use-toast', () => toastApi)
|
vi.mock ('@/components/ui/use-toast', () => toastApi)
|
||||||
vi.mock ('@/components/dialogues/DialogueProvider', async importOriginal => ({
|
vi.mock ('@/lib/dialogues/useDialogue', async importOriginal => ({
|
||||||
...await importOriginal<
|
...await importOriginal<
|
||||||
typeof import('@/components/dialogues/DialogueProvider')> (),
|
typeof import('@/lib/dialogues/useDialogue')> (),
|
||||||
useDialogue: () => dialogue }))
|
default: () => dialogue }))
|
||||||
|
|
||||||
const buildNicoTag = (values: Partial<NicoTag> = {}): NicoTag => ({
|
const buildNicoTag = (values: Partial<NicoTag> = {}): NicoTag => ({
|
||||||
...buildTag (),
|
...buildTag (),
|
||||||
@@ -74,7 +75,7 @@ describe ('NicoTagListPage', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith (
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
'/tags/nico',
|
'/tags/nico',
|
||||||
{ params: {
|
{ locale: 'ja', params: {
|
||||||
page: 2,
|
page: 2,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
name: 'linked',
|
name: 'linked',
|
||||||
@@ -106,7 +107,7 @@ describe ('NicoTagListPage', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenLastCalledWith (
|
expect (api.apiGet).toHaveBeenLastCalledWith (
|
||||||
'/tags/nico',
|
'/tags/nico',
|
||||||
{ params: {
|
{ locale: 'ja', params: {
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
name: 'linked',
|
name: 'linked',
|
||||||
@@ -151,7 +152,7 @@ describe ('NicoTagListPage', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenLastCalledWith (
|
expect (api.apiGet).toHaveBeenLastCalledWith (
|
||||||
'/tags/nico',
|
'/tags/nico',
|
||||||
{ params: expect.objectContaining ({
|
{ locale: 'ja', params: expect.objectContaining ({
|
||||||
page: 1,
|
page: 1,
|
||||||
name: 'source',
|
name: 'source',
|
||||||
linked_tag: 'destination',
|
linked_tag: 'destination',
|
||||||
@@ -191,7 +192,7 @@ describe ('NicoTagListPage', () => {
|
|||||||
expect (api.apiPut).toHaveBeenCalledWith (
|
expect (api.apiPut).toHaveBeenCalledWith (
|
||||||
'/tags/nico/7',
|
'/tags/nico/7',
|
||||||
expect.any (FormData),
|
expect.any (FormData),
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } },
|
{ locale: 'ja', headers: { 'Content-Type': 'multipart/form-data' } },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (await screen.findByText ('1 件')).toBeInTheDocument ()
|
expect (await screen.findByText ('1 件')).toBeInTheDocument ()
|
||||||
@@ -217,11 +218,11 @@ describe ('NicoTagListPage', () => {
|
|||||||
fireEvent.click (screen.getAllByRole ('button', { name: '編集' })[0])
|
fireEvent.click (screen.getAllByRole ('button', { name: '編集' })[0])
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (dialogue.confirm).toHaveBeenCalledWith ({
|
expect (dialogue.confirm).toHaveBeenCalledTimes (1)
|
||||||
title: '編集中の内容を破棄しますか?',
|
const options = dialogue.confirm.mock.calls[0][0]
|
||||||
confirmText: '破棄',
|
expect (textContentOf (options.title)).toBe ('編集中の内容を破棄しますか?')
|
||||||
variant: 'danger',
|
expect (textContentOf (options.confirmText)).toBe ('破棄')
|
||||||
})
|
expect (options.variant).toBe ('danger')
|
||||||
})
|
})
|
||||||
expect (screen.getAllByLabelText ('連携する広場タグ')).toHaveLength (1)
|
expect (screen.getAllByLabelText ('連携する広場タグ')).toHaveLength (1)
|
||||||
expect (screen.getByLabelText ('連携する広場タグ')).toHaveValue ('入力中')
|
expect (screen.getByLabelText ('連携する広場タグ')).toHaveValue ('入力中')
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
|
|||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import TagDetailPage from '@/pages/tags/TagDetailPage'
|
import TagDetailPage from '@/pages/tags/TagDetailPage'
|
||||||
import { buildTag } from '@/test/factories'
|
import { buildTag } from '@/test/factories'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
@@ -60,11 +61,12 @@ describe ('TagDetailPage', () => {
|
|||||||
fireEvent.submit (screen.getByRole ('button', { name: '更新' }).closest ('form')!)
|
fireEvent.submit (screen.getByRole ('button', { name: '更新' }).closest ('form')!)
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiPut).toHaveBeenCalledWith ('/tags/7', expect.any (FormData))
|
expect (api.apiPut).toHaveBeenCalledWith ('/tags/7', expect.any (FormData), { locale: 'ja' })
|
||||||
})
|
})
|
||||||
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
||||||
expect (formData.get ('name')).toBe ('new')
|
expect (formData.get ('name')).toBe ('new')
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ description: '更新しました.' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].description)).toBe ('更新しました.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('keeps nico tags disabled', async () => {
|
it ('keeps nico tags disabled', async () => {
|
||||||
@@ -77,7 +79,7 @@ describe ('TagDetailPage', () => {
|
|||||||
|
|
||||||
it ('shows validation errors returned for tag fields', async () => {
|
it ('shows validation errors returned for tag fields', async () => {
|
||||||
tagsApi.fetchTag.mockResolvedValueOnce (buildTag ({ id: 7, name: 'old' }))
|
tagsApi.fetchTag.mockResolvedValueOnce (buildTag ({ id: 7, name: 'old' }))
|
||||||
api.isApiError.mockReturnValue (true)
|
api.isApiError.mockImplementation (error => error?.response != null)
|
||||||
api.apiPut.mockRejectedValueOnce ({
|
api.apiPut.mockRejectedValueOnce ({
|
||||||
response: {
|
response: {
|
||||||
status: 422,
|
status: 422,
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ describe ('TagListPage', () => {
|
|||||||
order: 'post_count:desc',
|
order: 'post_count:desc',
|
||||||
postCountGTE: 5,
|
postCountGTE: 5,
|
||||||
deprecated: true,
|
deprecated: true,
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (await screen.findByRole ('link', { name: '虹夏' })).toHaveAttribute (
|
expect (await screen.findByRole ('link', { name: '虹夏' })).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/tags/7',
|
'/ja/tags/7',
|
||||||
)
|
)
|
||||||
expect (screen.getAllByText ('キャラクター').length).toBeGreaterThan (0)
|
expect (screen.getAllByText ('キャラクター').length).toBeGreaterThan (0)
|
||||||
expect (screen.getAllByRole ('combobox')[2]).toHaveValue ('1')
|
expect (screen.getAllByRole ('combobox')[2]).toHaveValue ('1')
|
||||||
@@ -77,7 +77,7 @@ describe ('TagListPage', () => {
|
|||||||
name: '虹夏',
|
name: '虹夏',
|
||||||
category: 'character',
|
category: 'character',
|
||||||
page: 1,
|
page: 1,
|
||||||
}),
|
}), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (screen.getByRole ('textbox')).toHaveValue ('虹夏')
|
expect (screen.getByRole ('textbox')).toHaveValue ('虹夏')
|
||||||
@@ -104,7 +104,7 @@ describe ('TagListPage', () => {
|
|||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (tagsApi.fetchTags).toHaveBeenCalledWith (
|
expect (tagsApi.fetchTags).toHaveBeenCalledWith (
|
||||||
expect.objectContaining ({ limit: 50, order: 'updated_at:desc' }),
|
expect.objectContaining ({ limit: 50, order: 'updated_at:desc' }), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (JSON.parse (localStorage.getItem (CLIENT_SETTINGS_STORAGE_KEY) ?? '{}'))
|
expect (JSON.parse (localStorage.getItem (CLIENT_SETTINGS_STORAGE_KEY) ?? '{}'))
|
||||||
@@ -123,7 +123,7 @@ describe ('TagListPage', () => {
|
|||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (tagsApi.fetchTags).toHaveBeenLastCalledWith (
|
expect (tagsApi.fetchTags).toHaveBeenLastCalledWith (
|
||||||
expect.objectContaining ({ page: 1, limit: 100 }),
|
expect.objectContaining ({ page: 1, limit: 100 }), 'ja',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -152,10 +152,10 @@ describe ('TagListPage', () => {
|
|||||||
const externalLink =
|
const externalLink =
|
||||||
screen.getByRole ('link', { name: 'nico:external_collision' })
|
screen.getByRole ('link', { name: 'nico:external_collision' })
|
||||||
|
|
||||||
expect (internalLink).toHaveAttribute ('href', '/tags/7')
|
expect (internalLink).toHaveAttribute ('href', '/ja/tags/7')
|
||||||
expect (externalLink).toHaveAttribute (
|
expect (externalLink).toHaveAttribute (
|
||||||
'href',
|
'href',
|
||||||
'/tags/nico?name=nico%3Aexternal_collision',
|
'/ja/tags/nico?name=nico%3Aexternal_collision',
|
||||||
)
|
)
|
||||||
|
|
||||||
const internalRow = internalLink.closest ('tr')
|
const internalRow = internalLink.closest ('tr')
|
||||||
@@ -166,10 +166,10 @@ describe ('TagListPage', () => {
|
|||||||
|
|
||||||
expect (
|
expect (
|
||||||
within (internalRow!).getByRole ('link', { name: '耕作履歴' }),
|
within (internalRow!).getByRole ('link', { name: '耕作履歴' }),
|
||||||
).toHaveAttribute ('href', '/posts/changes?tag=7')
|
).toHaveAttribute ('href', '/ja/posts/changes?tag=7')
|
||||||
|
|
||||||
expect (
|
expect (
|
||||||
within (externalRow!).getByRole ('link', { name: '耕作履歴' }),
|
within (externalRow!).getByRole ('link', { name: '耕作履歴' }),
|
||||||
).toHaveAttribute ('href', '/posts/changes?external_tag=7')
|
).toHaveAttribute ('href', '/ja/posts/changes?external_tag=7')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||||
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import TheatreDetailPage from '@/pages/theatres/TheatreDetailPage'
|
import TheatreDetailPage from '@/pages/theatres/TheatreDetailPage'
|
||||||
|
import { theatresKeys } from '@/lib/queryKeys'
|
||||||
import { buildPost,
|
import { buildPost,
|
||||||
buildTag,
|
buildTag,
|
||||||
buildTheatre,
|
buildTheatre,
|
||||||
@@ -40,10 +42,10 @@ const postEmbed = vi.hoisted (() => ({
|
|||||||
|
|
||||||
vi.mock ('@/lib/api', () => api)
|
vi.mock ('@/lib/api', () => api)
|
||||||
vi.mock ('@/lib/posts', () => postsApi)
|
vi.mock ('@/lib/posts', () => postsApi)
|
||||||
vi.mock ('@/components/dialogues/DialogueProvider', async importOriginal => ({
|
vi.mock ('@/lib/dialogues/useDialogue', async importOriginal => ({
|
||||||
...await importOriginal<
|
...await importOriginal<
|
||||||
typeof import('@/components/dialogues/DialogueProvider')> (),
|
typeof import('@/lib/dialogues/useDialogue')> (),
|
||||||
useDialogue: () => dialogue }))
|
default: () => dialogue }))
|
||||||
vi.mock ('@/components/PostEmbed', () => ({
|
vi.mock ('@/components/PostEmbed', () => ({
|
||||||
default: (props: {
|
default: (props: {
|
||||||
ref?: { current: unknown }
|
ref?: { current: unknown }
|
||||||
@@ -94,19 +96,28 @@ const watchingUsers = [
|
|||||||
{ id: 2, name: 'another' },
|
{ id: 2, name: 'another' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const renderPage = (user = buildUser ({ id: 1, role: 'member' })) =>
|
const renderPage = (
|
||||||
renderWithProviders (
|
user = buildUser ({ id: 1, role: 'member' }),
|
||||||
|
prefetched = true,
|
||||||
|
) => {
|
||||||
|
const queryClient = new QueryClient ({
|
||||||
|
defaultOptions: { queries: { retry: false, staleTime: Infinity } } })
|
||||||
|
if (prefetched)
|
||||||
|
{
|
||||||
|
queryClient.setQueryData (theatresKeys.show ('7', 'ja'), theatre)
|
||||||
|
queryClient.setQueryData (theatresKeys.programmes ('7', 'ja'), [programme])
|
||||||
|
}
|
||||||
|
return renderWithProviders (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/theatres/:id" element={<TheatreDetailPage user={user}/>}/>
|
<Route path="/theatres/:id" element={<TheatreDetailPage user={user}/>}/>
|
||||||
</Routes>,
|
</Routes>,
|
||||||
{ route: '/theatres/7' },
|
{ route: '/theatres/7', queryClient })
|
||||||
)
|
}
|
||||||
|
|
||||||
const tagSection = (): HTMLElement =>
|
const tagSection = (): HTMLElement =>
|
||||||
screen.getAllByRole ('heading', { name: 'タグ' })[0].closest ('section')!
|
screen.getAllByRole ('heading', { name: 'タグ' })[0].closest ('section')!
|
||||||
|
|
||||||
const mockDefaultApi = () => {
|
const mockDefaultApi = () => {
|
||||||
let commentsFetched = false
|
|
||||||
let currentTheatreInfo = buildTheatreInfo ({
|
let currentTheatreInfo = buildTheatreInfo ({
|
||||||
postId: currentPost.id,
|
postId: currentPost.id,
|
||||||
postStartedAt: '2026-01-02T03:04:05.000Z',
|
postStartedAt: '2026-01-02T03:04:05.000Z',
|
||||||
@@ -120,17 +131,16 @@ const mockDefaultApi = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
api.apiGet.mockImplementation ((path: string) => {
|
api.apiGet.mockImplementation ((path: string, options?: { params?: { no_gt?: number } }) => {
|
||||||
switch (path)
|
switch (path)
|
||||||
{
|
{
|
||||||
case '/theatres/7':
|
case '/theatres/7':
|
||||||
return Promise.resolve (theatre)
|
return Promise.resolve (theatre)
|
||||||
|
|
||||||
case '/theatres/7/comments':
|
case '/theatres/7/comments':
|
||||||
if (commentsFetched)
|
if ((options?.params?.no_gt ?? 0) >= 2)
|
||||||
return Promise.resolve ([])
|
return Promise.resolve ([])
|
||||||
|
|
||||||
commentsFetched = true
|
|
||||||
return Promise.resolve ([
|
return Promise.resolve ([
|
||||||
buildTheatreComment ({
|
buildTheatreComment ({
|
||||||
theatreId: 7,
|
theatreId: 7,
|
||||||
@@ -192,30 +202,32 @@ describe ('TheatreDetailPage', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it ('loads theatre state, comments, current post, programme history, and weights', async () => {
|
it ('loads theatre state, comments, current post, programme history, and weights', async () => {
|
||||||
renderPage ()
|
renderPage (undefined, false)
|
||||||
|
|
||||||
expect (await screen.findByText ('上映会場『上映室』')).toBeInTheDocument ()
|
expect (await screen.findByText ('上映会場『上映室』')).toBeInTheDocument ()
|
||||||
expect (await screen.findByText ('Embed:上映中の投稿')).toBeInTheDocument ()
|
expect (await screen.findByText ('Embed:上映中の投稿', {}, { timeout: 4_000 })).toBeInTheDocument ()
|
||||||
expect (screen.getAllByText ('視聴コメント')[0]).toBeInTheDocument ()
|
expect ((await screen.findAllByText ('視聴コメント', {}, { timeout: 4_000 }))[0])
|
||||||
|
.toBeInTheDocument ()
|
||||||
expect (screen.getAllByText ('上映中の投稿')[0]).toBeInTheDocument ()
|
expect (screen.getAllByText ('上映中の投稿')[0]).toBeInTheDocument ()
|
||||||
expect (screen.getByText ('penalty 2')).toBeInTheDocument ()
|
expect (screen.getByText ('penalty 2')).toBeInTheDocument ()
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (postsApi.fetchPost).toHaveBeenCalledWith ('10')
|
expect (postsApi.fetchPost).toHaveBeenCalledWith ('10', 'ja')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('votes to skip the current post', async () => {
|
it ('votes to skip the current post', async () => {
|
||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
await screen.findByText ('Embed:上映中の投稿')
|
await screen.findByText ('Embed:上映中の投稿', {}, { timeout: 4_000 })
|
||||||
|
|
||||||
fireEvent.click (screen.getByRole ('button', { name: 'スキップ 0 / 2' }))
|
fireEvent.click (await screen.findByRole ('button',
|
||||||
|
{ name: 'スキップ 0 / 2' }, { timeout: 4_000 }))
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiPut).toHaveBeenCalledWith (
|
expect (api.apiPut).toHaveBeenCalledWith (
|
||||||
'/theatres/7/skip_vote',
|
'/theatres/7/skip_vote',
|
||||||
{ post_id: 10 },
|
{ post_id: 10 }, { locale: 'ja' },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (await screen.findByRole ('button', { name: 'スキップ取消 1 / 2' }))
|
expect (await screen.findByRole ('button', { name: 'スキップ取消 1 / 2' }))
|
||||||
@@ -247,7 +259,7 @@ describe ('TheatreDetailPage', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
renderPage ()
|
renderPage ()
|
||||||
await screen.findByText ('Embed:上映中の投稿')
|
await screen.findByText ('Embed:上映中の投稿', {}, { timeout: 4_000 })
|
||||||
|
|
||||||
const props = postEmbed.props.mock.calls.at (-1)![0]
|
const props = postEmbed.props.mock.calls.at (-1)![0]
|
||||||
|
|
||||||
@@ -256,6 +268,7 @@ describe ('TheatreDetailPage', () => {
|
|||||||
props.onPlaybackChange (0)
|
props.onPlaybackChange (0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await waitFor (() => expect (postEmbed.seek).toHaveBeenCalled ())
|
||||||
const seekMs = postEmbed.seek.mock.calls[0][0]
|
const seekMs = postEmbed.seek.mock.calls[0][0]
|
||||||
expect (seekMs).toBeGreaterThanOrEqual (7_000)
|
expect (seekMs).toBeGreaterThanOrEqual (7_000)
|
||||||
expect (seekMs).toBeLessThan (10_000)
|
expect (seekMs).toBeLessThan (10_000)
|
||||||
@@ -279,14 +292,14 @@ describe ('TheatreDetailPage', () => {
|
|||||||
children: [childTag],
|
children: [childTag],
|
||||||
})
|
})
|
||||||
|
|
||||||
postsApi.fetchPost.mockResolvedValueOnce (buildPost ({
|
postsApi.fetchPost.mockResolvedValue (buildPost ({
|
||||||
...currentPost,
|
...currentPost,
|
||||||
tags: [parentTag, duplicateParentTag],
|
tags: [parentTag, duplicateParentTag],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
await screen.findByText ('Embed:上映中の投稿')
|
await screen.findByText ('Embed:上映中の投稿', {}, { timeout: 4_000 })
|
||||||
|
|
||||||
expect (within (tagSection ()).getByRole ('link', { name: '親タグ' }))
|
expect (within (tagSection ()).getByRole ('link', { name: '親タグ' }))
|
||||||
.toBeInTheDocument ()
|
.toBeInTheDocument ()
|
||||||
@@ -337,9 +350,9 @@ describe ('TheatreDetailPage', () => {
|
|||||||
|
|
||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
await screen.findByText ('Embed:上映中の投稿')
|
await screen.findByText ('Embed:上映中の投稿', {}, { timeout: 4_000 })
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiPut).toHaveBeenCalledWith ('/theatres/7/watching')
|
expect (api.apiPut).toHaveBeenCalledWith ('/theatres/7/watching', {}, { locale: 'ja' })
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
@@ -351,13 +364,16 @@ describe ('TheatreDetailPage', () => {
|
|||||||
it ('deletes an owned comment after confirmation', async () => {
|
it ('deletes an owned comment after confirmation', async () => {
|
||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
fireEvent.click ((await screen.findAllByLabelText ('コメントを削除'))[0])
|
await screen.findByRole ('button', { name: 'スキップ 0 / 2' }, { timeout: 4_000 })
|
||||||
|
|
||||||
|
fireEvent.click ((await screen.findAllByLabelText (
|
||||||
|
'コメントを削除', {}, { timeout: 4_000 }))[0])
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (dialogue.confirm).toHaveBeenCalled ()
|
expect (dialogue.confirm).toHaveBeenCalled ()
|
||||||
})
|
})
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiDelete).toHaveBeenCalledWith ('/theatres/7/comments/2')
|
expect (api.apiDelete).toHaveBeenCalledWith ('/theatres/7/comments/2', { locale: 'ja' })
|
||||||
})
|
})
|
||||||
expect (await screen.findAllByText ('削除されました.')).toHaveLength (2)
|
expect (await screen.findAllByText ('削除されました.')).toHaveLength (2)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import SettingPage from '@/pages/users/SettingPage'
|
import SettingPage from '@/pages/users/SettingPage'
|
||||||
import { buildUser } from '@/test/factories'
|
import { buildUser } from '@/test/factories'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
@@ -68,7 +69,8 @@ describe ('SettingPage', () => {
|
|||||||
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
||||||
expect (formData.get ('name')).toBe ('new')
|
expect (formData.get ('name')).toBe ('new')
|
||||||
expect (setUser).toHaveBeenCalled ()
|
expect (setUser).toHaveBeenCalled ()
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ title: '表示名を更新しました.' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].title)).toBe ('表示名を更新しました.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('shows validation errors returned for the name field', async () => {
|
it ('shows validation errors returned for the name field', async () => {
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ const postsApi = vi.hoisted (() => ({
|
|||||||
fetchPosts: vi.fn (),
|
fetchPosts: vi.fn (),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock ('@/lib/wiki', () => wikiApi)
|
vi.mock ('@/lib/wiki', () => ({
|
||||||
|
...wikiApi,
|
||||||
|
fetchWikiAutolinkCandidates: async () => [],
|
||||||
|
fetchWikiPageByReference: (reference: string, ...args: unknown[]) =>
|
||||||
|
/^\d+$/.test (reference)
|
||||||
|
? wikiApi.fetchWikiPage (reference, ...args)
|
||||||
|
: wikiApi.fetchWikiPageByTitle (reference, ...args) }))
|
||||||
vi.mock ('@/lib/tags', () => tagsApi)
|
vi.mock ('@/lib/tags', () => tagsApi)
|
||||||
vi.mock ('@/lib/posts', () => postsApi)
|
vi.mock ('@/lib/posts', () => postsApi)
|
||||||
vi.mock ('@/lib/help', () => ({ getHelpTitleIndex: vi.fn () }))
|
vi.mock ('@/lib/help', () => ({ getHelpTitleIndex: vi.fn () }))
|
||||||
@@ -63,15 +69,15 @@ describe ('WikiDetailPage', () => {
|
|||||||
vi.mocked (getHelpTitleIndex).mockReturnValueOnce (pending)
|
vi.mocked (getHelpTitleIndex).mockReturnValueOnce (pending)
|
||||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: '既存タグ', hasWiki: false }))
|
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: '既存タグ', hasWiki: false }))
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<Routes><Route path="/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
<Routes><Route path="/ja/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
||||||
{ route: `/wiki/${ encodeURIComponent ('既存タグ') }` })
|
{ route: `/ja/wiki/${ encodeURIComponent ('既存タグ') }` })
|
||||||
await waitFor (() => expect (getHelpTitleIndex).toHaveBeenCalled ())
|
await waitFor (() => expect (getHelpTitleIndex).toHaveBeenCalled ())
|
||||||
expect (screen.queryByText ('404')).not.toBeInTheDocument ()
|
expect (screen.queryByText ('404')).not.toBeInTheDocument ()
|
||||||
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
||||||
.not.toBeInTheDocument ()
|
.not.toBeInTheDocument ()
|
||||||
await act (async () => finish ({ }))
|
await act (async () => finish ({ }))
|
||||||
expect (await screen.findByRole ('link', { name: '新規作成してください' }))
|
expect (await screen.findByRole ('link', { name: '新規作成してください' }))
|
||||||
.toHaveAttribute ('href', `/wiki/new?title=${ encodeURIComponent ('既存タグ') }`)
|
.toHaveAttribute ('href', `/ja/wiki/new?title=${ encodeURIComponent ('既存タグ') }`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('creates a tag description through the existing form and displays its content', async () => {
|
it ('creates a tag description through the existing form and displays its content', async () => {
|
||||||
@@ -79,13 +85,13 @@ describe ('WikiDetailPage', () => {
|
|||||||
resourceType: 'tag_description' })
|
resourceType: 'tag_description' })
|
||||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: page.title, hasWiki: false }))
|
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: page.title, hasWiki: false }))
|
||||||
vi.mocked (apiPost).mockResolvedValueOnce (page)
|
vi.mocked (apiPost).mockResolvedValueOnce (page)
|
||||||
const path = `/wiki/${ encodeURIComponent (page.title) }`
|
const path = `/ja/wiki/${ encodeURIComponent (page.title) }`
|
||||||
const { container, queryClient } = renderWithProviders (
|
const { container, queryClient } = renderWithProviders (
|
||||||
<>
|
<>
|
||||||
<Location/>
|
<Location/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/wiki/new" element={<WikiNewPage user={buildUser ({ role: 'member' })}/>}/>
|
<Route path="/ja/wiki/new" element={<WikiNewPage user={buildUser ({ role: 'member' })}/>}/>
|
||||||
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
|
<Route path="/ja/wiki/:title" element={<WikiDetailPage/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>, { route: path })
|
</>, { route: path })
|
||||||
fireEvent.click (await screen.findByRole ('link', { name: '新規作成してください' }))
|
fireEvent.click (await screen.findByRole ('link', { name: '新規作成してください' }))
|
||||||
@@ -108,11 +114,10 @@ describe ('WikiDetailPage', () => {
|
|||||||
expect (form.get ('title')).toBe (page.title)
|
expect (form.get ('title')).toBe (page.title)
|
||||||
expect (form.get ('body')).toBe (page.body)
|
expect (form.get ('body')).toBe (page.body)
|
||||||
expect (apiPost).toHaveBeenCalledWith ('/wiki', form,
|
expect (apiPost).toHaveBeenCalledWith ('/wiki', form,
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
{ locale: 'ja', headers: { 'Content-Type': 'multipart/form-data' } })
|
||||||
expect (screen.queryByText ('404')).not.toBeInTheDocument ()
|
expect (screen.queryByText ('404')).not.toBeInTheDocument ()
|
||||||
expect (queryClient.getQueryData (wikiKeys.show (page.title, { }))).toEqual (page)
|
expect (queryClient.getQueryData (wikiKeys.show (page.title, { }))).toEqual (page)
|
||||||
await waitFor (() => expect (queryClient.getQueryData (wikiKeys.index ({ })))
|
expect (queryClient.getQueryState (wikiKeys.index ({ }))?.isInvalidated).toBe (true)
|
||||||
.toEqual ([page]))
|
|
||||||
await waitFor (() => expect (queryClient.getQueryData (tagsKeys.show (page.title)))
|
await waitFor (() => expect (queryClient.getQueryData (tagsKeys.show (page.title)))
|
||||||
.toMatchObject ({ hasWiki: true }))
|
.toMatchObject ({ hasWiki: true }))
|
||||||
expect (getHelpTitleIndex).toHaveBeenCalledTimes (1)
|
expect (getHelpTitleIndex).toHaveBeenCalledTimes (1)
|
||||||
@@ -121,7 +126,7 @@ describe ('WikiDetailPage', () => {
|
|||||||
it ('keeps failed saves on the form without changing cached Wiki or tag state', async () => {
|
it ('keeps failed saves on the form without changing cached Wiki or tag state', async () => {
|
||||||
const title = '既存タグ'
|
const title = '既存タグ'
|
||||||
const tag = buildTag ({ name: title, hasWiki: false })
|
const tag = buildTag ({ name: title, hasWiki: false })
|
||||||
const path = `/wiki/new?title=${ encodeURIComponent (title) }`
|
const path = `/ja/wiki/new?title=${ encodeURIComponent (title) }`
|
||||||
vi.mocked (apiPost).mockRejectedValueOnce (new Error ('save failed'))
|
vi.mocked (apiPost).mockRejectedValueOnce (new Error ('save failed'))
|
||||||
const { queryClient } = renderWithProviders (
|
const { queryClient } = renderWithProviders (
|
||||||
<><Location/><WikiNewPage user={buildUser ({ role: 'member' })}/></>, { route: path })
|
<><Location/><WikiNewPage user={buildUser ({ role: 'member' })}/></>, { route: path })
|
||||||
@@ -141,8 +146,8 @@ describe ('WikiDetailPage', () => {
|
|||||||
|
|
||||||
it ('shows NotFound when neither a tag nor a Help title exists', async () => {
|
it ('shows NotFound when neither a tag nor a Help title exists', async () => {
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<Routes><Route path="/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
<Routes><Route path="/ja/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
||||||
{ route: '/wiki/missing' })
|
{ route: '/ja/wiki/missing' })
|
||||||
expect (await screen.findByText ('404')).toBeInTheDocument ()
|
expect (await screen.findByText ('404')).toBeInTheDocument ()
|
||||||
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
||||||
.not.toBeInTheDocument ()
|
.not.toBeInTheDocument ()
|
||||||
@@ -153,8 +158,8 @@ describe ('WikiDetailPage', () => {
|
|||||||
wikiApi.fetchWikiPageByTitle.mockRejectedValue (new Error (message))
|
wikiApi.fetchWikiPageByTitle.mockRejectedValue (new Error (message))
|
||||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: '既存タグ' }))
|
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: '既存タグ' }))
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<Routes><Route path="/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
<Routes><Route path="/ja/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
||||||
{ route: `/wiki/${ encodeURIComponent ('既存タグ') }` })
|
{ route: `/ja/wiki/${ encodeURIComponent ('既存タグ') }` })
|
||||||
expect (await screen.findByRole ('alert')).toHaveTextContent (message)
|
expect (await screen.findByRole ('alert')).toHaveTextContent (message)
|
||||||
expect (getHelpTitleIndex).not.toHaveBeenCalled ()
|
expect (getHelpTitleIndex).not.toHaveBeenCalled ()
|
||||||
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
||||||
@@ -174,9 +179,9 @@ describe ('WikiDetailPage', () => {
|
|||||||
|
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
|
<Route path="/ja/wiki/:title" element={<WikiDetailPage/>}/>
|
||||||
</Routes>,
|
</Routes>,
|
||||||
{ route: '/wiki/%E6%97%A7%E3%82%BF%E3%82%B0' },
|
{ route: '/ja/wiki/%E6%97%A7%E3%82%BF%E3%82%B0' },
|
||||||
)
|
)
|
||||||
|
|
||||||
const marker = await screen.findByText ('(廃止)')
|
const marker = await screen.findByText ('(廃止)')
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ describe ('WikiDiffPage', () => {
|
|||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith (
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
'/wiki/3/diff',
|
'/wiki/3/diff',
|
||||||
{ params: { from: '1', to: '2' } },
|
{ locale: 'ja', params: { from: '1', to: '2' } },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
expect (screen.getByText ('差分対象')).toBeInTheDocument ()
|
expect (await screen.findByText ('差分対象')).toBeInTheDocument ()
|
||||||
expect (screen.getByText ('same')).toBeInTheDocument ()
|
expect (screen.getByText ('same')).toBeInTheDocument ()
|
||||||
expect (screen.getByText ('added line')).toBeInTheDocument ()
|
expect (screen.getByText ('added line')).toBeInTheDocument ()
|
||||||
expect (screen.getByText ('removed line')).toBeInTheDocument ()
|
expect (screen.getByText ('removed line')).toBeInTheDocument ()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
|
|||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import WikiEditPage from '@/pages/wiki/WikiEditPage'
|
import WikiEditPage from '@/pages/wiki/WikiEditPage'
|
||||||
import { buildUser, buildWikiPage } from '@/test/factories'
|
import { buildUser, buildWikiPage } from '@/test/factories'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
@@ -50,9 +51,9 @@ describe ('WikiEditPage', () => {
|
|||||||
renderPage ()
|
renderPage ()
|
||||||
|
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (api.apiGet).toHaveBeenCalledWith ('/wiki/9')
|
expect (api.apiGet).toHaveBeenCalledWith ('/wiki/9', { locale: 'ja' })
|
||||||
})
|
})
|
||||||
expect (screen.getAllByRole ('textbox')[0]).toHaveValue ('既存')
|
expect ((await screen.findAllByRole ('textbox'))[0]).toHaveValue ('既存')
|
||||||
expect (screen.getByLabelText ('本文エディタ')).toHaveValue ('本文')
|
expect (screen.getByLabelText ('本文エディタ')).toHaveValue ('本文')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -71,12 +72,13 @@ describe ('WikiEditPage', () => {
|
|||||||
expect (api.apiPut).toHaveBeenCalledWith (
|
expect (api.apiPut).toHaveBeenCalledWith (
|
||||||
'/wiki/9',
|
'/wiki/9',
|
||||||
expect.any (FormData),
|
expect.any (FormData),
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } },
|
{ locale: 'ja', headers: { 'Content-Type': 'multipart/form-data' } },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
const formData = api.apiPut.mock.calls[0]?.[1] as FormData
|
||||||
expect (formData.get ('title')).toBe ('更新済み')
|
expect (formData.get ('title')).toBe ('更新済み')
|
||||||
expect (formData.get ('body')).toBe ('更新本文')
|
expect (formData.get ('body')).toBe ('更新本文')
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ title: '投稿成功!' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].title)).toBe ('投稿成功!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const renderWiki = (title: string) => {
|
|||||||
<Location/>
|
<Location/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
|
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
|
||||||
<Route path="/help/:identifier" element={<HelpDetailPage/>}/>
|
<Route path="/ja/help/:identifier" element={<HelpDetailPage/>}/>
|
||||||
<Route path="/404" element={<div>unexpected redirect</div>}/>
|
<Route path="/404" element={<div>unexpected redirect</div>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>, { route })
|
</>, { route })
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe ('WikiHistoryPage', () => {
|
|||||||
const link = await screen.findByRole ('link', { name: '旧タグ' })
|
const link = await screen.findByRole ('link', { name: '旧タグ' })
|
||||||
const marker = screen.getByText ('(廃止)')
|
const marker = screen.getByText ('(廃止)')
|
||||||
|
|
||||||
expect (link).toHaveAttribute ('href', '/wiki/%E6%97%A7%E3%82%BF%E3%82%B0?version=2')
|
expect (link).toHaveAttribute ('href', '/ja/wiki/3?version=2')
|
||||||
expect (marker.closest ('a')).toBeNull ()
|
expect (marker.closest ('a')).toBeNull ()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { textContentOf } from '@/test/text'
|
||||||
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
||||||
import { buildUser, buildWikiPage } from '@/test/factories'
|
import { buildUser, buildWikiPage } from '@/test/factories'
|
||||||
import { renderWithProviders } from '@/test/render'
|
import { renderWithProviders } from '@/test/render'
|
||||||
@@ -50,12 +51,13 @@ describe ('WikiNewPage', () => {
|
|||||||
expect (api.apiPost).toHaveBeenCalledWith (
|
expect (api.apiPost).toHaveBeenCalledWith (
|
||||||
'/wiki',
|
'/wiki',
|
||||||
expect.any (FormData),
|
expect.any (FormData),
|
||||||
{ headers: { 'Content-Type': 'multipart/form-data' } },
|
{ locale: 'ja', headers: { 'Content-Type': 'multipart/form-data' } },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const formData = api.apiPost.mock.calls[0]?.[1] as FormData
|
const formData = api.apiPost.mock.calls[0]?.[1] as FormData
|
||||||
expect (formData.get ('title')).toBe ('下書き')
|
expect (formData.get ('title')).toBe ('下書き')
|
||||||
expect (formData.get ('body')).toBe ('本文')
|
expect (formData.get ('body')).toBe ('本文')
|
||||||
expect (toastApi.toast).toHaveBeenCalledWith ({ title: '投稿成功!' })
|
expect (toastApi.toast).toHaveBeenCalledTimes (1)
|
||||||
|
expect (textContentOf (toastApi.toast.mock.calls[0][0].title)).toBe ('投稿成功!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ describe ('Wiki resource identity', () => {
|
|||||||
vi.mocked (apiGet).mockResolvedValueOnce ([buildWikiPage ({ title: '虹夏' })])
|
vi.mocked (apiGet).mockResolvedValueOnce ([buildWikiPage ({ title: '虹夏' })])
|
||||||
renderWithProviders (<WikiSearchPage/>)
|
renderWithProviders (<WikiSearchPage/>)
|
||||||
expect (await screen.findByRole ('link', { name: '虹夏' }))
|
expect (await screen.findByRole ('link', { name: '虹夏' }))
|
||||||
.toHaveAttribute ('href', '/wiki/%E8%99%B9%E5%A4%8F')
|
.toHaveAttribute ('href', '/ja/wiki/%E8%99%B9%E5%A4%8F')
|
||||||
expect (apiGet).toHaveBeenCalledWith ('/wiki', { params: { title: '' } })
|
expect (apiGet).toHaveBeenCalledWith ('/wiki', { locale: 'ja', params: { title: '' } })
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('searches by title on submit', async () => {
|
it ('searches by title on submit', async () => {
|
||||||
@@ -28,7 +28,7 @@ describe ('Wiki resource identity', () => {
|
|||||||
fireEvent.change (screen.getAllByRole ('textbox')[0], { target: { value: '検索' } })
|
fireEvent.change (screen.getAllByRole ('textbox')[0], { target: { value: '検索' } })
|
||||||
fireEvent.click (screen.getByRole ('button', { name: '検索' }))
|
fireEvent.click (screen.getByRole ('button', { name: '検索' }))
|
||||||
await waitFor (() => expect (apiGet).toHaveBeenLastCalledWith (
|
await waitFor (() => expect (apiGet).toHaveBeenLastCalledWith (
|
||||||
'/wiki', { params: { title: '検索' } }))
|
'/wiki', { locale: 'ja', params: { title: '検索' } }))
|
||||||
expect (await screen.findByRole ('link', { name: '検索結果' })).toBeInTheDocument ()
|
expect (await screen.findByRole ('link', { name: '検索結果' })).toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ describe ('Wiki resource identity', () => {
|
|||||||
|
|
||||||
await screen.findByRole ('link', { name: '説明' })
|
await screen.findByRole ('link', { name: '説明' })
|
||||||
expect (screen.getByRole ('link', { name: 'タググループ:一覧' }))
|
expect (screen.getByRole ('link', { name: 'タググループ:一覧' }))
|
||||||
.toHaveAttribute ('href', `/wiki/${ encodeURIComponent (group.title) }`)
|
.toHaveAttribute ('href', `/ja/wiki/${ encodeURIComponent (group.title) }`)
|
||||||
await waitFor (() => {
|
await waitFor (() => {
|
||||||
expect (errors.mock.calls.some (args => args.some (value =>
|
expect (errors.mock.calls.some (args => args.some (value =>
|
||||||
String (value).includes ('same key')))).toBe (false)
|
String (value).includes ('same key')))).toBe (false)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const transformMdx = async (source: string, suffix = '.mdx') => {
|
|||||||
const plugin = plugins.find (entry => entry.name === '@mdx-js/rollup')
|
const plugin = plugins.find (entry => entry.name === '@mdx-js/rollup')
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
throw new Error ('The application MDX compiler is not registered')
|
throw new Error ('The application MDX compiler is not registered')
|
||||||
return plugin.transform.call (plugin, source, `/test/help-table${suffix}`)
|
return await plugin.transform.call (plugin, source, `/test/help-table${suffix}`) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { UnsavedChangesGuardProvider } from '@/lib/useUnsavedChangesGuard'
|
|||||||
import type { ReactElement, ReactNode } from 'react'
|
import type { ReactElement, ReactNode } from 'react'
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
route?: string
|
route?: string
|
||||||
}
|
queryClient?: QueryClient }
|
||||||
|
|
||||||
const TestContentContext = createContext<ReactNode> (null)
|
const TestContentContext = createContext<ReactNode> (null)
|
||||||
|
|
||||||
@@ -34,10 +34,8 @@ export const renderWithProviders = (
|
|||||||
ui: ReactElement,
|
ui: ReactElement,
|
||||||
options: Options = {},
|
options: Options = {},
|
||||||
) => {
|
) => {
|
||||||
const queryClient = new QueryClient ({
|
const queryClient = options.queryClient ?? new QueryClient ({
|
||||||
defaultOptions: {
|
defaultOptions: { queries: { retry: false } } })
|
||||||
queries: { retry: false } },
|
|
||||||
})
|
|
||||||
|
|
||||||
const Wrapper = ({ children }: { children: ReactNode }) => {
|
const Wrapper = ({ children }: { children: ReactNode }) => {
|
||||||
const [router] = useState (() => createMemoryRouter ([{
|
const [router] = useState (() => createMemoryRouter ([{
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { renderToStaticMarkup } from 'react-dom/server'
|
||||||
|
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
export const textContentOf = (node: ReactNode): string => {
|
||||||
|
const container = document.createElement ('div')
|
||||||
|
container.innerHTML = renderToStaticMarkup (<>{node}</>)
|
||||||
|
return container.textContent ?? ''
|
||||||
|
}
|
||||||
@@ -7,5 +7,6 @@ export default mergeConfig (
|
|||||||
viteConfig,
|
viteConfig,
|
||||||
defineConfig ({
|
defineConfig ({
|
||||||
test: { environment: 'jsdom',
|
test: { environment: 'jsdom',
|
||||||
|
exclude: ['**/node_modules/**', '**/dist/**', 'server.ssr.test.mjs'],
|
||||||
setupFiles: './src/test/setup.ts',
|
setupFiles: './src/test/setup.ts',
|
||||||
globals: false } }))
|
globals: false } }))
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする