diff --git a/spec/models/grid_artifact_spec.rb b/spec/models/grid_artifact_spec.rb index bb1d0c4..7d53a6d 100644 --- a/spec/models/grid_artifact_spec.rb +++ b/spec/models/grid_artifact_spec.rb @@ -6,6 +6,7 @@ RSpec.describe GridArtifact, type: :model do describe 'associations' do it { is_expected.to belong_to(:grid_character) } it { is_expected.to belong_to(:artifact) } + it { is_expected.to belong_to(:collection_artifact).optional } end describe 'validations' do @@ -93,4 +94,99 @@ RSpec.describe GridArtifact, type: :model do expect(GridArtifact).to respond_to(:amoeba_block) end end + + describe 'Collection Sync' do + let(:user) { create(:user) } + let(:grid_character) { create(:grid_character) } + let(:artifact) { create(:artifact) } + let(:collection_artifact) do + create(:collection_artifact, + user: user, + artifact: artifact, + element: 2, + level: 4, + skill1: { 'modifier' => 1, 'strength' => 10 }, + skill2: { 'modifier' => 2, 'strength' => 5 }, + skill3: {}, + skill4: {}, + reroll_slot: 3) + end + + describe '#sync_from_collection!' do + context 'when collection_artifact is linked' do + let(:linked_grid_artifact) do + create(:grid_artifact, + grid_character: grid_character, + artifact: artifact, + collection_artifact: collection_artifact, + element: 1, + level: 2, + skill1: {}, skill2: {}, skill3: {}, skill4: {}) + end + + it 'copies customizations from collection' do + expect(linked_grid_artifact.sync_from_collection!).to be true + linked_grid_artifact.reload + + expect(linked_grid_artifact.element).to eq(2) + expect(linked_grid_artifact.level).to eq(4) + expect(linked_grid_artifact.skill1).to eq({ 'modifier' => 1, 'strength' => 10 }) + expect(linked_grid_artifact.reroll_slot).to eq(3) + end + end + + context 'when no collection_artifact is linked' do + let(:unlinked_grid_artifact) do + create(:grid_artifact, + grid_character: grid_character, + artifact: artifact, + element: 1, + level: 2, + skill1: {}, skill2: {}, skill3: {}, skill4: {}) + end + + it 'returns false' do + expect(unlinked_grid_artifact.sync_from_collection!).to be false + end + end + end + + describe '#out_of_sync?' do + context 'when collection_artifact is linked' do + let(:linked_grid_artifact) do + create(:grid_artifact, + grid_character: grid_character, + artifact: artifact, + collection_artifact: collection_artifact, + element: 1, + level: 2, + skill1: {}, skill2: {}, skill3: {}, skill4: {}) + end + + it 'returns true when values differ' do + expect(linked_grid_artifact.out_of_sync?).to be true + end + + it 'returns false after sync' do + linked_grid_artifact.sync_from_collection! + expect(linked_grid_artifact.out_of_sync?).to be false + end + end + + context 'when no collection_artifact is linked' do + let(:unlinked_grid_artifact) do + create(:grid_artifact, + grid_character: grid_character, + artifact: artifact, + element: 1, + level: 2, + skill1: {}, skill2: {}, skill3: {}, skill4: {}) + end + + it 'returns false' do + expect(unlinked_grid_artifact.out_of_sync?).to be false + end + end + end + end end diff --git a/spec/models/grid_characters_spec.rb b/spec/models/grid_characters_spec.rb index efbdf82..d535bee 100644 --- a/spec/models/grid_characters_spec.rb +++ b/spec/models/grid_characters_spec.rb @@ -16,6 +16,7 @@ RSpec.describe GridCharacter, type: :model do it { is_expected.to belong_to(:character) } it { is_expected.to belong_to(:party) } it { is_expected.to belong_to(:awakening).optional } + it { is_expected.to belong_to(:collection_character).optional } # Use the canonical "Balanced" awakening already loaded from CSV. before(:all) do @@ -163,4 +164,101 @@ RSpec.describe GridCharacter, type: :model do end end end + + describe 'Collection Sync' do + let(:user) { create(:user) } + let(:collection_character) do + create(:collection_character, + user: user, + character: character, + uncap_level: 5, + transcendence_step: 3, + perpetuity: true, + ring1: { 'modifier' => '1', 'strength' => 1500 }, + ring2: { 'modifier' => '2', 'strength' => 750 }, + ring3: { 'modifier' => nil, 'strength' => nil }, + ring4: { 'modifier' => nil, 'strength' => nil }, + earring: { 'modifier' => '3', 'strength' => 20 }, + awakening: @balanced_awakening, + awakening_level: 7) + end + + describe '#sync_from_collection!' do + context 'when collection_character is linked' do + before do + character.update!(ulb: true) # Enable transcendence + @grid_char = create(:grid_character, + valid_attributes.merge( + collection_character: collection_character, + uncap_level: 3, + transcendence_step: 0 + )) + end + + it 'copies all customizations from collection' do + expect(@grid_char.sync_from_collection!).to be true + @grid_char.reload + + expect(@grid_char.uncap_level).to eq(5) + expect(@grid_char.transcendence_step).to eq(3) + expect(@grid_char.perpetuity).to be true + expect(@grid_char.ring1).to eq({ 'modifier' => '1', 'strength' => 1500 }) + expect(@grid_char.ring2).to eq({ 'modifier' => '2', 'strength' => 750 }) + expect(@grid_char.awakening_level).to eq(7) + end + end + + context 'when no collection_character is linked' do + before do + @grid_char = create(:grid_character, valid_attributes) + end + + it 'returns false and does not change anything' do + original_uncap = @grid_char.uncap_level + expect(@grid_char.sync_from_collection!).to be false + expect(@grid_char.uncap_level).to eq(original_uncap) + end + end + end + + describe '#out_of_sync?' do + context 'when collection_character is linked' do + before do + character.update!(ulb: true) + @grid_char = create(:grid_character, + valid_attributes.merge(collection_character: collection_character)) + end + + it 'returns true when uncap_level differs' do + @grid_char.update!(uncap_level: 4) + expect(@grid_char.out_of_sync?).to be true + end + + it 'returns true when transcendence_step differs' do + @grid_char.update!(transcendence_step: 1) + expect(@grid_char.out_of_sync?).to be true + end + + it 'returns true when perpetuity differs' do + @grid_char.update!(perpetuity: false) + expect(@grid_char.out_of_sync?).to be true + end + + it 'returns false when all values match' do + @grid_char.sync_from_collection! + expect(@grid_char.out_of_sync?).to be false + end + end + + context 'when no collection_character is linked' do + before do + @grid_char = create(:grid_character, valid_attributes) + end + + it 'returns false' do + expect(@grid_char.out_of_sync?).to be false + end + end + end + end end diff --git a/spec/models/grid_summons_spec.rb b/spec/models/grid_summons_spec.rb index a16a2cf..2a783f6 100644 --- a/spec/models/grid_summons_spec.rb +++ b/spec/models/grid_summons_spec.rb @@ -18,6 +18,13 @@ RSpec.describe GridSummon, type: :model do expect(association).not_to be_nil expect(association.macro).to eq(:belongs_to) end + + it 'belongs to a collection_summon (optional)' do + association = described_class.reflect_on_association(:collection_summon) + expect(association).not_to be_nil + expect(association.macro).to eq(:belongs_to) + expect(association.options[:optional]).to be true + end end describe 'validations' do @@ -232,4 +239,97 @@ RSpec.describe GridSummon, type: :model do expect(grid_summon.blueprint).to eq(GridSummonBlueprint) end end + + describe 'Collection Sync' do + let(:party) { create(:party) } + let(:user) { create(:user) } + let(:summon) { Summon.find_by!(granblue_id: '2040433000') } + let(:collection_summon) do + create(:collection_summon, + user: user, + summon: summon, + uncap_level: 5, + transcendence_step: 2) + end + + describe '#sync_from_collection!' do + context 'when collection_summon is linked' do + let(:linked_grid_summon) do + # Ensure summon has transcendence for valid transcendence_step + summon.update!(transcendence: true, ulb: true, flb: true) + create(:grid_summon, + party: party, + summon: summon, + position: 1, + collection_summon: collection_summon, + uncap_level: 3, + transcendence_step: 0) + end + + it 'copies customizations from collection' do + expect(linked_grid_summon.sync_from_collection!).to be true + linked_grid_summon.reload + + expect(linked_grid_summon.uncap_level).to eq(5) + expect(linked_grid_summon.transcendence_step).to eq(2) + end + end + + context 'when no collection_summon is linked' do + let(:unlinked_grid_summon) do + build(:grid_summon, + party: party, + summon: summon, + position: 1, + uncap_level: 3, + transcendence_step: 0) + end + + it 'returns false' do + unlinked_grid_summon.save! + expect(unlinked_grid_summon.sync_from_collection!).to be false + end + end + end + + describe '#out_of_sync?' do + context 'when collection_summon is linked' do + let(:linked_grid_summon) do + summon.update!(transcendence: true, ulb: true, flb: true) + create(:grid_summon, + party: party, + summon: summon, + position: 1, + collection_summon: collection_summon, + uncap_level: 3, + transcendence_step: 0) + end + + it 'returns true when values differ' do + expect(linked_grid_summon.out_of_sync?).to be true + end + + it 'returns false after sync' do + linked_grid_summon.sync_from_collection! + expect(linked_grid_summon.out_of_sync?).to be false + end + end + + context 'when no collection_summon is linked' do + let(:unlinked_grid_summon) do + build(:grid_summon, + party: party, + summon: summon, + position: 1, + uncap_level: 3, + transcendence_step: 0) + end + + it 'returns false' do + unlinked_grid_summon.save! + expect(unlinked_grid_summon.out_of_sync?).to be false + end + end + end + end end diff --git a/spec/models/grid_weapon_spec.rb b/spec/models/grid_weapon_spec.rb index 9ccd74a..da9864c 100644 --- a/spec/models/grid_weapon_spec.rb +++ b/spec/models/grid_weapon_spec.rb @@ -13,6 +13,7 @@ RSpec.describe GridWeapon, type: :model do it { is_expected.to belong_to(:weapon_key3).optional } it { is_expected.to belong_to(:weapon_key4).optional } it { is_expected.to belong_to(:awakening).optional } + it { is_expected.to belong_to(:collection_weapon).optional } # Setup common test objects using FactoryBot. let(:party) { create(:party) } @@ -134,4 +135,71 @@ RSpec.describe GridWeapon, type: :model do expect(grid_weapon.blueprint).to eq(GridWeaponBlueprint) end end + + describe 'Collection Sync' do + let(:user) { create(:user) } + let(:collection_weapon) do + create(:collection_weapon, + user: user, + weapon: weapon, + uncap_level: 5, + transcendence_step: 0, + element: 2) + end + + describe '#sync_from_collection!' do + context 'when collection_weapon is linked' do + let(:linked_grid_weapon) do + create(:grid_weapon, + party: party, + weapon: weapon, + position: 0, + collection_weapon: collection_weapon, + uncap_level: 3) + end + + it 'copies customizations from collection' do + expect(linked_grid_weapon.sync_from_collection!).to be true + linked_grid_weapon.reload + + expect(linked_grid_weapon.uncap_level).to eq(5) + expect(linked_grid_weapon.element).to eq(2) + end + end + + context 'when no collection_weapon is linked' do + it 'returns false' do + expect(grid_weapon.sync_from_collection!).to be false + end + end + end + + describe '#out_of_sync?' do + context 'when collection_weapon is linked' do + let(:linked_grid_weapon) do + create(:grid_weapon, + party: party, + weapon: weapon, + position: 0, + collection_weapon: collection_weapon, + uncap_level: 3) + end + + it 'returns true when values differ' do + expect(linked_grid_weapon.out_of_sync?).to be true + end + + it 'returns false after sync' do + linked_grid_weapon.sync_from_collection! + expect(linked_grid_weapon.out_of_sync?).to be false + end + end + + context 'when no collection_weapon is linked' do + it 'returns false' do + expect(grid_weapon.out_of_sync?).to be false + end + end + end + end end