diff --git a/.github/workflows/vitest.yml b/.github/workflows/backend_vitest.yml similarity index 94% rename from .github/workflows/vitest.yml rename to .github/workflows/backend_vitest.yml index c18dc6a0..ad80ab7b 100644 --- a/.github/workflows/vitest.yml +++ b/.github/workflows/backend_vitest.yml @@ -1,4 +1,4 @@ -name: Vitest +name: Backend Vitest on: push: diff --git a/.github/workflows/frontend_vitest.yml b/.github/workflows/frontend_vitest.yml new file mode 100644 index 00000000..cfac4547 --- /dev/null +++ b/.github/workflows/frontend_vitest.yml @@ -0,0 +1,24 @@ +name: Frontend Vitest + +on: + push: + workflow_dispatch: + +jobs: + vitest: + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - name: Checkout latest repo + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + run_install: true + + - name: Run Vitest + run: pnpm run test diff --git a/frontend/app/components/book-detail/BookDetailBorrower.tsx b/frontend/app/components/book-detail/BookDetailBorrower.tsx index fb67e561..f2646700 100644 --- a/frontend/app/components/book-detail/BookDetailBorrower.tsx +++ b/frontend/app/components/book-detail/BookDetailBorrower.tsx @@ -1,39 +1,49 @@ -import { Badge, Blockquote, Group, Loader, rem, Stack, Text } from '@mantine/core' -import { useGetLoans } from 'orval/client' +import { + Badge, + Blockquote, + Group, + Loader, + rem, + Stack, + Text, +} from "@mantine/core"; +import { useGetLoans } from "client/client"; import { MdError } from "react-icons/md"; const BookDetailBorrower = () => { - const loans = useGetLoans() + const loans = useGetLoans(); if (loans.isError) { return (
} mt="xl"> データの取得に失敗しました
- ) + ); } return ( - + 借りている人 - {loans.isPending ? : + {loans.isPending ? ( + + ) : ( - {loans.data.data.loans.map((loan) => loan.users && - - {loan.users.name} - + {loans.data.data.loans.map( + (loan) => + loan.users && ( + + {loan.users.name} + + ) )} - } + )} - ) -} + ); +}; -export default BookDetailBorrower \ No newline at end of file +export default BookDetailBorrower; diff --git a/frontend/app/components/book-detail/BookDetailComponent.tsx b/frontend/app/components/book-detail/BookDetailComponent.tsx index 5846b357..920c2b61 100644 --- a/frontend/app/components/book-detail/BookDetailComponent.tsx +++ b/frontend/app/components/book-detail/BookDetailComponent.tsx @@ -1,40 +1,39 @@ -import { Stack, Grid, rem } from '@mantine/core' -import ErrorComponent from '~/components/common/ErrorComponent' +import { Grid, rem, Stack } from "@mantine/core"; +import ErrorComponent from "~/components/common/ErrorComponent"; -import BookDetailContent from './BookDetailContent' -import { getBookResponse } from 'orval/client' -import BookDetailControlPanel from './BookDetailControlPanel' +import { getBookResponse } from "client/client"; +import BookDetailContent from "./BookDetailContent"; +import BookDetailControlPanel from "./BookDetailControlPanel"; interface BookDetailComponentProps { - bookResponse: getBookResponse + bookResponse: getBookResponse; } const BookDetailComponent = ({ bookResponse }: BookDetailComponentProps) => { switch (bookResponse.status) { case 400: - return + return ; case 404: - return + return ; case 500: - return + return ; } return ( - + - + - ) -} + ); +}; -export default BookDetailComponent \ No newline at end of file +export default BookDetailComponent; diff --git a/frontend/app/components/book-detail/BookDetailContent.tsx b/frontend/app/components/book-detail/BookDetailContent.tsx index d1dab3d0..ed0237ab 100644 --- a/frontend/app/components/book-detail/BookDetailContent.tsx +++ b/frontend/app/components/book-detail/BookDetailContent.tsx @@ -1,31 +1,31 @@ -import { Stack } from '@mantine/core' -import { Book } from 'orval/client.schemas' -import BookDetailTitle from './BookDetailTitle' -import BookDetailContentTable from './BookDetailContentTable' -import BookDetailDescription from './BookDetailDescription' -import { useAtom } from 'jotai' -import { userAtom } from '~/stores/userAtom' -import BookDetailBorrower from './BookDetailBorrower' +import { Stack } from "@mantine/core"; +import { Book } from "client/client.schemas"; +import { useAtom } from "jotai"; +import { userAtom } from "~/stores/userAtom"; +import BookDetailBorrower from "./BookDetailBorrower"; +import BookDetailContentTable from "./BookDetailContentTable"; +import BookDetailDescription from "./BookDetailDescription"; +import BookDetailTitle from "./BookDetailTitle"; interface BookDetailComponentProps { - book: Book + book: Book; } const BookDetailContent = ({ book }: BookDetailComponentProps) => { - const [user] = useAtom(userAtom) + const [user] = useAtom(userAtom); return ( {!!user && } - ) -} + ); +}; -export default BookDetailContent \ No newline at end of file +export default BookDetailContent; diff --git a/frontend/app/components/book-detail/BookDetailContentTable.tsx b/frontend/app/components/book-detail/BookDetailContentTable.tsx index d660fccd..0fc95725 100644 --- a/frontend/app/components/book-detail/BookDetailContentTable.tsx +++ b/frontend/app/components/book-detail/BookDetailContentTable.tsx @@ -1,23 +1,25 @@ -import { Group, rem, Stack, Table, Text } from '@mantine/core' -import { Book } from 'orval/client.schemas' -import BookDetailAuthorBadge from './BookDetailAuthorBadge' +import { Group, rem, Stack, Table, Text } from "@mantine/core"; +import { Book } from "client/client.schemas"; +import BookDetailAuthorBadge from "./BookDetailAuthorBadge"; interface BookDetailContentTableProps { - book: Book + book: Book; } const BookDetailContentTable = ({ book }: BookDetailContentTableProps) => { return ( - + 書籍情報 - 著者 - {book.authors.map((author, id) => )} + 著者 + + + {book.authors.map((author, id) => ( + + ))} + + 出版社 @@ -37,7 +39,7 @@ const BookDetailContentTable = ({ book }: BookDetailContentTableProps) => {
- ) -} + ); +}; -export default BookDetailContentTable \ No newline at end of file +export default BookDetailContentTable; diff --git a/frontend/app/components/book-search/BookSearchAuthorForm.tsx b/frontend/app/components/book-search/BookSearchAuthorForm.tsx index 1fc4d476..82075344 100644 --- a/frontend/app/components/book-search/BookSearchAuthorForm.tsx +++ b/frontend/app/components/book-search/BookSearchAuthorForm.tsx @@ -1,9 +1,12 @@ -import { TextInput } from '@mantine/core' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' +import { TextInput } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { GetBooksParams } from "client/client.schemas"; interface BookSearchAuthorFormProps { - form: UseFormReturnType GetBooksParams> + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; } const BookSearchAuthorForm = ({ form }: BookSearchAuthorFormProps) => { @@ -11,10 +14,10 @@ const BookSearchAuthorForm = ({ form }: BookSearchAuthorFormProps) => { - ) -} + ); +}; -export default BookSearchAuthorForm \ No newline at end of file +export default BookSearchAuthorForm; diff --git a/frontend/app/components/book-search/BookSearchComponent.tsx b/frontend/app/components/book-search/BookSearchComponent.tsx index 2b3058c2..e2235712 100644 --- a/frontend/app/components/book-search/BookSearchComponent.tsx +++ b/frontend/app/components/book-search/BookSearchComponent.tsx @@ -1,14 +1,17 @@ -import BookSearchModeButton from './BookSearchModeButton' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' -import BookSearchForm from './BookSearchForm' +import type { UseFormReturnType } from "@mantine/form"; +import type { GetBooksParams } from "client/client.schemas"; +import BookSearchForm from "./BookSearchForm"; +import BookSearchModeButton from "./BookSearchModeButton"; interface BookSearchComponentProps { - isOpen: boolean - open: () => void - close: () => void - form: UseFormReturnType GetBooksParams> - handleSubmit: (props: GetBooksParams) => void + isOpen: boolean; + open: () => void; + close: () => void; + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; + handleSubmit: (props: GetBooksParams) => void; } const BookSearchComponent = ({ @@ -16,14 +19,14 @@ const BookSearchComponent = ({ open, close, form, - handleSubmit + handleSubmit, }: BookSearchComponentProps) => { return ( <> - ) -} + ); +}; -export default BookSearchComponent \ No newline at end of file +export default BookSearchComponent; diff --git a/frontend/app/components/book-search/BookSearchForm.tsx b/frontend/app/components/book-search/BookSearchForm.tsx index 121608bf..44dc3a91 100644 --- a/frontend/app/components/book-search/BookSearchForm.tsx +++ b/frontend/app/components/book-search/BookSearchForm.tsx @@ -1,30 +1,26 @@ -import { Collapse } from '@mantine/core' -import FormLayout from '../layouts/FormLayout' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' -import BookSearchTitleForm from './BookSearchTitleForm' -import BookSearchAuthorForm from './BookSearchAuthorForm' -import BookSearchPublisherForm from './BookSearchPublisherForm' -import BookSearchIsbnForm from './BookSearchIsbnForm' -import BookSearchSubmitButton from './BookSearchSubmitButton' +import { Collapse } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { GetBooksParams } from "client/client.schemas"; +import FormLayout from "../layouts/FormLayout"; +import BookSearchAuthorForm from "./BookSearchAuthorForm"; +import BookSearchIsbnForm from "./BookSearchIsbnForm"; +import BookSearchPublisherForm from "./BookSearchPublisherForm"; +import BookSearchSubmitButton from "./BookSearchSubmitButton"; +import BookSearchTitleForm from "./BookSearchTitleForm"; interface SearchFormProps { - isOpen: boolean - form: UseFormReturnType GetBooksParams> - handleSubmit: (props: GetBooksParams) => void + isOpen: boolean; + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; + handleSubmit: (props: GetBooksParams) => void; } -const BookSearchForm = ({ - isOpen, - form, - handleSubmit -}: SearchFormProps) => { +const BookSearchForm = ({ isOpen, form, handleSubmit }: SearchFormProps) => { return ( - - form={form} - handleSubmit={handleSubmit} - > + form={form} handleSubmit={handleSubmit}> @@ -32,7 +28,7 @@ const BookSearchForm = ({ - ) -} + ); +}; -export default BookSearchForm \ No newline at end of file +export default BookSearchForm; diff --git a/frontend/app/components/book-search/BookSearchIsbnForm.tsx b/frontend/app/components/book-search/BookSearchIsbnForm.tsx index 507acc34..528dbf1d 100644 --- a/frontend/app/components/book-search/BookSearchIsbnForm.tsx +++ b/frontend/app/components/book-search/BookSearchIsbnForm.tsx @@ -1,9 +1,12 @@ -import { TextInput } from '@mantine/core' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' +import { TextInput } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { GetBooksParams } from "client/client.schemas"; interface SearchIsbnFormProps { - form: UseFormReturnType GetBooksParams> + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; } const BookSearchIsbnForm = ({ form }: SearchIsbnFormProps) => { @@ -11,10 +14,10 @@ const BookSearchIsbnForm = ({ form }: SearchIsbnFormProps) => { - ) -} + ); +}; -export default BookSearchIsbnForm \ No newline at end of file +export default BookSearchIsbnForm; diff --git a/frontend/app/components/book-search/BookSearchPublisherForm.tsx b/frontend/app/components/book-search/BookSearchPublisherForm.tsx index 0bcd148e..7f941111 100644 --- a/frontend/app/components/book-search/BookSearchPublisherForm.tsx +++ b/frontend/app/components/book-search/BookSearchPublisherForm.tsx @@ -1,9 +1,12 @@ -import { TextInput } from '@mantine/core' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' +import { TextInput } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { GetBooksParams } from "client/client.schemas"; interface SearchPublisherFormProps { - form: UseFormReturnType GetBooksParams> + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; } const BookSearchPublisherForm = ({ form }: SearchPublisherFormProps) => { @@ -11,10 +14,10 @@ const BookSearchPublisherForm = ({ form }: SearchPublisherFormProps) => { - ) -} + ); +}; -export default BookSearchPublisherForm \ No newline at end of file +export default BookSearchPublisherForm; diff --git a/frontend/app/components/book-search/BookSearchTitleForm.tsx b/frontend/app/components/book-search/BookSearchTitleForm.tsx index f4c368a5..d66fcd3f 100644 --- a/frontend/app/components/book-search/BookSearchTitleForm.tsx +++ b/frontend/app/components/book-search/BookSearchTitleForm.tsx @@ -1,9 +1,12 @@ -import { TextInput } from '@mantine/core' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' +import { TextInput } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { GetBooksParams } from "client/client.schemas"; interface SearchTitleFormProps { - form: UseFormReturnType GetBooksParams> + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; } const BookSearchTitleForm = ({ form }: SearchTitleFormProps) => { @@ -11,10 +14,10 @@ const BookSearchTitleForm = ({ form }: SearchTitleFormProps) => { - ) -} + ); +}; -export default BookSearchTitleForm \ No newline at end of file +export default BookSearchTitleForm; diff --git a/frontend/app/components/books/BookCard.tsx b/frontend/app/components/books/BookCard.tsx index e0dec570..f5328a2b 100644 --- a/frontend/app/components/books/BookCard.tsx +++ b/frontend/app/components/books/BookCard.tsx @@ -1,6 +1,6 @@ import { Card } from "@mantine/core"; +import type { Book } from "client/client.schemas"; import { useAtom } from "jotai"; -import type { Book } from "orval/client.schemas"; import { userAtom } from "~/stores/userAtom"; import BookCardFooter from "./BookCardFooter"; import BookCardHeader from "./BookCardHeader"; diff --git a/frontend/app/components/books/BookCards.tsx b/frontend/app/components/books/BookCards.tsx index 3c0b0677..d0e84a48 100644 --- a/frontend/app/components/books/BookCards.tsx +++ b/frontend/app/components/books/BookCards.tsx @@ -1,6 +1,6 @@ import { ScrollArea, SimpleGrid } from "@mantine/core"; +import { Book } from "client/client.schemas"; import { useAtom } from "jotai"; -import { Book } from "orval/client.schemas"; import { userAtom } from "~/stores/userAtom"; import BookCard from "./BookCard"; import BookSelectedDialog from "./BookSelectedDialog"; diff --git a/frontend/app/components/books/BookListComponent.tsx b/frontend/app/components/books/BookListComponent.tsx index d7f5b1a1..37f4e861 100644 --- a/frontend/app/components/books/BookListComponent.tsx +++ b/frontend/app/components/books/BookListComponent.tsx @@ -1,25 +1,28 @@ -import { Stack } from '@mantine/core' -import { getBooksResponse } from 'orval/client' -import ErrorComponent from '../common/ErrorComponent' -import BookCards from './BookCards' -import type { UseFormReturnType } from '@mantine/form' -import type { GetBooksParams } from 'orval/client.schemas' -import BookSearchComponent from '../book-search/BookSearchComponent' -import ContentsHeader from '../common/ContentsHeader' -import PaginationComponent from '../common/PaginationComponent' +import { Stack } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import { getBooksResponse } from "client/client"; +import type { GetBooksParams } from "client/client.schemas"; +import BookSearchComponent from "../book-search/BookSearchComponent"; +import ContentsHeader from "../common/ContentsHeader"; +import ErrorComponent from "../common/ErrorComponent"; +import PaginationComponent from "../common/PaginationComponent"; +import BookCards from "./BookCards"; interface BookListComponentProps { - booksResponse: getBooksResponse - form: UseFormReturnType GetBooksParams> - handleSubmit: (props: GetBooksParams) => void - isOpen: boolean - open: () => void - close: () => void - handlePaginationChange: (newPage: number) => void - handleLimitChange: (newLimit: number) => void - page?: number - limit?: number - totalBook: number + booksResponse: getBooksResponse; + form: UseFormReturnType< + GetBooksParams, + (values: GetBooksParams) => GetBooksParams + >; + handleSubmit: (props: GetBooksParams) => void; + isOpen: boolean; + open: () => void; + close: () => void; + handlePaginationChange: (newPage: number) => void; + handleLimitChange: (newLimit: number) => void; + page?: number; + limit?: number; + totalBook: number; } const BookListComponent = ({ @@ -33,20 +36,36 @@ const BookListComponent = ({ handleLimitChange, page, limit, - totalBook + totalBook, }: BookListComponentProps) => { return ( - - - - {booksResponse.status !== 200 ? : } - + + + + {booksResponse.status !== 200 ? ( + + ) : ( + + )} + - ) -} + ); +}; -export default BookListComponent \ No newline at end of file +export default BookListComponent; diff --git a/frontend/app/components/header/HeaderComponent.tsx b/frontend/app/components/header/HeaderComponent.tsx index f1d1e90b..593679ef 100644 --- a/frontend/app/components/header/HeaderComponent.tsx +++ b/frontend/app/components/header/HeaderComponent.tsx @@ -1,47 +1,40 @@ -import { AppShell, Group } from '@mantine/core' -import HeaderTitleLogo from './HeaderTitleLogo' -import HeaderMainComponent from './HeaderMainComponent' -import { useAtom } from "jotai" -import { getUser } from "orval/client" -import { User } from "orval/client.schemas" -import { userAtom } from "~/stores/userAtom" +import { AppShell, Group } from "@mantine/core"; +import { getUser } from "client/client"; +import { User } from "client/client.schemas"; +import { useAtom } from "jotai"; +import { userAtom } from "~/stores/userAtom"; +import HeaderMainComponent from "./HeaderMainComponent"; +import HeaderTitleLogo from "./HeaderTitleLogo"; const getCookieUserId = () => { - if (typeof document === "undefined") return undefined + if (typeof document === "undefined") return undefined; return document.cookie .split("; ") .find((row) => row.startsWith("__Secure-user_id=")) ?.split("=")[1]; -} +}; const HeaderComponent = () => { - const [user, setUser] = useAtom(userAtom) - const userId = getCookieUserId() + const [user, setUser] = useAtom(userAtom); + const userId = getCookieUserId(); if (userId) { if (!user) { // ユーザ情報を取得するAPIを呼び出す。 getUser(userId).then((response) => { if (response.status === 200) { - setUser(response.data as User) + setUser(response.data as User); } - }) + }); } } return ( - - + + - ) -} + ); +}; -export default HeaderComponent \ No newline at end of file +export default HeaderComponent; diff --git a/frontend/app/components/header/HeaderLoginComponent.tsx b/frontend/app/components/header/HeaderLoginComponent.tsx index 55752177..a6ca5430 100644 --- a/frontend/app/components/header/HeaderLoginComponent.tsx +++ b/frontend/app/components/header/HeaderLoginComponent.tsx @@ -1,43 +1,43 @@ -import { Button, Group, Modal, Stack, Text } from '@mantine/core' -import HeaderBookMenu from './HeaderBookMenu' -import HeaderUserMenu from './HeaderUserMenu'; -import { useDisclosure } from '@mantine/hooks' -import { useLogout } from 'orval/client'; -import { useNavigate } from '@remix-run/react'; +import { Button, Group, Modal, Stack, Text } from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { useNavigate } from "@remix-run/react"; +import { useLogout } from "client/client"; +import { useAtom } from "jotai"; import { LuLogOut } from "react-icons/lu"; -import { errorNotifications, successNotifications } from '~/utils/notification'; -import HeaderUsersMenu from './HeaderUsersMenu'; -import { useAtom } from 'jotai'; -import { userAtom } from '~/stores/userAtom'; +import { userAtom } from "~/stores/userAtom"; +import { errorNotifications, successNotifications } from "~/utils/notification"; +import HeaderBookMenu from "./HeaderBookMenu"; +import HeaderUserMenu from "./HeaderUserMenu"; +import HeaderUsersMenu from "./HeaderUsersMenu"; const HeaderLoginComponent = () => { - const [, setUser] = useAtom(userAtom) - const [opened, { open, close }] = useDisclosure() - const navigate = useNavigate() - const logoutTask = useLogout() - const handleLogoout = () => { + const [, setUser] = useAtom(userAtom); + const [opened, { open, close }] = useDisclosure(); + const navigate = useNavigate(); + const logoutTask = useLogout(); + const handleLogout = () => { logoutTask.mutate(undefined, { onSuccess: (response) => { switch (response.status) { case 204: - setUser(undefined) - successNotifications('ログアウトしました') - navigate('/home') - break + setUser(undefined); + successNotifications("ログアウトしました"); + navigate("/home"); + break; case 500: - errorNotifications('サーバーエラーが発生しました') - break + errorNotifications("サーバーエラーが発生しました"); + break; default: - errorNotifications('エラーが発生しました') - break + errorNotifications("エラーが発生しました"); + break; } }, onError: () => { - errorNotifications('ログアウトに失敗しました') - close() - } - }) - } + errorNotifications("ログアウトに失敗しました"); + close(); + }, + }); + }; return ( <> @@ -45,23 +45,23 @@ const HeaderLoginComponent = () => { - - - ログアウトしますか? - - - + - ) -} + ); +}; -export default HeaderLoginComponent \ No newline at end of file +export default HeaderLoginComponent; diff --git a/frontend/app/components/layouts/FormLayout.tsx b/frontend/app/components/layouts/FormLayout.tsx index c97fda54..90916c26 100644 --- a/frontend/app/components/layouts/FormLayout.tsx +++ b/frontend/app/components/layouts/FormLayout.tsx @@ -1,30 +1,25 @@ -import React from 'react' -import type { UseFormReturnType } from '@mantine/form' -import { Stack } from '@mantine/core' +import { Stack } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import React from "react"; interface FormLayoutProps { - form: UseFormReturnType T> - handleSubmit: (props: T) => void - children: React.ReactNode + form: UseFormReturnType T>; + handleSubmit: (props: T) => void; + children: React.ReactNode; } - const FormLayout = ({ form, handleSubmit, - children + children, }: FormLayoutProps) => { return ( -
(handleSubmit(values)))}> - + handleSubmit(values))}> + {children} - ) -} + ); +}; -export default FormLayout \ No newline at end of file +export default FormLayout; diff --git a/frontend/app/components/login/LoginEmailForm.tsx b/frontend/app/components/login/LoginEmailForm.tsx index 226d0a03..c57f5f9f 100644 --- a/frontend/app/components/login/LoginEmailForm.tsx +++ b/frontend/app/components/login/LoginEmailForm.tsx @@ -1,9 +1,9 @@ -import type { UseFormReturnType } from '@mantine/form' -import type { LoginBody } from 'orval/client.schemas' -import { TextInput } from '@mantine/core' +import { TextInput } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { LoginBody } from "client/client.schemas"; interface LoginEmailFormProps { - form: UseFormReturnType LoginBody> + form: UseFormReturnType LoginBody>; } const LoginEmailForm = ({ form }: LoginEmailFormProps) => { @@ -11,11 +11,12 @@ const LoginEmailForm = ({ form }: LoginEmailFormProps) => { - ) -} + ); +}; -export default LoginEmailForm \ No newline at end of file +export default LoginEmailForm; diff --git a/frontend/app/components/login/LoginFormComponent.tsx b/frontend/app/components/login/LoginFormComponent.tsx index 5db320b6..e5ac8ec6 100644 --- a/frontend/app/components/login/LoginFormComponent.tsx +++ b/frontend/app/components/login/LoginFormComponent.tsx @@ -1,30 +1,27 @@ -import FormLayout from '../layouts/FormLayout' -import LoginEmailForm from './LoginEmailForm' -import LoginPasswordForm from './LoginPasswordForm' -import LoginSubmitButton from './LoginSubmitButton' -import type { UseFormReturnType } from '@mantine/form' -import type { LoginBody } from 'orval/client.schemas' -import LoginFormTitle from './LoginFormTitle' -import LoginFormHelpText from './LoginFormHelpText' -import FormBaseLayout from '../layouts/FormBaseLayout' +import type { UseFormReturnType } from "@mantine/form"; +import type { LoginBody } from "client/client.schemas"; +import FormBaseLayout from "../layouts/FormBaseLayout"; +import FormLayout from "../layouts/FormLayout"; +import LoginEmailForm from "./LoginEmailForm"; +import LoginFormHelpText from "./LoginFormHelpText"; +import LoginFormTitle from "./LoginFormTitle"; +import LoginPasswordForm from "./LoginPasswordForm"; +import LoginSubmitButton from "./LoginSubmitButton"; interface LoginFormComponentProps { isPending: boolean; - form: UseFormReturnType LoginBody> - handleSubmit: (props: LoginBody) => void + form: UseFormReturnType LoginBody>; + handleSubmit: (props: LoginBody) => void; } const LoginFormComponent = ({ isPending, form, - handleSubmit + handleSubmit, }: LoginFormComponentProps) => { return ( - - form={form} - handleSubmit={handleSubmit} - > + form={form} handleSubmit={handleSubmit}> @@ -32,7 +29,7 @@ const LoginFormComponent = ({ - ) -} + ); +}; -export default LoginFormComponent \ No newline at end of file +export default LoginFormComponent; diff --git a/frontend/app/components/login/LoginPasswordForm.tsx b/frontend/app/components/login/LoginPasswordForm.tsx index 943736e3..a3982cf2 100644 --- a/frontend/app/components/login/LoginPasswordForm.tsx +++ b/frontend/app/components/login/LoginPasswordForm.tsx @@ -1,9 +1,9 @@ -import type { UseFormReturnType } from '@mantine/form' -import type { LoginBody } from 'orval/client.schemas' -import { PasswordInput } from '@mantine/core' +import { PasswordInput } from "@mantine/core"; +import type { UseFormReturnType } from "@mantine/form"; +import type { LoginBody } from "client/client.schemas"; interface LoginPasswordFormProps { - form: UseFormReturnType LoginBody> + form: UseFormReturnType LoginBody>; } const LoginPasswordForm = ({ form }: LoginPasswordFormProps) => { @@ -11,11 +11,12 @@ const LoginPasswordForm = ({ form }: LoginPasswordFormProps) => { - ) -} + ); +}; -export default LoginPasswordForm \ No newline at end of file +export default LoginPasswordForm; diff --git a/frontend/app/routes/auth.login.tsx b/frontend/app/routes/auth.login.tsx index 87df39b6..0135fa8d 100644 --- a/frontend/app/routes/auth.login.tsx +++ b/frontend/app/routes/auth.login.tsx @@ -1,69 +1,76 @@ -import { useForm } from '@mantine/form' -import type { LoginBody } from 'orval/client.schemas'; -import { useLogin } from 'orval/client'; -import { useNavigate } from '@remix-run/react'; -import { errorNotifications, successNotifications } from '~/utils/notification'; -import LoginFormComponent from '~/components/login/LoginFormComponent'; -import { useAtom } from 'jotai'; -import { userAtom } from '~/stores/userAtom'; +import { useForm } from "@mantine/form"; +import { useNavigate } from "@remix-run/react"; +import { useLogin } from "client/client"; +import type { LoginBody } from "client/client.schemas"; +import { useAtom } from "jotai"; +import LoginFormComponent from "~/components/login/LoginFormComponent"; +import { userAtom } from "~/stores/userAtom"; +import { errorNotifications, successNotifications } from "~/utils/notification"; const LoginPage = () => { - const loginTask = useLogin() - const navigate = useNavigate() - const [, setUser] = useAtom(userAtom) + const loginTask = useLogin(); + const navigate = useNavigate(); + const [, setUser] = useAtom(userAtom); const form = useForm({ - mode: 'uncontrolled', + mode: "uncontrolled", initialValues: { - email: '', - password: '' + email: "", + password: "", }, validate: { - email: (value) => (/^[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+$/i.test(value) ? null : '有効でないメールアドレスです'), + email: (value) => + /^[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+$/i.test(value) + ? null + : "有効でないメールアドレスです", password: (value) => { - if (value.length < 8) - return 'パスワードは8文字以上で入力してください' - else if (/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]+$/.test(value)) - return null - else - return 'パスワードにはアルファベットと数字を含んでください' - } - } - }) + if (value.length < 8) return "パスワードは8文字以上で入力してください"; + else if (/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]+$/.test(value)) return null; + else return "パスワードにはアルファベットと数字を含めてください"; + }, + }, + }); const handleSubmit = (props: LoginBody) => { loginTask.mutate( { - data: props - }, { - onSuccess: (response) => { - switch (response.status) { - case 200: - successNotifications('ログインに成功しました') - setUser(response.data) - navigate('/home/mypage') - break - case 400: - errorNotifications('メールアドレスまたはパスワードが間違っています') - break - case 401: - errorNotifications('メールアドレスまたはパスワードが間違っています') - break - case 404: - errorNotifications("ユーザーが見つかりません") - break - case 500: - errorNotifications('サーバーエラーが発生しました') - break - default: - errorNotifications('エラーが発生しました') - } + data: props, }, - onError: () => { - errorNotifications('APIに問題が発生しています。サーバが起動されているか確認してください。') + { + onSuccess: (response) => { + switch (response.status) { + case 200: + successNotifications("ログインに成功しました"); + setUser(response.data); + navigate("/home/mypage"); + break; + case 400: + errorNotifications( + "メールアドレスまたはパスワードが間違っています" + ); + break; + case 401: + errorNotifications( + "メールアドレスまたはパスワードが間違っています" + ); + break; + case 404: + errorNotifications("ユーザーが見つかりません"); + break; + case 500: + errorNotifications("サーバーエラーが発生しました"); + break; + default: + errorNotifications("エラーが発生しました"); + } + }, + onError: () => { + errorNotifications( + "APIに問題が発生しています。サーバが起動されているか確認してください。" + ); + }, } - }, - ) - } + ); + }; return ( { form={form} handleSubmit={handleSubmit} /> - ) -} + ); +}; -export default LoginPage \ No newline at end of file +export default LoginPage; diff --git a/frontend/app/routes/home._index.tsx b/frontend/app/routes/home._index.tsx index 0205f878..3cb986a0 100644 --- a/frontend/app/routes/home._index.tsx +++ b/frontend/app/routes/home._index.tsx @@ -1,130 +1,170 @@ -import { getBooks } from 'orval/client' -import { json } from '@remix-run/cloudflare' -import type { LoaderFunctionArgs } from '@remix-run/cloudflare' -import { useLoaderData, useNavigate } from '@remix-run/react' -import BookListComponent from '~/components/books/BookListComponent' -import { useForm } from '@mantine/form' -import { GetBooksParams } from 'orval/client.schemas' -import { useDisclosure } from '@mantine/hooks' -import { useAtom } from 'jotai' -import { selectedBooksAtom } from '~/stores/cartAtom' -import { useEffect } from 'react' +import { useForm } from "@mantine/form"; +import { useDisclosure } from "@mantine/hooks"; +import type { LoaderFunctionArgs } from "@remix-run/cloudflare"; +import { json } from "@remix-run/cloudflare"; +import { useLoaderData, useNavigate } from "@remix-run/react"; +import { getBooks } from "client/client"; +import { GetBooksParams } from "client/client.schemas"; +import { useAtom } from "jotai"; +import { useEffect } from "react"; +import BookListComponent from "~/components/books/BookListComponent"; +import { selectedBooksAtom } from "~/stores/cartAtom"; export const loader = async ({ request }: LoaderFunctionArgs) => { - const url = new URL(request.url) - const title = url.searchParams.get('title') ?? undefined - const publisher = url.searchParams.get('publisher') ?? undefined - const isbn = url.searchParams.get('isbn') ?? undefined - const author = url.searchParams.get('author') ?? undefined - const page = url.searchParams.get('page') ?? undefined - const limit = url.searchParams.get('limit') ?? undefined - const response = await getBooks({ title: title, author: author, publisher: publisher, isbn: isbn, page: page, limit: limit }) - return json({ booksResponse: response, title: title, author: author, publisher: publisher, isbn: isbn, page: page, limit: limit }) -} + const url = new URL(request.url); + const title = url.searchParams.get("title") ?? undefined; + const publisher = url.searchParams.get("publisher") ?? undefined; + const isbn = url.searchParams.get("isbn") ?? undefined; + const auther = url.searchParams.get("author") ?? undefined; + const page = url.searchParams.get("page") ?? undefined; + const limit = url.searchParams.get("limit") ?? undefined; + const response = await getBooks({ + title: title, + author: auther, + publisher: publisher, + isbn: isbn, + page: page, + limit: limit, + }); + return json({ + booksResponse: response, + title: title, + author: auther, + publisher: publisher, + isbn: isbn, + page: page, + limit: limit, + }); +}; const BooKListPage = () => { - const { - booksResponse, - title, - author, - publisher, - isbn, - page, - limit - } = useLoaderData() - const [opened, { open, close }] = useDisclosure() - const navigate = useNavigate() - const [, setSelectedBook] = useAtom(selectedBooksAtom) + const { booksResponse, title, author, publisher, isbn, page, limit } = + useLoaderData(); + const [opened, { open, close }] = useDisclosure(); + const navigate = useNavigate(); + const [, setSelectedBook] = useAtom(selectedBooksAtom); const form = useForm({ - mode: 'uncontrolled', + mode: "uncontrolled", initialValues: { - title: title ?? '', - author: author ?? '', - publisher: publisher ?? '', - isbn: isbn ?? '', - } - }) + title: title ?? "", + author: author ?? "", + publisher: publisher ?? "", + isbn: isbn ?? "", + }, + }); // 選択中の書籍をリセットする useEffect(() => { - setSelectedBook([]) - }, []) + setSelectedBook([]); + }, []); const handleSubmit = (props: GetBooksParams) => { - let url = '/home' - let initial = true - if (props.title && props.title !== '') { - url = (initial === true) ? `${url}?title=${props.title}` : `${url}&title=${props.title}` - initial = false + let url = "/home"; + let initial = true; + if (props.title && props.title !== "") { + url = + initial === true + ? `${url}?title=${props.title}` + : `${url}&title=${props.title}`; + initial = false; } - if (props.author && props.author !== '') { - url = (initial === true) ? `${url}?author=${props.author}` : `${url}&author=${props.author}` - initial = false + if (props.author && props.author !== "") { + url = + initial === true + ? `${url}?author=${props.author}` + : `${url}&author=${props.author}`; + initial = false; } - if (props.publisher && props.publisher !== '') { - url = (initial === true) ? `${url}?publisher=${props.publisher}` : `${url}&publisher=${props.publisher}` - initial = false + if (props.publisher && props.publisher !== "") { + url = + initial === true + ? `${url}?publisher=${props.publisher}` + : `${url}&publisher=${props.publisher}`; + initial = false; } - if (props.isbn && props.isbn !== '') { - url = (initial === true) ? `${url}?isbn=${props.isbn}` : `${url}&isbn=${props.isbn}` - initial = false + if (props.isbn && props.isbn !== "") { + url = + initial === true + ? `${url}?isbn=${props.isbn}` + : `${url}&isbn=${props.isbn}`; + initial = false; } if (limit) { - url = (initial === true) ? `${url}?limit=${limit}` : `${url}&limit=${limit}` - initial = false + url = + initial === true ? `${url}?limit=${limit}` : `${url}&limit=${limit}`; + initial = false; } - navigate(url) - } + navigate(url); + }; const handlePaginationChange = (newPage: number) => { - let url = '/home' - let initial = true + let url = "/home"; + let initial = true; if (title) { - url = (initial === true) ? `${url}?title=${title}` : `${url}&title=${title}` - initial = false + url = + initial === true ? `${url}?title=${title}` : `${url}&title=${title}`; + initial = false; } if (author) { - url = (initial === true) ? `${url}?author=${author}` : `${url}&author=${author}` - initial = false + url = + initial === true + ? `${url}?author=${author}` + : `${url}&author=${author}`; + initial = false; } if (publisher) { - url = (initial === true) ? `${url}?publisher=${publisher}` : `${url}&publisher=${publisher}` - initial = false + url = + initial === true + ? `${url}?publisher=${publisher}` + : `${url}&publisher=${publisher}`; + initial = false; } if (isbn) { - url = (initial === true) ? `${url}?isbn=${isbn}` : `${url}&isbn=${isbn}` - initial = false + url = initial === true ? `${url}?isbn=${isbn}` : `${url}&isbn=${isbn}`; + initial = false; } if (limit) { - url = (initial === true) ? `${url}?limit=${limit}` : `${url}&limit=${limit}` - initial = false + url = + initial === true ? `${url}?limit=${limit}` : `${url}&limit=${limit}`; + initial = false; } - url = (initial === true) ? `${url}?page=${newPage}` : `${url}&page=${newPage}` - navigate(url) - } + url = + initial === true ? `${url}?page=${newPage}` : `${url}&page=${newPage}`; + navigate(url); + }; const handleLimitChange = (newLimit: number) => { - let url = '/home' - let initial = true + let url = "/home"; + let initial = true; if (title) { - url = (initial === true) ? `${url}?title=${title}` : `${url}&title=${title}` - initial = false + url = + initial === true ? `${url}?title=${title}` : `${url}&title=${title}`; + initial = false; } if (author) { - url = (initial === true) ? `${url}?author=${author}` : `${url}&author=${author}` - initial = false + url = + initial === true + ? `${url}?author=${author}` + : `${url}&author=${author}`; + initial = false; } if (publisher) { - url = (initial === true) ? `${url}?publisher=${publisher}` : `${url}&publisher=${publisher}` - initial = false + url = + initial === true + ? `${url}?publisher=${publisher}` + : `${url}&publisher=${publisher}`; + initial = false; } if (isbn) { - url = (initial === true) ? `${url}?isbn=${isbn}` : `${url}&isbn=${isbn}` - initial = false + url = initial === true ? `${url}?isbn=${isbn}` : `${url}&isbn=${isbn}`; + initial = false; } - url = (initial === true) ? `${url}?limit=${newLimit}` : `${url}&limit=${newLimit}` - navigate(url) - } + url = + initial === true + ? `${url}?limit=${newLimit}` + : `${url}&limit=${newLimit}`; + navigate(url); + }; return ( { limit={limit ? Number(limit) : undefined} totalBook={booksResponse.data.totalBook} /> - ) -} + ); +}; -export default BooKListPage \ No newline at end of file +export default BooKListPage; diff --git a/frontend/app/routes/home.books.$bookId.tsx b/frontend/app/routes/home.books.$bookId.tsx index 8c44e481..b8475b28 100644 --- a/frontend/app/routes/home.books.$bookId.tsx +++ b/frontend/app/routes/home.books.$bookId.tsx @@ -1,54 +1,55 @@ -import { json, redirect } from '@remix-run/cloudflare' -import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/cloudflare' -import { deleteBook, getBook } from 'orval/client' -import { useLoaderData } from '@remix-run/react' -import BookDetailComponent from '~/components/book-detail/BookDetailComponent' -import { errorNotifications, successNotifications } from '~/utils/notification' -import type { HeadersInit } from '@cloudflare/workers-types' +import type { + ActionFunctionArgs, + LoaderFunctionArgs, +} from "@remix-run/cloudflare"; +import { json, redirect } from "@remix-run/cloudflare"; +import { useLoaderData } from "@remix-run/react"; +import { deleteBook, getBook } from "client/client"; +import BookDetailComponent from "~/components/book-detail/BookDetailComponent"; +import { errorNotifications, successNotifications } from "~/utils/notification"; interface Response { - method: string - status: number + method: string; + status: number; } export const loader = async ({ params }: LoaderFunctionArgs) => { - const bookId = params.bookId ?? '' - const response = await getBook(bookId) - return json({ bookResponse: response }) -} + const bookId = params.bookId ?? ""; + const response = await getBook(bookId); + return json({ bookResponse: response }); +}; -export const action = async ({ request }: ActionFunctionArgs) => { // delete: 本の削除 - const cookieHeader = request.headers.get('Cookie') - const formData = await request.formData() +export const action = async ({ request }: ActionFunctionArgs) => { + // delete: 本の削除 + const cookieHeader = request.headers.get("Cookie"); + const formData = await request.formData(); if (request.method === "DELETE") { - const bookId = String(formData.get('bookId')) - const response = await deleteBook(bookId, { headers: { 'Cookie': cookieHeader ?? "" } }) + const bookId = String(formData.get("bookId")); + const response = await deleteBook(bookId, { + headers: { Cookie: cookieHeader ?? "" }, + }); switch (response.status) { case 204: - successNotifications('削除しました') - return redirect('/home') + successNotifications("削除しました"); + return redirect("/home"); case 401: - errorNotifications('ログインしてください') - return redirect('/auth/login') + errorNotifications("ログインしてください"); + return redirect("/auth/login"); case 404: - errorNotifications('書籍が見つかりませんでした') - return redirect('/home') + errorNotifications("書籍が見つかりませんでした"); + return redirect("/home"); case 500: - errorNotifications('サーバーエラーが発生しました') - return json({ method: 'DELETE', status: 500 }) + errorNotifications("サーバーエラーが発生しました"); + return json({ method: "DELETE", status: 500 }); } } - return null -} + return null; +}; const BookDetailPage = () => { - const { bookResponse } = useLoaderData() - return ( - - ) -} + const { bookResponse } = useLoaderData(); + return ; +}; -export default BookDetailPage \ No newline at end of file +export default BookDetailPage; diff --git a/frontend/app/stores/userAtom.ts b/frontend/app/stores/userAtom.ts index 6407ff10..41c3bc48 100644 --- a/frontend/app/stores/userAtom.ts +++ b/frontend/app/stores/userAtom.ts @@ -1,7 +1,12 @@ -import { createJSONStorage, atomWithStorage } from "jotai/utils"; -import type { User } from "orval/client.schemas"; +import type { User } from "client/client.schemas"; +import { atomWithStorage, createJSONStorage } from "jotai/utils"; -const storage = createJSONStorage(() => sessionStorage) +const storage = createJSONStorage(() => sessionStorage); -// ユーザ情報を管理するAtom 生存時間: セッションストレージ(タブが閉じられるまで) -export const userAtom = atomWithStorage('user', undefined, storage); \ No newline at end of file +// ユーザ情報を管理するAtom +// 生存時間: セッションストレージ(タブが閉じられるまで) +export const userAtom = atomWithStorage( + "user", + undefined, + storage +); diff --git a/frontend/orval/client.schemas.ts b/frontend/client/client.schemas.ts similarity index 100% rename from frontend/orval/client.schemas.ts rename to frontend/client/client.schemas.ts diff --git a/frontend/orval/client.ts b/frontend/client/client.ts similarity index 100% rename from frontend/orval/client.ts rename to frontend/client/client.ts diff --git a/frontend/orval/mutator.ts b/frontend/client/mutator.ts similarity index 100% rename from frontend/orval/mutator.ts rename to frontend/client/mutator.ts diff --git a/frontend/package.json b/frontend/package.json index e84daac0..a4b6f2b6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "frontend", + "name": "kitcc-library-web", "private": true, "sideEffects": false, "type": "module", @@ -8,6 +8,7 @@ "deploy": "pnpm run build && wrangler pages deploy", "dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 remix vite:dev", "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", + "test": "vitest", "start": "wrangler pages dev ./build/client", "typecheck": "tsc", "typegen": "wrangler types", @@ -23,8 +24,6 @@ "@remix-run/cloudflare-pages": "2.12.0", "@remix-run/react": "2.12.0", "@tanstack/react-query": "^5.59.15", - "@types/react": "npm:types-react@rc", - "@types/react-dom": "npm:types-react-dom@rc", "add": "^2.0.6", "isbot": "^4.1.0", "jotai": "^2.10.1", @@ -39,9 +38,13 @@ }, "devDependencies": { "@cloudflare/workers-types": "^4.20241004.0", + "@faker-js/faker": "^9.0.3", "@remix-run/dev": "2.12.0", - "@types/react": "^18.2.20", - "@types/react-dom": "^18.2.7", + "@testing-library/jest-dom": "^6.6.2", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "autoprefixer": "^10.4.19", @@ -51,6 +54,8 @@ "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", + "happy-dom": "^15.7.4", + "msw": "^2.5.1", "orval": "^7.1.1", "postcss": "^8.4.47", "postcss-preset-mantine": "^1.17.0", @@ -58,9 +63,10 @@ "typescript": "^5.1.6", "vite": "^5.1.0", "vite-tsconfig-paths": "^4.2.1", + "vitest": "^2.1.3", "wrangler": "3.57.1" }, "engines": { "node": ">=20.0.0" } -} \ No newline at end of file +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a8a12442..6f97bbd8 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -32,12 +32,6 @@ importers: '@tanstack/react-query': specifier: ^5.59.15 version: 5.59.15(react@19.0.0-rc-09111202-20241011) - '@types/react': - specifier: npm:types-react@rc - version: types-react@19.0.0-rc.1 - '@types/react-dom': - specifier: npm:types-react-dom@rc - version: types-react-dom@19.0.0-rc.1 add: specifier: ^2.0.6 version: 2.0.6 @@ -63,9 +57,27 @@ importers: '@cloudflare/workers-types': specifier: ^4.20241004.0 version: 4.20241004.0 + '@faker-js/faker': + specifier: ^9.0.3 + version: 9.0.3 '@remix-run/dev': specifier: 2.12.0 version: 2.12.0(@remix-run/react@2.12.0(react-dom@19.0.0-rc-09111202-20241011(react@19.0.0-rc-09111202-20241011))(react@19.0.0-rc-09111202-20241011)(typescript@5.6.2))(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47))(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)))(wrangler@3.57.1(@cloudflare/workers-types@4.20241004.0)) + '@testing-library/jest-dom': + specifier: ^6.6.2 + version: 6.6.2 + '@testing-library/react': + specifier: ^16.0.1 + version: 16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-09111202-20241011(react@19.0.0-rc-09111202-20241011))(react@19.0.0-rc-09111202-20241011)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@testing-library/user-event': + specifier: ^14.5.2 + version: 14.5.2(@testing-library/dom@10.4.0) + '@types/react': + specifier: npm:types-react@19.0.0-rc.1 + version: types-react@19.0.0-rc.1 + '@types/react-dom': + specifier: npm:types-react-dom@19.0.0-rc.1 + version: types-react-dom@19.0.0-rc.1 '@typescript-eslint/eslint-plugin': specifier: ^6.7.4 version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) @@ -93,6 +105,12 @@ importers: eslint-plugin-react-hooks: specifier: ^4.6.0 version: 4.6.2(eslint@8.57.1) + happy-dom: + specifier: ^15.7.4 + version: 15.7.4 + msw: + specifier: ^2.5.1 + version: 2.5.1(@types/node@22.7.4)(typescript@5.6.2) orval: specifier: ^7.1.1 version: 7.1.1(openapi-types@12.1.3)(typescript@5.6.2) @@ -114,12 +132,18 @@ importers: vite-tsconfig-paths: specifier: ^4.2.1 version: 4.3.2(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47))) + vitest: + specifier: ^2.1.3 + version: 2.1.3(@edge-runtime/vm@4.0.3)(@types/node@22.7.4)(happy-dom@15.7.4)(msw@2.5.1(@types/node@22.7.4)(typescript@5.6.2))(sugarss@4.0.1(postcss@8.4.47)) wrangler: specifier: 3.57.1 version: 3.57.1(@cloudflare/workers-types@4.20241004.0) packages: + '@adobe/css-tools@4.4.0': + resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -285,6 +309,15 @@ packages: resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} engines: {node: '>=6.9.0'} + '@bundled-es-modules/cookie@2.0.0': + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@cloudflare/kv-asset-handler@0.1.3': resolution: {integrity: sha512-FNcunDuTmEfQTLRLtA6zz+buIXUHj1soPvSWzzQFBC+n2lsy+CGf/NIrR3SEPCmsVNQj70/Jx2lViCpq+09YpQ==} @@ -329,6 +362,14 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@edge-runtime/primitives@5.1.0': + resolution: {integrity: sha512-bs379S/qL7b9B1fXM3xYe+g2orW7Uy0m8oIudiXLcHQyZLsdd0Gfw9STngFDnaAfAcRN5g+/YEMPSsDqiPm0TQ==} + engines: {node: '>=16'} + + '@edge-runtime/vm@4.0.3': + resolution: {integrity: sha512-2EKlqxSbZTV4D+XG8DTX+9P1SL+m48ahvNbDuxz+dZkmUZ+ju4hl/m28j7QMbC9kU5S+4HUJCYKCAfA+3gggLw==} + engines: {node: '>=16'} + '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} @@ -903,6 +944,10 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@faker-js/faker@9.0.3': + resolution: {integrity: sha512-lWrrK4QNlFSU+13PL9jMbMKLJYXDFu3tQfayBsMXX7KL/GiQeqfB1CzHkqD5UHBUtPAuPo6XwGbMFNdVMZObRA==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} @@ -949,6 +994,24 @@ packages: resolution: {integrity: sha512-yNdrVw9OZ8AHb1FR+o4k1ST5c+kXOKOMKYkRi1nFBtOlOuAZU+FMym9tnFa3lOS9+ePipul5DBJj3Hhd70vFpw==} engines: {node: '>=16.0.0'} + '@inquirer/confirm@5.0.0': + resolution: {integrity: sha512-6QEzj6bZg8atviRIL+pR0tODC854cYSjvZxkyCarr8DVaOJPEyuGys7GmEG3W0Rb8kKSQec7P6okt0sJvNneFw==} + engines: {node: '>=18'} + + '@inquirer/core@10.0.0': + resolution: {integrity: sha512-7dwoKCGvgZGHWTZfOj2KLmbIAIdiXP9NTrwGaTO/XDfKMEmyBahZpnombiG6JDHmiOrmK3GLEJRXrWExXCDLmQ==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.7': + resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.0': + resolution: {integrity: sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1031,6 +1094,10 @@ packages: '@mdx-js/mdx@2.3.0': resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + '@mswjs/interceptors@0.36.6': + resolution: {integrity: sha512-issnYydStyH0wPEeU7CMwfO7kI668ffVtzKRMRS7H7BliOYuPuwEZxh9dwiXV+oeHBxT5SXT0wPwV8T7V2PJUA==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1063,6 +1130,15 @@ packages: resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@orval/angular@7.1.1': resolution: {integrity: sha512-oMn38oQsmo3hgfghin5d9La/KwkiyMLxJ+b1MpILjmvnlJiLWL0SrHtfUlqLs+8wwMGWmzbuS875l2KIfNmyjQ==} @@ -1351,9 +1427,41 @@ packages: peerDependencies: react: ^18 || ^19 + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.6.2': + resolution: {integrity: sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.0.1': + resolution: {integrity: sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 + '@types/react-dom': ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.5.2': + resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -1402,6 +1510,12 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1481,6 +1595,36 @@ packages: '@vanilla-extract/private@1.0.6': resolution: {integrity: sha512-ytsG/JLweEjw7DBuZ/0JCN4WAQgM9erfSTdS1NQY778hFQSZ6cfCDEZZ0sgVm4k54uNz6ImKB33AYvSR//fjxw==} + '@vitest/expect@2.1.3': + resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} + + '@vitest/mocker@2.1.3': + resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} + peerDependencies: + '@vitest/spy': 2.1.3 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.3': + resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} + + '@vitest/runner@2.1.3': + resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} + + '@vitest/snapshot@2.1.3': + resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} + + '@vitest/spy@2.1.3': + resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} + + '@vitest/utils@2.1.3': + resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} + '@web3-storage/multipart-parser@1.0.0': resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} @@ -1547,6 +1691,10 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1563,6 +1711,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -1583,6 +1735,9 @@ packages: aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -1625,6 +1780,10 @@ packages: as-table@1.0.55: resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -1734,10 +1893,18 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1754,6 +1921,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1777,6 +1948,10 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1851,6 +2026,9 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1917,6 +2095,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -1977,6 +2159,12 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -2021,6 +2209,10 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -2462,10 +2654,18 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.9.0: + resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gunzip-maybe@1.4.2: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} hasBin: true + happy-dom@15.7.4: + resolution: {integrity: sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==} + engines: {node: '>=18.0.0'} + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -2502,6 +2702,9 @@ packages: hast-util-whitespace@2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hosted-git-info@6.1.1: resolution: {integrity: sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -2666,6 +2869,9 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -2934,6 +3140,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2944,9 +3153,16 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} engines: {node: '>=0.10.0'} @@ -3123,6 +3339,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + miniflare@3.20240512.0: resolution: {integrity: sha512-X0PlKR0AROKpxFoJNmRtCMIuJxj+ngEcyTOlEokj2rAQ0TBwUhB4/1uiPvdI6ofW5NugPOD1uomAv+gLjwsLDQ==} engines: {node: '>=16.13'} @@ -3202,10 +3422,24 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.5.1: + resolution: {integrity: sha512-V0BmHvFkbWGXqbyrc+XiuQ8DU3qzcb6lb8gB9Vzltp3cgHLHLCDF/KmmFo0xw58StNaRMTebw3/xpWVvU9xq9g==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3361,6 +3595,9 @@ packages: outdent@0.8.0: resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -3426,6 +3663,10 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + peek-stream@1.1.3: resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} @@ -3551,6 +3792,10 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-ms@7.0.1: resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} engines: {node: '>=10'} @@ -3587,6 +3832,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} @@ -3604,6 +3852,9 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3628,6 +3879,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-number-format@5.4.2: resolution: {integrity: sha512-cg//jVdS49PYDgmcYoBnMMHl4XNTMuV723ZnHD2aXYtWWWqbVF3hjQ8iB+UZEuXapLbeA8P8H+1o6ZB1lcw3vg==} peerDependencies: @@ -3708,6 +3962,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} @@ -3749,6 +4007,9 @@ packages: require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3895,6 +4156,9 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3951,6 +4215,9 @@ packages: resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktracey@2.1.8: resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} @@ -3958,6 +4225,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -3972,6 +4242,9 @@ packages: stream-slice@0.1.2: resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -4033,6 +4306,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -4086,6 +4363,24 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -4101,6 +4396,10 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -4160,6 +4459,10 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@4.26.1: resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} engines: {node: '>=16'} @@ -4247,6 +4550,10 @@ packages: unist-util-visit@4.1.2: resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -4267,6 +4574,9 @@ packages: urijs@1.19.11: resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-callback-ref@1.3.2: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} @@ -4355,6 +4665,11 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + vite-node@2.1.3: + resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-tsconfig-paths@4.3.2: resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: @@ -4394,6 +4709,31 @@ packages: terser: optional: true + vitest@2.1.3: + resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.3 + '@vitest/ui': 2.1.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -4407,6 +4747,14 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -4435,6 +4783,11 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4454,6 +4807,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -4527,6 +4884,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + youch@3.3.3: resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} @@ -4538,6 +4899,8 @@ packages: snapshots: + '@adobe/css-tools@4.4.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -4776,6 +5139,19 @@ snapshots: '@babel/helper-validator-identifier': 7.25.7 to-fast-properties: 2.0.0 + '@bundled-es-modules/cookie@2.0.0': + dependencies: + cookie: 0.5.0 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + '@cloudflare/kv-asset-handler@0.1.3': dependencies: mime: 2.6.0 @@ -4805,6 +5181,14 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@edge-runtime/primitives@5.1.0': + optional: true + + '@edge-runtime/vm@4.0.3': + dependencies: + '@edge-runtime/primitives': 5.1.0 + optional: true + '@emotion/hash@0.9.2': {} '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': @@ -5112,6 +5496,8 @@ snapshots: '@exodus/schemasafe@1.3.0': {} + '@faker-js/faker@9.0.3': {} + '@fastify/busboy@2.1.1': {} '@floating-ui/core@1.6.8': @@ -5168,6 +5554,33 @@ snapshots: transitivePeerDependencies: - encoding + '@inquirer/confirm@5.0.0(@types/node@22.7.4)': + dependencies: + '@inquirer/core': 10.0.0(@types/node@22.7.4) + '@inquirer/type': 3.0.0(@types/node@22.7.4) + transitivePeerDependencies: + - '@types/node' + + '@inquirer/core@10.0.0(@types/node@22.7.4)': + dependencies: + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@22.7.4) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + + '@inquirer/figures@1.0.7': {} + + '@inquirer/type@3.0.0(@types/node@22.7.4)': + dependencies: + '@types/node': 22.7.4 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -5274,6 +5687,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@mswjs/interceptors@0.36.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5321,6 +5743,15 @@ snapshots: dependencies: which: 3.0.1 + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@orval/angular@7.1.1(openapi-types@12.1.3)': dependencies: '@orval/core': 7.1.1(openapi-types@12.1.3) @@ -5806,10 +6237,47 @@ snapshots: '@tanstack/query-core': 5.59.13 react: 19.0.0-rc-09111202-20241011 + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/runtime': 7.25.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.6.2': + dependencies: + '@adobe/css-tools': 4.4.0 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(react-dom@19.0.0-rc-09111202-20241011(react@19.0.0-rc-09111202-20241011))(react@19.0.0-rc-09111202-20241011)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + dependencies: + '@babel/runtime': 7.25.7 + '@testing-library/dom': 10.4.0 + react: 19.0.0-rc-09111202-20241011 + react-dom: 19.0.0-rc-09111202-20241011(react@19.0.0-rc-09111202-20241011) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 + + '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': + dependencies: + '@testing-library/dom': 10.4.0 + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.6 + '@types/aria-query@5.0.4': {} + '@types/cookie@0.6.0': {} '@types/debug@4.1.12': @@ -5859,6 +6327,10 @@ snapshots: '@types/semver@7.5.8': {} + '@types/statuses@2.0.5': {} + + '@types/tough-cookie@4.0.5': {} + '@types/unist@2.0.11': {} '@types/urijs@1.19.25': {} @@ -6003,6 +6475,47 @@ snapshots: '@vanilla-extract/private@1.0.6': {} + '@vitest/expect@2.1.3': + dependencies: + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.7.4)(typescript@5.6.2))(vite@5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)))': + dependencies: + '@vitest/spy': 2.1.3 + estree-walker: 3.0.3 + magic-string: 0.30.12 + optionalDependencies: + msw: 2.5.1(@types/node@22.7.4)(typescript@5.6.2) + vite: 5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)) + + '@vitest/pretty-format@2.1.3': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.3': + dependencies: + '@vitest/utils': 2.1.3 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.3': + dependencies: + '@vitest/pretty-format': 2.1.3 + magic-string: 0.30.12 + pathe: 1.1.2 + + '@vitest/spy@2.1.3': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.3': + dependencies: + '@vitest/pretty-format': 2.1.3 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + '@web3-storage/multipart-parser@1.0.0': {} '@zxing/text-encoding@0.9.0': @@ -6062,6 +6575,10 @@ snapshots: ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -6074,6 +6591,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} anymatch@3.1.3: @@ -6093,6 +6612,10 @@ snapshots: dependencies: deep-equal: 2.2.3 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -6166,6 +6689,8 @@ snapshots: dependencies: printable-characters: 1.0.42 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} astring@1.9.0: {} @@ -6296,12 +6821,25 @@ snapshots: ccount@2.0.1: {} + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6315,6 +6853,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -6339,6 +6879,8 @@ snapshots: cli-spinners@2.9.2: {} + cli-width@4.1.0: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -6395,6 +6937,8 @@ snapshots: css-what@6.1.0: {} + css.escape@1.5.1: {} + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -6441,6 +6985,8 @@ snapshots: dedent@1.5.3: {} + deep-eql@5.0.2: {} + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -6508,6 +7054,10 @@ snapshots: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.25.7 @@ -6550,6 +7100,8 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + err-code@2.0.3: {} es-abstract@1.23.3: @@ -7278,6 +7830,8 @@ snapshots: graphemer@1.4.0: {} + graphql@16.9.0: {} + gunzip-maybe@1.4.2: dependencies: browserify-zlib: 0.1.4 @@ -7287,6 +7841,12 @@ snapshots: pumpify: 1.5.1 through2: 2.0.5 + happy-dom@15.7.4: + dependencies: + entities: 4.5.0 + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -7331,6 +7891,8 @@ snapshots: hast-util-whitespace@2.0.1: {} + headers-polyfill@4.0.3: {} + hosted-git-info@6.1.1: dependencies: lru-cache: 7.18.3 @@ -7475,6 +8037,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -7690,6 +8254,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.1.2: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -7698,10 +8264,16 @@ snapshots: lru-cache@7.18.3: {} + lz-string@1.5.0: {} + magic-string@0.25.9: dependencies: sourcemap-codec: 1.4.8 + magic-string@0.30.12: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + markdown-extensions@1.1.1: {} mdast-util-definitions@5.1.2: @@ -8057,6 +8629,8 @@ snapshots: mimic-fn@2.1.0: {} + min-indent@1.0.1: {} + miniflare@3.20240512.0: dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -8140,8 +8714,34 @@ snapshots: ms@2.1.3: {} + msw@2.5.1(@types/node@22.7.4)(typescript@5.6.2): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.0.0(@types/node@22.7.4) + '@mswjs/interceptors': 0.36.6 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + chalk: 4.1.2 + graphql: 16.9.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.26.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.6.2 + transitivePeerDependencies: + - '@types/node' + mustache@4.2.0: {} + mute-stream@2.0.0: {} + nanoid@3.3.7: {} natural-compare@1.4.0: {} @@ -8354,6 +8954,8 @@ snapshots: outdent@0.8.0: {} + outvariant@1.4.3: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -8410,6 +9012,8 @@ snapshots: pathe@1.1.2: {} + pathval@2.0.0: {} + peek-stream@1.1.3: dependencies: buffer-from: 1.1.2 @@ -8527,6 +9131,12 @@ snapshots: prettier@2.8.8: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-ms@7.0.1: dependencies: parse-ms: 2.1.0 @@ -8557,6 +9167,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + psl@1.9.0: {} + pump@2.0.1: dependencies: end-of-stream: 1.4.4 @@ -8579,6 +9191,8 @@ snapshots: dependencies: side-channel: 1.0.6 + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} range-parser@1.2.1: {} @@ -8601,6 +9215,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-number-format@5.4.2(react-dom@19.0.0-rc-09111202-20241011(react@19.0.0-rc-09111202-20241011))(react@19.0.0-rc-09111202-20241011): dependencies: react: 19.0.0-rc-09111202-20241011 @@ -8688,6 +9304,11 @@ snapshots: dependencies: picomatch: 2.3.1 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + reflect.getprototypeof@1.0.6: dependencies: call-bind: 1.0.7 @@ -8751,6 +9372,8 @@ snapshots: require-like@0.1.2: {} + requires-port@1.0.0: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -8944,6 +9567,8 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -8989,6 +9614,8 @@ snapshots: dependencies: minipass: 7.1.2 + stackback@0.0.2: {} + stacktracey@2.1.8: dependencies: as-table: 1.0.55 @@ -8996,6 +9623,8 @@ snapshots: statuses@2.0.1: {} + std-env@3.7.0: {} + stop-iteration-iterator@1.0.0: dependencies: internal-slot: 1.0.7 @@ -9006,6 +9635,8 @@ snapshots: stream-slice@0.1.2: {} + strict-event-emitter@0.5.1: {} + string-argv@0.3.2: {} string-hash@1.1.3: {} @@ -9091,6 +9722,10 @@ snapshots: strip-final-newline@2.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} style-to-object@0.4.4: @@ -9162,6 +9797,16 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 + tinybench@2.9.0: {} + + tinyexec@0.3.1: {} + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + to-fast-properties@2.0.0: {} to-regex-range@5.0.1: @@ -9172,6 +9817,13 @@ snapshots: toml@3.0.0: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tr46@0.0.3: {} trim-lines@3.0.1: {} @@ -9215,6 +9867,8 @@ snapshots: type-fest@0.20.2: {} + type-fest@0.21.3: {} + type-fest@4.26.1: {} type-is@1.6.18: @@ -9333,6 +9987,8 @@ snapshots: unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 + universalify@0.2.0: {} + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -9349,6 +10005,11 @@ snapshots: urijs@1.19.11: {} + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + use-callback-ref@1.3.2(react@19.0.0-rc-09111202-20241011)(types-react@19.0.0-rc.1): dependencies: react: 19.0.0-rc-09111202-20241011 @@ -9443,6 +10104,23 @@ snapshots: - supports-color - terser + vite-node@2.1.3(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-tsconfig-paths@4.3.2(typescript@5.6.2)(vite@5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47))): dependencies: debug: 4.3.7 @@ -9464,6 +10142,42 @@ snapshots: fsevents: 2.3.3 sugarss: 4.0.1(postcss@8.4.47) + vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@22.7.4)(happy-dom@15.7.4)(msw@2.5.1(@types/node@22.7.4)(typescript@5.6.2))(sugarss@4.0.1(postcss@8.4.47)): + dependencies: + '@vitest/expect': 2.1.3 + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.7.4)(typescript@5.6.2))(vite@5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47))) + '@vitest/pretty-format': 2.1.3 + '@vitest/runner': 2.1.3 + '@vitest/snapshot': 2.1.3 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 + chai: 5.1.2 + debug: 4.3.7 + magic-string: 0.30.12 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.8(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)) + vite-node: 2.1.3(@types/node@22.7.4)(sugarss@4.0.1(postcss@8.4.47)) + why-is-node-running: 2.3.0 + optionalDependencies: + '@edge-runtime/vm': 4.0.3 + '@types/node': 22.7.4 + happy-dom: 15.7.4 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -9478,6 +10192,10 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} + + whatwg-mimetype@3.0.0: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -9529,6 +10247,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} workerd@1.20240512.0: @@ -9563,6 +10286,12 @@ snapshots: - supports-color - utf-8-validate + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -9609,6 +10338,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.2: {} + youch@3.3.3: dependencies: cookie: 0.5.0 diff --git a/frontend/test/helpers/wrapper.tsx b/frontend/test/helpers/wrapper.tsx new file mode 100644 index 00000000..84d1dbe8 --- /dev/null +++ b/frontend/test/helpers/wrapper.tsx @@ -0,0 +1,29 @@ +import { MantineProvider } from "@mantine/core"; +import { Notifications } from "@mantine/notifications"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { ReactElement } from "react"; + +export const renderWithWrapper = (children: ReactElement) => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + // データが古いと判断されるまでの時間[ms] + staleTime: Infinity, + }, + }, + }); + return { + user: userEvent.setup(), + ...render( + + + + {children} + + + ), + }; +}; diff --git a/frontend/test/mocks/handlers.ts b/frontend/test/mocks/handlers.ts new file mode 100644 index 00000000..4d931b56 --- /dev/null +++ b/frontend/test/mocks/handlers.ts @@ -0,0 +1,3 @@ +import { getKITCCLibraryAPIMock } from "./mock"; + +export const handlers = getKITCCLibraryAPIMock(); diff --git a/frontend/test/mocks/mock.ts b/frontend/test/mocks/mock.ts new file mode 100644 index 00000000..0e33c71b --- /dev/null +++ b/frontend/test/mocks/mock.ts @@ -0,0 +1,1465 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import { + useMutation, + useQuery +} from '@tanstack/react-query' +import type { + MutationFunction, + QueryFunction, + QueryKey, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult +} from '@tanstack/react-query' +import type { + BadRequestResponse, + CreateBookBody, + CreateUserBody, + Error, + GetBooksParams, + GetLoansParams, + GetUsersParams, + InternalServerErrorResponse, + LoginBody, + NotFoundResponse, + SearchBooksParams, + UnauthorizedResponse, + UpdateBookBody, + UpdateUserBody, + UpsertLoans404, + UpsertLoans409, + UpsertLoansBodyItem +} from './model' +import { + faker +} from '@faker-js/faker' +import { + HttpResponse, + http +} from 'msw' +import type { + Book, + DeleteUser204, + GetBooks200, + GetLoans200, + GetUsers200, + Loan, + SearchBooks200, + User +} from './model' + + +type AwaitedInput = PromiseLike | T; + + type Awaited = O extends AwaitedInput ? T : never; + + + +/** + * ページ番号が指定されなかった場合は1ページ目を返す + * @summary 書籍の情報を取得する + */ +export type getBooksResponse = { + data: GetBooks200; + status: number; +} + +export const getGetBooksUrl = (params?: GetBooksParams,) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + } + }); + + return normalizedParams.size ? `https://localhost:8787/books?${normalizedParams.toString()}` : `https://localhost:8787/books` +} + +export const getBooks = async (params?: GetBooksParams, options?: RequestInit): Promise => { + + const res = await fetch(getGetBooksUrl(params), + { + ...options, + method: 'GET' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + +export const getGetBooksQueryKey = (params?: GetBooksParams,) => { + return [`https://localhost:8787/books`, ...(params ? [params]: [])] as const; + } + + +export const getGetBooksQueryOptions = >, TError = BadRequestResponse | InternalServerErrorResponse>(params?: GetBooksParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} +) => { + +const {query: queryOptions, fetch: fetchOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetBooksQueryKey(params); + + + + const queryFn: QueryFunction>> = ({ signal }) => getBooks(params, { signal, ...fetchOptions }); + + + + + + return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey } +} + +export type GetBooksQueryResult = NonNullable>> +export type GetBooksQueryError = BadRequestResponse | InternalServerErrorResponse + + +/** + * @summary 書籍の情報を取得する + */ + +export function useGetBooks>, TError = BadRequestResponse | InternalServerErrorResponse>( + params?: GetBooksParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} + + ): UseQueryResult & { queryKey: QueryKey } { + + const queryOptions = getGetBooksQueryOptions(params,options) + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +/** + * @summary 書籍を追加する + */ +export type createBookResponse = { + data: Book; + status: number; +} + +export const getCreateBookUrl = () => { + + + return `https://localhost:8787/books` +} + +export const createBook = async (createBookBody: CreateBookBody, options?: RequestInit): Promise => { + + const res = await fetch(getCreateBookUrl(), + { + ...options, + method: 'POST', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify( + createBookBody,) + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getCreateBookMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: CreateBookBody}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{data: CreateBookBody}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {data: CreateBookBody}> = (props) => { + const {data} = props ?? {}; + + return createBook(data,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type CreateBookMutationResult = NonNullable>> + export type CreateBookMutationBody = CreateBookBody + export type CreateBookMutationError = BadRequestResponse | UnauthorizedResponse | InternalServerErrorResponse + + /** + * @summary 書籍を追加する + */ +export const useCreateBook = (options?: { mutation?:UseMutationOptions>, TError,{data: CreateBookBody}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {data: CreateBookBody}, + TContext + > => { + + const mutationOptions = getCreateBookMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * @summary 特定の書籍の情報を取得する + */ +export type getBookResponse = { + data: Book; + status: number; +} + +export const getGetBookUrl = (bookId: string,) => { + + + return `https://localhost:8787/books/${bookId}` +} + +export const getBook = async (bookId: string, options?: RequestInit): Promise => { + + const res = await fetch(getGetBookUrl(bookId), + { + ...options, + method: 'GET' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + +export const getGetBookQueryKey = (bookId: string,) => { + return [`https://localhost:8787/books/${bookId}`] as const; + } + + +export const getGetBookQueryOptions = >, TError = BadRequestResponse | NotFoundResponse | InternalServerErrorResponse>(bookId: string, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} +) => { + +const {query: queryOptions, fetch: fetchOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetBookQueryKey(bookId); + + + + const queryFn: QueryFunction>> = ({ signal }) => getBook(bookId, { signal, ...fetchOptions }); + + + + + + return { queryKey, queryFn, enabled: !!(bookId), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey } +} + +export type GetBookQueryResult = NonNullable>> +export type GetBookQueryError = BadRequestResponse | NotFoundResponse | InternalServerErrorResponse + + +/** + * @summary 特定の書籍の情報を取得する + */ + +export function useGetBook>, TError = BadRequestResponse | NotFoundResponse | InternalServerErrorResponse>( + bookId: string, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} + + ): UseQueryResult & { queryKey: QueryKey } { + + const queryOptions = getGetBookQueryOptions(bookId,options) + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +/** + * リクエストボディで指定された情報のみ更新する. 書籍が登録済みの場合は蔵書数を+1する. + + * @summary 特定の書籍の情報を更新する + */ +export type updateBookResponse = { + data: Book; + status: number; +} + +export const getUpdateBookUrl = (bookId: string,) => { + + + return `https://localhost:8787/books/${bookId}` +} + +export const updateBook = async (bookId: string, + updateBookBody: UpdateBookBody, options?: RequestInit): Promise => { + + const res = await fetch(getUpdateBookUrl(bookId), + { + ...options, + method: 'PATCH', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify( + updateBookBody,) + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getUpdateBookMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{bookId: string;data: UpdateBookBody}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{bookId: string;data: UpdateBookBody}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {bookId: string;data: UpdateBookBody}> = (props) => { + const {bookId,data} = props ?? {}; + + return updateBook(bookId,data,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type UpdateBookMutationResult = NonNullable>> + export type UpdateBookMutationBody = UpdateBookBody + export type UpdateBookMutationError = BadRequestResponse | UnauthorizedResponse | NotFoundResponse | InternalServerErrorResponse + + /** + * @summary 特定の書籍の情報を更新する + */ +export const useUpdateBook = (options?: { mutation?:UseMutationOptions>, TError,{bookId: string;data: UpdateBookBody}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {bookId: string;data: UpdateBookBody}, + TContext + > => { + + const mutationOptions = getUpdateBookMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * @summary 特定の書籍を削除する + */ +export type deleteBookResponse = { + data: void; + status: number; +} + +export const getDeleteBookUrl = (bookId: string,) => { + + + return `https://localhost:8787/books/${bookId}` +} + +export const deleteBook = async (bookId: string, options?: RequestInit): Promise => { + + const res = await fetch(getDeleteBookUrl(bookId), + { + ...options, + method: 'DELETE' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getDeleteBookMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{bookId: string}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{bookId: string}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {bookId: string}> = (props) => { + const {bookId} = props ?? {}; + + return deleteBook(bookId,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type DeleteBookMutationResult = NonNullable>> + + export type DeleteBookMutationError = BadRequestResponse | UnauthorizedResponse | NotFoundResponse | InternalServerErrorResponse + + /** + * @summary 特定の書籍を削除する + */ +export const useDeleteBook = (options?: { mutation?:UseMutationOptions>, TError,{bookId: string}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {bookId: string}, + TContext + > => { + + const mutationOptions = getDeleteBookMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * @summary 書籍を検索する + */ +export type searchBooksResponse = { + data: SearchBooks200; + status: number; +} + +export const getSearchBooksUrl = (params?: SearchBooksParams,) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + } + }); + + return normalizedParams.size ? `https://localhost:8787/books/search?${normalizedParams.toString()}` : `https://localhost:8787/books/search` +} + +export const searchBooks = async (params?: SearchBooksParams, options?: RequestInit): Promise => { + + const res = await fetch(getSearchBooksUrl(params), + { + ...options, + method: 'GET' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + +export const getSearchBooksQueryKey = (params?: SearchBooksParams,) => { + return [`https://localhost:8787/books/search`, ...(params ? [params]: [])] as const; + } + + +export const getSearchBooksQueryOptions = >, TError = BadRequestResponse | InternalServerErrorResponse>(params?: SearchBooksParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} +) => { + +const {query: queryOptions, fetch: fetchOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getSearchBooksQueryKey(params); + + + + const queryFn: QueryFunction>> = ({ signal }) => searchBooks(params, { signal, ...fetchOptions }); + + + + + + return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey } +} + +export type SearchBooksQueryResult = NonNullable>> +export type SearchBooksQueryError = BadRequestResponse | InternalServerErrorResponse + + +/** + * @summary 書籍を検索する + */ + +export function useSearchBooks>, TError = BadRequestResponse | InternalServerErrorResponse>( + params?: SearchBooksParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} + + ): UseQueryResult & { queryKey: QueryKey } { + + const queryOptions = getSearchBooksQueryOptions(params,options) + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +/** + * ページ番号が指定されなかった場合は1ページ目を返す + * @summary ユーザーの情報を取得する + */ +export type getUsersResponse = { + data: GetUsers200; + status: number; +} + +export const getGetUsersUrl = (params?: GetUsersParams,) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + } + }); + + return normalizedParams.size ? `https://localhost:8787/users?${normalizedParams.toString()}` : `https://localhost:8787/users` +} + +export const getUsers = async (params?: GetUsersParams, options?: RequestInit): Promise => { + + const res = await fetch(getGetUsersUrl(params), + { + ...options, + method: 'GET' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + +export const getGetUsersQueryKey = (params?: GetUsersParams,) => { + return [`https://localhost:8787/users`, ...(params ? [params]: [])] as const; + } + + +export const getGetUsersQueryOptions = >, TError = BadRequestResponse | InternalServerErrorResponse>(params?: GetUsersParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} +) => { + +const {query: queryOptions, fetch: fetchOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetUsersQueryKey(params); + + + + const queryFn: QueryFunction>> = ({ signal }) => getUsers(params, { signal, ...fetchOptions }); + + + + + + return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey } +} + +export type GetUsersQueryResult = NonNullable>> +export type GetUsersQueryError = BadRequestResponse | InternalServerErrorResponse + + +/** + * @summary ユーザーの情報を取得する + */ + +export function useGetUsers>, TError = BadRequestResponse | InternalServerErrorResponse>( + params?: GetUsersParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} + + ): UseQueryResult & { queryKey: QueryKey } { + + const queryOptions = getGetUsersQueryOptions(params,options) + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +/** + * @summary ユーザーを追加する + */ +export type createUserResponse = { + data: User; + status: number; +} + +export const getCreateUserUrl = () => { + + + return `https://localhost:8787/users` +} + +export const createUser = async (createUserBody: CreateUserBody, options?: RequestInit): Promise => { + + const res = await fetch(getCreateUserUrl(), + { + ...options, + method: 'POST', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify( + createUserBody,) + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getCreateUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: CreateUserBody}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{data: CreateUserBody}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {data: CreateUserBody}> = (props) => { + const {data} = props ?? {}; + + return createUser(data,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type CreateUserMutationResult = NonNullable>> + export type CreateUserMutationBody = CreateUserBody + export type CreateUserMutationError = BadRequestResponse | UnauthorizedResponse | Error | InternalServerErrorResponse + + /** + * @summary ユーザーを追加する + */ +export const useCreateUser = (options?: { mutation?:UseMutationOptions>, TError,{data: CreateUserBody}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {data: CreateUserBody}, + TContext + > => { + + const mutationOptions = getCreateUserMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * @summary 特定のユーザーの情報を取得する + */ +export type getUserResponse = { + data: User; + status: number; +} + +export const getGetUserUrl = (userId: string,) => { + + + return `https://localhost:8787/users/${userId}` +} + +export const getUser = async (userId: string, options?: RequestInit): Promise => { + + const res = await fetch(getGetUserUrl(userId), + { + ...options, + method: 'GET' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + +export const getGetUserQueryKey = (userId: string,) => { + return [`https://localhost:8787/users/${userId}`] as const; + } + + +export const getGetUserQueryOptions = >, TError = BadRequestResponse | NotFoundResponse | InternalServerErrorResponse>(userId: string, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} +) => { + +const {query: queryOptions, fetch: fetchOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetUserQueryKey(userId); + + + + const queryFn: QueryFunction>> = ({ signal }) => getUser(userId, { signal, ...fetchOptions }); + + + + + + return { queryKey, queryFn, enabled: !!(userId), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey } +} + +export type GetUserQueryResult = NonNullable>> +export type GetUserQueryError = BadRequestResponse | NotFoundResponse | InternalServerErrorResponse + + +/** + * @summary 特定のユーザーの情報を取得する + */ + +export function useGetUser>, TError = BadRequestResponse | NotFoundResponse | InternalServerErrorResponse>( + userId: string, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} + + ): UseQueryResult & { queryKey: QueryKey } { + + const queryOptions = getGetUserQueryOptions(userId,options) + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +/** + * リクエストボディに含まれている情報のみ更新する + * @summary 特定のユーザーの情報を更新する + */ +export type updateUserResponse = { + data: User; + status: number; +} + +export const getUpdateUserUrl = (userId: string,) => { + + + return `https://localhost:8787/users/${userId}` +} + +export const updateUser = async (userId: string, + updateUserBody: UpdateUserBody, options?: RequestInit): Promise => { + + const res = await fetch(getUpdateUserUrl(userId), + { + ...options, + method: 'PATCH', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify( + updateUserBody,) + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getUpdateUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{userId: string;data: UpdateUserBody}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{userId: string;data: UpdateUserBody}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {userId: string;data: UpdateUserBody}> = (props) => { + const {userId,data} = props ?? {}; + + return updateUser(userId,data,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type UpdateUserMutationResult = NonNullable>> + export type UpdateUserMutationBody = UpdateUserBody + export type UpdateUserMutationError = BadRequestResponse | UnauthorizedResponse | NotFoundResponse | InternalServerErrorResponse + + /** + * @summary 特定のユーザーの情報を更新する + */ +export const useUpdateUser = (options?: { mutation?:UseMutationOptions>, TError,{userId: string;data: UpdateUserBody}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {userId: string;data: UpdateUserBody}, + TContext + > => { + + const mutationOptions = getUpdateUserMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * @summary 特定のユーザーを削除する + */ +export type deleteUserResponse = { + data: DeleteUser204; + status: number; +} + +export const getDeleteUserUrl = (userId: string,) => { + + + return `https://localhost:8787/users/${userId}` +} + +export const deleteUser = async (userId: string, options?: RequestInit): Promise => { + + const res = await fetch(getDeleteUserUrl(userId), + { + ...options, + method: 'DELETE' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getDeleteUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{userId: string}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{userId: string}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {userId: string}> = (props) => { + const {userId} = props ?? {}; + + return deleteUser(userId,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type DeleteUserMutationResult = NonNullable>> + + export type DeleteUserMutationError = BadRequestResponse | Error | NotFoundResponse | InternalServerErrorResponse + + /** + * @summary 特定のユーザーを削除する + */ +export const useDeleteUser = (options?: { mutation?:UseMutationOptions>, TError,{userId: string}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {userId: string}, + TContext + > => { + + const mutationOptions = getDeleteUserMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * 指定された条件に合致する貸出履歴を返す + * @summary 貸出履歴を取得する + */ +export type getLoansResponse = { + data: GetLoans200; + status: number; +} + +export const getGetLoansUrl = (params?: GetLoansParams,) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()) + } + }); + + return normalizedParams.size ? `https://localhost:8787/loans?${normalizedParams.toString()}` : `https://localhost:8787/loans` +} + +export const getLoans = async (params?: GetLoansParams, options?: RequestInit): Promise => { + + const res = await fetch(getGetLoansUrl(params), + { + ...options, + method: 'GET' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + +export const getGetLoansQueryKey = (params?: GetLoansParams,) => { + return [`https://localhost:8787/loans`, ...(params ? [params]: [])] as const; + } + + +export const getGetLoansQueryOptions = >, TError = BadRequestResponse | UnauthorizedResponse | InternalServerErrorResponse>(params?: GetLoansParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} +) => { + +const {query: queryOptions, fetch: fetchOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetLoansQueryKey(params); + + + + const queryFn: QueryFunction>> = ({ signal }) => getLoans(params, { signal, ...fetchOptions }); + + + + + + return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey } +} + +export type GetLoansQueryResult = NonNullable>> +export type GetLoansQueryError = BadRequestResponse | UnauthorizedResponse | InternalServerErrorResponse + + +/** + * @summary 貸出履歴を取得する + */ + +export function useGetLoans>, TError = BadRequestResponse | UnauthorizedResponse | InternalServerErrorResponse>( + params?: GetLoansParams, options?: { query?:UseQueryOptions>, TError, TData>, fetch?: RequestInit} + + ): UseQueryResult & { queryKey: QueryKey } { + + const queryOptions = getGetLoansQueryOptions(params,options) + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +/** + * @summary 貸出履歴を更新する + */ +export type upsertLoansResponse = { + data: Loan[]; + status: number; +} + +export const getUpsertLoansUrl = () => { + + + return `https://localhost:8787/loans` +} + +export const upsertLoans = async (upsertLoansBodyItem: UpsertLoansBodyItem[], options?: RequestInit): Promise => { + + const res = await fetch(getUpsertLoansUrl(), + { + ...options, + method: 'PATCH', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify( + upsertLoansBodyItem,) + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getUpsertLoansMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: UpsertLoansBodyItem[]}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{data: UpsertLoansBodyItem[]}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {data: UpsertLoansBodyItem[]}> = (props) => { + const {data} = props ?? {}; + + return upsertLoans(data,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type UpsertLoansMutationResult = NonNullable>> + export type UpsertLoansMutationBody = UpsertLoansBodyItem[] + export type UpsertLoansMutationError = BadRequestResponse | UnauthorizedResponse | UpsertLoans404 | UpsertLoans409 | InternalServerErrorResponse + + /** + * @summary 貸出履歴を更新する + */ +export const useUpsertLoans = (options?: { mutation?:UseMutationOptions>, TError,{data: UpsertLoansBodyItem[]}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {data: UpsertLoansBodyItem[]}, + TContext + > => { + + const mutationOptions = getUpsertLoansMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * セッションIDをCookieに保存する + * @summary ログインする + */ +export type loginResponse = { + data: User; + status: number; +} + +export const getLoginUrl = () => { + + + return `https://localhost:8787/auth` +} + +export const login = async (loginBody: LoginBody, options?: RequestInit): Promise => { + + const res = await fetch(getLoginUrl(), + { + ...options, + method: 'POST', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify( + loginBody,) + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getLoginMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: LoginBody}, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,{data: LoginBody}, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, {data: LoginBody}> = (props) => { + const {data} = props ?? {}; + + return login(data,fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type LoginMutationResult = NonNullable>> + export type LoginMutationBody = LoginBody + export type LoginMutationError = BadRequestResponse | UnauthorizedResponse | NotFoundResponse | InternalServerErrorResponse + + /** + * @summary ログインする + */ +export const useLogin = (options?: { mutation?:UseMutationOptions>, TError,{data: LoginBody}, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + {data: LoginBody}, + TContext + > => { + + const mutationOptions = getLoginMutationOptions(options); + + return useMutation(mutationOptions); + } + +/** + * CookieからセッションIDを削除する + * @summary ログアウトする + */ +export type logoutResponse = { + data: void; + status: number; +} + +export const getLogoutUrl = () => { + + + return `https://localhost:8787/auth` +} + +export const logout = async ( options?: RequestInit): Promise => { + + const res = await fetch(getLogoutUrl(), + { + ...options, + method: 'DELETE' + + + } + + ) + const data = await res.json() + + return { status: res.status, data } +} + + + + +export const getLogoutMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,void, TContext>, fetch?: RequestInit} +): UseMutationOptions>, TError,void, TContext> => { +const {mutation: mutationOptions, fetch: fetchOptions} = options ?? {}; + + + + + const mutationFn: MutationFunction>, void> = () => { + + + return logout(fetchOptions) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type LogoutMutationResult = NonNullable>> + + export type LogoutMutationError = InternalServerErrorResponse + + /** + * @summary ログアウトする + */ +export const useLogout = (options?: { mutation?:UseMutationOptions>, TError,void, TContext>, fetch?: RequestInit} +): UseMutationResult< + Awaited>, + TError, + void, + TContext + > => { + + const mutationOptions = getLogoutMutationOptions(options); + + return useMutation(mutationOptions); + } + + + +export const getGetBooksResponseMock = (overrideResponse: Partial< GetBooks200 > = {}): GetBooks200 => ({books: Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => ({authors: Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => (faker.word.sample())), description: faker.word.sample(), id: faker.number.int({min: undefined, max: undefined}), isbn: faker.helpers.fromRegExp('^\d{10}(\d{3})?$'), publishedDate: faker.word.sample(), publisher: faker.word.sample(), stock: faker.number.int({min: undefined, max: undefined}), thumbnail: faker.helpers.arrayElement([faker.internet.url(), undefined]), title: faker.word.sample()})), totalBook: faker.number.int({min: undefined, max: undefined}), ...overrideResponse}) + +export const getCreateBookResponseMock = (): Book => ({"id":1,"title":"計算機プログラムの構造と解釈","authors":["Harold Abelson","Gerald Jay Sussman","Julie Sussman"],"publisher":"翔泳社","publishedDate":"2012-07-06","description":"言わずと知れた計算機科学の古典的名著","thumbnail":"http://books.google.com/books/content?id=LlH-oAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api","isbn":"9784798135984","stock":1}) + +export const getGetBookResponseMock = (): Book => ({"id":1,"title":"計算機プログラムの構造と解釈","authors":["Harold Abelson","Gerald Jay Sussman","Julie Sussman"],"publisher":"翔泳社","publishedDate":"2012-07-06","description":"言わずと知れた計算機科学の古典的名著","thumbnail":"http://books.google.com/books/content?id=LlH-oAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api","isbn":"9784798135984","stock":1}) + +export const getUpdateBookResponseMock = (): Book => ({"id":1,"title":"計算機プログラムの構造と解釈","authors":["Harold Abelson","Gerald Jay Sussman","Julie Sussman"],"publisher":"翔泳社","publishedDate":"2012-07-06","description":"言わずと知れた計算機科学の古典的名著","thumbnail":"http://books.google.com/books/content?id=LlH-oAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api","isbn":"9784798135984","stock":1}) + +export const getSearchBooksResponseMock = (): SearchBooks200 => ({"totalBook":30,"books":[{"title":"計算機プログラムの構造と解釈","authors":["Harold Abelson","Gerald Jay Sussman","Julie Sussman"],"publisher":"翔泳社","publishedDate":"2018-07-01","description":"言わずと知れた計算機科学の古典的名著","thumbnail":"http://books.google.com/books/content?id=LlH-oAEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api","isbn":"9784798135984"}]}) + +export const getGetUsersResponseMock = (overrideResponse: Partial< GetUsers200 > = {}): GetUsers200 => ({totalUser: faker.number.int({min: undefined, max: undefined}), users: Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => ({email: faker.internet.email(), id: faker.number.int({min: undefined, max: undefined}), name: faker.word.sample(), sessionToken: faker.helpers.arrayElement([faker.helpers.arrayElement([faker.word.sample(), null]), undefined])})), ...overrideResponse}) + +export const getCreateUserResponseMock = (): User => ({"id":1,"name":"比企谷八幡","email":"hikigaya@oregairu.com"}) + +export const getGetUserResponseMock = (): User => ({"id":1,"name":"比企谷八幡","email":"hikigaya@oregairu.com"}) + +export const getUpdateUserResponseMock = (): User => ({"id":1,"name":"比企谷八幡","email":"hikigaya@oregairu.com"}) + +export const getDeleteUserResponseMock = (): DeleteUser204 => ({"message":"No Content"}) + +export const getGetLoansResponseMock = (overrideResponse: Partial< GetLoans200 > = {}): GetLoans200 => ({loans: Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => ({books: faker.helpers.arrayElement([{authors: Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => (faker.word.sample())), description: faker.word.sample(), id: faker.number.int({min: undefined, max: undefined}), isbn: faker.helpers.fromRegExp('^\d{10}(\d{3})?$'), publishedDate: faker.word.sample(), publisher: faker.word.sample(), stock: faker.number.int({min: undefined, max: undefined}), thumbnail: faker.helpers.arrayElement([faker.internet.url(), undefined]), title: faker.word.sample()}, undefined]), loans: faker.helpers.arrayElement([{bookId: faker.number.int({min: undefined, max: undefined}), createdAt: faker.helpers.arrayElement([faker.number.int({min: undefined, max: undefined}), undefined]), updatedAt: faker.helpers.arrayElement([faker.number.int({min: undefined, max: undefined}), undefined]), userId: faker.number.int({min: undefined, max: undefined}), volume: faker.number.int({min: undefined, max: undefined})}, undefined]), users: faker.helpers.arrayElement([{email: faker.internet.email(), id: faker.number.int({min: undefined, max: undefined}), name: faker.word.sample(), sessionToken: faker.helpers.arrayElement([faker.helpers.arrayElement([faker.word.sample(), null]), undefined])}, undefined])})), totalLoan: faker.number.int({min: undefined, max: undefined}), ...overrideResponse}) + +export const getUpsertLoansResponseMock = (): Loan[] => (Array.from({ length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1).map(() => ({bookId: faker.number.int({min: undefined, max: undefined}), createdAt: faker.helpers.arrayElement([faker.number.int({min: undefined, max: undefined}), undefined]), updatedAt: faker.helpers.arrayElement([faker.number.int({min: undefined, max: undefined}), undefined]), userId: faker.number.int({min: undefined, max: undefined}), volume: faker.number.int({min: undefined, max: undefined})}))) + +export const getLoginResponseMock = (): User => ({"id":1,"name":"比企谷八幡","email":"hikigaya@oregairu.com"}) + + +export const getGetBooksMockHandler = (overrideResponse?: GetBooks200 | ((info: Parameters[1]>[0]) => Promise | GetBooks200)) => { + return http.get('*/books', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getGetBooksResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getCreateBookMockHandler = (overrideResponse?: Book | ((info: Parameters[1]>[0]) => Promise | Book)) => { + return http.post('*/books', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getCreateBookResponseMock()), + { status: 201, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getGetBookMockHandler = (overrideResponse?: Book | ((info: Parameters[1]>[0]) => Promise | Book)) => { + return http.get('*/books/:bookId', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getGetBookResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getUpdateBookMockHandler = (overrideResponse?: Book | ((info: Parameters[1]>[0]) => Promise | Book)) => { + return http.patch('*/books/:bookId', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getUpdateBookResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getDeleteBookMockHandler = (overrideResponse?: void | ((info: Parameters[1]>[0]) => Promise | void)) => { + return http.delete('*/books/:bookId', async (info) => { + if (typeof overrideResponse === 'function') {await overrideResponse(info); } + return new HttpResponse(null, + { status: 204, + + }) + }) +} + +export const getSearchBooksMockHandler = (overrideResponse?: SearchBooks200 | ((info: Parameters[1]>[0]) => Promise | SearchBooks200)) => { + return http.get('*/books/search', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getSearchBooksResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getGetUsersMockHandler = (overrideResponse?: GetUsers200 | ((info: Parameters[1]>[0]) => Promise | GetUsers200)) => { + return http.get('*/users', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getGetUsersResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getCreateUserMockHandler = (overrideResponse?: User | ((info: Parameters[1]>[0]) => Promise | User)) => { + return http.post('*/users', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getCreateUserResponseMock()), + { status: 201, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getGetUserMockHandler = (overrideResponse?: User | ((info: Parameters[1]>[0]) => Promise | User)) => { + return http.get('*/users/:userId', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getGetUserResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getUpdateUserMockHandler = (overrideResponse?: User | ((info: Parameters[1]>[0]) => Promise | User)) => { + return http.patch('*/users/:userId', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getUpdateUserResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getDeleteUserMockHandler = (overrideResponse?: DeleteUser204 | ((info: Parameters[1]>[0]) => Promise | DeleteUser204)) => { + return http.delete('*/users/:userId', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getDeleteUserResponseMock()), + { status: 204, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getGetLoansMockHandler = (overrideResponse?: GetLoans200 | ((info: Parameters[1]>[0]) => Promise | GetLoans200)) => { + return http.get('*/loans', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getGetLoansResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getUpsertLoansMockHandler = (overrideResponse?: Loan[] | ((info: Parameters[1]>[0]) => Promise | Loan[])) => { + return http.patch('*/loans', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getUpsertLoansResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getLoginMockHandler = (overrideResponse?: User | ((info: Parameters[1]>[0]) => Promise | User)) => { + return http.post('*/auth', async (info) => { + + return new HttpResponse(JSON.stringify(overrideResponse !== undefined + ? (typeof overrideResponse === "function" ? await overrideResponse(info) : overrideResponse) + : getLoginResponseMock()), + { status: 200, + headers: { 'Content-Type': 'application/json' } + }) + }) +} + +export const getLogoutMockHandler = (overrideResponse?: void | ((info: Parameters[1]>[0]) => Promise | void)) => { + return http.delete('*/auth', async (info) => { + if (typeof overrideResponse === 'function') {await overrideResponse(info); } + return new HttpResponse(null, + { status: 204, + + }) + }) +} +export const getKITCCLibraryAPIMock = () => [ + getGetBooksMockHandler(), + getCreateBookMockHandler(), + getGetBookMockHandler(), + getUpdateBookMockHandler(), + getDeleteBookMockHandler(), + getSearchBooksMockHandler(), + getGetUsersMockHandler(), + getCreateUserMockHandler(), + getGetUserMockHandler(), + getUpdateUserMockHandler(), + getDeleteUserMockHandler(), + getGetLoansMockHandler(), + getUpsertLoansMockHandler(), + getLoginMockHandler(), + getLogoutMockHandler() +] diff --git a/frontend/test/mocks/model/badRequestResponse.ts b/frontend/test/mocks/model/badRequestResponse.ts new file mode 100644 index 00000000..0f30bf3a --- /dev/null +++ b/frontend/test/mocks/model/badRequestResponse.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { Error } from './error'; + +/** + * リクエストの構文が誤っている + */ +export type BadRequestResponse = Error; diff --git a/frontend/test/mocks/model/book.ts b/frontend/test/mocks/model/book.ts new file mode 100644 index 00000000..e3b62292 --- /dev/null +++ b/frontend/test/mocks/model/book.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export interface Book { + authors: string[]; + description: string; + id: number; + /** @pattern ^\d{10}(\d{3})?$ */ + isbn: string; + publishedDate: string; + publisher: string; + stock: number; + thumbnail?: string; + title: string; +} diff --git a/frontend/test/mocks/model/createBookBody.ts b/frontend/test/mocks/model/createBookBody.ts new file mode 100644 index 00000000..fdfc83a6 --- /dev/null +++ b/frontend/test/mocks/model/createBookBody.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type CreateBookBody = { + authors: string[]; + description: string; + /** @pattern ^\d{10}(\d{3})?$ */ + isbn: string; + publishedDate: string; + publisher: string; + stock: number; + thumbnail?: string; + title: string; +}; diff --git a/frontend/test/mocks/model/createUserBody.ts b/frontend/test/mocks/model/createUserBody.ts new file mode 100644 index 00000000..5950ae69 --- /dev/null +++ b/frontend/test/mocks/model/createUserBody.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type CreateUserBody = { + email: string; + name: string; + /** + * @minLength 8 + * @pattern ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]+$ + */ + password: string; +}; diff --git a/frontend/test/mocks/model/deleteUser204.ts b/frontend/test/mocks/model/deleteUser204.ts new file mode 100644 index 00000000..f158a114 --- /dev/null +++ b/frontend/test/mocks/model/deleteUser204.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type DeleteUser204 = { + message?: string; +}; diff --git a/frontend/test/mocks/model/error.ts b/frontend/test/mocks/model/error.ts new file mode 100644 index 00000000..0f5ecba2 --- /dev/null +++ b/frontend/test/mocks/model/error.ts @@ -0,0 +1,10 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export interface Error { + message: string; +} diff --git a/frontend/test/mocks/model/getBooks200.ts b/frontend/test/mocks/model/getBooks200.ts new file mode 100644 index 00000000..92f8c588 --- /dev/null +++ b/frontend/test/mocks/model/getBooks200.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { Book } from './book'; + +export type GetBooks200 = { + books: Book[]; + /** 総書籍数 */ + totalBook: number; +}; diff --git a/frontend/test/mocks/model/getBooksParams.ts b/frontend/test/mocks/model/getBooksParams.ts new file mode 100644 index 00000000..63e1de73 --- /dev/null +++ b/frontend/test/mocks/model/getBooksParams.ts @@ -0,0 +1,39 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { GetBooksSort } from './getBooksSort'; + +export type GetBooksParams = { +/** + * ページ番号 + */ +page?: string; +/** + * 1ページあたりの表示数 + */ +limit?: string; +/** + * タイトル + */ +title?: string; +/** + * 著者 + */ +author?: string; +/** + * 出版社 + */ +publisher?: string; +/** + * ISBN + */ +isbn?: string; +/** + * ソート順 1: id昇順 2: id降順 3: 出版日昇順 4: 出版日降順 + + */ +sort?: GetBooksSort; +}; diff --git a/frontend/test/mocks/model/getBooksSort.ts b/frontend/test/mocks/model/getBooksSort.ts new file mode 100644 index 00000000..4743b90d --- /dev/null +++ b/frontend/test/mocks/model/getBooksSort.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type GetBooksSort = typeof GetBooksSort[keyof typeof GetBooksSort]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const GetBooksSort = { + NUMBER_0: '0', + NUMBER_1: '1', + NUMBER_2: '2', + NUMBER_3: '3', +} as const; diff --git a/frontend/test/mocks/model/getLoans200.ts b/frontend/test/mocks/model/getLoans200.ts new file mode 100644 index 00000000..ed54b083 --- /dev/null +++ b/frontend/test/mocks/model/getLoans200.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { GetLoans200LoansItem } from './getLoans200LoansItem'; + +export type GetLoans200 = { + loans: GetLoans200LoansItem[]; + totalLoan: number; +}; diff --git a/frontend/test/mocks/model/getLoans200LoansItem.ts b/frontend/test/mocks/model/getLoans200LoansItem.ts new file mode 100644 index 00000000..5cec375f --- /dev/null +++ b/frontend/test/mocks/model/getLoans200LoansItem.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { Book } from './book'; +import type { Loan } from './loan'; +import type { User } from './user'; + +export type GetLoans200LoansItem = { + books?: Book; + loans?: Loan; + users?: User; +}; diff --git a/frontend/test/mocks/model/getLoansParams.ts b/frontend/test/mocks/model/getLoansParams.ts new file mode 100644 index 00000000..275d5ecf --- /dev/null +++ b/frontend/test/mocks/model/getLoansParams.ts @@ -0,0 +1,30 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { GetLoansSort } from './getLoansSort'; + +export type GetLoansParams = { +/** + * 貸出履歴を取得するユーザーのID + */ +userId?: string; +/** + * 貸出履歴を取得する書籍のID + */ +bookId?: string; +/** + * ページ番号 + */ +page?: string; +/** + * 1ページあたりの表示数 + */ +limit?: string; +/** + * ソート順 + */ +sort?: GetLoansSort; +}; diff --git a/frontend/test/mocks/model/getLoansSort.ts b/frontend/test/mocks/model/getLoansSort.ts new file mode 100644 index 00000000..49279e3e --- /dev/null +++ b/frontend/test/mocks/model/getLoansSort.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type GetLoansSort = typeof GetLoansSort[keyof typeof GetLoansSort]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const GetLoansSort = { + NUMBER_0: '0', + NUMBER_1: '1', + NUMBER_2: '2', + NUMBER_3: '3', + NUMBER_4: '4', + NUMBER_5: '5', +} as const; diff --git a/frontend/test/mocks/model/getUsers200.ts b/frontend/test/mocks/model/getUsers200.ts new file mode 100644 index 00000000..12536ca1 --- /dev/null +++ b/frontend/test/mocks/model/getUsers200.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { User } from './user'; + +export type GetUsers200 = { + /** 総ユーザー数 */ + totalUser: number; + users: User[]; +}; diff --git a/frontend/test/mocks/model/getUsersParams.ts b/frontend/test/mocks/model/getUsersParams.ts new file mode 100644 index 00000000..b4c45f88 --- /dev/null +++ b/frontend/test/mocks/model/getUsersParams.ts @@ -0,0 +1,25 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type GetUsersParams = { +/** + * ページ番号 + */ +page?: string; +/** + * 1ページあたりの表示数 + */ +limit?: string; +/** + * ユーザー名 + */ +name?: string; +/** + * メールアドレス + */ +email?: string; +}; diff --git a/frontend/test/mocks/model/index.ts b/frontend/test/mocks/model/index.ts new file mode 100644 index 00000000..9aca2c56 --- /dev/null +++ b/frontend/test/mocks/model/index.ts @@ -0,0 +1,36 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export * from './badRequestResponse'; +export * from './book'; +export * from './createBookBody'; +export * from './createUserBody'; +export * from './deleteUser204'; +export * from './error'; +export * from './getBooks200'; +export * from './getBooksParams'; +export * from './getBooksSort'; +export * from './getLoans200'; +export * from './getLoans200LoansItem'; +export * from './getLoansParams'; +export * from './getLoansSort'; +export * from './getUsers200'; +export * from './getUsersParams'; +export * from './internalServerErrorResponse'; +export * from './loan'; +export * from './loginBody'; +export * from './notFoundResponse'; +export * from './searchBooks200'; +export * from './searchBooks200BooksItem'; +export * from './searchBooksParams'; +export * from './unauthorizedResponse'; +export * from './updateBookBody'; +export * from './updateUserBody'; +export * from './upsertLoans404'; +export * from './upsertLoans409'; +export * from './upsertLoansBodyItem'; +export * from './user'; \ No newline at end of file diff --git a/frontend/test/mocks/model/internalServerErrorResponse.ts b/frontend/test/mocks/model/internalServerErrorResponse.ts new file mode 100644 index 00000000..6ed2d9d0 --- /dev/null +++ b/frontend/test/mocks/model/internalServerErrorResponse.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { Error } from './error'; + +/** + * サーバ内部でエラーが発生した + */ +export type InternalServerErrorResponse = Error; diff --git a/frontend/test/mocks/model/loan.ts b/frontend/test/mocks/model/loan.ts new file mode 100644 index 00000000..31c120e3 --- /dev/null +++ b/frontend/test/mocks/model/loan.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export interface Loan { + bookId: number; + /** レコードが作成されたUNIX時間 */ + createdAt?: number; + /** レコードが最後に更新されたUNIX時間 */ + updatedAt?: number; + userId: number; + volume: number; +} diff --git a/frontend/test/mocks/model/loginBody.ts b/frontend/test/mocks/model/loginBody.ts new file mode 100644 index 00000000..99192f9c --- /dev/null +++ b/frontend/test/mocks/model/loginBody.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type LoginBody = { + email: string; + /** + * @minLength 8 + * @pattern ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]+$ + */ + password: string; +}; diff --git a/frontend/test/mocks/model/notFoundResponse.ts b/frontend/test/mocks/model/notFoundResponse.ts new file mode 100644 index 00000000..814ccb13 --- /dev/null +++ b/frontend/test/mocks/model/notFoundResponse.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { Error } from './error'; + +/** + * 指定されたリソースが存在しない + */ +export type NotFoundResponse = Error; diff --git a/frontend/test/mocks/model/searchBooks200.ts b/frontend/test/mocks/model/searchBooks200.ts new file mode 100644 index 00000000..a43b245b --- /dev/null +++ b/frontend/test/mocks/model/searchBooks200.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { SearchBooks200BooksItem } from './searchBooks200BooksItem'; + +export type SearchBooks200 = { + books: SearchBooks200BooksItem[]; + /** 総書籍数 */ + totalBook: number; +}; diff --git a/frontend/test/mocks/model/searchBooks200BooksItem.ts b/frontend/test/mocks/model/searchBooks200BooksItem.ts new file mode 100644 index 00000000..5b297ac3 --- /dev/null +++ b/frontend/test/mocks/model/searchBooks200BooksItem.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type SearchBooks200BooksItem = { + authors: string[]; + description?: string; + isbn?: string; + publishedDate?: string; + publisher?: string; + thumbnail?: string; + title: string; +}; diff --git a/frontend/test/mocks/model/searchBooksParams.ts b/frontend/test/mocks/model/searchBooksParams.ts new file mode 100644 index 00000000..b39d72b4 --- /dev/null +++ b/frontend/test/mocks/model/searchBooksParams.ts @@ -0,0 +1,37 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type SearchBooksParams = { +/** + * ページ番号 + */ +page?: string; +/** + * 1ページあたりの表示数 + */ +limit?: string; +/** + * 検索キーワード + */ +keyword?: string; +/** + * タイトル + */ +intitle?: string; +/** + * 著者 + */ +inauthor?: string; +/** + * 出版社 + */ +inpublisher?: string; +/** + * ISBN + */ +isbn?: string; +}; diff --git a/frontend/test/mocks/model/unauthorizedResponse.ts b/frontend/test/mocks/model/unauthorizedResponse.ts new file mode 100644 index 00000000..bbf5c47d --- /dev/null +++ b/frontend/test/mocks/model/unauthorizedResponse.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ +import type { Error } from './error'; + +/** + * 適切な認証情報を持たずにリソースにアクセスしようとした + */ +export type UnauthorizedResponse = Error; diff --git a/frontend/test/mocks/model/updateBookBody.ts b/frontend/test/mocks/model/updateBookBody.ts new file mode 100644 index 00000000..a3b0c67c --- /dev/null +++ b/frontend/test/mocks/model/updateBookBody.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type UpdateBookBody = { + authors?: string[]; + /** @pattern ^\d{10}(\d{3})?$ */ + isbn?: string; + publishedDate?: string; + publisher?: string; + stock?: number; + title?: string; +}; diff --git a/frontend/test/mocks/model/updateUserBody.ts b/frontend/test/mocks/model/updateUserBody.ts new file mode 100644 index 00000000..5c37f4bb --- /dev/null +++ b/frontend/test/mocks/model/updateUserBody.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type UpdateUserBody = { + email?: string; + name?: string; + /** + * @minLength 8 + * @pattern ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]+$ + */ + password?: string; +}; diff --git a/frontend/test/mocks/model/upsertLoans404.ts b/frontend/test/mocks/model/upsertLoans404.ts new file mode 100644 index 00000000..7a9b1e10 --- /dev/null +++ b/frontend/test/mocks/model/upsertLoans404.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type UpsertLoans404 = { + bookId: number; + userId: number; +}; diff --git a/frontend/test/mocks/model/upsertLoans409.ts b/frontend/test/mocks/model/upsertLoans409.ts new file mode 100644 index 00000000..422d16ad --- /dev/null +++ b/frontend/test/mocks/model/upsertLoans409.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +/** + * 貸出数が在庫数を超えているリクエスト + */ +export type UpsertLoans409 = { + bookId: number; + userId: number; + volume: number; +}; diff --git a/frontend/test/mocks/model/upsertLoansBodyItem.ts b/frontend/test/mocks/model/upsertLoansBodyItem.ts new file mode 100644 index 00000000..86f46edb --- /dev/null +++ b/frontend/test/mocks/model/upsertLoansBodyItem.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export type UpsertLoansBodyItem = { + bookId: number; + userId: number; + volume: number; +}; diff --git a/frontend/test/mocks/model/user.ts b/frontend/test/mocks/model/user.ts new file mode 100644 index 00000000..734e885f --- /dev/null +++ b/frontend/test/mocks/model/user.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.2.0 🍺 + * Do not edit manually. + * KITCC Library API + * OpenAPI spec version: 1.0.0 + */ + +export interface User { + email: string; + id: number; + name: string; + /** @nullable */ + sessionToken?: string | null; +} diff --git a/frontend/test/routes/auth.login.test.tsx b/frontend/test/routes/auth.login.test.tsx new file mode 100644 index 00000000..b848efc7 --- /dev/null +++ b/frontend/test/routes/auth.login.test.tsx @@ -0,0 +1,77 @@ +import { screen } from "@testing-library/react"; +import { renderWithWrapper } from "test/helpers/wrapper"; +import LoginPage from "~/routes/auth.login"; + +describe("Login page", () => { + it("should login successfully", async () => { + const { user } = renderWithWrapper(); + + // メールアドレスを入力 + const emailForm = screen.getByTestId("email-input"); + await user.type(emailForm, "user@example.com"); + expect(emailForm).toHaveValue("user@example.com"); + + // パスワードを入力 + const passwordForm = screen.getByTestId("password-input"); + await user.type(passwordForm, "passw0rd"); + expect(passwordForm).toHaveValue("passw0rd"); + + // ログインボタンをクリック + const submitButton = screen.getByRole("button", { name: "ログイン" }); + await user.click(submitButton); + + // ログイン成功の通知が表示される + const banner = await screen.findByText("ログインに成功しました"); + expect(banner).toBeInTheDocument(); + }); + + it("should fail to pass when email is invalid", async () => { + const { user } = renderWithWrapper(); + + // メールアドレスを入力 + const emailForm = screen.getByTestId("email-input"); + await user.type(emailForm, "user@invalid"); + + // ログインボタンをクリック + const submitButton = screen.getByRole("button", { name: "ログイン" }); + await user.click(submitButton); + + // エラーメッセージが表示される + const message = await screen.findByText("有効でないメールアドレスです"); + expect(message).toBeInTheDocument(); + }); + + test("should fail to pass when password length is less than 8", async () => { + const { user } = renderWithWrapper(); + + // パスワードを入力 + const passwordForm = screen.getByTestId("password-input"); + await user.type(passwordForm, "pass"); + + // ログインボタンをクリック + const submitButton = screen.getByRole("button", { name: "ログイン" }); + await user.click(submitButton); + + // エラーメッセージが表示される + // prettier-ignore + const message = await screen.findByText("パスワードは8文字以上で入力してください"); + expect(message).toBeInTheDocument(); + }); + + test("should fail to pass when password is not alphanumeric", async () => { + const { user } = renderWithWrapper(); + + // パスワードを入力 + const passwordForm = screen.getByTestId("password-input"); + await user.type(passwordForm, "password"); + + // ログインボタンをクリック + const submitButton = screen.getByRole("button", { name: "ログイン" }); + await user.click(submitButton); + + // エラーメッセージが表示される + // prettier-ignore + const message = await screen.findByText("パスワードにはアルファベットと数字を含めてください"); + expect(message).toBeInTheDocument(); + }); +}); diff --git a/frontend/test/setup.ts b/frontend/test/setup.ts new file mode 100644 index 00000000..4129d02e --- /dev/null +++ b/frontend/test/setup.ts @@ -0,0 +1,31 @@ +import "@testing-library/jest-dom/vitest"; +import { cleanup } from "@testing-library/react"; +import { setupServer } from "msw/node"; +import { handlers } from "./mocks/handlers"; + +vi.mock("@remix-run/react", () => { + const useNavigate = vi.fn(); + const form = vi + .fn() + .mockImplementation(({ children }: { children: React.ReactElement }) => { + return children; + }); + return { + useNavigate: useNavigate, + Form: form, + }; +}); + +const server = setupServer(...handlers); + +beforeAll(() => { + server.listen(); +}); + +afterEach(() => { + cleanup(); +}); + +afterAll(() => { + server.close(); +}); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 857ca9db..1e097232 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -5,14 +5,16 @@ "**/.server/**/*.ts", "**/.server/**/*.tsx", "**/.client/**/*.ts", - "**/.client/**/*.tsx" + "**/.client/**/*.tsx", + "./test/setup.ts" ], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], "types": [ "@remix-run/cloudflare", "vite/client", - "@cloudflare/workers-types/2023-07-01" + "@cloudflare/workers-types/2023-07-01", + "vitest/globals" ], "isolatedModules": true, "esModuleInterop": true, diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c9b6b18f..9287e2f4 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,3 +1,4 @@ +/// import { vitePlugin as remix, cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, @@ -8,13 +9,15 @@ import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [ remixCloudflareDevProxy(), - remix({ - future: { - v3_fetcherPersist: true, - v3_relativeSplatPath: true, - v3_throwAbortReason: true, - }, - }), + !process.env.VITEST + ? remix({ + future: { + v3_fetcherPersist: true, + v3_relativeSplatPath: true, + v3_throwAbortReason: true, + }, + }) + : null, tsconfigPaths(), ], server: { @@ -24,4 +27,15 @@ export default defineConfig({ cert: "./certs/cert.pem", }, }, + test: { + alias: { + "~/*": "./app/*", + }, + env: { + NODE_TLS_REJECT_UNAUTHORIZED: "0", + }, + environment: "happy-dom", + globals: true, + setupFiles: ["./test/setup.ts"], + }, }); diff --git a/orval.config.dev.js b/orval.config.dev.js index cf2c3778..037ac9be 100644 --- a/orval.config.dev.js +++ b/orval.config.dev.js @@ -7,7 +7,7 @@ module.exports = { client: 'react-query', httpClient: 'fetch', mode: 'split', - target: './frontend/orval/client.ts', + target: './frontend/client/client.ts', baseUrl: 'https://localhost:8787', override: { mutator: { @@ -17,4 +17,26 @@ module.exports = { }, }, }, + msw: { + input: { + target: './api/bundle.yml', + }, + output: { + baseUrl: 'https://localhost:8787', + client: 'react-query', + httpClient: 'fetch', + mock: { + type: 'msw', + delay: false, + useExamples: true, + }, + mode: 'single', + target: './frontend/test/mocks/mock.ts', + schemas: './frontend/test/mocks/model', + override: { + // なぜかタイトルが上書きされない + title: 'TestAPI' + } + }, + }, }; diff --git a/orval.config.js b/orval.config.js index 440aaed1..6a1fcb86 100644 --- a/orval.config.js +++ b/orval.config.js @@ -8,7 +8,7 @@ module.exports = { client: 'react-query', httpClient: 'fetch', mode: 'split', - target: './frontend/orval/client.ts', + target: './frontend/client/client.ts', override: { mutator: { path: './frontend/orval/mutator.ts',