refactor pagination
This commit is contained in:
parent
d603f48bad
commit
4c52f82393
|
|
@ -43,42 +43,23 @@ class ClosetController extends Controller
|
||||||
public function getClosetData(Request $request)
|
public function getClosetData(Request $request)
|
||||||
{
|
{
|
||||||
$category = $request->input('category', 'skin');
|
$category = $request->input('category', 'skin');
|
||||||
$page = abs($request->input('page', 1));
|
$search = $request->input('q');
|
||||||
$perPage = (int) $request->input('perPage', 6);
|
|
||||||
$q = $request->input('q', null);
|
|
||||||
|
|
||||||
$perPage = $perPage > 0 ? $perPage : 6;
|
$query = auth()->user()->closet();
|
||||||
|
|
||||||
$user = auth()->user();
|
|
||||||
$closet = $user->closet();
|
|
||||||
|
|
||||||
if ($category == 'cape') {
|
if ($category == 'cape') {
|
||||||
$closet = $closet->where('type', 'cape');
|
$query = $query->where('type', 'cape');
|
||||||
} else {
|
} else {
|
||||||
$closet = $closet->where(function ($query) {
|
$query = $query->where(function ($query) {
|
||||||
return $query->where('type', 'steve')->orWhere('type', 'alex');
|
return $query->where('type', 'steve')->orWhere('type', 'alex');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($q) {
|
if ($search) {
|
||||||
$closet = $closet->where('item_name', 'like', "%$q%");
|
$query = $query->where('item_name', 'like', "%$search%");
|
||||||
}
|
}
|
||||||
|
|
||||||
$total = $closet->count();
|
return $query->paginate(6);
|
||||||
$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,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function add(Request $request)
|
public function add(Request $request)
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ return [
|
||||||
Illuminate\Hashing\HashServiceProvider::class,
|
Illuminate\Hashing\HashServiceProvider::class,
|
||||||
Illuminate\Mail\MailServiceProvider::class,
|
Illuminate\Mail\MailServiceProvider::class,
|
||||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||||
|
Illuminate\Pagination\PaginationServiceProvider::class,
|
||||||
Illuminate\Queue\QueueServiceProvider::class,
|
Illuminate\Queue\QueueServiceProvider::class,
|
||||||
Illuminate\Redis\RedisServiceProvider::class,
|
Illuminate\Redis\RedisServiceProvider::class,
|
||||||
Illuminate\Session\SessionServiceProvider::class,
|
Illuminate\Session\SessionServiceProvider::class,
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,12 @@ export type TextureType = 'steve' | 'alex' | 'cape'
|
||||||
export type ClosetItem = Texture & {
|
export type ClosetItem = Texture & {
|
||||||
pivot: { user_uid: number; texture_tid: number; item_name: string }
|
pivot: { user_uid: number; texture_tid: number; item_name: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Paginator<T> = {
|
||||||
|
data: T[]
|
||||||
|
current_page: number
|
||||||
|
last_page: number
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import debounce from 'lodash.debounce'
|
||||||
import { t } from '@/scripts/i18n'
|
import { t } from '@/scripts/i18n'
|
||||||
import * as fetch from '@/scripts/net'
|
import * as fetch from '@/scripts/net'
|
||||||
import { showModal, toast } from '@/scripts/notify'
|
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 Loading from '@/components/Loading'
|
||||||
import Pagination from '@/components/Pagination'
|
import Pagination from '@/components/Pagination'
|
||||||
import ClosetItem from './ClosetItem'
|
import ClosetItem from './ClosetItem'
|
||||||
|
|
@ -37,23 +37,13 @@ const Closet: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getItems = async () => {
|
const getItems = async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const {
|
const { data, last_page } = await fetch.get<Paginator<Item>>(
|
||||||
data: { items, category: c, total_pages: totalPages },
|
'/user/closet/list',
|
||||||
} = await fetch.get<
|
{ category, q: query, page },
|
||||||
fetch.ResponseBody<{
|
)
|
||||||
items: Item[]
|
|
||||||
category: Category
|
|
||||||
total_pages: number
|
|
||||||
}>
|
|
||||||
>('/user/closet/list', {
|
|
||||||
category,
|
|
||||||
q: query,
|
|
||||||
page,
|
|
||||||
})
|
|
||||||
|
|
||||||
setItems(items)
|
setItems(data)
|
||||||
setCategory(c)
|
setTotalPages(last_page)
|
||||||
setTotalPages(totalPages)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
getItems()
|
getItems()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { render, fireEvent, wait } from '@testing-library/react'
|
||||||
import $ from 'jquery'
|
import $ from 'jquery'
|
||||||
import { t } from '@/scripts/i18n'
|
import { t } from '@/scripts/i18n'
|
||||||
import * as fetch from '@/scripts/net'
|
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'
|
import Closet from '@/views/user/Closet'
|
||||||
|
|
||||||
jest.mock('@/scripts/net')
|
jest.mock('@/scripts/net')
|
||||||
|
|
@ -49,6 +49,17 @@ const fixturePlayer: Readonly<Player> = Object.freeze<Player>({
|
||||||
tid_cape: 2,
|
tid_cape: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function createPaginator(data: ClosetItem[]): Paginator<ClosetItem> {
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
total: data.length,
|
||||||
|
from: 1,
|
||||||
|
to: data.length,
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
container.id = 'previewer'
|
container.id = 'previewer'
|
||||||
|
|
@ -60,17 +71,13 @@ afterEach(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('loading indicator', () => {
|
test('loading indicator', () => {
|
||||||
fetch.get.mockResolvedValue({
|
fetch.get.mockResolvedValue(createPaginator([]))
|
||||||
data: { items: [], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
const { queryByTitle } = render(<Closet />)
|
const { queryByTitle } = render(<Closet />)
|
||||||
expect(queryByTitle('Loading...')).toBeInTheDocument()
|
expect(queryByTitle('Loading...')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('empty closet', async () => {
|
test('empty closet', async () => {
|
||||||
fetch.get.mockResolvedValue({
|
fetch.get.mockResolvedValue(createPaginator([]))
|
||||||
data: { items: [], category: 'skin', total_pages: 0 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const { queryByText } = render(<Closet />)
|
const { queryByText } = render(<Closet />)
|
||||||
await wait()
|
await wait()
|
||||||
|
|
@ -79,15 +86,9 @@ test('empty closet', async () => {
|
||||||
|
|
||||||
test('categories', async () => {
|
test('categories', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
.mockResolvedValueOnce(createPaginator([fixtureCape]))
|
||||||
})
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
.mockResolvedValueOnce({
|
|
||||||
data: { items: [fixtureCape], category: 'cape', total_pages: 1 },
|
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const { getByText, queryByText } = render(<Closet />)
|
const { getByText, queryByText } = render(<Closet />)
|
||||||
await wait()
|
await wait()
|
||||||
|
|
@ -107,12 +108,8 @@ test('categories', async () => {
|
||||||
|
|
||||||
test('search textures', async () => {
|
test('search textures', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
.mockResolvedValueOnce(createPaginator([]))
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
data: { items: [], category: 'skin', total_pages: 0 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const { getByPlaceholderText, queryByText } = render(<Closet />)
|
const { getByPlaceholderText, queryByText } = render(<Closet />)
|
||||||
await wait()
|
await wait()
|
||||||
|
|
@ -127,12 +124,8 @@ test('search textures', async () => {
|
||||||
|
|
||||||
test('switch page', async () => {
|
test('switch page', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({ ...createPaginator([]), last_page: 2 })
|
||||||
data: { items: [], category: 'skin', total_pages: 2 },
|
.mockResolvedValueOnce({ ...createPaginator([fixtureSkin]), last_page: 2 })
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 2 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const { getByText, queryByText } = render(<Closet />)
|
const { getByText, queryByText } = render(<Closet />)
|
||||||
await wait()
|
await wait()
|
||||||
|
|
@ -144,9 +137,7 @@ test('switch page', async () => {
|
||||||
|
|
||||||
describe('rename item', () => {
|
describe('rename item', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetch.get.mockResolvedValue({
|
fetch.get.mockResolvedValue(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('succeeded', async () => {
|
it('succeeded', async () => {
|
||||||
|
|
@ -234,9 +225,7 @@ describe('rename item', () => {
|
||||||
|
|
||||||
describe('remove item', () => {
|
describe('remove item', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetch.get.mockResolvedValue({
|
fetch.get.mockResolvedValue(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('succeeded', async () => {
|
it('succeeded', async () => {
|
||||||
|
|
@ -287,12 +276,8 @@ describe('remove item', () => {
|
||||||
describe('select textures', () => {
|
describe('select textures', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
.mockResolvedValueOnce(createPaginator([fixtureCape]))
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
data: { items: [fixtureCape], category: 'cape', total_pages: 1 },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('select skin', async () => {
|
it('select skin', async () => {
|
||||||
|
|
@ -336,9 +321,7 @@ describe('select textures', () => {
|
||||||
|
|
||||||
describe('set avatar', () => {
|
describe('set avatar', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetch.get.mockResolvedValue({
|
fetch.get.mockResolvedValue(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const img = document.createElement('img')
|
const img = document.createElement('img')
|
||||||
img.alt = 'User Image'
|
img.alt = 'User Image'
|
||||||
|
|
@ -398,9 +381,7 @@ describe('set avatar', () => {
|
||||||
|
|
||||||
describe('apply textures to player', () => {
|
describe('apply textures to player', () => {
|
||||||
it('selected nothing', async () => {
|
it('selected nothing', async () => {
|
||||||
fetch.get.mockResolvedValue({
|
fetch.get.mockResolvedValue(createPaginator([]))
|
||||||
data: { items: [], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
|
|
||||||
const { getByText, getByRole, queryByText } = render(<Closet />)
|
const { getByText, getByRole, queryByText } = render(<Closet />)
|
||||||
await wait()
|
await wait()
|
||||||
|
|
@ -413,9 +394,7 @@ describe('apply textures to player', () => {
|
||||||
|
|
||||||
it('search players', async () => {
|
it('search players', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -440,9 +419,7 @@ describe('apply textures to player', () => {
|
||||||
|
|
||||||
it('succeeded', async () => {
|
it('succeeded', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
||||||
fetch.post.mockResolvedValue({ code: 0, message: 'success' })
|
fetch.post.mockResolvedValue({ code: 0, message: 'success' })
|
||||||
|
|
||||||
|
|
@ -470,9 +447,7 @@ describe('apply textures to player', () => {
|
||||||
|
|
||||||
it('failed', async () => {
|
it('failed', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
||||||
fetch.post.mockResolvedValue({ code: 1, message: 'failed' })
|
fetch.post.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
|
||||||
|
|
@ -500,9 +475,7 @@ describe('apply textures to player', () => {
|
||||||
|
|
||||||
it('close dialog', async () => {
|
it('close dialog', async () => {
|
||||||
fetch.get
|
fetch.get
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce(createPaginator([fixtureSkin]))
|
||||||
data: { items: [fixtureSkin], category: 'skin', total_pages: 1 },
|
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
.mockResolvedValueOnce({ data: [fixturePlayer] })
|
||||||
|
|
||||||
const { getByText, getByAltText } = render(<Closet />)
|
const { getByText, getByAltText } = render(<Closet />)
|
||||||
|
|
|
||||||
|
|
@ -40,46 +40,28 @@ class ClosetControllerTest extends TestCase
|
||||||
// Use default query parameters
|
// Use default query parameters
|
||||||
$this->getJson('/user/closet/list')
|
$this->getJson('/user/closet/list')
|
||||||
->assertJsonStructure([
|
->assertJsonStructure([
|
||||||
'data' => [
|
'data' => [['tid', 'name', 'type']],
|
||||||
'category',
|
|
||||||
'total_pages',
|
|
||||||
'items' => [['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
|
// Get capes
|
||||||
$cape = factory(Texture::class)->states('cape')->create();
|
$cape = factory(Texture::class)->states('cape')->create();
|
||||||
$this->user->closet()->attach($cape->tid, ['item_name' => 'custom_name']);
|
$this->user->closet()->attach($cape->tid, ['item_name' => 'custom_name']);
|
||||||
$this->getJson('/user/closet/list?category=cape')
|
$this->getJson('/user/closet/list?category=cape')
|
||||||
->assertJson(['data' => [
|
->assertJson(['data' => [[
|
||||||
'category' => 'cape',
|
|
||||||
'total_pages' => 1,
|
|
||||||
'items' => [[
|
|
||||||
'tid' => $cape->tid,
|
'tid' => $cape->tid,
|
||||||
'name' => 'custom_name',
|
|
||||||
'type' => 'cape',
|
'type' => 'cape',
|
||||||
]],
|
'pivot' => ['item_name' => 'custom_name'],
|
||||||
|
],
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
// Search by keyword
|
// Search by keyword
|
||||||
$random = $textures->random();
|
$random = $textures->random();
|
||||||
$this->getJson('/user/closet/list?q='.$random->name)
|
$this->getJson('/user/closet/list?q='.$random->name)
|
||||||
->assertJson(['data' => [
|
->assertJson(['data' => [[
|
||||||
'category' => 'skin',
|
|
||||||
'total_pages' => 1,
|
|
||||||
'items' => [[
|
|
||||||
'tid' => $random->tid,
|
'tid' => $random->tid,
|
||||||
'name' => $random->name,
|
'name' => $random->name,
|
||||||
'type' => $random->type,
|
'type' => $random->type,
|
||||||
]],
|
],
|
||||||
]]);
|
]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user