From 4c52f82393aa220dff43cb15e23c3aabde040741 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Fri, 20 Mar 2020 19:03:10 +0800 Subject: [PATCH] refactor pagination --- app/Http/Controllers/ClosetController.php | 33 ++----- config/app.php | 1 + resources/assets/src/scripts/types.ts | 9 ++ .../assets/src/views/user/Closet/index.tsx | 24 ++--- .../assets/tests/views/user/Closet.test.tsx | 89 +++++++------------ .../ControllersTest/ClosetControllerTest.php | 30 ++----- 6 files changed, 61 insertions(+), 125 deletions(-) diff --git a/app/Http/Controllers/ClosetController.php b/app/Http/Controllers/ClosetController.php index 62f089e4..36e8c4cc 100644 --- a/app/Http/Controllers/ClosetController.php +++ b/app/Http/Controllers/ClosetController.php @@ -43,42 +43,23 @@ class ClosetController extends Controller public function getClosetData(Request $request) { $category = $request->input('category', 'skin'); - $page = abs($request->input('page', 1)); - $perPage = (int) $request->input('perPage', 6); - $q = $request->input('q', null); + $search = $request->input('q'); - $perPage = $perPage > 0 ? $perPage : 6; - - $user = auth()->user(); - $closet = $user->closet(); + $query = auth()->user()->closet(); if ($category == 'cape') { - $closet = $closet->where('type', 'cape'); + $query = $query->where('type', 'cape'); } else { - $closet = $closet->where(function ($query) { + $query = $query->where(function ($query) { return $query->where('type', 'steve')->orWhere('type', 'alex'); }); } - if ($q) { - $closet = $closet->where('item_name', 'like', "%$q%"); + if ($search) { + $query = $query->where('item_name', 'like', "%$search%"); } - $total = $closet->count(); - $closet->offset(($page - 1) * $perPage)->limit($perPage); - - $totalPages = ceil($total / $perPage); - $items = $closet->get()->map(function ($t) { - $t->name = $t->pivot->item_name; - - return $t; - }); - - return json('', 0, [ - 'category' => $category, - 'items' => $items, - 'total_pages' => $totalPages, - ]); + return $query->paginate(6); } public function add(Request $request) diff --git a/config/app.php b/config/app.php index fb64c51d..d10cf6af 100644 --- a/config/app.php +++ b/config/app.php @@ -174,6 +174,7 @@ return [ Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, diff --git a/resources/assets/src/scripts/types.ts b/resources/assets/src/scripts/types.ts index 4f02a9c2..30cd790f 100644 --- a/resources/assets/src/scripts/types.ts +++ b/resources/assets/src/scripts/types.ts @@ -22,3 +22,12 @@ export type TextureType = 'steve' | 'alex' | 'cape' export type ClosetItem = Texture & { pivot: { user_uid: number; texture_tid: number; item_name: string } } + +export type Paginator = { + data: T[] + current_page: number + last_page: number + from: number + to: number + total: number +} diff --git a/resources/assets/src/views/user/Closet/index.tsx b/resources/assets/src/views/user/Closet/index.tsx index b7b75448..d2b4c08e 100644 --- a/resources/assets/src/views/user/Closet/index.tsx +++ b/resources/assets/src/views/user/Closet/index.tsx @@ -4,7 +4,7 @@ import debounce from 'lodash.debounce' import { t } from '@/scripts/i18n' import * as fetch from '@/scripts/net' import { showModal, toast } from '@/scripts/notify' -import { ClosetItem as Item, Texture } from '@/scripts/types' +import { ClosetItem as Item, Texture, Paginator } from '@/scripts/types' import Loading from '@/components/Loading' import Pagination from '@/components/Pagination' import ClosetItem from './ClosetItem' @@ -37,23 +37,13 @@ const Closet: React.FC = () => { useEffect(() => { const getItems = async () => { setIsLoading(true) - const { - data: { items, category: c, total_pages: totalPages }, - } = await fetch.get< - fetch.ResponseBody<{ - items: Item[] - category: Category - total_pages: number - }> - >('/user/closet/list', { - category, - q: query, - page, - }) + const { data, last_page } = await fetch.get>( + '/user/closet/list', + { category, q: query, page }, + ) - setItems(items) - setCategory(c) - setTotalPages(totalPages) + setItems(data) + setTotalPages(last_page) setIsLoading(false) } getItems() diff --git a/resources/assets/tests/views/user/Closet.test.tsx b/resources/assets/tests/views/user/Closet.test.tsx index 842c1f12..cd3b27e6 100644 --- a/resources/assets/tests/views/user/Closet.test.tsx +++ b/resources/assets/tests/views/user/Closet.test.tsx @@ -3,7 +3,7 @@ import { render, fireEvent, wait } from '@testing-library/react' import $ from 'jquery' import { t } from '@/scripts/i18n' import * as fetch from '@/scripts/net' -import { ClosetItem, Player } from '@/scripts/types' +import { ClosetItem, Player, Paginator } from '@/scripts/types' import Closet from '@/views/user/Closet' jest.mock('@/scripts/net') @@ -49,6 +49,17 @@ const fixturePlayer: Readonly = Object.freeze({ tid_cape: 2, }) +function createPaginator(data: ClosetItem[]): Paginator { + return { + data, + total: data.length, + from: 1, + to: data.length, + current_page: 1, + last_page: 1, + } +} + beforeEach(() => { const container = document.createElement('div') container.id = 'previewer' @@ -60,17 +71,13 @@ afterEach(() => { }) test('loading indicator', () => { - fetch.get.mockResolvedValue({ - data: { items: [], category: 'skin', total_pages: 1 }, - }) + fetch.get.mockResolvedValue(createPaginator([])) const { queryByTitle } = render() expect(queryByTitle('Loading...')).toBeInTheDocument() }) test('empty closet', async () => { - fetch.get.mockResolvedValue({ - data: { items: [], category: 'skin', total_pages: 0 }, - }) + fetch.get.mockResolvedValue(createPaginator([])) const { queryByText } = render() await wait() @@ -79,15 +86,9 @@ test('empty closet', async () => { test('categories', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) - .mockResolvedValueOnce({ - data: { items: [fixtureCape], category: 'cape', total_pages: 1 }, - }) - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) + .mockResolvedValueOnce(createPaginator([fixtureCape])) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) const { getByText, queryByText } = render() await wait() @@ -107,12 +108,8 @@ test('categories', async () => { test('search textures', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) - .mockResolvedValueOnce({ - data: { items: [], category: 'skin', total_pages: 0 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) + .mockResolvedValueOnce(createPaginator([])) const { getByPlaceholderText, queryByText } = render() await wait() @@ -127,12 +124,8 @@ test('search textures', async () => { test('switch page', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [], category: 'skin', total_pages: 2 }, - }) - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 2 }, - }) + .mockResolvedValueOnce({ ...createPaginator([]), last_page: 2 }) + .mockResolvedValueOnce({ ...createPaginator([fixtureSkin]), last_page: 2 }) const { getByText, queryByText } = render() await wait() @@ -144,9 +137,7 @@ test('switch page', async () => { describe('rename item', () => { beforeEach(() => { - fetch.get.mockResolvedValue({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + fetch.get.mockResolvedValue(createPaginator([fixtureSkin])) }) it('succeeded', async () => { @@ -234,9 +225,7 @@ describe('rename item', () => { describe('remove item', () => { beforeEach(() => { - fetch.get.mockResolvedValue({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + fetch.get.mockResolvedValue(createPaginator([fixtureSkin])) }) it('succeeded', async () => { @@ -287,12 +276,8 @@ describe('remove item', () => { describe('select textures', () => { beforeEach(() => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) - .mockResolvedValueOnce({ - data: { items: [fixtureCape], category: 'cape', total_pages: 1 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) + .mockResolvedValueOnce(createPaginator([fixtureCape])) }) it('select skin', async () => { @@ -336,9 +321,7 @@ describe('select textures', () => { describe('set avatar', () => { beforeEach(() => { - fetch.get.mockResolvedValue({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + fetch.get.mockResolvedValue(createPaginator([fixtureSkin])) const img = document.createElement('img') img.alt = 'User Image' @@ -398,9 +381,7 @@ describe('set avatar', () => { describe('apply textures to player', () => { it('selected nothing', async () => { - fetch.get.mockResolvedValue({ - data: { items: [], category: 'skin', total_pages: 1 }, - }) + fetch.get.mockResolvedValue(createPaginator([])) const { getByText, getByRole, queryByText } = render() await wait() @@ -413,9 +394,7 @@ describe('apply textures to player', () => { it('search players', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) .mockResolvedValueOnce({ data: [fixturePlayer] }) const { @@ -440,9 +419,7 @@ describe('apply textures to player', () => { it('succeeded', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) .mockResolvedValueOnce({ data: [fixturePlayer] }) fetch.post.mockResolvedValue({ code: 0, message: 'success' }) @@ -470,9 +447,7 @@ describe('apply textures to player', () => { it('failed', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) .mockResolvedValueOnce({ data: [fixturePlayer] }) fetch.post.mockResolvedValue({ code: 1, message: 'failed' }) @@ -500,9 +475,7 @@ describe('apply textures to player', () => { it('close dialog', async () => { fetch.get - .mockResolvedValueOnce({ - data: { items: [fixtureSkin], category: 'skin', total_pages: 1 }, - }) + .mockResolvedValueOnce(createPaginator([fixtureSkin])) .mockResolvedValueOnce({ data: [fixturePlayer] }) const { getByText, getByAltText } = render() diff --git a/tests/HttpTest/ControllersTest/ClosetControllerTest.php b/tests/HttpTest/ControllersTest/ClosetControllerTest.php index 2f8a5a7b..cf08f3cb 100644 --- a/tests/HttpTest/ControllersTest/ClosetControllerTest.php +++ b/tests/HttpTest/ControllersTest/ClosetControllerTest.php @@ -40,46 +40,28 @@ class ClosetControllerTest extends TestCase // Use default query parameters $this->getJson('/user/closet/list') ->assertJsonStructure([ - 'data' => [ - 'category', - 'total_pages', - 'items' => [['tid', 'name', 'type']], - ], + 'data' => [['tid', 'name', 'type']], ]); - // Responsive - $result = $this->json('get', '/user/closet/list?perPage=0')->json()['data']; - $this->assertCount(6, $result['items']); - $result = $this->json('get', '/user/closet/list?perPage=8')->json()['data']; - $this->assertCount(8, $result['items']); - $result = $this->json('get', '/user/closet/list?perPage=8&page=2')->json()['data']; - $this->assertCount(2, $result['items']); - // Get capes $cape = factory(Texture::class)->states('cape')->create(); $this->user->closet()->attach($cape->tid, ['item_name' => 'custom_name']); $this->getJson('/user/closet/list?category=cape') - ->assertJson(['data' => [ - 'category' => 'cape', - 'total_pages' => 1, - 'items' => [[ + ->assertJson(['data' => [[ 'tid' => $cape->tid, - 'name' => 'custom_name', 'type' => 'cape', - ]], + 'pivot' => ['item_name' => 'custom_name'], + ], ]]); // Search by keyword $random = $textures->random(); $this->getJson('/user/closet/list?q='.$random->name) - ->assertJson(['data' => [ - 'category' => 'skin', - 'total_pages' => 1, - 'items' => [[ + ->assertJson(['data' => [[ 'tid' => $random->tid, 'name' => $random->name, 'type' => $random->type, - ]], + ], ]]); }