$overrides ResourceLocation factory attributes.
+ */
+function attachLocation(
+ ResourceData $resourceData,
+ string $system,
+ string $typeName,
+ string $locationName,
+ string $groupName = 'SpaceShip_Mineables',
+ string $resourceKind = 'mineable',
+ array $overrides = [],
+): ResourceLocation {
+ $test = test();
+
+ $starmapLocation = StarmapLocation::factory()->create();
+ $locationData = StarmapLocationData::factory()->create([
+ 'starmap_location_id' => $starmapLocation->id,
+ 'game_version_id' => $test->defaultVersion->id,
+ 'name' => $locationName,
+ 'type_name' => $typeName,
+ 'system' => $system,
+ ]);
+
+ $resourceLocation = ResourceLocation::factory()->create(array_merge([
+ 'resource_data_id' => $resourceData->id,
+ 'resource_kind' => $resourceKind,
+ 'group_name' => $groupName,
+ ], $overrides));
+
+ $resourceLocation->starmapLocationData()->attach($locationData->id);
+
+ return $resourceLocation;
+}
diff --git a/tests/Support/ShipMatrixReferenceDataFactory.php b/tests/Support/ShipMatrixReferenceDataFactory.php
new file mode 100644
index 000000000..b0593b940
--- /dev/null
+++ b/tests/Support/ShipMatrixReferenceDataFactory.php
@@ -0,0 +1,48 @@
+productionStatus etc.
+ */
+ function createShipMatrixReferenceData(): void
+ {
+ $test = test();
+
+ $test->productionStatus = ProductionStatus::query()->create([
+ 'name' => 'In Production',
+ 'slug' => 'in-production',
+ ]);
+
+ $test->productionNote = ProductionNote::query()->create([
+ 'translation' => ['en' => 'None'],
+ ]);
+
+ $test->size = ShipSize::query()->create([
+ 'slug' => 'small',
+ 'size' => 'Small',
+ ]);
+
+ $test->type = ShipType::query()->create([
+ 'slug' => 'fighter',
+ 'type' => 'Fighter',
+ ]);
+
+ $test->shipMatrixManufacturer = ShipMatrixManufacturer::query()->create([
+ 'cig_id' => 1,
+ 'name' => 'Anvil Aerospace',
+ 'name_short' => 'ANV',
+ 'slug' => 'anvil-aerospace',
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/ImportItemPricesEnrichmentTest.php b/tests/Unit/Console/Commands/ImportItemPricesEnrichmentTest.php
new file mode 100644
index 000000000..e8add777f
--- /dev/null
+++ b/tests/Unit/Console/Commands/ImportItemPricesEnrichmentTest.php
@@ -0,0 +1,131 @@
+version = GameVersion::factory()->create(['is_default' => true]);
+});
+
+it('dispatches item enrichment batch for items with prices', function (): void {
+ $item = Item::factory()->create();
+ ItemData::factory()->create([
+ 'item_id' => $item->id,
+ 'game_version_id' => $this->version->id,
+ 'uex_prices' => [['terminal_code' => 'T1', 'terminal_name' => 'T', 'price_buy' => 100, 'price_sell' => 50, 'date_updated' => '2024-01-01T00:00:00+00:00']],
+ ]);
+
+ Bus::fake();
+ Http::fake(['api.uexcorp.uk/*' => Http::response(['data' => []])]);
+
+ ImportItemPrices::dispatchEnrichmentBatches($this->version, 50, null);
+
+ Bus::assertBatchCount(1);
+
+ Bus::assertBatched(function ($batch): bool {
+ return $batch->jobs->count() === 1
+ && $batch->jobs->first() instanceof EnrichItemPricesJob;
+ });
+});
+
+it('dispatches vehicle enrichment batch for vehicles with prices', function (): void {
+ $vehicle = Vehicle::factory()->create();
+ VehicleData::factory()->create([
+ 'vehicle_id' => $vehicle->id,
+ 'game_version_id' => $this->version->id,
+ 'uex_purchase_prices' => [['terminal_code' => 'T1', 'terminal_name' => 'T', 'price_buy' => 1000000, 'date_updated' => '2024-01-01T00:00:00+00:00']],
+ ]);
+
+ Bus::fake();
+ Http::fake(['api.uexcorp.uk/*' => Http::response(['data' => []])]);
+
+ ImportItemPrices::dispatchEnrichmentBatches($this->version, 50, null);
+
+ Bus::assertBatchCount(1);
+
+ Bus::assertBatched(function ($batch): bool {
+ return $batch->jobs->count() === 1
+ && $batch->jobs->first() instanceof EnrichVehiclePricesJob;
+ });
+});
+
+it('chunks item enrichment jobs correctly', function (): void {
+ $items = Item::factory()->count(5)->create();
+ foreach ($items as $item) {
+ ItemData::factory()->create([
+ 'item_id' => $item->id,
+ 'game_version_id' => $this->version->id,
+ 'uex_prices' => [['terminal_code' => 'T', 'terminal_name' => 'T', 'price_buy' => 100, 'price_sell' => 50, 'date_updated' => '2024-01-01T00:00:00+00:00']],
+ ]);
+ }
+
+ Bus::fake();
+ Http::fake(['api.uexcorp.uk/*' => Http::response(['data' => []])]);
+
+ ImportItemPrices::dispatchEnrichmentBatches($this->version, 2, null);
+
+ Bus::assertBatchCount(1);
+
+ Bus::assertBatched(fn ($batch): bool => $batch->jobs->count() === 3);
+});
+
+it('chunks vehicle enrichment jobs correctly', function (): void {
+ $vehicles = Vehicle::factory()->count(5)->create();
+ foreach ($vehicles as $vehicle) {
+ VehicleData::factory()->create([
+ 'vehicle_id' => $vehicle->id,
+ 'game_version_id' => $this->version->id,
+ 'uex_purchase_prices' => [['terminal_code' => 'T', 'terminal_name' => 'T', 'price_buy' => 100, 'date_updated' => '2024-01-01T00:00:00+00:00']],
+ ]);
+ }
+
+ Bus::fake();
+ Http::fake(['api.uexcorp.uk/*' => Http::response(['data' => []])]);
+
+ ImportItemPrices::dispatchEnrichmentBatches($this->version, 2, null);
+
+ Bus::assertBatchCount(1);
+
+ Bus::assertBatched(fn ($batch): bool => $batch->jobs->count() === 3);
+});
+
+it('dispatches both item and vehicle enrichment batches', function (): void {
+ $item = Item::factory()->create();
+ ItemData::factory()->create([
+ 'item_id' => $item->id,
+ 'game_version_id' => $this->version->id,
+ 'uex_prices' => [['terminal_code' => 'T', 'terminal_name' => 'T', 'price_buy' => 100, 'price_sell' => 50, 'date_updated' => '2024-01-01T00:00:00+00:00']],
+ ]);
+
+ $vehicle = Vehicle::factory()->create();
+ VehicleData::factory()->create([
+ 'vehicle_id' => $vehicle->id,
+ 'game_version_id' => $this->version->id,
+ 'uex_purchase_prices' => [['terminal_code' => 'T', 'terminal_name' => 'T', 'price_buy' => 1000000, 'date_updated' => '2024-01-01T00:00:00+00:00']],
+ ]);
+
+ Bus::fake();
+ Http::fake(['api.uexcorp.uk/*' => Http::response(['data' => []])]);
+
+ ImportItemPrices::dispatchEnrichmentBatches($this->version, 50, null);
+
+ Bus::assertBatchCount(2);
+});
+
+it('skips enrichment when no prices exist', function (): void {
+ Bus::fake();
+
+ ImportItemPrices::dispatchEnrichmentBatches($this->version, 50, null);
+
+ Bus::assertNothingBatched();
+});
\ No newline at end of file
diff --git a/tests/Unit/GForceResistanceResourceTest.php b/tests/Unit/GForceResistanceExtractionTest.php
similarity index 100%
rename from tests/Unit/GForceResistanceResourceTest.php
rename to tests/Unit/GForceResistanceExtractionTest.php
diff --git a/tests/Unit/Parser/CommLink/LayoutSystemExtractorIntegrationTest.php b/tests/Unit/Parser/CommLink/LayoutSystemExtractorIntegrationTest.php
index 73672c451..e9e556522 100644
--- a/tests/Unit/Parser/CommLink/LayoutSystemExtractorIntegrationTest.php
+++ b/tests/Unit/Parser/CommLink/LayoutSystemExtractorIntegrationTest.php
@@ -10,91 +10,6 @@ function extractedText(string $content): string
return trim((string) preg_replace('/\s+/u', ' ', strip_tags(html_entity_decode($content))));
}
-it('extracts narrative-group content from html', function () {
- $html = <<<'HTML'
-
-
-
-
-
- HTML;
-
- $crawler = new Crawler($html);
- $extractor = new LayoutSystemExtractor($crawler);
- $text = extractedText($extractor->getContent());
-
- expect($text)
- ->toContain('Test Headline')
- ->toContain('Test Byline');
-});
-
-it('extracts illustration content from html', function () {
- $html = <<<'HTML'
-
-
-
- HTML;
-
- $crawler = new Crawler($html);
- $extractor = new LayoutSystemExtractor($crawler);
- $text = extractedText($extractor->getContent());
-
- expect($text)
- ->toContain('By')
- ->toContain('TestArtist');
-});
-
-it('extracts author content from html', function () {
- $html = <<<'HTML'
-
-
-
- HTML;
-
- $crawler = new Crawler($html);
- $extractor = new LayoutSystemExtractor($crawler);
- $text = extractedText($extractor->getContent());
-
- expect($text)
- ->toContain('John Doe')
- ->toContain('Writer');
-});
-
-it('extracts faq content from html', function () {
- $html = <<<'HTML'
-
-
-
- HTML;
-
- $crawler = new Crawler($html);
- $extractor = new LayoutSystemExtractor($crawler);
- $text = extractedText($extractor->getContent());
-
- expect($text)
- ->toContain('Question 1')
- ->toContain('Answer 1');
-});
-
-it('extracts header content from html', function () {
- $html = <<<'HTML'
-
-
- Test Title
- Test Content
-
-
- HTML;
-
- $crawler = new Crawler($html);
- $extractor = new LayoutSystemExtractor($crawler);
- $text = extractedText($extractor->getContent());
-
- expect($text)
- ->toContain('Test Title')
- ->toContain('Test Content');
-});
-
it('extracts all 5 new traits from complex html', function () {
$html = <<<'HTML'
@@ -143,4 +58,4 @@ function extractedText(string $content): string
->and($text)->toContain('Layout Content')
->and($content)->not->toContain('and($content)->not->toContain('id="layout-system"');
-});
+});
\ No newline at end of file
diff --git a/tests/Unit/Parser/CommLink/ParserIntegrationTest.php b/tests/Unit/Parser/CommLink/ParserIntegrationTest.php
index 16ea75149..ea7638ffc 100644
--- a/tests/Unit/Parser/CommLink/ParserIntegrationTest.php
+++ b/tests/Unit/Parser/CommLink/ParserIntegrationTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Feature\Parser\CommLink;
+namespace Tests\Unit\Parser\CommLink;
use App\Services\Parser\CommLink\Content\AlexandriaExtractor;
use App\Services\Parser\CommLink\Content\DefaultExtractor;
diff --git a/tests/Unit/Parser/CommLink/Traits/AlexandriaComponentExtractorTraitTest.php b/tests/Unit/Parser/CommLink/Traits/AlexandriaComponentExtractorTraitTest.php
index 2981450f7..cde1376ca 100644
--- a/tests/Unit/Parser/CommLink/Traits/AlexandriaComponentExtractorTraitTest.php
+++ b/tests/Unit/Parser/CommLink/Traits/AlexandriaComponentExtractorTraitTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Feature\Parser\CommLink\Traits;
+namespace Tests\Unit\Parser\CommLink\Traits;
use App\Services\Parser\CommLink\Content\Traits\AlexandriaComponentExtractorTrait;
use Symfony\Component\DomCrawler\Crawler;
diff --git a/tests/Unit/Parser/CommLink/Traits/GFaqExtractorTraitTest.php b/tests/Unit/Parser/CommLink/Traits/GFaqExtractorTraitTest.php
index fb80b49fb..2b8c822a1 100644
--- a/tests/Unit/Parser/CommLink/Traits/GFaqExtractorTraitTest.php
+++ b/tests/Unit/Parser/CommLink/Traits/GFaqExtractorTraitTest.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-namespace Tests\Feature\Parser\CommLink\Traits;
+namespace Tests\Unit\Parser\CommLink\Traits;
use App\Services\Parser\CommLink\Content\Traits\GFaqExtractorTrait;
use Symfony\Component\DomCrawler\Crawler;
diff --git a/tests/Unit/Services/PdqHasherTest.php b/tests/Unit/Services/PdqHasherTest.php
index fbf138459..05dccc579 100644
--- a/tests/Unit/Services/PdqHasherTest.php
+++ b/tests/Unit/Services/PdqHasherTest.php
@@ -21,3 +21,17 @@
->and($result->quality)->toBeGreaterThanOrEqual(0)
->and($result->quality)->toBeLessThanOrEqual(100);
});
+
+it('produces deterministic hashes for identical input', function () {
+ if (! extension_loaded('gd')) {
+ $this->markTestSkipped('GD extension is required for PDQ hashing.');
+ }
+
+ $uploadedFile = UploadedFile::fake()->image('hash.jpg', 8, 8);
+ $contents = file_get_contents($uploadedFile->getPathname());
+
+ $hasher = app(PdqHasher::class);
+
+ expect($hasher->hashContents($contents)->toBitString())
+ ->toBe($hasher->hashContents($contents)->toBitString());
+});
\ No newline at end of file
diff --git a/tests/Unit/Support/Blueprints/BlueprintShowViewDataTest.php b/tests/Unit/Support/Blueprints/BlueprintShowViewDataTest.php
index 71f49a7c6..668bc42fd 100644
--- a/tests/Unit/Support/Blueprints/BlueprintShowViewDataTest.php
+++ b/tests/Unit/Support/Blueprints/BlueprintShowViewDataTest.php
@@ -4,219 +4,201 @@
use App\Support\Blueprints\BlueprintShowViewData;
-it('builds grouped blueprint detail view data', function (): void {
- app('request')->query->set('version', '4.0.0-PTU');
-
- $builder = app(BlueprintShowViewData::class);
- $blueprintUuid = fake()->uuid();
- $outputItemUuid = fake()->uuid();
+/**
+ * Shared blueprint fixture for requirement-groups tests. Each test overrides
+ * only the fields it cares about, keeping the payload DRY.
+ *
+ * @param array $overrides Top-level blueprint keys to merge.
+ * @return array
+ */
+function blueprintFixture(array $overrides = []): array
+{
$laraniteUuid = fake()->uuid();
$aslariteUuid = fake()->uuid();
$stileronUuid = fake()->uuid();
- $page = $builder->build(
- mode: 'detail',
- blueprint: [
- 'uuid' => $blueprintUuid,
- 'key' => 'BP_CRAFT_vgl_utility_light_legs_01_01_01',
- 'output_name' => 'Chiron Legs',
- 'output_class' => 'utility_light_legs',
- 'craft_time_seconds' => 180,
- 'craft_time_label' => '3 minutes',
- 'ingredient_count' => 3,
- 'ingredients' => [
- [
- 'name' => 'Laranite',
- 'resource_type_uuid' => $laraniteUuid,
- ],
- [
- 'name' => 'Aslarite',
- 'resource_type_uuid' => $aslariteUuid,
- ],
- [
- 'name' => 'Stileron',
- 'resource_type_uuid' => $stileronUuid,
- ],
- ],
- 'web_url' => route('web.blueprints.show', [
- 'blueprint' => $blueprintUuid,
- 'version' => '4.0.0-PTU',
- ]),
- 'is_available_by_default' => false,
- 'output' => [
- 'uuid' => $outputItemUuid,
- 'type' => 'Armor',
- 'subtype' => 'Legs',
- 'grade' => '1',
- 'item_web_url' => route('web.items.show', [
- 'item' => $outputItemUuid,
- 'version' => '4.0.0-PTU',
- ]),
- ],
- 'summary_properties' => [
+ $defaultRequirementGroups = [
+ [
+ 'key' => 'ASPECTS',
+ 'name' => '<= PLACEHOLDER =>',
+ 'required_count' => 2,
+ 'children' => [
[
- 'property_key' => 'armor_temperaturemax',
- 'label' => 'Armor Temperature Max',
- 'better_when' => 'higher',
- ],
- [
- 'property_key' => 'armor_damagemitigation',
- 'label' => 'Armor Damage Mitigation',
- 'better_when' => 'higher',
+ 'kind' => 'group',
+ 'key' => 'CASING',
+ 'name' => 'Casing',
+ 'required_count' => 1,
+ 'children' => [
+ [
+ 'kind' => 'resource',
+ 'uuid' => $laraniteUuid,
+ 'name' => 'Laranite',
+ 'quantity_scu' => 0.03,
+ 'min_quality' => 0,
+ ],
+ ],
],
- ],
- 'requirement_groups' => [
[
- 'key' => 'ASPECTS',
- 'name' => '<= PLACEHOLDER =>',
- 'required_count' => 2,
+ 'kind' => 'group',
+ 'key' => 'INSULATIVE LINER',
+ 'name' => 'Insulative Liner',
+ 'required_count' => 1,
+ 'modifiers' => [
+ [
+ 'property_key' => 'armor_temperaturemax',
+ 'quality_range' => ['min' => 0, 'max' => 1000],
+ 'modifier_range' => ['at_min_quality' => 0.8, 'at_max_quality' => 1.2],
+ 'better_when' => 'higher',
+ ],
+ ],
'children' => [
[
- 'kind' => 'group',
- 'key' => 'CASING',
- 'name' => 'Casing',
- 'required_count' => 1,
- 'children' => [
- [
- 'kind' => 'resource',
- 'uuid' => $laraniteUuid,
- 'name' => 'Laranite',
- 'quantity_scu' => 0.03,
- 'min_quality' => 0,
- ],
- ],
+ 'kind' => 'resource',
+ 'uuid' => $aslariteUuid,
+ 'name' => 'Aslarite',
+ 'quantity_scu' => 0.02,
+ 'min_quality' => 0,
],
+ ],
+ ],
+ [
+ 'kind' => 'group',
+ 'key' => 'CASING WEAVE',
+ 'name' => 'Casing Weave',
+ 'required_count' => 1,
+ 'modifiers' => [
[
- 'kind' => 'group',
- 'key' => 'INSULATIVE LINER',
- 'name' => 'Insulative Liner',
- 'required_count' => 1,
- 'modifiers' => [
- [
- 'property_key' => 'armor_temperaturemax',
- 'quality_range' => [
- 'min' => 0,
- 'max' => 1000,
- ],
- 'modifier_range' => [
- 'at_min_quality' => 0.8,
- 'at_max_quality' => 1.2,
- ],
- 'better_when' => 'higher',
- ],
- ],
- 'children' => [
- [
- 'kind' => 'resource',
- 'uuid' => $aslariteUuid,
- 'name' => 'Aslarite',
- 'quantity_scu' => 0.02,
- 'min_quality' => 0,
- ],
- ],
+ 'property_key' => 'armor_damagemitigation',
+ 'quality_range' => ['min' => 0, 'max' => 1000],
+ 'modifier_range' => ['at_min_quality' => 0.95, 'at_max_quality' => 1.05],
+ 'better_when' => 'higher',
],
+ ],
+ 'children' => [
[
- 'kind' => 'group',
- 'key' => 'CASING WEAVE',
- 'name' => 'Casing Weave',
- 'required_count' => 1,
- 'modifiers' => [
- [
- 'property_key' => 'armor_damagemitigation',
- 'quality_range' => [
- 'min' => 0,
- 'max' => 1000,
- ],
- 'modifier_range' => [
- 'at_min_quality' => 0.95,
- 'at_max_quality' => 1.05,
- ],
- 'better_when' => 'higher',
- ],
- ],
- 'children' => [
- [
- 'kind' => 'resource',
- 'uuid' => $stileronUuid,
- 'name' => 'Stileron',
- 'quantity_scu' => 0.03,
- 'min_quality' => 0,
- ],
- ],
+ 'kind' => 'resource',
+ 'uuid' => $stileronUuid,
+ 'name' => 'Stileron',
+ 'quantity_scu' => 0.03,
+ 'min_quality' => 0,
],
],
],
],
- 'unlocking_missions_grouped' => [],
+ ],
+ ];
+
+ $blueprintUuid = fake()->uuid();
+ $outputItemUuid = fake()->uuid();
+
+ $defaults = [
+ 'uuid' => $blueprintUuid,
+ 'key' => 'BP_CRAFT_vgl_utility_light_legs_01_01_01',
+ 'output_name' => 'Chiron Legs',
+ 'output_class' => 'utility_light_legs',
+ 'craft_time_seconds' => 180,
+ 'craft_time_label' => '3 minutes',
+ 'ingredient_count' => 3,
+ 'ingredients' => [
+ ['name' => 'Laranite', 'resource_type_uuid' => $laraniteUuid],
+ ['name' => 'Aslarite', 'resource_type_uuid' => $aslariteUuid],
+ ['name' => 'Stileron', 'resource_type_uuid' => $stileronUuid],
+ ],
+ 'web_url' => route('web.blueprints.show', ['blueprint' => $blueprintUuid, 'version' => '4.0.0-PTU']),
+ 'is_available_by_default' => false,
+ 'output' => [
+ 'uuid' => $outputItemUuid,
+ 'type' => 'Armor',
+ 'subtype' => 'Legs',
+ 'grade' => '1',
+ 'item_web_url' => route('web.items.show', ['item' => $outputItemUuid, 'version' => '4.0.0-PTU']),
+ ],
+ 'summary_properties' => [
+ ['property_key' => 'armor_temperaturemax', 'label' => 'Armor Temperature Max', 'better_when' => 'higher'],
+ ['property_key' => 'armor_damagemitigation', 'label' => 'Armor Damage Mitigation', 'better_when' => 'higher'],
+ ],
+ 'requirement_groups' => $defaultRequirementGroups,
+ 'unlocking_missions_grouped' => [],
+ 'aspects' => [
'aspects' => [
- 'aspects' => [
- [
- 'key' => 'CASING',
- 'name' => 'Casing',
- 'required_count' => 1,
- 'selection_group' => ['key' => 'ASPECTS', 'name' => 'Aspects', 'required_count' => 2, 'option_count' => 3],
- 'input' => ['kind' => 'resource', 'uuid' => $laraniteUuid, 'name' => 'Laranite', 'quantity' => null, 'quantity_scu' => 0.03, 'min_quality' => 0, 'web_url' => null],
- 'modifiers' => [],
- 'initial_quality' => 500,
- 'slider_min' => 0,
- 'slider_max' => 1000,
- 'has_modifiers' => false,
- 'has_dynamic_modifiers' => false,
- 'is_selected' => true,
- ],
- [
- 'key' => 'INSULATIVE LINER',
- 'name' => 'Insulative Liner',
- 'required_count' => 1,
- 'selection_group' => ['key' => 'ASPECTS', 'name' => 'Aspects', 'required_count' => 2, 'option_count' => 3],
- 'input' => ['kind' => 'resource', 'uuid' => $aslariteUuid, 'name' => 'Aslarite', 'quantity' => null, 'quantity_scu' => 0.02, 'min_quality' => 0, 'web_url' => null],
- 'modifiers' => [
- ['property_key' => 'armor_temperaturemax', 'quality_range' => ['min' => 0, 'max' => 1000], 'modifier_range' => ['at_min_quality' => 0.8, 'at_max_quality' => 1.2], 'better_when' => 'higher'],
- ],
- 'initial_quality' => 500,
- 'slider_min' => 0,
- 'slider_max' => 1000,
- 'has_modifiers' => true,
- 'has_dynamic_modifiers' => true,
- 'is_selected' => true,
- ],
- [
- 'key' => 'CASING WEAVE',
- 'name' => 'Casing Weave',
- 'required_count' => 1,
- 'selection_group' => ['key' => 'ASPECTS', 'name' => 'Aspects', 'required_count' => 2, 'option_count' => 3],
- 'input' => ['kind' => 'resource', 'uuid' => $stileronUuid, 'name' => 'Stileron', 'quantity' => null, 'quantity_scu' => 0.03, 'min_quality' => 0, 'web_url' => null],
- 'modifiers' => [
- ['property_key' => 'armor_damagemitigation', 'quality_range' => ['min' => 0, 'max' => 1000], 'modifier_range' => ['at_min_quality' => 0.95, 'at_max_quality' => 1.05], 'better_when' => 'higher'],
- ],
- 'initial_quality' => 500,
- 'slider_min' => 0,
- 'slider_max' => 1000,
- 'has_modifiers' => true,
- 'has_dynamic_modifiers' => true,
- 'is_selected' => false,
+ [
+ 'key' => 'CASING',
+ 'name' => 'Casing',
+ 'required_count' => 1,
+ 'selection_group' => ['key' => 'ASPECTS', 'name' => 'Aspects', 'required_count' => 2, 'option_count' => 3],
+ 'input' => ['kind' => 'resource', 'uuid' => $laraniteUuid, 'name' => 'Laranite', 'quantity' => null, 'quantity_scu' => 0.03, 'min_quality' => 0, 'web_url' => null],
+ 'modifiers' => [],
+ 'initial_quality' => 500,
+ 'slider_min' => 0,
+ 'slider_max' => 1000,
+ 'has_modifiers' => false,
+ 'has_dynamic_modifiers' => false,
+ 'is_selected' => true,
+ ],
+ [
+ 'key' => 'INSULATIVE LINER',
+ 'name' => 'Insulative Liner',
+ 'required_count' => 1,
+ 'selection_group' => ['key' => 'ASPECTS', 'name' => 'Aspects', 'required_count' => 2, 'option_count' => 3],
+ 'input' => ['kind' => 'resource', 'uuid' => $aslariteUuid, 'name' => 'Aslarite', 'quantity' => null, 'quantity_scu' => 0.02, 'min_quality' => 0, 'web_url' => null],
+ 'modifiers' => [
+ ['property_key' => 'armor_temperaturemax', 'quality_range' => ['min' => 0, 'max' => 1000], 'modifier_range' => ['at_min_quality' => 0.8, 'at_max_quality' => 1.2], 'better_when' => 'higher'],
],
+ 'initial_quality' => 500,
+ 'slider_min' => 0,
+ 'slider_max' => 1000,
+ 'has_modifiers' => true,
+ 'has_dynamic_modifiers' => true,
+ 'is_selected' => true,
],
- 'aspect_groups' => [
- [
- 'key' => 'ASPECTS',
- 'name' => 'Aspects',
- 'display_name' => null,
- 'required_count' => 2,
- 'option_count' => 3,
- 'is_choice_group' => true,
- 'selected_count' => 2,
- 'aspect_indexes' => [0, 1, 2],
+ [
+ 'key' => 'CASING WEAVE',
+ 'name' => 'Casing Weave',
+ 'required_count' => 1,
+ 'selection_group' => ['key' => 'ASPECTS', 'name' => 'Aspects', 'required_count' => 2, 'option_count' => 3],
+ 'input' => ['kind' => 'resource', 'uuid' => $stileronUuid, 'name' => 'Stileron', 'quantity' => null, 'quantity_scu' => 0.03, 'min_quality' => 0, 'web_url' => null],
+ 'modifiers' => [
+ ['property_key' => 'armor_damagemitigation', 'quality_range' => ['min' => 0, 'max' => 1000], 'modifier_range' => ['at_min_quality' => 0.95, 'at_max_quality' => 1.05], 'better_when' => 'higher'],
],
+ 'initial_quality' => 500,
+ 'slider_min' => 0,
+ 'slider_max' => 1000,
+ 'has_modifiers' => true,
+ 'has_dynamic_modifiers' => true,
+ 'is_selected' => false,
],
- 'has_interactive_aspects' => true,
],
+ 'aspect_groups' => [
+ [
+ 'key' => 'ASPECTS',
+ 'name' => 'Aspects',
+ 'display_name' => null,
+ 'required_count' => 2,
+ 'option_count' => 3,
+ 'is_choice_group' => true,
+ 'selected_count' => 2,
+ 'aspect_indexes' => [0, 1, 2],
+ ],
+ ],
+ 'has_interactive_aspects' => true,
],
- search: [
- 'filters' => [],
- 'results' => [],
- 'result_count' => 0,
- ],
+ ];
+
+ return array_merge($defaults, $overrides);
+}
+
+it('builds grouped blueprint detail view data', function (): void {
+ app('request')->query->set('version', '4.0.0-PTU');
+
+ $blueprint = blueprintFixture();
+ $laraniteUuid = collect($blueprint['ingredients'])->firstWhere('name', 'Laranite')['resource_type_uuid'];
+ $aslariteUuid = collect($blueprint['ingredients'])->firstWhere('name', 'Aslarite')['resource_type_uuid'];
+ $stileronUuid = collect($blueprint['ingredients'])->firstWhere('name', 'Stileron')['resource_type_uuid'];
+
+ $page = app(BlueprintShowViewData::class)->build(
+ mode: 'detail',
+ blueprint: $blueprint,
+ search: ['filters' => [], 'results' => [], 'result_count' => 0],
pageTitle: 'Chiron & Legs',
);
@@ -229,12 +211,12 @@
->and($page['craftTimeLabel'])->toBe('3 minutes')
->and($page['resolvedVersionCode'])->toBe('4.0.0-PTU')
->and($page['outputItemWebUrl'])->toBe(route('web.items.show', [
- 'item' => $outputItemUuid,
+ 'item' => $blueprint['output']['uuid'],
'version' => '4.0.0-PTU',
]))
->and($page['hasSearchFilters'])->toBeFalse()
->and($page['renderSearchResultCount'])->toBe(1)
- ->and($initialResult['uuid'])->toBe($blueprintUuid)
+ ->and($initialResult['uuid'])->toBe($blueprint['uuid'])
->and(collect($initialResult['ingredients'])->pluck('resource_type_uuid')->all())->toBe([
$laraniteUuid,
$aslariteUuid,
@@ -266,13 +248,7 @@
'name' => 'Casing',
'required_count' => 1,
'children' => [
- [
- 'kind' => 'resource',
- 'uuid' => $laraniteUuid,
- 'name' => 'Laranite',
- 'quantity_scu' => 0.03,
- 'min_quality' => 0,
- ],
+ ['kind' => 'resource', 'uuid' => $laraniteUuid, 'name' => 'Laranite', 'quantity_scu' => 0.03, 'min_quality' => 0],
],
],
[
@@ -281,27 +257,10 @@
'name' => 'Insulative Liner',
'required_count' => 1,
'modifiers' => [
- [
- 'property_key' => 'armor_temperaturemax',
- 'quality_range' => [
- 'min' => 0,
- 'max' => 1000,
- ],
- 'modifier_range' => [
- 'at_min_quality' => 0.8,
- 'at_max_quality' => 1.2,
- ],
- 'better_when' => 'higher',
- ],
+ ['property_key' => 'armor_temperaturemax', 'quality_range' => ['min' => 0, 'max' => 1000], 'modifier_range' => ['at_min_quality' => 0.8, 'at_max_quality' => 1.2], 'better_when' => 'higher'],
],
'children' => [
- [
- 'kind' => 'resource',
- 'uuid' => $aslariteUuid,
- 'name' => 'Aslarite',
- 'quantity_scu' => 0.02,
- 'min_quality' => 0,
- ],
+ ['kind' => 'resource', 'uuid' => $aslariteUuid, 'name' => 'Aslarite', 'quantity_scu' => 0.02, 'min_quality' => 0],
],
],
[
@@ -310,13 +269,7 @@
'name' => 'Casing Weave',
'required_count' => 1,
'children' => [
- [
- 'kind' => 'resource',
- 'uuid' => $stileronUuid,
- 'name' => 'Stileron',
- 'quantity_scu' => 0.03,
- 'min_quality' => 0,
- ],
+ ['kind' => 'resource', 'uuid' => $stileronUuid, 'name' => 'Stileron', 'quantity_scu' => 0.03, 'min_quality' => 0],
],
],
],
@@ -325,39 +278,17 @@
'unlocking_missions_grouped' => [],
'aspects' => [
'aspects' => [
- [
- 'key' => 'CASING',
- 'name' => 'Casing',
- 'is_selected' => true,
- ],
- [
- 'key' => 'INSULATIVE LINER',
- 'name' => 'Insulative Liner',
- 'is_selected' => true,
- ],
- [
- 'key' => 'CASING WEAVE',
- 'name' => 'Casing Weave',
- 'is_selected' => false,
- ],
+ ['key' => 'CASING', 'name' => 'Casing', 'is_selected' => true],
+ ['key' => 'INSULATIVE LINER', 'name' => 'Insulative Liner', 'is_selected' => true],
+ ['key' => 'CASING WEAVE', 'name' => 'Casing Weave', 'is_selected' => false],
],
'aspect_groups' => [
- [
- 'key' => 'ASPECTS',
- 'is_choice_group' => true,
- 'selected_count' => 2,
- 'display_name' => null,
- 'aspect_indexes' => [0, 1, 2],
- ],
+ ['key' => 'ASPECTS', 'is_choice_group' => true, 'selected_count' => 2, 'display_name' => null, 'aspect_indexes' => [0, 1, 2]],
],
'has_interactive_aspects' => true,
],
],
- search: [
- 'filters' => [],
- 'results' => [],
- 'result_count' => 0,
- ],
+ search: ['filters' => [], 'results' => [], 'result_count' => 0],
pageTitle: 'Chiron Legs',
);
@@ -419,35 +350,31 @@
app('request')->query->set('version', '4.0.0-PTU');
$unsafeBlueprintName = 'Unsafe ';
- $page = app(BlueprintShowViewData::class)->build(
- mode: 'detail',
- blueprint: [
- 'uuid' => $blueprintUuid = fake()->uuid(),
- 'output_name' => $unsafeBlueprintName,
- 'output' => [
- 'name' => $unsafeBlueprintName,
- 'class' => 'unsafe_output',
- ],
- 'requirement_groups' => [],
- 'unlocking_missions_grouped' => [],
- 'aspects' => [
- 'aspects' => [],
- 'aspect_groups' => [],
- 'has_interactive_aspects' => false,
- ],
+ $blueprint = blueprintFixture([
+ 'output_name' => $unsafeBlueprintName,
+ 'output' => [
+ 'name' => $unsafeBlueprintName,
+ 'class' => 'unsafe_output',
],
- search: [
- 'filters' => [],
- 'results' => [],
- 'result_count' => 0,
+ 'requirement_groups' => [],
+ 'unlocking_missions_grouped' => [],
+ 'aspects' => [
+ 'aspects' => [],
+ 'aspect_groups' => [],
+ 'has_interactive_aspects' => false,
],
+ ]);
+ $page = app(BlueprintShowViewData::class)->build(
+ mode: 'detail',
+ blueprint: $blueprint,
+ search: ['filters' => [], 'results' => [], 'result_count' => 0],
pageTitle: $unsafeBlueprintName,
);
$clientPayload = json_decode($page['clientPayload'], true, 512, JSON_THROW_ON_ERROR);
expect($page['clientPayload'])->not->toContain('')
- ->and($clientPayload['search']['currentBlueprintUuid'])->toBe($blueprintUuid)
+ ->and($clientPayload['search']['currentBlueprintUuid'])->toBe($blueprint['uuid'])
->and($clientPayload['search']['initialResults'][0]['output_name'])->toBe($unsafeBlueprintName);
});
@@ -458,13 +385,7 @@
$page = app(BlueprintShowViewData::class)->build(
mode: 'detail',
- blueprint: [
- 'uuid' => fake()->uuid(),
- 'output_name' => 'Test Blueprint',
- 'output' => [
- 'name' => 'Test Blueprint',
- 'class' => 'test_output',
- ],
+ blueprint: blueprintFixture([
'requirement_groups' => [],
'unlocking_missions_grouped' => [
[
@@ -485,12 +406,8 @@
'aspect_groups' => [],
'has_interactive_aspects' => false,
],
- ],
- search: [
- 'filters' => [],
- 'results' => [],
- 'result_count' => 0,
- ],
+ ]),
+ search: ['filters' => [], 'results' => [], 'result_count' => 0],
pageTitle: 'Test Blueprint',
);
@@ -499,4 +416,4 @@
->and($page['unlockingMissions'][0]['missions'])->toHaveCount(1)
->and($page['unlockingMissions'][0]['missions'][0]['title'])->toBe('Eliminate Threat')
->and($page['unlockingMissions'][0]['missions'][0]['web_url'])->toBe(route('web.missions.show', ['mission' => $missionUuid]));
-});
+});
\ No newline at end of file
diff --git a/tests/Unit/Support/Seo/ShowSeoDataTest.php b/tests/Unit/Support/Seo/ShowSeoDataTest.php
index 072710c92..e5f5a2a9c 100644
--- a/tests/Unit/Support/Seo/ShowSeoDataTest.php
+++ b/tests/Unit/Support/Seo/ShowSeoDataTest.php
@@ -76,21 +76,11 @@
->and($seo['breadcrumbs'][0]['url'])->toBe(route('web.vehicles.index', ['version' => '4.1.0-LIVE']))
->and(data_get($seo, 'structuredData.0.@type'))->toBe('BreadcrumbList')
->and(data_get($seo, 'structuredData.1.@type'))->toBe('Vehicle')
- ->and(data_get($seo, 'structuredData.1.vehicleConfiguration'))->toBe('Medium Freight')
- ->and(data_get($seo, 'structuredData.1.image'))->toBe('https://example.com/mercury.jpg')
->and(data_get($seo, 'structuredData.1.brand.name'))->toBe('Crusader Industries')
->and(data_get($seo, 'structuredData.1.offers'))->toHaveCount(2)
- ->and(data_get($seo, 'structuredData.1.offers.0.@type'))->toBe('AggregateOffer')
->and(data_get($seo, 'structuredData.1.offers.0.priceCurrency'))->toBe('USD')
->and(data_get($seo, 'structuredData.1.offers.0.lowPrice'))->toBe(220)
- ->and(data_get($seo, 'structuredData.1.offers.0.offers.0.price'))->toBe(220)
- ->and(data_get($seo, 'structuredData.1.offers.0.offers.0.url'))->toBe('https://robertsspaceindustries.com/pledge/ships/mercury-star-runner')
- ->and(data_get($seo, 'structuredData.1.offers.1.@type'))->toBe('AggregateOffer')
->and(data_get($seo, 'structuredData.1.offers.1.priceCurrency'))->toBe('aUEC')
- ->and(data_get($seo, 'structuredData.1.offers.1.lowPrice'))->toBe(6_500_000.0)
- ->and(data_get($seo, 'structuredData.1.offers.1.highPrice'))->toBe(6_800_000.0)
- ->and(data_get($seo, 'structuredData.1.offers.1.offers'))->toHaveCount(2)
- ->and(data_get($seo, 'structuredData.1.offers.1.offers.0.seller.name'))->toBe('Port Olisar')
->and(data_get($seo, 'structuredData.1.additionalProperty'))->toHaveCount(19);
});
@@ -213,12 +203,9 @@
->and(data_get($seo, 'structuredData.0.@type'))->toBe('BreadcrumbList')
->and(data_get($seo, 'structuredData.1.@type'))->toBe('Item')
->and(data_get($seo, 'structuredData.1.brand.name'))->toBe('Klaus & Werner')
- ->and(data_get($seo, 'structuredData.1.image'))->toBe('https://example.com/original.jpg')
->and(data_get($seo, 'structuredData.1.offers.@type'))->toBe('AggregateOffer')
->and(data_get($seo, 'structuredData.1.offers.lowPrice'))->toBe(1500.0)
- ->and(data_get($seo, 'structuredData.1.offers.highPrice'))->toBe(1600.0)
->and(data_get($seo, 'structuredData.1.offers.offers'))->toHaveCount(2)
- ->and(data_get($seo, 'structuredData.1.offers.offers.0.seller.name'))->toBe('Port Olisar')
->and(data_get($seo, 'structuredData.1.additionalProperty'))->toHaveCount(9);
});