Add index page of skin library
This commit is contained in:
parent
7e4ae5381b
commit
190e54578a
|
|
@ -56,9 +56,6 @@ class SkinlibController extends Controller
|
|||
// Keyword to search
|
||||
$keyword = $request->input('keyword', '');
|
||||
|
||||
// Check if user logged in
|
||||
$anonymous = !Auth::check();
|
||||
|
||||
if ($filter == "skin") {
|
||||
$query = Texture::where(function ($innerQuery) {
|
||||
// Nested condition, DO NOT MODIFY
|
||||
|
|
@ -76,7 +73,7 @@ class SkinlibController extends Controller
|
|||
$query = $query->where('uploader', $uploader);
|
||||
}
|
||||
|
||||
if ($anonymous) {
|
||||
if (! $currentUser) {
|
||||
// Show public textures only to anonymous visitors
|
||||
$query = $query->where('public', true);
|
||||
} else {
|
||||
|
|
@ -95,7 +92,7 @@ class SkinlibController extends Controller
|
|||
->take($itemsPerPage)
|
||||
->get();
|
||||
|
||||
if (! $anonymous) {
|
||||
if ($currentUser) {
|
||||
$closet = new Closet($currentUser->uid);
|
||||
foreach ($textures as $item) {
|
||||
$item->liked = $closet->has($item->tid);
|
||||
|
|
@ -104,7 +101,7 @@ class SkinlibController extends Controller
|
|||
|
||||
return response()->json([
|
||||
'items' => $textures,
|
||||
'anonymous' => $anonymous,
|
||||
'current_uid' => $currentUser ? $currentUser->uid : 0,
|
||||
'total_pages' => $totalPages
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,4 +54,9 @@ export default [
|
|||
component: () => import('./auth/Reset'),
|
||||
el: 'form'
|
||||
},
|
||||
{
|
||||
path: 'skinlib',
|
||||
component: () => import('./skinlib/List'),
|
||||
el: '.content-wrapper'
|
||||
},
|
||||
];
|
||||
|
|
|
|||
272
resources/assets/src/components/skinlib/List.vue
Normal file
272
resources/assets/src/components/skinlib/List.vue
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
<template>
|
||||
<div class="content-wrapper">
|
||||
<div class="container">
|
||||
<!-- Content Header (Page header) -->
|
||||
<section class="content-header">
|
||||
<h1>{{ $t('general.skinlib') }}</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><i class="fas fa-tags"></i> {{ $t('skinlib.nowShowing') }}</li>
|
||||
<li>
|
||||
<span v-if="filter === 'cape'" v-t="'general.cape'"></span>
|
||||
<span v-else>
|
||||
{{ $t('general.skin') }}
|
||||
{{ $t('skinlib.filter.' + filter) }}
|
||||
</span>
|
||||
</li>
|
||||
<li>{{ uploaderIndicator }}</li>
|
||||
<li class="active">{{ sortIndicator }}</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="box box-default">
|
||||
<div class="box-header">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-4">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="filter"
|
||||
value="skin"
|
||||
v-model="filter"
|
||||
>
|
||||
{{ $t('skinlib.anyModels') }}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="filter"
|
||||
value="steve"
|
||||
v-model="filter"
|
||||
>
|
||||
Steve
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="filter"
|
||||
value="alex"
|
||||
v-model="filter"
|
||||
>
|
||||
Alex
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="filter"
|
||||
value="cape"
|
||||
v-model="filter"
|
||||
>
|
||||
{{ $t('general.cape') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<form class="col-md-4" @submit.prevent="fetchData">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
v-model="keyword"
|
||||
:placeholder="$t('vendor.datatable.search')"
|
||||
class="form-control"
|
||||
>
|
||||
<span class="input-group-btn">
|
||||
<button @click="fetchData" class="btn btn-success">
|
||||
{{ $t('general.submit') }}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group filter-btn">
|
||||
<button
|
||||
class="btn btn-primary dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>{{ $t('skinlib.sort.title') }} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a @click="sort = 'likes'" v-t="'skinlib.sort.likes'" href="#"></a></li>
|
||||
<li><a @click="sort = 'time'" v-t="'skinlib.sort.time'" href="#"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a
|
||||
v-if="currentUid"
|
||||
class="btn btn-default btn-tail"
|
||||
@click="uploader = currentUid"
|
||||
>{{ $t('skinlib.seeMyUpload') }}</a>
|
||||
<button class="btn btn-warning btn-tail" @click="reset">
|
||||
{{ $t('skinlib.reset') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Container of Skin Library -->
|
||||
<div class="box-body">
|
||||
<template v-if="items.length">
|
||||
<skin-lib-item
|
||||
v-for="(item, index) in items"
|
||||
:key="item.tid"
|
||||
:tid="item.tid"
|
||||
:name="item.name"
|
||||
:type="item.type"
|
||||
:liked="item.liked"
|
||||
:isPublic="item.public"
|
||||
:anonymous="anonymous"
|
||||
@like-toggled="onLikeToggled(index, $event)"
|
||||
></skin-lib-item>
|
||||
</template>
|
||||
<p v-else style="text-align: center; margin: 30px 0;">
|
||||
{{ $t('general.noResult') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="box-footer">
|
||||
<paginate
|
||||
v-model="page"
|
||||
:page-count="totalPages"
|
||||
class="pull-right"
|
||||
container-class="pagination pagination-sm no-margin"
|
||||
first-button-text="«"
|
||||
prev-text="‹"
|
||||
next-text="›"
|
||||
last-button-text="»"
|
||||
:click-handler="pageChanged"
|
||||
:first-last-button="true"
|
||||
></paginate>
|
||||
</div>
|
||||
|
||||
<div v-show="pending" class="overlay">
|
||||
<span>
|
||||
<i class="fas fa-sync-alt fa-spin"></i>
|
||||
{{ $t('general.loading') }}
|
||||
</span>
|
||||
</div>
|
||||
</div><!-- /.box -->
|
||||
</section><!-- /.content -->
|
||||
</div><!-- /.container -->
|
||||
</div><!-- /.content-wrapper -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Paginate from 'vuejs-paginate';
|
||||
import SkinLibItem from './SkinLibItem';
|
||||
import { queryString } from '../../js/utils';
|
||||
|
||||
export default {
|
||||
name: 'SkinLibrary',
|
||||
components: {
|
||||
Paginate,
|
||||
SkinLibItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filter: 'skin',
|
||||
uploader: +queryString('uploader', 0),
|
||||
sort: 'time',
|
||||
keyword: '',
|
||||
page: 1,
|
||||
items: [],
|
||||
totalPages: 0,
|
||||
currentUid: 0,
|
||||
pending: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
anonymous() {
|
||||
return !this.currentUid;
|
||||
},
|
||||
uploaderIndicator() {
|
||||
return this.uploader
|
||||
? this.$t('skinlib.filter.uploader', { uid: this.uploader })
|
||||
: this.$t('skinlib.filter.allUsers');
|
||||
},
|
||||
sortIndicator() {
|
||||
return this.$t('skinlib.sort.' + this.sort);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filter(value) {
|
||||
$(`[value="${value}"]`).iCheck('check');
|
||||
this.fetchData();
|
||||
},
|
||||
uploader() {
|
||||
this.fetchData();
|
||||
},
|
||||
sort() {
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let available = true;
|
||||
const origin = this.fetchData;
|
||||
this.fetchData = () => {
|
||||
if (available) {
|
||||
available = false;
|
||||
setTimeout(() => available = true, 50);
|
||||
origin();
|
||||
}
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.pending = true;
|
||||
const { items, total_pages, current_uid } = await this.$http.get(
|
||||
'/skinlib/data',
|
||||
{
|
||||
filter: this.filter,
|
||||
uploader: this.uploader,
|
||||
sort: this.sort,
|
||||
keyword: this.keyword,
|
||||
page: this.page
|
||||
}
|
||||
);
|
||||
this.items = items;
|
||||
this.totalPages = total_pages;
|
||||
this.currentUid = current_uid;
|
||||
this.pending = false;
|
||||
},
|
||||
pageChanged(page) {
|
||||
this.page = page;
|
||||
this.fetchData();
|
||||
},
|
||||
reset() {
|
||||
this.filter = 'skin';
|
||||
this.uploader = 0;
|
||||
this.sort = 'time';
|
||||
this.keyword = '';
|
||||
this.page = 1;
|
||||
},
|
||||
onLikeToggled(index, action) {
|
||||
this.items[index].liked = action;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.overlay span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -40px;
|
||||
margin-top: 25px;
|
||||
color: #000;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.filter-btn {
|
||||
margin-left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-tail {
|
||||
margin-left: 6px;
|
||||
}
|
||||
</style>
|
||||
161
resources/assets/src/components/skinlib/SkinLibItem.vue
Normal file
161
resources/assets/src/components/skinlib/SkinLibItem.vue
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<a :href="urlToDetail">
|
||||
<div class="item">
|
||||
<div class="item-body">
|
||||
<img :src="urlToPreview">
|
||||
</div>
|
||||
|
||||
<div class="item-footer">
|
||||
<p class="texture-name">
|
||||
<span :title="name">{{ name }}
|
||||
<small>{{ $t('skinlib.filter.' + type) }}</small>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<a
|
||||
:title="likeActionText"
|
||||
class="more like"
|
||||
:class="{ liked, anonymous }"
|
||||
href="#"
|
||||
@click.stop="toggleLiked"
|
||||
><i class="fas fa-heart"></i></a>
|
||||
|
||||
<small v-if="!isPublic" class="more private-label">
|
||||
{{ $t('skinlib.private') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { swal } from '../../js/notify';
|
||||
import toastr from 'toastr';
|
||||
|
||||
export default {
|
||||
name: 'SkinLibItem',
|
||||
props: {
|
||||
tid: Number,
|
||||
name: String,
|
||||
type: {
|
||||
validator: value => ['steve', 'alex', 'cape'].includes(value)
|
||||
},
|
||||
liked: Boolean,
|
||||
anonymous: Boolean,
|
||||
isPublic: Boolean // `public` is a reserved keyword
|
||||
},
|
||||
computed: {
|
||||
urlToDetail() {
|
||||
return `${blessing.base_url}/skinlib/show/${this.tid}`;
|
||||
},
|
||||
urlToPreview() {
|
||||
return `${blessing.base_url}/preview/${this.tid}.png`;
|
||||
},
|
||||
likeActionText() {
|
||||
if (this.anonymous) return this.$t('skinlib.anonymous');
|
||||
|
||||
return this.liked
|
||||
? this.$t('skinlib.removeFromCloset')
|
||||
: this.$t('skinlib.addToCloset');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleLiked() {
|
||||
if (this.anonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.liked) {
|
||||
this.removeFromCloset();
|
||||
} else {
|
||||
this.addToCloset();
|
||||
}
|
||||
},
|
||||
async addToCloset() {
|
||||
const { dismiss, value } = await swal({
|
||||
title: this.$t('skinlib.setItemName'),
|
||||
inputValue: this.name,
|
||||
input: 'text',
|
||||
showCancelButton: true,
|
||||
inputValidator: value => !value && this.$t('skinlib.emptyItemName')
|
||||
});
|
||||
if (dismiss) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/closet/add',
|
||||
{ tid: this.tid, name: value }
|
||||
);
|
||||
if (errno === 0) {
|
||||
swal({ type: 'success', text: msg });
|
||||
this.$emit('like-toggled', true);
|
||||
} else {
|
||||
toastr.warning(msg);
|
||||
}
|
||||
},
|
||||
async removeFromCloset() {
|
||||
const { dismiss } = await swal({
|
||||
text: this.$t('user.removeFromClosetNotice'),
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: '#3085d6',
|
||||
confirmButtonColor: '#d33'
|
||||
});
|
||||
if (dismiss) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/closet/remove',
|
||||
{ tid: this.tid }
|
||||
);
|
||||
if (errno === 0) {
|
||||
this.$emit('like-toggled', false);
|
||||
swal({ type: 'success', text: msg });
|
||||
} else {
|
||||
toastr.warning(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.texture-name {
|
||||
width: 65%;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.item {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.item-body > img {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.texture-name {
|
||||
width: 65%;
|
||||
}
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.like:hover,
|
||||
.liked {
|
||||
color: #e0353b;
|
||||
}
|
||||
}
|
||||
|
||||
.private-label {
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -9,3 +9,20 @@ export function debounce(func, delay) {
|
|||
timer = setTimeout(func, delay);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parameters in query string with key.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} defaultValue
|
||||
* @return {string}
|
||||
*/
|
||||
export function queryString(key, defaultValue) {
|
||||
const result = location.search.match(new RegExp('[?&]'+key+'=([^&]+)', 'i'));
|
||||
|
||||
if (result === null || result.length < 1){
|
||||
return defaultValue;
|
||||
} else {
|
||||
return result[1];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
181
resources/assets/tests/components/skinlib/List.test.js
Normal file
181
resources/assets/tests/components/skinlib/List.test.js
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import Vue from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import List from '@/components/skinlib/List';
|
||||
|
||||
test('fetch data before mounting', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
mount(List);
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 0, sort: 'time', keyword: '', page: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
test('empty skin library', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
expect(wrapper.text()).toContain('general.noResult');
|
||||
});
|
||||
|
||||
test('toggle texture type', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
window.$ = jest.fn(() => ({
|
||||
iCheck() {}
|
||||
}));
|
||||
const wrapper = mount(List);
|
||||
const breadcrumb = wrapper.find('.breadcrumb');
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(breadcrumb.text()).toContain('skinlib.filter.skin');
|
||||
|
||||
wrapper.find('[value=steve]').setChecked();
|
||||
jest.runAllTimers();
|
||||
expect(breadcrumb.text()).toContain('skinlib.filter.steve');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'steve', uploader: 0, sort: 'time', keyword: '', page: 1 }
|
||||
);
|
||||
|
||||
wrapper.find('[value=alex]').setChecked();
|
||||
jest.runAllTimers();
|
||||
expect(breadcrumb.text()).toContain('skinlib.filter.alex');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'alex', uploader: 0, sort: 'time', keyword: '', page: 1 }
|
||||
);
|
||||
|
||||
wrapper.find('[value=cape]').setChecked();
|
||||
jest.runAllTimers();
|
||||
expect(breadcrumb.text()).toContain('general.cape');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'cape', uploader: 0, sort: 'time', keyword: '', page: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
test('check specified uploader', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 1
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
await wrapper.vm.$nextTick();
|
||||
const breadcrumb = wrapper.find('.breadcrumb');
|
||||
const button = wrapper.find('.btn-default');
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(breadcrumb.text()).toContain('skinlib.filter.allUsers');
|
||||
|
||||
button.trigger('click');
|
||||
expect(breadcrumb.text()).toContain('skinlib.filter.uploader');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 1, sort: 'time', keyword: '', page: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
test('sort items', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
const sortByLikes = wrapper.find('.dropdown-menu > li:nth-child(1) > a');
|
||||
const sortByTime = wrapper.find('.dropdown-menu > li:nth-child(2) > a');
|
||||
jest.runAllTimers();
|
||||
|
||||
sortByLikes.trigger('click');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 0, sort: 'likes', keyword: '', page: 1 }
|
||||
);
|
||||
expect(wrapper.text()).toContain('skinlib.sort.likes');
|
||||
jest.runAllTimers();
|
||||
|
||||
sortByTime.trigger('click');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 0, sort: 'time', keyword: '', page: 1 }
|
||||
);
|
||||
expect(wrapper.text()).toContain('skinlib.sort.time');
|
||||
});
|
||||
|
||||
test('search by keyword', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
const searchBox = wrapper.find('input[type=text]');
|
||||
jest.runAllTimers();
|
||||
|
||||
searchBox.setValue('a');
|
||||
wrapper.find('form').trigger('submit');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 0, sort: 'time', keyword: 'a', page: 1 }
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
searchBox.setValue('b');
|
||||
wrapper.find('.input-group-btn > button').trigger('click');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 0, sort: 'time', keyword: 'b', page: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
test('reset all filters', async () => {
|
||||
window.$ = jest.fn(() => ({
|
||||
iCheck() {}
|
||||
}));
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
jest.runAllTimers();
|
||||
wrapper.find('input[value=cape]').setChecked();
|
||||
jest.runAllTimers();
|
||||
wrapper.find('input[type=text]').setValue('abc');
|
||||
jest.runAllTimers();
|
||||
wrapper.find('.dropdown-menu > li:nth-child(1) > a').trigger('click');
|
||||
jest.runAllTimers();
|
||||
|
||||
Vue.prototype.$http.get.mockClear();
|
||||
wrapper.find('.btn-warning').trigger('click');
|
||||
expect(Vue.prototype.$http.get).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('is anonymous', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
expect(wrapper.vm.anonymous).toBeTrue();
|
||||
});
|
||||
|
||||
test('on page changed', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [], total_pages: 0, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
jest.runAllTimers();
|
||||
wrapper.vm.pageChanged(2);
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
||||
'/skinlib/data',
|
||||
{ filter: 'skin', uploader: 0, sort: 'time', keyword: '', page: 2 }
|
||||
);
|
||||
});
|
||||
|
||||
test('on like toggled', async () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue({
|
||||
items: [{ tid: 1, liked: false }], total_pages: 1, current_uid: 0
|
||||
});
|
||||
const wrapper = mount(List);
|
||||
await wrapper.vm.$nextTick();
|
||||
wrapper.vm.onLikeToggled(0, true);
|
||||
expect(wrapper.vm.items[0].liked).toBeTrue();
|
||||
});
|
||||
131
resources/assets/tests/components/skinlib/SkinLibItem.test.js
Normal file
131
resources/assets/tests/components/skinlib/SkinLibItem.test.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import Vue from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import SkinLibItem from '@/components/skinlib/SkinLibItem';
|
||||
import { flushPromises } from '../../utils';
|
||||
import { swal } from '@/js/notify';
|
||||
import toastr from 'toastr';
|
||||
|
||||
jest.mock('@/js/notify');
|
||||
jest.mock('toastr');
|
||||
|
||||
test('urls', () => {
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: { tid: 1 }
|
||||
});
|
||||
expect(wrapper.find('a').attributes()).toHaveProperty(
|
||||
'href',
|
||||
'/skinlib/show/1'
|
||||
);
|
||||
expect(wrapper.find('img').attributes()).toHaveProperty(
|
||||
'src',
|
||||
'/preview/1.png'
|
||||
);
|
||||
});
|
||||
|
||||
test('render basic information', () => {
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: {
|
||||
tid: 1,
|
||||
name: 'test',
|
||||
type: 'steve',
|
||||
}
|
||||
});
|
||||
expect(wrapper.text()).toContain('test');
|
||||
expect(wrapper.text()).toContain('skinlib.filter.steve');
|
||||
});
|
||||
|
||||
test('anonymous user', () => {
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: { anonymous: true }
|
||||
});
|
||||
const button = wrapper.find('.more');
|
||||
expect(button.attributes().title).toBe('skinlib.anonymous');
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('private texture', () => {
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: { isPublic: false }
|
||||
});
|
||||
expect(wrapper.text()).toContain('skinlib.private');
|
||||
|
||||
wrapper.setProps({ isPublic: true });
|
||||
expect(wrapper.text()).not.toContain('skinlib.private');
|
||||
});
|
||||
|
||||
test('liked state', () => {
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: { liked: true, anonymous: false }
|
||||
});
|
||||
const button = wrapper.find('.like');
|
||||
|
||||
expect(button.attributes()).toHaveProperty('title', 'skinlib.removeFromCloset');
|
||||
expect(button.classes()).toContain('liked');
|
||||
|
||||
wrapper.setProps({ liked: false });
|
||||
expect(button.attributes()).toHaveProperty('title', 'skinlib.addToCloset');
|
||||
expect(button.classes()).not.toContain('liked');
|
||||
});
|
||||
|
||||
test('remove from closet', async () => {
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1, msg: '1' })
|
||||
.mockResolvedValue({ errno: 0 });
|
||||
swal.mockResolvedValueOnce({ dismiss: 1 })
|
||||
.mockResolvedValue({});
|
||||
jest.spyOn(toastr, 'warning');
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: { tid: 1, liked: true, anonymous: false }
|
||||
});
|
||||
const button = wrapper.find('.like');
|
||||
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/closet/remove',
|
||||
{ tid: 1 }
|
||||
);
|
||||
expect(toastr.warning).toBeCalledWith('1');
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.emitted('like-toggled')[0]).toEqual([false]);
|
||||
});
|
||||
|
||||
test('add to closet', async () => {
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1, msg: '1' })
|
||||
.mockResolvedValue({ errno: 0 });
|
||||
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
|
||||
.mockImplementation(({ inputValidator }) => {
|
||||
if (inputValidator) {
|
||||
inputValidator();
|
||||
inputValidator('name');
|
||||
}
|
||||
return { value: 'name' };
|
||||
});
|
||||
jest.spyOn(toastr, 'warning');
|
||||
const wrapper = mount(SkinLibItem, {
|
||||
propsData: { tid: 1, liked: false, anonymous: false }
|
||||
});
|
||||
const button = wrapper.find('.like');
|
||||
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/closet/add',
|
||||
{ tid: 1, name: 'name' }
|
||||
);
|
||||
expect(toastr.warning).toBeCalledWith('1');
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.emitted('like-toggled')[0]).toEqual([true]);
|
||||
});
|
||||
|
|
@ -35,12 +35,16 @@ auth:
|
|||
reset-button: Reset
|
||||
|
||||
skinlib:
|
||||
reset: Reset Filter
|
||||
nowShowing: Now showing
|
||||
addToCloset: Add to closet
|
||||
removeFromCloset: Remove from closet
|
||||
setItemName: Set a name for this texture
|
||||
emptyItemName: Empty texture name.
|
||||
setNewTextureName: 'Please enter the new texture name:'
|
||||
emptyNewTextureName: Empty new texture name.
|
||||
anyModels: Any Models
|
||||
seeMyUpload: Check My Upload
|
||||
filter:
|
||||
skin: (Any Model)
|
||||
steve: (Steve)
|
||||
|
|
@ -49,6 +53,7 @@ skinlib:
|
|||
uploader: 'User (UID = :uid) Uploaded'
|
||||
allUsers: All Users
|
||||
sort:
|
||||
title: Sort
|
||||
time: Newestly Uploaded
|
||||
likes: Most Likes
|
||||
badSkinSize: The size of selected skin file is not valid
|
||||
|
|
@ -276,6 +281,8 @@ general:
|
|||
rotation: Rotation
|
||||
pause: Pause
|
||||
reset: Reset
|
||||
skinlib: Skin Library
|
||||
loading: Loading
|
||||
user:
|
||||
email: Email
|
||||
nickname: Nick Name
|
||||
|
|
|
|||
|
|
@ -35,12 +35,16 @@ auth:
|
|||
reset-button: 重置
|
||||
|
||||
skinlib:
|
||||
reset: 清除筛选
|
||||
nowShowing: 当前正显示
|
||||
addToCloset: 添加至衣柜
|
||||
removeFromCloset: 从衣柜中移除
|
||||
setItemName: 给你的皮肤起个名字吧~
|
||||
emptyItemName: 你还没有填写要收藏的材质名称啊
|
||||
anonymous: 请先登录
|
||||
private: 私密
|
||||
anyModels: 任意模型
|
||||
seeMyUpload: 查看我上传的
|
||||
filter:
|
||||
skin: (任意模型)
|
||||
steve: (Steve)
|
||||
|
|
@ -49,6 +53,7 @@ skinlib:
|
|||
uploader: '用户(UID = :uid)上传'
|
||||
allUsers: 所有用户
|
||||
sort:
|
||||
title: 排序
|
||||
time: 最新上传
|
||||
likes: 最多收藏
|
||||
badSkinSize: 所选皮肤文件的尺寸不对哦
|
||||
|
|
@ -270,6 +275,8 @@ general:
|
|||
rotation: 旋转
|
||||
pause: 暂停
|
||||
reset: 重置
|
||||
skinlib: 皮肤库
|
||||
loading: 正在加载
|
||||
user:
|
||||
email: 邮箱
|
||||
nickname: 昵称
|
||||
|
|
|
|||
|
|
@ -3,41 +3,5 @@
|
|||
@section('title', trans('general.skinlib'))
|
||||
|
||||
@section('content')
|
||||
<!-- Full Width Column -->
|
||||
<div class="content-wrapper">
|
||||
<div class="container">
|
||||
<!-- Content Header (Page header) -->
|
||||
<section class="content-header">
|
||||
<h1>@lang('general.skinlib')
|
||||
<small id="search-indicator"></small>
|
||||
</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><i class="fas fa-tags"></i> @lang('skinlib.filter.now-showing')</li>
|
||||
<li id="filter-indicator"></li>
|
||||
<li id="uploader-indicator"></li>
|
||||
<li class="active" id="sort-indicator"></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="box box-default">
|
||||
<!-- Container of Skin Library -->
|
||||
<div id="skinlib-container" class="box-body"></div>
|
||||
|
||||
<div class="box-footer">
|
||||
<!-- Pagination -->
|
||||
<div class="pull-right" id="skinlib-paginator"></div>
|
||||
<select class="pull-right pagination"></select>
|
||||
<p class="pull-right pagination"></p>
|
||||
</div>
|
||||
|
||||
<div class="overlay">
|
||||
<i class="fas fa-sync-alt fa-spin"></i>
|
||||
<span>@lang('general.loading')</span>
|
||||
</div>
|
||||
</div><!-- /.box -->
|
||||
</section><!-- /.content -->
|
||||
</div><!-- /.container -->
|
||||
</div><!-- /.content-wrapper -->
|
||||
<div class="content-wrapper"></div>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -37,60 +37,12 @@
|
|||
<li>
|
||||
<a href="{{ url('user/closet') }}">@lang('general.my-closet')</a>
|
||||
</li>
|
||||
|
||||
@unless (isset($with_out_filter))
|
||||
<!-- Filters -->
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fas fa-filter" aria-hidden="true"></i> @lang('skinlib.general.filter') <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a class="filter" data-filter="skin" href="#">@lang('general.skin') <small>@lang('skinlib.filter.any-model')</small></a></li>
|
||||
|
||||
<li><a class="filter" data-filter="steve" href="#">@lang('general.skin') <small>@lang('skinlib.filter.steve-model')</small></a></li>
|
||||
|
||||
<li><a class="filter" data-filter="alex" href="#">@lang('general.skin') <small>@lang('skinlib.filter.alex-model')</small></a></li>
|
||||
|
||||
<li class="divider"></li>
|
||||
|
||||
<li><a class="filter" data-filter="cape" href="#">@lang('general.cape')</a></li>
|
||||
|
||||
@if (!is_null($user))
|
||||
<li class="divider"></li>
|
||||
<li><a class="filter" data-filter="uploader" data-uid="{{ $user->uid }}" href="#">@lang('skinlib.general.my-upload')</a></li>
|
||||
@endif
|
||||
|
||||
<li class="divider"></li>
|
||||
<li><a href="{{ url('skinlib') }}">@lang('skinlib.filter.clean-filter')</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- Sort -->
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fas fa-sort-amount-down" aria-hidden="true"></i> @lang('skinlib.general.sort') <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a class="sort" data-sort="likes" href="#">@lang('skinlib.sort.most-likes')</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a class="sort" data-sort="time" href="#">@lang('skinlib.sort.newest-uploaded')</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@endunless
|
||||
</ul>
|
||||
|
||||
@unless (isset($with_out_filter))
|
||||
<form class="navbar-form navbar-left" id="search-form" role="search">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="navbar-search-input" name="q" placeholder="@lang('skinlib.general.search-textures')" value="{{ $q or '' }}" />
|
||||
</div>
|
||||
</form>
|
||||
@endunless
|
||||
</div><!-- /.navbar-collapse -->
|
||||
<!-- Navbar Right Menu -->
|
||||
<div class="navbar-custom-menu">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{ url('skinlib/upload') }}"><i class="fas fa--fileupload" aria-hidden="true"></i> <span class="description-text">@lang('skinlib.general.upload-new-skin')</span></a></li>
|
||||
<li><a href="{{ url('skinlib/upload') }}"><i class="fas fa-upload" aria-hidden="true"></i> <span class="description-text">@lang('skinlib.general.upload-new-skin')</span></a></li>
|
||||
|
||||
@include('common.language')
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => [],
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 0
|
||||
]);
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?filter=steve')
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?filter=what')
|
||||
->assertJson([
|
||||
'items' => [],
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 0
|
||||
]);
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?filter=cape')
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?uploader='.$uid)
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?sort=tid')
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($skins->sortByDesc('tid')->values()),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?keyword='.$keyword)
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?sort=likes&keyword='.$keyword)
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 1
|
||||
]);
|
||||
|
||||
|
|
@ -173,13 +173,13 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
$this->getJson('/skinlib/data?page=-5')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
$expected = $skins
|
||||
|
|
@ -191,19 +191,19 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?page=2')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
$this->getJson('/skinlib/data?page=8')
|
||||
->assertJson([
|
||||
'items' => [],
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
$this->getJson('/skinlib/data?items_per_page=-6&page=2')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
$expected = $skins
|
||||
|
|
@ -214,7 +214,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data?page=3&items_per_page=8')
|
||||
->assertJson([
|
||||
'items' => $this->serializeTextures($expected),
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 4
|
||||
]);
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => true,
|
||||
'current_uid' => 0,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ class SkinlibControllerTest extends TestCase
|
|||
->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => false,
|
||||
'current_uid' => $otherUser->uid,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ class SkinlibControllerTest extends TestCase
|
|||
$this->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => false,
|
||||
'current_uid' => $otherUser->uid,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
|
||||
|
|
@ -292,7 +292,7 @@ class SkinlibControllerTest extends TestCase
|
|||
->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => false,
|
||||
'current_uid' => $uploader->uid,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ class SkinlibControllerTest extends TestCase
|
|||
->getJson('/skinlib/data')
|
||||
->assertJson([
|
||||
'items' => $expected,
|
||||
'anonymous' => false,
|
||||
'current_uid' => $admin->uid,
|
||||
'total_pages' => 2
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user