diff --git a/app/Http/Controllers/ItemRestController.php b/app/Http/Controllers/ItemRestController.php index ae68f5f1d..e3d91ab19 100644 --- a/app/Http/Controllers/ItemRestController.php +++ b/app/Http/Controllers/ItemRestController.php @@ -29,6 +29,8 @@ public function index(): Collection 'description', 'appid', 'appdescription', + 'pinned', + 'order', ]; return Item::with('parents') @@ -45,6 +47,8 @@ public function index(): Collection 'description' => $item->description, 'appid' => $item->appid, 'appdescription' => $item->appdescription, + 'pinned' => $item->pinned, + 'pinned_order' => $item->order, 'tags' => $item->parents ->where('id', '!=', 0) ->pluck('title') diff --git a/public/js/app.js b/public/js/app.js index 4d41224d4..3f869baf7 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -4458,7 +4458,8 @@ var getCSRFToken = function getCSRFToken() { */ var mergeItemWithAppDetails = function mergeItemWithAppDetails(item, appDetails) { return { - pinned: 1, + pinned: item.pinned !== undefined ? item.pinned : 1, + order: item.pinned_order !== undefined ? item.pinned_order : null, tags: Array.isArray(item.tags) && item.tags.length ? item.tags : [0], appid: item.appid, title: item.title, diff --git a/resources/assets/js/itemImport.js b/resources/assets/js/itemImport.js index 146660ce6..7ff8aaccd 100644 --- a/resources/assets/js/itemImport.js +++ b/resources/assets/js/itemImport.js @@ -59,7 +59,8 @@ const getCSRFToken = () => { * @returns {object} */ const mergeItemWithAppDetails = (item, appDetails) => ({ - pinned: 1, + pinned: item.pinned !== undefined ? item.pinned : 1, + order: item.pinned_order !== undefined ? item.pinned_order : null, tags: Array.isArray(item.tags) && item.tags.length ? item.tags : [0], appid: item.appid, diff --git a/tests/Feature/ItemExportTest.php b/tests/Feature/ItemExportTest.php index 58c251793..44cf9dbb2 100644 --- a/tests/Feature/ItemExportTest.php +++ b/tests/Feature/ItemExportTest.php @@ -27,6 +27,8 @@ public function test_returns_exactly_the_defined_fields(): void "appid" => "123", "colour" => "#000", "description" => "Description", + "pinned" => 1, + "pinned_order" => 0, "title" => "Item Title", "url" => "http://gorczany.com/nihil-rerum-distinctio-voluptate-assumenda-accusantium-exercitationem" ]; @@ -38,6 +40,36 @@ public function test_returns_exactly_the_defined_fields(): void $response->assertExactJson([$exampleItem + ["tags" => []]]); } + public function test_exports_pinned_status_for_items(): void + { + $pinnedItem = Item::factory()->create([ + 'title' => 'Pinned App', + 'pinned' => 1, + ]); + $unpinnedItem = Item::factory()->create([ + 'title' => 'Unpinned App', + 'pinned' => 0, + ]); + + $response = $this->get('api/item'); + + $response->assertJsonCount(2); + $response->assertJsonPath('0.pinned', 1); + $response->assertJsonPath('1.pinned', 0); + } + + public function test_exports_pinned_order_for_items(): void + { + Item::factory()->create(['title' => 'First', 'order' => 1]); + Item::factory()->create(['title' => 'Second', 'order' => 2]); + + $response = $this->get('api/item'); + + $response->assertJsonCount(2); + $response->assertJsonPath('0.pinned_order', 1); + $response->assertJsonPath('1.pinned_order', 2); + } + public function test_exports_assigned_tag_titles_excluding_the_root_tag(): void { // Mirror the root/default dashboard row that production seeds (id 0), diff --git a/tests/Feature/ItemImportTest.php b/tests/Feature/ItemImportTest.php index e1370a320..84244aae9 100644 --- a/tests/Feature/ItemImportTest.php +++ b/tests/Feature/ItemImportTest.php @@ -104,4 +104,84 @@ public function test_import_with_root_tag_only_creates_no_tags(): void ItemTag::where('item_id', $item->id)->where('tag_id', 0)->exists() ); } + + public function test_import_saves_unpinned_item(): void + { + $this->seed(); + + $response = $this->postJson('api/item', $this->importPayload([ + 'title' => 'Unpinned App', + 'pinned' => 0, + ])); + + $response->assertStatus(200); + + $item = Item::where('type', 0)->where('title', 'Unpinned App')->first(); + $this->assertNotNull($item); + $this->assertEquals(0, $item->pinned); + } + + public function test_import_saves_pinned_order(): void + { + $this->seed(); + + $response = $this->postJson('api/item', $this->importPayload([ + 'title' => 'Ordered App', + 'order' => 5, + ])); + + $response->assertStatus(200); + + $item = Item::where('type', 0)->where('title', 'Ordered App')->first(); + $this->assertNotNull($item); + $this->assertEquals(5, $item->order); + } + + public function test_import_defaults_order_to_zero_when_not_provided(): void + { + $this->seed(); + + $response = $this->postJson('api/item', $this->importPayload([ + 'title' => 'No Order App', + ])); + + $response->assertStatus(200); + + $item = Item::where('type', 0)->where('title', 'No Order App')->first(); + $this->assertNotNull($item); + $this->assertEquals(0, $item->order); + } + + public function test_export_import_round_trip_preserves_pinned_and_order(): void + { + $this->seed(); + + // Create items with specific pinned/order values + $this->postJson('api/item', $this->importPayload([ + 'title' => 'App One', + 'pinned' => 1, + 'order' => 3, + ]))->assertStatus(200); + + $this->postJson('api/item', $this->importPayload([ + 'title' => 'App Two', + 'pinned' => 0, + 'order' => 7, + ]))->assertStatus(200); + + // Export + $export = $this->get('api/item'); + $export->assertJsonCount(2); + + $exported = $export->json(); + + // Verify the exported JSON has the right keys/values + $appOne = collect($exported)->firstWhere('title', 'App One'); + $appTwo = collect($exported)->firstWhere('title', 'App Two'); + + $this->assertEquals(1, $appOne['pinned']); + $this->assertEquals(3, $appOne['pinned_order']); + $this->assertEquals(0, $appTwo['pinned']); + $this->assertEquals(7, $appTwo['pinned_order']); + } }