generate URLs by codegen

This commit is contained in:
Pig Fang 2020-05-30 10:44:36 +08:00
parent 46223eba4f
commit 5094cff07a
37 changed files with 385 additions and 158 deletions

View File

@ -85,10 +85,13 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: yarn
run: |
yarn
composer install --prefer-dist --no-progress --no-suggest --no-dev
- name: Run checks
run: |
# yarn lint
yarn build:urls
yarn tsc -p . --noEmit
yarn tsc -p ./resources/assets/tests --noEmit
jest:
@ -99,7 +102,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: yarn
run: |
yarn
composer install --prefer-dist --no-progress --no-suggest --no-dev
- name: Build
run: yarn build:urls
- name: Run tests
run: yarn test --coverage
- name: Upload coverage report

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ storage/options.php
.php_cs.cache
resources/views/overrides
public/sw.js
resources/assets/src/scripts/urls.ts

View File

@ -14,7 +14,8 @@
"build": "webpack --mode=production -p --progress",
"lint": "eslint --ext=ts -f=beauty .",
"fmt": "prettier --write resources/assets",
"test": "jest"
"test": "jest",
"build:urls": "ts-node -P tsconfig.node.json scripts/generateUrls.ts"
},
"dependencies": {
"@emotion/core": "^10.0.28",
@ -79,6 +80,7 @@
"style-loader": "^1.2.1",
"ts-jest": "^26.0.0",
"ts-loader": "^7.0.4",
"ts-node": "^8.10.2",
"typescript": "^3.9.2",
"url-loader": "^4.1.0",
"webpack": "^4.43.0",

View File

@ -1,6 +1,7 @@
import { post } from './net'
import { t } from './i18n'
import { showModal } from './notify'
import urls from './urls'
export async function logout() {
try {
@ -12,7 +13,7 @@ export async function logout() {
return
}
await post('/auth/logout')
await post(urls.auth.logout())
window.location.href = blessing.base_url
}

View File

@ -6,6 +6,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { Player, Paginator } from '@/scripts/types'
import { toast, showModal } from '@/scripts/notify'
import urls from '@/scripts/urls'
import Loading from '@/components/Loading'
import Pagination from '@/components/Pagination'
import Header from '../UsersManagement/Header'
@ -32,7 +33,7 @@ const PlayersManagement: React.FC = () => {
const getPlayers = async () => {
setIsLoading(true)
const { data, last_page }: Paginator<Player> = await fetch.get(
'/admin/players/list',
urls.admin.players.list(),
{
q: query,
page,
@ -79,7 +80,7 @@ const PlayersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/players/${player.pid}/name`,
urls.admin.players.name(player.pid),
{ player_name: name },
)
if (code === 0) {
@ -107,7 +108,7 @@ const PlayersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/players/${player.pid}/owner`,
urls.admin.players.owner(player.pid),
{ uid },
)
if (code === 0) {
@ -124,7 +125,7 @@ const PlayersManagement: React.FC = () => {
const handleUpdateTexture = async (type: 'skin' | 'cape', tid: number) => {
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/players/${players[textureUpdating].pid}/textures`,
urls.admin.players.texture(players[textureUpdating].pid),
{ type, tid },
)
@ -150,7 +151,7 @@ const PlayersManagement: React.FC = () => {
}
const { code, message } = await fetch.del<fetch.ResponseBody>(
`/admin/players/${player.pid}`,
urls.admin.players.delete(player.pid),
)
if (code === 0) {
setPlayers((players) => players.filter(({ pid }) => pid !== player.pid))

View File

@ -7,6 +7,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { User, UserPermission, Paginator } from '@/scripts/types'
import { toast, showModal } from '@/scripts/notify'
import urls from '@/scripts/urls'
import type { Props as ModalInputProps } from '@/components/ModalInput'
import Loading from '@/components/Loading'
import Pagination from '@/components/Pagination'
@ -36,7 +37,7 @@ const UsersManagement: React.FC = () => {
const getUsers = async () => {
setIsLoading(true)
const { data, last_page }: Paginator<User> = await fetch.get(
'/admin/users/list',
urls.admin.users.list(),
{
q: query,
page,
@ -83,7 +84,7 @@ const UsersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/users/${user.uid}/email`,
urls.admin.users.email(user.uid),
{ email },
)
if (code === 0) {
@ -115,7 +116,7 @@ const UsersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/users/${user.uid}/nickname`,
urls.admin.users.nickname(user.uid),
{ nickname },
)
if (code === 0) {
@ -143,7 +144,7 @@ const UsersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/users/${user.uid}/score`,
urls.admin.users.score(user.uid),
{ score },
)
if (code === 0) {
@ -180,7 +181,7 @@ const UsersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/users/${user.uid}/permission`,
urls.admin.users.permission(user.uid),
{ permission },
)
if (code === 0) {
@ -195,7 +196,7 @@ const UsersManagement: React.FC = () => {
const handleVerificationToggle = async (user: User, index: number) => {
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/users/${user.uid}/verification`,
urls.admin.users.verification(user.uid),
)
if (code === 0) {
toast.success(message)
@ -222,7 +223,7 @@ const UsersManagement: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/admin/users/${user.uid}/password`,
urls.admin.users.password(user.uid),
{ password },
)
if (code === 0) {
@ -242,7 +243,7 @@ const UsersManagement: React.FC = () => {
return
}
const { code, message } = await fetch.del(`/admin/users/${user.uid}`)
const { code, message } = await fetch.del(urls.admin.users.delete(user.uid))
if (code === 0) {
toast.success(message)
setUsers((users) => users.filter(({ uid }) => uid !== user.uid))

View File

@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react'
import { hot } from 'react-hot-loader/root'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import urls from '@/scripts/urls'
import Alert from '@/components/Alert'
import Captcha from '@/components/Captcha'
@ -23,7 +24,7 @@ const Forgot: React.FC = () => {
const captcha = await ref.current!.execute()
const { code, message } = await fetch.post<fetch.ResponseBody>(
'/auth/forgot',
urls.auth.forgot(),
{ email, captcha },
)
if (code === 0) {

View File

@ -4,6 +4,7 @@ import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { showModal } from '@/scripts/notify'
import urls from '@/scripts/urls'
import Alert from '@/components/Alert'
import Captcha from '@/components/Captcha'
@ -58,7 +59,7 @@ const Login: React.FC = () => {
event.preventDefault()
setIsPending(true)
const response = await fetch.post<Response>('/auth/login', {
const response = await fetch.post<Response>(urls.auth.login(), {
identification,
password,
keep: remember,

View File

@ -4,6 +4,7 @@ import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { toast } from '@/scripts/notify'
import urls from '@/scripts/urls'
import Alert from '@/components/Alert'
import Captcha from '@/components/Captcha'
@ -55,7 +56,7 @@ const Registration: React.FC = () => {
setIsPending(true)
const { code, message } = await fetch.post<fetch.ResponseBody>(
'/auth/register',
urls.auth.register(),
Object.assign(
{ email, password, captcha: await captchaRef.current!.execute() },
requirePlayer ? { player_name: playerName } : { nickname: nickName },

View File

@ -3,6 +3,7 @@ import { hot } from 'react-hot-loader/root'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { toast } from '@/scripts/notify'
import urls from '@/scripts/urls'
import Alert from '@/components/Alert'
const Reset: React.FC = () => {
@ -37,7 +38,7 @@ const Reset: React.FC = () => {
if (code === 0) {
toast.success(message)
setTimeout(() => {
window.location.href = `${blessing.base_url}/auth/login`
window.location.href = blessing.base_url + urls.auth.login()
}, 2000)
} else {
setWarningMessage(message)

View File

@ -2,6 +2,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { showModal, toast } from '@/scripts/notify'
import { Texture } from '@/scripts/types'
import urls from '@/scripts/urls'
export default async function addClosetItem(
texture: Pick<Texture, 'tid' | 'name'>,
@ -25,7 +26,7 @@ export default async function addClosetItem(
}
const { code, message } = await fetch.post<fetch.ResponseBody>(
'/user/closet',
urls.user.closet.add(),
{ tid: texture.tid, name },
)
if (code === 0) {

View File

@ -5,6 +5,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { toast } from '@/scripts/notify'
import { Paginator, TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import Loading from '@/components/Loading'
import Pagination from '@/components/Pagination'
import addClosetItem from '../Show/addClosetItem'
@ -82,7 +83,7 @@ const SkinLibrary: React.FC = () => {
window.history.pushState(search.toString(), '', `?${search}`)
const result = await fetch.get<Paginator<LibraryItem>>(
'/skinlib/list',
urls.skinlib.list(),
search,
)
setItems(result.data)
@ -94,7 +95,7 @@ const SkinLibrary: React.FC = () => {
useEffect(() => {
const getCloset = async () => {
const closet = await fetch.get<number[]>('/user/closet/ids')
const closet = await fetch.get<number[]>(urls.user.closet.ids())
setCloset(closet)
}
if (currentUid) {

View File

@ -8,6 +8,7 @@ import * as fetch from '@/scripts/net'
import { showModal, toast } from '@/scripts/notify'
import { isAlex } from '@/scripts/textureUtils'
import { TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import FileInput from '@/components/FileInput'
import ViewerSkeleton from '@/components/ViewerSkeleton'
@ -86,11 +87,11 @@ const Upload: React.FC = () => {
setIsUploading(true)
const { code, message, data: { tid } = { tid: 0 } } = await fetch.post<
fetch.ResponseBody<{ tid: number }>
>('/skinlib/upload', formData)
>(urls.skinlib.upload(), formData)
setIsUploading(false)
if (code === 0) {
window.location.href = `${blessing.base_url}/skinlib/show/${tid}`
window.location.href = blessing.base_url + urls.skinlib.show(tid)
} else if (code === 2) {
try {
await showModal({
@ -98,7 +99,7 @@ const Upload: React.FC = () => {
text: message,
okButtonText: t('user.viewInSkinlib'),
})
window.location.href = `${blessing.base_url}/skinlib/show/${tid}`
window.location.href = blessing.base_url + urls.skinlib.show(tid)
} catch {
//
}

View File

@ -4,6 +4,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { toast } from '@/scripts/notify'
import { Player } from '@/scripts/types'
import urls from '@/scripts/urls'
import Loading from '@/components/Loading'
import Modal from '@/components/Modal'
@ -29,7 +30,7 @@ const ModalApply: React.FC<Props> = (props) => {
const getPlayers = async () => {
setIsLoading(true)
const players = await fetch.get<Player[]>('/user/player/list')
const players = await fetch.get<Player[]>(urls.user.player.list())
setPlayers(players)
setIsLoading(false)
}
@ -42,7 +43,7 @@ const ModalApply: React.FC<Props> = (props) => {
const handleSelect = async (player: Player) => {
const { code, message } = await fetch.post<fetch.ResponseBody>(
`/user/player/set/${player.pid}`,
urls.user.player.set(player.pid),
{
skin: props.skin,
cape: props.cape,

View File

@ -10,6 +10,7 @@ import {
Paginator,
TextureType,
} from '@/scripts/types'
import urls from '@/scripts/urls'
import Loading from '@/components/Loading'
import Pagination from '@/components/Pagination'
import ClosetItem from './ClosetItem'
@ -43,7 +44,7 @@ const Closet: React.FC = () => {
const getItems = async () => {
setIsLoading(true)
const { data, last_page } = await fetch.get<Paginator<Item>>(
'/user/closet/list',
urls.user.closet.list(),
{ category, q: query, page },
)
@ -106,7 +107,7 @@ const Closet: React.FC = () => {
}
const { code, message } = await fetch.put<fetch.ResponseBody>(
`/user/closet/${item.tid}`,
urls.user.closet.rename(item.tid),
{ name },
)
if (code === 0) {

View File

@ -1,6 +1,7 @@
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { showModal, toast } from '@/scripts/notify'
import urls from '@/scripts/urls'
export default async function removeClosetItem(tid: number): Promise<boolean> {
try {
@ -13,7 +14,7 @@ export default async function removeClosetItem(tid: number): Promise<boolean> {
}
const { code, message } = await fetch.del<fetch.ResponseBody>(
`/user/closet/${tid}`,
urls.user.closet.remove(tid),
)
if (code === 0) {
toast.success(message)

View File

@ -1,6 +1,7 @@
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { showModal, toast } from '@/scripts/notify'
import urls from '@/scripts/urls'
export default async function setAsAvatar(tid: number) {
try {
@ -13,7 +14,7 @@ export default async function setAsAvatar(tid: number) {
}
const { code, message } = await fetch.post<fetch.ResponseBody>(
'/user/profile/avatar',
urls.user.profile.avatar(),
{ tid },
)
if (code === 0) {

View File

@ -5,6 +5,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { toast } from '@/scripts/notify'
import useTween from '@/scripts/hooks/useTween'
import urls from '@/scripts/urls'
import * as breakpoints from '@/styles/breakpoints'
import InfoBox from './InfoBox'
import SignButton from './SignButton'
@ -57,7 +58,7 @@ const Dashboard: React.FC = () => {
const fetchInfo = async () => {
setLoading(true)
const { data } = await fetch.get<fetch.ResponseBody<ScoreInfo>>(
'/user/score-info',
urls.user.score(),
)
setPlayers(data.stats.players)
setStorage(data.stats.storage)
@ -74,7 +75,7 @@ const Dashboard: React.FC = () => {
setLoading(true)
const { code, message, data } = await fetch.post<
fetch.ResponseBody<SignReturn>
>('/user/sign')
>(urls.user.sign())
if (code === 0) {
toast.success(message)

View File

@ -3,6 +3,7 @@ import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { toast } from '@/scripts/notify'
import { Player } from '@/scripts/types'
import urls from '@/scripts/urls'
import Modal from '@/components/Modal'
type Extra = {
@ -28,7 +29,7 @@ const ModalAddPlayer: React.FC<Props> = (props) => {
const handleConfirm = async () => {
const { code, message, data: player } = await fetch.post<
fetch.ResponseBody<Player>
>('/user/player/add', { name })
>(urls.user.player.add(), { name })
if (code === 0) {
toast.success(message)
props.onAdd(player)

View File

@ -5,6 +5,7 @@ import * as fetch from '@/scripts/net'
import { showModal, toast } from '@/scripts/notify'
import useTexture from '@/scripts/hooks/useTexture'
import { Player, TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import Loading from '@/components/Loading'
import Row from './Row'
import Previewer from './Previewer'
@ -67,7 +68,7 @@ const Players: React.FC = () => {
}
const { code, message } = await fetch.post<fetch.ResponseBody>(
`/user/player/rename/${player.pid}`,
urls.user.player.rename(player.pid),
{ name },
)
if (code === 0) {
@ -88,7 +89,7 @@ const Players: React.FC = () => {
}
const { code, message } = await fetch.post<fetch.ResponseBody>(
`/user/player/texture/clear/${selected}`,
urls.user.player.clear(selected),
{ type: [skin && 'skin', cape && 'cape'].filter(Boolean) },
)
if (code === 0) {
@ -128,7 +129,7 @@ const Players: React.FC = () => {
}
const { code, message } = await fetch.post<fetch.ResponseBody>(
`/user/player/delete/${player.pid}`,
urls.user.player.delete(player.pid),
)
if (code === 0) {
toast.success(message)

View File

@ -1,6 +1,7 @@
import { logout } from '@/scripts/logout'
import { post } from '@/scripts/net'
import { showModal } from '@/scripts/notify'
import urls from '@/scripts/urls'
jest.mock('@/scripts/net')
jest.mock('@/scripts/notify')
@ -13,5 +14,5 @@ test('log out', async () => {
expect(post).not.toBeCalled()
await logout()
expect(post).toBeCalledWith('/auth/logout')
expect(post).toBeCalledWith(urls.auth.logout())
})

View File

@ -4,6 +4,7 @@ import { createPaginator } from '../../utils'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { Player } from '@/scripts/types'
import urls from '@/scripts/urls'
import PlayersManagement from '@/views/admin/PlayersManagement'
jest.mock('@/scripts/net')
@ -30,7 +31,10 @@ test('search players', async () => {
const { getByTitle, getByText } = render(<PlayersManagement />)
await waitFor(() =>
expect(fetch.get).toBeCalledWith('/admin/players/list', { q: '', page: 1 }),
expect(fetch.get).toBeCalledWith(urls.admin.players.list(), {
q: '',
page: 1,
}),
)
fireEvent.input(getByTitle(t('vendor.datatable.search')), {
@ -38,7 +42,7 @@ test('search players', async () => {
})
fireEvent.click(getByText(t('vendor.datatable.search')))
await waitFor(() =>
expect(fetch.get).toBeCalledWith('/admin/players/list', {
expect(fetch.get).toBeCalledWith(urls.admin.players.list(), {
q: 's',
page: 1,
}),
@ -103,7 +107,7 @@ describe('update player name', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/name`, {
expect(fetch.put).toBeCalledWith(urls.admin.players.name(fixture.pid), {
player_name: 'reina',
}),
)
@ -127,7 +131,7 @@ describe('update player name', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/name`, {
expect(fetch.put).toBeCalledWith(urls.admin.players.name(fixture.pid), {
player_name: 'reina',
}),
)
@ -170,7 +174,7 @@ describe('update owner', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/owner`, {
expect(fetch.put).toBeCalledWith(urls.admin.players.owner(fixture.pid), {
uid: 2,
}),
)
@ -194,7 +198,7 @@ describe('update owner', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/owner`, {
expect(fetch.put).toBeCalledWith(urls.admin.players.owner(fixture.pid), {
uid: 2,
}),
)
@ -237,7 +241,7 @@ describe('update texture', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/players/${fixture.pid}/textures`,
urls.admin.players.texture(fixture.pid),
{
type: 'skin',
tid: 2,
@ -265,7 +269,7 @@ describe('update texture', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/players/${fixture.pid}/textures`,
urls.admin.players.texture(fixture.pid),
{
type: 'cape',
tid: 2,
@ -292,7 +296,7 @@ describe('update texture', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/players/${fixture.pid}/textures`,
urls.admin.players.texture(fixture.pid),
{
type: 'skin',
tid: 2,
@ -332,7 +336,7 @@ describe('delete player', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.del).toBeCalledWith(`/admin/players/${fixture.pid}`),
expect(fetch.del).toBeCalledWith(urls.admin.players.delete(fixture.pid)),
)
expect(queryByText('ok')).toBeInTheDocument()
expect(queryByRole('status')).toHaveClass('alert-success')
@ -351,7 +355,7 @@ describe('delete player', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.del).toBeCalledWith(`/admin/players/${fixture.pid}`),
expect(fetch.del).toBeCalledWith(urls.admin.players.delete(fixture.pid)),
)
expect(queryByText('failed')).toBeInTheDocument()
expect(queryByRole('alert')).toHaveClass('alert-danger')

View File

@ -4,6 +4,7 @@ import { createPaginator } from '../../utils'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { User, UserPermission } from '@/scripts/types'
import urls from '@/scripts/urls'
import UsersManagement from '@/views/admin/UsersManagement'
jest.mock('@/scripts/net')
@ -40,7 +41,10 @@ test('search users', async () => {
const { getByTitle, getByText } = render(<UsersManagement />)
await waitFor(() =>
expect(fetch.get).toBeCalledWith('/admin/users/list', { q: '', page: 1 }),
expect(fetch.get).toBeCalledWith(urls.admin.users.list(), {
q: '',
page: 1,
}),
)
fireEvent.input(getByTitle(t('vendor.datatable.search')), {
@ -48,7 +52,7 @@ test('search users', async () => {
})
fireEvent.click(getByText(t('vendor.datatable.search')))
await waitFor(() =>
expect(fetch.get).toBeCalledWith('/admin/users/list', {
expect(fetch.get).toBeCalledWith(urls.admin.users.list(), {
q: 's',
page: 1,
}),
@ -267,7 +271,7 @@ describe('update email', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/email`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.email(fixture.uid), {
email: 'd@e.f',
}),
)
@ -291,7 +295,7 @@ describe('update email', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/email`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.email(fixture.uid), {
email: 'd@e.f',
}),
)
@ -339,7 +343,7 @@ describe('update nickname', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/nickname`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.nickname(fixture.uid), {
nickname: 'kumiko',
}),
)
@ -363,7 +367,7 @@ describe('update nickname', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/nickname`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.nickname(fixture.uid), {
nickname: 'kumiko',
}),
)
@ -403,7 +407,7 @@ describe('update score', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/score`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.score(fixture.uid), {
score: 999,
}),
)
@ -427,7 +431,7 @@ describe('update score', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/score`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.score(fixture.uid), {
score: 999,
}),
)
@ -466,7 +470,7 @@ describe('update permission', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/users/${fixture.uid}/permission`,
urls.admin.users.permission(fixture.uid),
{
permission: UserPermission.Banned,
},
@ -491,7 +495,7 @@ describe('update permission', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/users/${fixture.uid}/permission`,
urls.admin.users.permission(fixture.uid),
{
permission: UserPermission.Banned,
},
@ -523,7 +527,7 @@ describe('update permission', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/users/${fixture.uid}/permission`,
urls.admin.users.permission(fixture.uid),
{
permission: UserPermission.Admin,
},
@ -550,7 +554,7 @@ describe('toggle verification', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/users/${fixture.uid}/verification`,
urls.admin.users.verification(fixture.uid),
),
)
expect(queryByText('ok')).toBeInTheDocument()
@ -568,7 +572,7 @@ describe('toggle verification', () => {
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/users/${fixture.uid}/verification`,
urls.admin.users.verification(fixture.uid),
),
)
expect(queryByText('failed')).toBeInTheDocument()
@ -610,7 +614,7 @@ describe('update password', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/password`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.password(fixture.uid), {
password: '123',
}),
)
@ -636,7 +640,7 @@ describe('update password', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/admin/users/${fixture.uid}/password`, {
expect(fetch.put).toBeCalledWith(urls.admin.users.password(fixture.uid), {
password: '123',
}),
)
@ -670,7 +674,7 @@ describe('delete user', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.del).toBeCalledWith(`/admin/users/${fixture.uid}`),
expect(fetch.del).toBeCalledWith(urls.admin.users.delete(fixture.uid)),
)
expect(queryByText('ok')).toBeInTheDocument()
expect(queryByRole('status')).toHaveClass('alert-success')
@ -687,7 +691,7 @@ describe('delete user', () => {
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.del).toBeCalledWith(`/admin/users/${fixture.uid}`),
expect(fetch.del).toBeCalledWith(urls.admin.users.delete(fixture.uid)),
)
expect(queryByText('failed')).toBeInTheDocument()
expect(queryByRole('alert')).toHaveClass('alert-danger')
@ -764,11 +768,7 @@ describe('table mode', () => {
fireEvent.click(getByTitle('Table Mode'))
fireEvent.click(getByTitle(t('admin.toggleVerification')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(
`/admin/users/${fixture.uid}/verification`,
),
)
await waitFor(() => expect(fetch.put).toBeCalled())
expect(queryByText('ok')).toBeInTheDocument()
expect(queryByRole('status')).toHaveClass('alert-success')
expect(queryByText(t('admin.unverified'))).toBeInTheDocument()

View File

@ -2,6 +2,7 @@ import React from 'react'
import { render, waitFor, fireEvent } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import urls from '@/scripts/urls'
import Forgot from '@/views/auth/Forgot'
jest.mock('@/scripts/net')
@ -20,7 +21,7 @@ describe('submit', () => {
})
fireEvent.click(getByText(t('auth.forgot.button')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/forgot', {
expect(fetch.post).toBeCalledWith(urls.auth.forgot(), {
email: 'a@b.c',
captcha: 'abc',
}),
@ -41,7 +42,7 @@ describe('submit', () => {
})
fireEvent.click(getByText(t('auth.forgot.button')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/forgot', {
expect(fetch.post).toBeCalledWith(urls.auth.forgot(), {
email: 'a@b.c',
captcha: 'abc',
}),

View File

@ -2,6 +2,7 @@ import React from 'react'
import { render, waitFor, fireEvent } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import urls from '@/scripts/urls'
import Login from '@/views/auth/Login'
jest.mock('@/scripts/net')
@ -33,7 +34,7 @@ describe('submit form', () => {
})
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: false,
@ -60,7 +61,7 @@ describe('submit form', () => {
fireEvent.click(getByLabelText(t('auth.keep')))
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: true,
@ -84,7 +85,7 @@ describe('submit form', () => {
})
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: false,
@ -114,7 +115,7 @@ describe('submit form', () => {
})
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: false,
@ -130,7 +131,7 @@ describe('submit form', () => {
})
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: false,
@ -156,7 +157,7 @@ describe('submit form', () => {
})
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: false,
@ -183,7 +184,7 @@ describe('submit form', () => {
})
fireEvent.click(getByText(t('auth.login')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/login', {
expect(fetch.post).toBeCalledWith(urls.auth.login(), {
identification: 'a@b.c',
password: 'password',
keep: false,

View File

@ -2,6 +2,7 @@ import React from 'react'
import { render, waitFor, fireEvent } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import urls from '@/scripts/urls'
import Registration from '@/views/auth/Registration'
jest.mock('@/scripts/net')
@ -59,7 +60,7 @@ test('succeeded', async () => {
})
fireEvent.click(getByText(t('auth.register-button')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/register', {
expect(fetch.post).toBeCalledWith(urls.auth.register(), {
email: 'a@b.c',
password: 'password',
nickname: 't',
@ -94,7 +95,7 @@ test('failed', async () => {
})
fireEvent.click(getByText(t('auth.register-button')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/register', {
expect(fetch.post).toBeCalledWith(urls.auth.register(), {
email: 'a@b.c',
password: 'password',
nickname: 't',
@ -128,7 +129,7 @@ test('register with new player', async () => {
})
fireEvent.click(getByText(t('auth.register-button')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/auth/register', {
expect(fetch.post).toBeCalledWith(urls.auth.register(), {
email: 'a@b.c',
password: 'password',
player_name: 'player',

View File

@ -3,6 +3,7 @@ import { render, fireEvent, waitFor } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { Texture, TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import Show, { Badge } from '@/views/skinlib/Show'
jest.mock('@/scripts/net')
@ -376,7 +377,7 @@ describe('add to closet', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/user/closet', {
expect(fetch.post).toBeCalledWith(urls.user.closet.add(), {
tid: fixtureSkin.tid,
name: 't',
}),
@ -400,7 +401,7 @@ describe('add to closet', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/user/closet', {
expect(fetch.post).toBeCalledWith(urls.user.closet.add(), {
tid: fixtureSkin.tid,
name: 't',
}),

View File

@ -4,6 +4,7 @@ import { createPaginator } from '../../utils'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import SkinLibrary from '@/views/skinlib/SkinLibrary'
import { LibraryItem } from '@/views/skinlib/SkinLibrary/types'
@ -30,7 +31,7 @@ test('without authenticated', async () => {
await waitFor(() => expect(fetch.get).toBeCalled())
expect(fetch.get).toBeCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('skin')
expect(search.get('sort')).toBe('time')
@ -54,7 +55,7 @@ test('search by keyword', async () => {
fireEvent.click(getByText(t('general.submit')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('keyword')).toBe('k')
return true
@ -73,7 +74,7 @@ test('select uploaded by self', async () => {
fireEvent.click(getByText(t('skinlib.seeMyUpload')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('uploader')).toBe('1')
return true
@ -106,7 +107,7 @@ test('reset query', async () => {
fireEvent.click(getByText(t('skinlib.reset')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('skin')
expect(search.get('keyword')).toBeNull()
@ -136,7 +137,7 @@ test('browser goes back', async () => {
window.dispatchEvent(event)
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('skin')
return true
@ -155,7 +156,7 @@ test('pagination', async () => {
fireEvent.click(getByText('2'))
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('page')).toBe('2')
return true
@ -182,7 +183,7 @@ test('library item', async () => {
fireEvent.click(getByText(fixtureItem.nickname))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('uploader')).toBe(fixtureItem.uploader.toString())
return true
@ -217,7 +218,7 @@ describe('by filter', () => {
fireEvent.click(getByText(t('general.skin')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('skin')
return true
@ -236,7 +237,7 @@ describe('by filter', () => {
fireEvent.click(getByText('Steve'))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('steve')
return true
@ -255,7 +256,7 @@ describe('by filter', () => {
fireEvent.click(getByText('Alex'))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('alex')
return true
@ -274,7 +275,7 @@ describe('by filter', () => {
fireEvent.click(getByText(t('general.cape')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('filter')).toBe('cape')
return true
@ -301,7 +302,7 @@ describe('sorting', () => {
fireEvent.click(getByText(t('skinlib.sort.time')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('sort')).toBe('time')
return true
@ -319,7 +320,7 @@ describe('sorting', () => {
fireEvent.click(getByText(t('skinlib.sort.likes')))
await waitFor(() =>
expect(fetch.get).toHaveBeenLastCalledWith(
'/skinlib/list',
urls.skinlib.list(),
expect.toSatisfy((search: URLSearchParams) => {
expect(search.get('sort')).toBe('likes')
return true
@ -334,7 +335,7 @@ describe('sorting', () => {
describe('add to closet', () => {
beforeEach(() => {
fetch.get.mockImplementation((url: string) => {
if (url === '/skinlib/list') {
if (url === urls.skinlib.list()) {
return Promise.resolve(createPaginator([fixtureItem]))
} else {
return Promise.resolve([])
@ -382,7 +383,7 @@ describe('remove from closet', () => {
beforeEach(() => {
window.blessing.extra.currentUid = 1
fetch.get.mockImplementation((url: string) => {
if (url === '/skinlib/list') {
if (url === urls.skinlib.list()) {
return Promise.resolve(createPaginator([fixtureItem]))
} else {
return Promise.resolve([fixtureItem.tid])

View File

@ -3,6 +3,7 @@ import { render, fireEvent, waitFor } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { isAlex } from '@/scripts/textureUtils'
import urls from '@/scripts/urls'
import Upload from '@/views/skinlib/Upload'
jest.mock('@/scripts/net')
@ -188,7 +189,7 @@ describe('upload texture', () => {
expect(getByRole('alert')).toHaveClass('alert-danger')
})
it('no name', () => {
it('invalid file type', () => {
const { getByText, getByTitle, getByRole, queryByText } = render(<Upload />)
const file = new File([], 't.png', { type: 'image/jpeg' })
@ -213,7 +214,10 @@ describe('upload texture', () => {
fireEvent.click(getByText(t('skinlib.upload.button')))
expect(queryByText(t('skinlib.uploading'))).toBeInTheDocument()
expect(fetch.post).toBeCalledWith('/skinlib/upload', expect.any(FormData))
expect(fetch.post).toBeCalledWith(
urls.skinlib.upload(),
expect.any(FormData),
)
const formData: FormData = fetch.post.mock.calls[0][1]
expect(formData.get('name')).toBe('t')

View File

@ -5,6 +5,7 @@ import $ from 'jquery'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { ClosetItem, Player, TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import Closet from '@/views/user/Closet'
jest.mock('@/scripts/net')
@ -142,9 +143,10 @@ describe('rename item', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/user/closet/${fixtureSkin.tid}`, {
name: 'my skin',
}),
expect(fetch.put).toBeCalledWith(
urls.user.closet.rename(fixtureSkin.tid),
{ name: 'my skin' },
),
)
expect(queryByText('my skin')).toBeInTheDocument()
expect(queryByText('success')).toBeInTheDocument()
@ -180,9 +182,10 @@ describe('rename item', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.put).toBeCalledWith(`/user/closet/${fixtureSkin.tid}`, {
name: 'my skin',
}),
expect(fetch.put).toBeCalledWith(
urls.user.closet.rename(fixtureSkin.tid),
{ name: 'my skin' },
),
)
expect(queryByText(fixtureSkin.pivot.item_name)).toBeInTheDocument()
expect(queryByText('failed')).toBeInTheDocument()
@ -204,7 +207,9 @@ describe('remove item', () => {
fireEvent.click(getByText(t('user.removeItem')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.del).toBeCalledWith(`/user/closet/${fixtureSkin.tid}`),
expect(fetch.del).toBeCalledWith(
urls.user.closet.remove(fixtureSkin.tid),
),
)
expect(queryByText(/skin library/i)).toBeInTheDocument()
expect(queryByText('success')).toBeInTheDocument()
@ -220,7 +225,9 @@ describe('remove item', () => {
fireEvent.click(getByText(t('user.removeItem')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.del).toBeCalledWith(`/user/closet/${fixtureSkin.tid}`),
expect(fetch.del).toBeCalledWith(
urls.user.closet.remove(fixtureSkin.tid),
),
)
expect(queryByText(fixtureSkin.pivot.item_name)).toBeInTheDocument()
expect(queryByText('failed')).toBeInTheDocument()
@ -306,7 +313,7 @@ describe('set avatar', () => {
fireEvent.click(getByText(t('user.setAsAvatar')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/user/profile/avatar', {
expect(fetch.post).toBeCalledWith(urls.user.profile.avatar(), {
tid: fixtureSkin.tid,
}),
)
@ -324,7 +331,7 @@ describe('set avatar', () => {
fireEvent.click(getByText(t('user.setAsAvatar')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/user/profile/avatar', {
expect(fetch.post).toBeCalledWith(urls.user.profile.avatar(), {
tid: fixtureSkin.tid,
}),
)
@ -401,10 +408,8 @@ describe('apply textures to player', () => {
fireEvent.click(getByTitle(fixturePlayer.name))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(
`/user/player/set/${fixturePlayer.pid}`,
{
skin: fixtureSkin.tid,
},
urls.user.player.set(fixturePlayer.pid),
{ skin: fixtureSkin.tid },
),
)
expect(queryByText('success')).toBeInTheDocument()
@ -432,10 +437,8 @@ describe('apply textures to player', () => {
fireEvent.click(getByTitle(fixturePlayer.name))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(
`/user/player/set/${fixturePlayer.pid}`,
{
skin: fixtureSkin.tid,
},
urls.user.player.set(fixturePlayer.pid),
{ skin: fixtureSkin.tid },
),
)
expect(queryByText('failed')).toBeInTheDocument()

View File

@ -2,6 +2,7 @@ import React from 'react'
import { render, fireEvent, waitFor } from '@testing-library/react'
import * as fetch from '@/scripts/net'
import { t } from '@/scripts/i18n'
import urls from '@/scripts/urls'
import Dashboard from '@/views/user/Dashboard'
jest.mock('@/scripts/net')
@ -78,7 +79,7 @@ describe('sign', () => {
const button = getByRole('button')
fireEvent.click(button)
await waitFor(() => expect(fetch.post).toBeCalledWith('/user/sign'))
await waitFor(() => expect(fetch.post).toBeCalledWith(urls.user.sign()))
expect(getByText('ok')).toBeInTheDocument()
expect(getByRole('status')).toHaveClass('alert-success')
expect(button).toBeDisabled()
@ -92,7 +93,7 @@ describe('sign', () => {
await waitFor(() => expect(fetch.get).toBeCalledTimes(1))
fireEvent.click(getByRole('button'))
await waitFor(() => expect(fetch.post).toBeCalledWith('/user/sign'))
await waitFor(() => expect(fetch.post).toBeCalledWith(urls.user.sign()))
expect(getByText('f')).toBeInTheDocument()
expect(getByRole('alert')).toHaveClass('alert-warning')
})

View File

@ -3,6 +3,7 @@ import { render, fireEvent, waitFor } from '@testing-library/react'
import { t } from '@/scripts/i18n'
import * as fetch from '@/scripts/net'
import { Player, TextureType } from '@/scripts/types'
import urls from '@/scripts/urls'
import Players from '@/views/user/Players'
jest.mock('@/scripts/net')
@ -199,7 +200,7 @@ describe('create player', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/user/player/add', {
expect(fetch.post).toBeCalledWith(urls.user.player.add(), {
name: fixture.name,
}),
)
@ -223,7 +224,7 @@ describe('create player', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith('/user/player/add', {
expect(fetch.post).toBeCalledWith(urls.user.player.add(), {
name: fixture.name,
}),
)
@ -286,7 +287,7 @@ describe('edit player name', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(`/user/player/rename/${fixture.pid}`, {
expect(fetch.post).toBeCalledWith(urls.user.player.rename(fixture.pid), {
name: 'reina',
}),
)
@ -330,7 +331,7 @@ describe('edit player name', () => {
})
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(`/user/player/rename/${fixture.pid}`, {
expect(fetch.post).toBeCalledWith(urls.user.player.rename(fixture.pid), {
name: 'reina',
}),
)
@ -376,12 +377,9 @@ describe('reset texture', () => {
fireEvent.click(getByLabelText(t('general.cape')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(
`/user/player/texture/clear/${fixture.pid}`,
{
type: ['skin', 'cape'],
},
),
expect(fetch.post).toBeCalledWith(urls.user.player.clear(fixture.pid), {
type: ['skin', 'cape'],
}),
)
expect(queryByText('success')).toBeInTheDocument()
expect(getByRole('status')).toHaveClass('alert-success')
@ -399,12 +397,9 @@ describe('reset texture', () => {
fireEvent.click(getByLabelText(t('general.skin')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(
`/user/player/texture/clear/${fixture.pid}`,
{
type: ['skin'],
},
),
expect(fetch.post).toBeCalledWith(urls.user.player.clear(fixture.pid), {
type: ['skin'],
}),
)
expect(queryByText('success')).toBeInTheDocument()
expect(getByRole('status')).toHaveClass('alert-success')
@ -422,12 +417,9 @@ describe('reset texture', () => {
fireEvent.click(getByLabelText(t('general.cape')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(
`/user/player/texture/clear/${fixture.pid}`,
{
type: ['cape'],
},
),
expect(fetch.post).toBeCalledWith(urls.user.player.clear(fixture.pid), {
type: ['cape'],
}),
)
expect(queryByText('success')).toBeInTheDocument()
expect(getByRole('status')).toHaveClass('alert-success')
@ -456,12 +448,9 @@ describe('reset texture', () => {
fireEvent.click(getByLabelText(t('general.skin')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(
`/user/player/texture/clear/${fixture.pid}`,
{
type: ['skin'],
},
),
expect(fetch.post).toBeCalledWith(urls.user.player.clear(fixture.pid), {
type: ['skin'],
}),
)
expect(queryByText('failed')).toBeInTheDocument()
expect(getByRole('alert')).toHaveClass('alert-danger')
@ -495,7 +484,7 @@ describe('delete player', () => {
fireEvent.click(getByText(t('user.player.delete-player')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(`/user/player/delete/${fixture.pid}`),
expect(fetch.post).toBeCalledWith(urls.user.player.delete(fixture.pid)),
)
expect(getByText('success')).toBeInTheDocument()
expect(getByRole('status')).toHaveClass('alert-success')
@ -511,7 +500,7 @@ describe('delete player', () => {
fireEvent.click(getByText(t('user.player.delete-player')))
fireEvent.click(getByText(t('general.confirm')))
await waitFor(() =>
expect(fetch.post).toBeCalledWith(`/user/player/delete/${fixture.pid}`),
expect(fetch.post).toBeCalledWith(urls.user.player.delete(fixture.pid)),
)
expect(getByText('failed')).toBeInTheDocument()
expect(getByRole('alert')).toHaveClass('alert-danger')

View File

@ -57,10 +57,10 @@ Route::prefix('user')
Route::get('reports', 'ReportController@track');
Route::prefix('profile')->group(function () {
Route::get('', 'UserController@profile')->name('profile');
Route::post('', 'UserController@handleProfile')->name('profile');
Route::post('avatar', 'UserController@setAvatar')->name('profile.avatar');
Route::prefix('profile')->name('profile.')->group(function () {
Route::get('', 'UserController@profile');
Route::post('', 'UserController@handleProfile');
Route::post('avatar', 'UserController@setAvatar')->name('avatar');
});
Route::post('email-verification', 'UserController@sendVerificationEmail');

View File

@ -11,6 +11,7 @@ if (Test-Path ./public/app) {
}
# Run webpack
yarn build:urls
yarn build
Move-Item -Path ./public/app/sw.js -Destination ./public -Force

145
scripts/generateUrls.ts Normal file
View File

@ -0,0 +1,145 @@
import { spawnSync } from 'child_process'
import * as fs from 'fs'
import ts from 'typescript'
import prettier from 'prettier'
type Route = { uri: string; name: string | null }
const supportedPrefixes = ['auth.', 'user.', 'skinlib.', 'admin.']
type TreeObject = { [key: string]: Tree }
type Tree = TreeObject | string
const tree: TreeObject = {}
function parseURI(uri: string): ts.ArrowFunction {
const matches = /\{([a-z]+)\}/.exec(uri)
if (matches) {
const param = matches[1]
const type =
param.endsWith('id') ||
param === 'texture' ||
param === 'user' ||
param === 'player' ||
param === 'report'
? ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
: ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
return ts.createArrowFunction(
undefined,
undefined,
[
ts.createParameter(
undefined,
undefined,
undefined,
ts.createIdentifier(param),
undefined,
type,
undefined,
),
],
undefined,
ts.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
ts.createTemplateExpression(
ts.createTemplateHead(
'/' + uri.slice(0, matches.index),
'/' + uri.slice(0, matches.index),
),
[
ts.createTemplateSpan(
ts.createIdentifier(param),
ts.createTemplateTail(
uri.slice(matches.index + matches[0].length),
uri.slice(matches.index + matches[0].length),
),
),
],
),
)
}
return ts.createArrowFunction(
undefined,
undefined,
[],
undefined,
ts.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
ts.createAsExpression(
ts.createStringLiteral(`/${uri}`),
ts.createTypeReferenceNode(ts.createIdentifier('const'), undefined),
),
)
}
function parseTree(tree: Tree): ts.ObjectLiteralExpression {
const properties = Object.entries(tree).map(([key, value]) => {
if (typeof value === 'string') {
return ts.createPropertyAssignment(
ts.createIdentifier(key),
parseURI(value),
)
} else {
return ts.createPropertyAssignment(
ts.createIdentifier(key),
parseTree(value),
)
}
})
return ts.createObjectLiteral(properties)
}
const routes: Route[] = JSON.parse(
spawnSync('php', ['artisan', 'route:list', '--json', '--columns=uri,name'])
.stdout,
)
routes
.filter(
(route) =>
route.name &&
supportedPrefixes.some((prefix) => route.name!.startsWith(prefix)) &&
!route.name.endsWith('.'),
)
.forEach((route) => {
const path = route.name!.split('.')
const length = path.length
path.reduce((object: TreeObject, p, index) => {
if (index === length - 1) {
object[p] = route.uri
return tree as TreeObject
}
if (!object[p]) {
object[p] = {}
}
return object[p] as TreeObject
}, tree)
})
const ast = ts.createExportAssignment(
undefined,
undefined,
undefined,
parseTree(tree),
)
const sourceFile = ts.createSourceFile(
'urls.ts',
'',
ts.ScriptTarget.ES2017,
false,
ts.ScriptKind.TS,
)
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
})
const code = prettier.format(
printer.printNode(ts.EmitHint.Unspecified, ast, sourceFile),
{
parser: 'typescript',
semi: false,
singleQuote: true,
},
)
fs.writeFileSync('./resources/assets/src/scripts/urls.ts', code, {
encoding: 'utf-8',
})

8
tsconfig.node.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false,
"module": "commonjs"
}
}

View File

@ -1587,6 +1587,11 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -3082,6 +3087,11 @@ diff-sequences@^26.0.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6"
integrity sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@ -5721,6 +5731,11 @@ make-error@1.x:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@ -8135,6 +8150,14 @@ source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.5.17:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-support@^0.5.6:
version "0.5.12"
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
@ -8769,6 +8792,17 @@ ts-loader@^7.0.4:
micromatch "^4.0.0"
semver "^6.0.0"
ts-node@^8.10.2:
version "8.10.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
dependencies:
arg "^4.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
@ -9529,6 +9563,11 @@ yargs@^15.3.1:
y18n "^4.0.0"
yargs-parser "^18.1.1"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
zrender@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.3.0.tgz#9f056121b20bbae44414d287bf6a119ff7042661"