├── .github
└── workflows
│ └── codecov.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── mockServiceWorker.js
└── robots.txt
├── screenshot.png
├── src
├── App.test.tsx
├── App.tsx
├── Layouts
│ ├── AppShell.tsx
│ └── NavLink.tsx
├── components
│ └── Breadcrumbs.tsx
├── constants
│ └── url.ts
├── helpers
│ └── fetcher.ts
├── hooks
│ ├── useFilmData.ts
│ └── usePeopleData.ts
├── index.tsx
├── mocks
│ ├── handlers.ts
│ ├── response
│ │ ├── films.ts
│ │ └── people.ts
│ └── server.ts
├── pages
│ ├── Films.tsx
│ ├── People.tsx
│ └── __tests__
│ │ ├── Films.test.tsx
│ │ └── People.test.tsx
├── react-app-env.d.ts
└── setupTests.ts
└── tsconfig.json
/.github/workflows/codecov.yml:
--------------------------------------------------------------------------------
1 | name: Code Coverage for Codecov
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 | paths-ignore:
9 | - '**.md'
10 |
11 | jobs:
12 | codecov:
13 | name: Codecov
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout repository
17 | uses: actions/checkout@v3
18 |
19 | - name: Setup node
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: '14'
23 | cache: 'npm'
24 |
25 | - uses: actions/cache@v3
26 | with:
27 | path: ~/.npm
28 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
29 | restore-keys: |
30 | ${{ runner.os }}-node-
31 |
32 | - name: Install dependencies
33 | run: npm install
34 |
35 | - name: Run test with coverage
36 | run: npm test -- --coverage
37 |
38 | - uses: codecov/codecov-action@v2
39 | with:
40 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
41 | file: ./coverage/clover.xml
42 | flags: unittests # optional
43 | name: codecov-umbrella # optional
44 | fail_ci_if_error: true # optional (default = false)
45 | verbose: true # optional (default = false)
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Irfan Maulana
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🧪 React Testing Workshop
2 |
3 | [](https://codecov.io/gh/mazipan/react-testing-workshop)
4 |
5 | Code sample for showcasing basic React Testing Library
6 |
7 | 
8 |
9 | ## Available Scripts
10 |
11 | In the project directory, you can run:
12 |
13 | ### `npm start`
14 |
15 | Runs the app in the development mode.\
16 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
17 |
18 | ### `npm test`
19 |
20 | Launches the test runner in the interactive watch mode.\
21 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
22 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.1.1",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.4.1",
10 | "@types/node": "^16.11.27",
11 | "@types/react": "^18.0.5",
12 | "@types/react-dom": "^18.0.1",
13 | "@types/react-router-dom": "^5.3.3",
14 | "msw": "^0.39.2"
15 | },
16 | "dependencies": {
17 | "@mantine/core": "^4.1.3",
18 | "@mantine/hooks": "^4.1.3",
19 | "husky": "^7.0.4",
20 | "lint-staged": "^12.3.8",
21 | "prettier": "^2.6.2",
22 | "react": "^18.0.0",
23 | "react-dom": "^18.0.0",
24 | "react-router-dom": "^6.3.0",
25 | "react-scripts": "5.0.1",
26 | "swr": "^1.3.0",
27 | "tabler-icons-react": "^1.45.0",
28 | "typescript": "^4.6.3",
29 | "web-vitals": "^2.1.4"
30 | },
31 | "scripts": {
32 | "dev": "npm run start",
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "test": "react-scripts test",
36 | "eject": "react-scripts eject"
37 | },
38 | "husky": {
39 | "hooks": {
40 | "pre-commit": "lint-staged"
41 | }
42 | },
43 | "lint-staged": {
44 | "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
45 | "prettier --write"
46 | ]
47 | },
48 | "eslintConfig": {
49 | "extends": [
50 | "react-app",
51 | "react-app/jest"
52 | ]
53 | },
54 | "browserslist": {
55 | "production": [
56 | ">0.2%",
57 | "not dead",
58 | "not op_mini all"
59 | ],
60 | "development": [
61 | "last 1 chrome version",
62 | "last 1 firefox version",
63 | "last 1 safari version"
64 | ]
65 | },
66 | "msw": {
67 | "workerDirectory": "public"
68 | }
69 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mazipan/react-testing-workshop/eeb062c3c708a1b9953d0c45e972ff1e0f26f712/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | 🧪 React Testing Workshop
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mazipan/react-testing-workshop/eeb062c3c708a1b9953d0c45e972ff1e0f26f712/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mazipan/react-testing-workshop/eeb062c3c708a1b9953d0c45e972ff1e0f26f712/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/mockServiceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* tslint:disable */
3 |
4 | /**
5 | * Mock Service Worker (0.39.2).
6 | * @see https://github.com/mswjs/msw
7 | * - Please do NOT modify this file.
8 | * - Please do NOT serve this file on production.
9 | */
10 |
11 | const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929'
12 | const bypassHeaderName = 'x-msw-bypass'
13 | const activeClientIds = new Set()
14 |
15 | self.addEventListener('install', function () {
16 | return self.skipWaiting()
17 | })
18 |
19 | self.addEventListener('activate', async function (event) {
20 | return self.clients.claim()
21 | })
22 |
23 | self.addEventListener('message', async function (event) {
24 | const clientId = event.source.id
25 |
26 | if (!clientId || !self.clients) {
27 | return
28 | }
29 |
30 | const client = await self.clients.get(clientId)
31 |
32 | if (!client) {
33 | return
34 | }
35 |
36 | const allClients = await self.clients.matchAll()
37 |
38 | switch (event.data) {
39 | case 'KEEPALIVE_REQUEST': {
40 | sendToClient(client, {
41 | type: 'KEEPALIVE_RESPONSE',
42 | })
43 | break
44 | }
45 |
46 | case 'INTEGRITY_CHECK_REQUEST': {
47 | sendToClient(client, {
48 | type: 'INTEGRITY_CHECK_RESPONSE',
49 | payload: INTEGRITY_CHECKSUM,
50 | })
51 | break
52 | }
53 |
54 | case 'MOCK_ACTIVATE': {
55 | activeClientIds.add(clientId)
56 |
57 | sendToClient(client, {
58 | type: 'MOCKING_ENABLED',
59 | payload: true,
60 | })
61 | break
62 | }
63 |
64 | case 'MOCK_DEACTIVATE': {
65 | activeClientIds.delete(clientId)
66 | break
67 | }
68 |
69 | case 'CLIENT_CLOSED': {
70 | activeClientIds.delete(clientId)
71 |
72 | const remainingClients = allClients.filter((client) => {
73 | return client.id !== clientId
74 | })
75 |
76 | // Unregister itself when there are no more clients
77 | if (remainingClients.length === 0) {
78 | self.registration.unregister()
79 | }
80 |
81 | break
82 | }
83 | }
84 | })
85 |
86 | // Resolve the "main" client for the given event.
87 | // Client that issues a request doesn't necessarily equal the client
88 | // that registered the worker. It's with the latter the worker should
89 | // communicate with during the response resolving phase.
90 | async function resolveMainClient(event) {
91 | const client = await self.clients.get(event.clientId)
92 |
93 | if (client.frameType === 'top-level') {
94 | return client
95 | }
96 |
97 | const allClients = await self.clients.matchAll()
98 |
99 | return allClients
100 | .filter((client) => {
101 | // Get only those clients that are currently visible.
102 | return client.visibilityState === 'visible'
103 | })
104 | .find((client) => {
105 | // Find the client ID that's recorded in the
106 | // set of clients that have registered the worker.
107 | return activeClientIds.has(client.id)
108 | })
109 | }
110 |
111 | async function handleRequest(event, requestId) {
112 | const client = await resolveMainClient(event)
113 | const response = await getResponse(event, client, requestId)
114 |
115 | // Send back the response clone for the "response:*" life-cycle events.
116 | // Ensure MSW is active and ready to handle the message, otherwise
117 | // this message will pend indefinitely.
118 | if (client && activeClientIds.has(client.id)) {
119 | ;(async function () {
120 | const clonedResponse = response.clone()
121 | sendToClient(client, {
122 | type: 'RESPONSE',
123 | payload: {
124 | requestId,
125 | type: clonedResponse.type,
126 | ok: clonedResponse.ok,
127 | status: clonedResponse.status,
128 | statusText: clonedResponse.statusText,
129 | body:
130 | clonedResponse.body === null ? null : await clonedResponse.text(),
131 | headers: serializeHeaders(clonedResponse.headers),
132 | redirected: clonedResponse.redirected,
133 | },
134 | })
135 | })()
136 | }
137 |
138 | return response
139 | }
140 |
141 | async function getResponse(event, client, requestId) {
142 | const { request } = event
143 | const requestClone = request.clone()
144 | const getOriginalResponse = () => fetch(requestClone)
145 |
146 | // Bypass mocking when the request client is not active.
147 | if (!client) {
148 | return getOriginalResponse()
149 | }
150 |
151 | // Bypass initial page load requests (i.e. static assets).
152 | // The absence of the immediate/parent client in the map of the active clients
153 | // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
154 | // and is not ready to handle requests.
155 | if (!activeClientIds.has(client.id)) {
156 | return await getOriginalResponse()
157 | }
158 |
159 | // Bypass requests with the explicit bypass header
160 | if (requestClone.headers.get(bypassHeaderName) === 'true') {
161 | const cleanRequestHeaders = serializeHeaders(requestClone.headers)
162 |
163 | // Remove the bypass header to comply with the CORS preflight check.
164 | delete cleanRequestHeaders[bypassHeaderName]
165 |
166 | const originalRequest = new Request(requestClone, {
167 | headers: new Headers(cleanRequestHeaders),
168 | })
169 |
170 | return fetch(originalRequest)
171 | }
172 |
173 | // Send the request to the client-side MSW.
174 | const reqHeaders = serializeHeaders(request.headers)
175 | const body = await request.text()
176 |
177 | const clientMessage = await sendToClient(client, {
178 | type: 'REQUEST',
179 | payload: {
180 | id: requestId,
181 | url: request.url,
182 | method: request.method,
183 | headers: reqHeaders,
184 | cache: request.cache,
185 | mode: request.mode,
186 | credentials: request.credentials,
187 | destination: request.destination,
188 | integrity: request.integrity,
189 | redirect: request.redirect,
190 | referrer: request.referrer,
191 | referrerPolicy: request.referrerPolicy,
192 | body,
193 | bodyUsed: request.bodyUsed,
194 | keepalive: request.keepalive,
195 | },
196 | })
197 |
198 | switch (clientMessage.type) {
199 | case 'MOCK_SUCCESS': {
200 | return delayPromise(
201 | () => respondWithMock(clientMessage),
202 | clientMessage.payload.delay,
203 | )
204 | }
205 |
206 | case 'MOCK_NOT_FOUND': {
207 | return getOriginalResponse()
208 | }
209 |
210 | case 'NETWORK_ERROR': {
211 | const { name, message } = clientMessage.payload
212 | const networkError = new Error(message)
213 | networkError.name = name
214 |
215 | // Rejecting a request Promise emulates a network error.
216 | throw networkError
217 | }
218 |
219 | case 'INTERNAL_ERROR': {
220 | const parsedBody = JSON.parse(clientMessage.payload.body)
221 |
222 | console.error(
223 | `\
224 | [MSW] Uncaught exception in the request handler for "%s %s":
225 |
226 | ${parsedBody.location}
227 |
228 | This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
229 | `,
230 | request.method,
231 | request.url,
232 | )
233 |
234 | return respondWithMock(clientMessage)
235 | }
236 | }
237 |
238 | return getOriginalResponse()
239 | }
240 |
241 | self.addEventListener('fetch', function (event) {
242 | const { request } = event
243 | const accept = request.headers.get('accept') || ''
244 |
245 | // Bypass server-sent events.
246 | if (accept.includes('text/event-stream')) {
247 | return
248 | }
249 |
250 | // Bypass navigation requests.
251 | if (request.mode === 'navigate') {
252 | return
253 | }
254 |
255 | // Opening the DevTools triggers the "only-if-cached" request
256 | // that cannot be handled by the worker. Bypass such requests.
257 | if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
258 | return
259 | }
260 |
261 | // Bypass all requests when there are no active clients.
262 | // Prevents the self-unregistered worked from handling requests
263 | // after it's been deleted (still remains active until the next reload).
264 | if (activeClientIds.size === 0) {
265 | return
266 | }
267 |
268 | const requestId = uuidv4()
269 |
270 | return event.respondWith(
271 | handleRequest(event, requestId).catch((error) => {
272 | if (error.name === 'NetworkError') {
273 | console.warn(
274 | '[MSW] Successfully emulated a network error for the "%s %s" request.',
275 | request.method,
276 | request.url,
277 | )
278 | return
279 | }
280 |
281 | // At this point, any exception indicates an issue with the original request/response.
282 | console.error(
283 | `\
284 | [MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
285 | request.method,
286 | request.url,
287 | `${error.name}: ${error.message}`,
288 | )
289 | }),
290 | )
291 | })
292 |
293 | function serializeHeaders(headers) {
294 | const reqHeaders = {}
295 | headers.forEach((value, name) => {
296 | reqHeaders[name] = reqHeaders[name]
297 | ? [].concat(reqHeaders[name]).concat(value)
298 | : value
299 | })
300 | return reqHeaders
301 | }
302 |
303 | function sendToClient(client, message) {
304 | return new Promise((resolve, reject) => {
305 | const channel = new MessageChannel()
306 |
307 | channel.port1.onmessage = (event) => {
308 | if (event.data && event.data.error) {
309 | return reject(event.data.error)
310 | }
311 |
312 | resolve(event.data)
313 | }
314 |
315 | client.postMessage(JSON.stringify(message), [channel.port2])
316 | })
317 | }
318 |
319 | function delayPromise(cb, duration) {
320 | return new Promise((resolve) => {
321 | setTimeout(() => resolve(cb()), duration)
322 | })
323 | }
324 |
325 | function respondWithMock(clientMessage) {
326 | return new Response(clientMessage.payload.body, {
327 | ...clientMessage.payload,
328 | headers: clientMessage.payload.headers,
329 | })
330 | }
331 |
332 | function uuidv4() {
333 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
334 | const r = (Math.random() * 16) | 0
335 | const v = c == 'x' ? r : (r & 0x3) | 0x8
336 | return v.toString(16)
337 | })
338 | }
339 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mazipan/react-testing-workshop/eeb062c3c708a1b9953d0c45e972ff1e0f26f712/screenshot.png
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import userEvent from '@testing-library/user-event'
5 |
6 | test('Should render header link', async () => {
7 | render();
8 | const linkElement = screen.getByText('🧪 React Testing Workshop');
9 | expect(linkElement).toBeInTheDocument();
10 |
11 | // Check navbar Films + Breadcrumbs
12 | const navbarFilms = await screen.findAllByText('Films');
13 | expect(navbarFilms).toHaveLength(2);
14 |
15 | // Expect first item of films are visible
16 | expect(await screen.findByText('Castle in the Sky')).toBeVisible();
17 |
18 | // Check navbar People
19 | const navbarPeople = await screen.findByText('People');
20 | expect(navbarPeople).toBeInTheDocument();
21 |
22 | userEvent.click(navbarPeople);
23 |
24 | // Expect first item of people are visible
25 | expect(await screen.findByText('Haku')).toBeVisible();
26 | });
27 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import AppShell from './Layouts/AppShell';
2 | import { MantineProvider } from '@mantine/core';
3 |
4 | import {
5 | Routes,
6 | Route
7 | } from "react-router-dom";
8 |
9 | import Films from './pages/Films';
10 | import People from './pages/People';
11 |
12 | function App() {
13 | return (
14 |
15 |
16 | }>
17 | } />
18 | } />
19 | } />
20 |
21 |
22 |
26 | There's nothing here!
27 |
28 | }
29 | />
30 |
31 |
32 | );
33 | }
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/src/Layouts/AppShell.tsx:
--------------------------------------------------------------------------------
1 | import { AppShell, Navbar, Header, MediaQuery, Burger, Text, useMantineTheme, Footer, Center, Button, Group } from '@mantine/core';
2 | import { useState } from 'react';
3 | import { Link, Outlet } from 'react-router-dom';
4 |
5 | import { Movie, Users, BrandGithub } from 'tabler-icons-react';
6 |
7 |
8 | import NavLink from './NavLink';
9 |
10 | function AppShellLayout() {
11 | const theme = useMantineTheme();
12 | const [opened, setOpened] = useState(false);
13 |
14 | return (
15 |
26 |
27 | } href="/films" />
28 | } href="/people" />
29 |
30 |
31 | }
32 | header={
33 |
34 |
35 |
36 | setOpened((o) => !o)}
39 | size="sm"
40 | color={theme.colors.gray[6]}
41 | mr="xl"
42 | />
43 |
44 |
45 |
48 |
49 |
52 |
53 |
54 |
55 | }
56 | footer={
57 |
62 | }
63 | >
64 |
65 |
66 | );
67 | }
68 |
69 | export default AppShellLayout;
--------------------------------------------------------------------------------
/src/Layouts/NavLink.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeIcon, Text, UnstyledButton, Group } from '@mantine/core';
2 | import { ReactElement } from 'react';
3 | import { Link } from 'react-router-dom';
4 |
5 | interface NavLinkProps {
6 | label: string;
7 | href: string;
8 | icon: ReactElement;
9 | color: string;
10 | }
11 |
12 | function NavLink({ label, icon, color, href }: NavLinkProps) {
13 | return (
14 | component={Link} to={href}
15 | sx={(theme) => ({
16 | display: 'block',
17 | width: '100%',
18 | padding: theme.spacing.xs,
19 | borderRadius: theme.radius.sm,
20 | color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
21 |
22 | '&:hover': {
23 | backgroundColor:
24 | theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
25 | },
26 | })}
27 | >
28 |
29 |
30 | {icon}
31 |
32 |
33 | {label}
34 |
35 |
36 | );
37 | }
38 |
39 | export default NavLink;
--------------------------------------------------------------------------------
/src/components/Breadcrumbs.tsx:
--------------------------------------------------------------------------------
1 | import { Breadcrumbs as BreadcrumbsMantine, Anchor } from '@mantine/core';
2 |
3 | interface BreadcrumbProps {
4 | title: string;
5 | href: string;
6 | };
7 |
8 | const HOME_LINK = { title: 'Home', href: '/' };
9 |
10 | const renderItem = (items: BreadcrumbProps[]) => {
11 | return items.map((item) => (
12 |
13 | {item.title}
14 |
15 | ))
16 | }
17 |
18 |
19 | export default function Breadcrumbs({ items }: { items?: BreadcrumbProps[] }) {
20 | const itemsWithHome = items ? [HOME_LINK, ...items] : [HOME_LINK];
21 |
22 | return (
23 | <>
24 | {renderItem(itemsWithHome)}
25 | >
26 | );
27 | }
--------------------------------------------------------------------------------
/src/constants/url.ts:
--------------------------------------------------------------------------------
1 | export const BASE_API_URL = 'https://ghibliapi.herokuapp.com'
2 | export const API_URL_FILMS = '/films'
3 | export const API_URL_PEOPLE = '/people'
--------------------------------------------------------------------------------
/src/helpers/fetcher.ts:
--------------------------------------------------------------------------------
1 | export const defaultFetchOption = {
2 | headers: new Headers({ 'Content-Type': 'application/json' })
3 | }
4 |
5 | export const defaultGetOption = {
6 | method: 'GET',
7 | ...defaultFetchOption
8 | }
9 |
10 | export const fetcher = (url: string, opts: any) =>
11 | fetch(url, {
12 | ...defaultGetOption,
13 | ...opts
14 | }).then((r) => r.json())
--------------------------------------------------------------------------------
/src/hooks/useFilmData.ts:
--------------------------------------------------------------------------------
1 | import useSWR from 'swr'
2 |
3 | import { API_URL_FILMS, BASE_API_URL } from '../constants/url'
4 | import { fetcher } from '../helpers/fetcher'
5 |
6 | export interface Film {
7 | id: string
8 | title: string
9 | original_title: string
10 | original_title_romanised: string
11 | image: string
12 | movie_banner: string
13 | description: string
14 | director: string
15 | producer: string
16 | release_date: string
17 | running_time: string
18 | rt_score: string
19 | people: string[]
20 | species: string[]
21 | locations: string[]
22 | vehicles: string[]
23 | url: string
24 | }
25 |
26 | const useFilmData = () => {
27 | const { data, error } = useSWR(`${BASE_API_URL}${API_URL_FILMS}`, fetcher)
28 | const isLoading = !error && !data
29 |
30 | return {
31 | data: data,
32 | isLoading,
33 | isError: !isLoading && Boolean(error)
34 | }
35 | }
36 |
37 | export default useFilmData
38 |
--------------------------------------------------------------------------------
/src/hooks/usePeopleData.ts:
--------------------------------------------------------------------------------
1 | import useSWR from 'swr'
2 |
3 | import { API_URL_PEOPLE, BASE_API_URL } from '../constants/url'
4 | import { fetcher } from '../helpers/fetcher'
5 |
6 | export interface Person {
7 | id: string
8 | name: string
9 | gender?: string
10 | age: string
11 | eye_color: string
12 | hair_color: string
13 | films: string[]
14 | species: string
15 | url: string
16 | gander?: string
17 | }
18 |
19 |
20 | const usePeopleData = () => {
21 | const { data, error } = useSWR(`${BASE_API_URL}${API_URL_PEOPLE}`, fetcher)
22 | const isLoading = !error && !data
23 |
24 | return {
25 | data: data,
26 | isLoading,
27 | isError: !isLoading && Boolean(error)
28 | }
29 | }
30 |
31 | export default usePeopleData
32 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom/client';
2 | import { BrowserRouter } from "react-router-dom";
3 |
4 | import App from './App';
5 |
6 | const root = ReactDOM.createRoot(
7 | document.getElementById('root') as HTMLElement
8 | );
9 |
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/src/mocks/handlers.ts:
--------------------------------------------------------------------------------
1 | // src/mocks/handlers.js
2 | import { rest } from 'msw';
3 | import { API_URL_FILMS, API_URL_PEOPLE, BASE_API_URL } from '../constants/url';
4 |
5 | import MockFilmResponse from './response/films';
6 | import MockPeopleResponse from './response/people';
7 |
8 | export const handlers = [
9 | rest.get(`${BASE_API_URL}${API_URL_FILMS}`, (req, res, ctx) => {
10 | console.debug(`[MSW]: mock ${BASE_API_URL}${API_URL_FILMS}`)
11 | return res(ctx.json(MockFilmResponse))
12 | }),
13 | rest.get(`${BASE_API_URL}${API_URL_PEOPLE}`, (req, res, ctx) => {
14 | console.debug(`[MSW]: mock ${BASE_API_URL}${API_URL_PEOPLE}`)
15 | return res(ctx.json(MockPeopleResponse))
16 | }),
17 | ]
--------------------------------------------------------------------------------
/src/mocks/response/films.ts:
--------------------------------------------------------------------------------
1 | const MockFilmResponse = [
2 | {
3 | "id": "2baf70d1-42bb-4437-b551-e5fed5a87abe",
4 | "title": "Castle in the Sky",
5 | "original_title": "天空の城ラピュタ",
6 | "original_title_romanised": "Tenkū no shiro Rapyuta",
7 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/npOnzAbLh6VOIu3naU5QaEcTepo.jpg",
8 | "movie_banner": "https://image.tmdb.org/t/p/w533_and_h300_bestv2/3cyjYtLWCBE1uvWINHFsFnE8LUK.jpg",
9 | "description": "The orphan Sheeta inherited a mysterious crystal that links her to the mythical sky-kingdom of Laputa. With the help of resourceful Pazu and a rollicking band of sky pirates, she makes her way to the ruins of the once-great civilization. Sheeta and Pazu must outwit the evil Muska, who plans to use Laputa's science to make himself ruler of the world.",
10 | "director": "Hayao Miyazaki",
11 | "producer": "Isao Takahata",
12 | "release_date": "1986",
13 | "running_time": "124",
14 | "rt_score": "95",
15 | "people": [
16 | "https://ghibliapi.herokuapp.com/people/598f7048-74ff-41e0-92ef-87dc1ad980a9",
17 | "https://ghibliapi.herokuapp.com/people/fe93adf2-2f3a-4ec4-9f68-5422f1b87c01",
18 | "https://ghibliapi.herokuapp.com/people/3bc0b41e-3569-4d20-ae73-2da329bf0786",
19 | "https://ghibliapi.herokuapp.com/people/40c005ce-3725-4f15-8409-3e1b1b14b583",
20 | "https://ghibliapi.herokuapp.com/people/5c83c12a-62d5-4e92-8672-33ac76ae1fa0",
21 | "https://ghibliapi.herokuapp.com/people/e08880d0-6938-44f3-b179-81947e7873fc",
22 | "https://ghibliapi.herokuapp.com/people/2a1dad70-802a-459d-8cc2-4ebd8821248b"
23 | ],
24 | "species": [
25 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
26 | ],
27 | "locations": [
28 | "https://ghibliapi.herokuapp.com/locations/"
29 | ],
30 | "vehicles": [
31 | "https://ghibliapi.herokuapp.com/vehicles/4e09b023-f650-4747-9ab9-eacf14540cfb"
32 | ],
33 | "url": "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
34 | },
35 | {
36 | "id": "12cfb892-aac0-4c5b-94af-521852e46d6a",
37 | "title": "Grave of the Fireflies",
38 | "original_title": "火垂るの墓",
39 | "original_title_romanised": "Hotaru no haka",
40 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/qG3RYlIVpTYclR9TYIsy8p7m7AT.jpg",
41 | "movie_banner": "https://image.tmdb.org/t/p/original/vkZSd0Lp8iCVBGpFH9L7LzLusjS.jpg",
42 | "description": "In the latter part of World War II, a boy and his sister, orphaned when their mother is killed in the firebombing of Tokyo, are left to survive on their own in what remains of civilian life in Japan. The plot follows this boy and his sister as they do their best to survive in the Japanese countryside, battling hunger, prejudice, and pride in their own quiet, personal battle.",
43 | "director": "Isao Takahata",
44 | "producer": "Toru Hara",
45 | "release_date": "1988",
46 | "running_time": "89",
47 | "rt_score": "97",
48 | "people": [
49 | "https://ghibliapi.herokuapp.com/people/"
50 | ],
51 | "species": [
52 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
53 | ],
54 | "locations": [
55 | "https://ghibliapi.herokuapp.com/locations/"
56 | ],
57 | "vehicles": [
58 | "https://ghibliapi.herokuapp.com/vehicles/"
59 | ],
60 | "url": "https://ghibliapi.herokuapp.com/films/12cfb892-aac0-4c5b-94af-521852e46d6a"
61 | },
62 | {
63 | "id": "58611129-2dbc-4a81-a72f-77ddfc1b1b49",
64 | "title": "My Neighbor Totoro",
65 | "original_title": "となりのトトロ",
66 | "original_title_romanised": "Tonari no Totoro",
67 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/rtGDOeG9LzoerkDGZF9dnVeLppL.jpg",
68 | "movie_banner": "https://image.tmdb.org/t/p/original/etqr6fOOCXQOgwrQXaKwenTSuzx.jpg",
69 | "description": "Two sisters move to the country with their father in order to be closer to their hospitalized mother, and discover the surrounding trees are inhabited by Totoros, magical spirits of the forest. When the youngest runs away from home, the older sister seeks help from the spirits to find her.",
70 | "director": "Hayao Miyazaki",
71 | "producer": "Hayao Miyazaki",
72 | "release_date": "1988",
73 | "running_time": "86",
74 | "rt_score": "93",
75 | "people": [
76 | "https://ghibliapi.herokuapp.com/people/986faac6-67e3-4fb8-a9ee-bad077c2e7fe",
77 | "https://ghibliapi.herokuapp.com/people/d5df3c04-f355-4038-833c-83bd3502b6b9",
78 | "https://ghibliapi.herokuapp.com/people/3031caa8-eb1a-41c6-ab93-dd091b541e11",
79 | "https://ghibliapi.herokuapp.com/people/87b68b97-3774-495b-bf80-495a5f3e672d",
80 | "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f",
81 | "https://ghibliapi.herokuapp.com/people/591524bc-04fe-4e60-8d61-2425e42ffb2a",
82 | "https://ghibliapi.herokuapp.com/people/c491755a-407d-4d6e-b58a-240ec78b5061",
83 | "https://ghibliapi.herokuapp.com/people/f467e18e-3694-409f-bdb3-be891ade1106",
84 | "https://ghibliapi.herokuapp.com/people/08ffbce4-7f94-476a-95bc-76d3c3969c19",
85 | "https://ghibliapi.herokuapp.com/people/0f8ef701-b4c7-4f15-bd15-368c7fe38d0a"
86 | ],
87 | "species": [
88 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
89 | "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
90 | "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5"
91 | ],
92 | "locations": [
93 | "https://ghibliapi.herokuapp.com/locations/"
94 | ],
95 | "vehicles": [
96 | "https://ghibliapi.herokuapp.com/vehicles/"
97 | ],
98 | "url": "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
99 | },
100 | {
101 | "id": "ea660b10-85c4-4ae3-8a5f-41cea3648e3e",
102 | "title": "Kiki's Delivery Service",
103 | "original_title": "魔女の宅急便",
104 | "original_title_romanised": "Majo no takkyūbin",
105 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/7nO5DUMnGUuXrA4r2h6ESOKQRrx.jpg",
106 | "movie_banner": "https://image.tmdb.org/t/p/original/h5pAEVma835u8xoE60kmLVopLct.jpg",
107 | "description": "A young witch, on her mandatory year of independent life, finds fitting into a new community difficult while she supports herself by running an air courier service.",
108 | "director": "Hayao Miyazaki",
109 | "producer": "Hayao Miyazaki",
110 | "release_date": "1989",
111 | "running_time": "102",
112 | "rt_score": "96",
113 | "people": [
114 | "https://ghibliapi.herokuapp.com/people/2409052a-9029-4e8d-bfaf-70fd82c8e48d",
115 | "https://ghibliapi.herokuapp.com/people/7151abc6-1a9e-4e6a-9711-ddb50ea572ec",
116 | "https://ghibliapi.herokuapp.com/people/1c1a8054-3a34-4185-bfcf-e8011506f09a",
117 | "https://ghibliapi.herokuapp.com/people/bc838920-7849-43ea-bfb8-7d5e98dc20b6",
118 | "https://ghibliapi.herokuapp.com/people/33f5fea9-c21b-490b-90e0-c4051c372826",
119 | "https://ghibliapi.herokuapp.com/people/d1de1c0e-3fcd-4cef-94eb-bb95cc2314aa"
120 | ],
121 | "species": [
122 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
123 | "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3"
124 | ],
125 | "locations": [
126 | "https://ghibliapi.herokuapp.com/locations/"
127 | ],
128 | "vehicles": [
129 | "https://ghibliapi.herokuapp.com/vehicles/"
130 | ],
131 | "url": "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
132 | },
133 | {
134 | "id": "4e236f34-b981-41c3-8c65-f8c9000b94e7",
135 | "title": "Only Yesterday",
136 | "original_title": "おもひでぽろぽろ",
137 | "original_title_romanised": "Omoide poro poro",
138 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/xjJU6rwzLX7Jk8HFQfVW6H5guMC.jpg",
139 | "movie_banner": "https://image.tmdb.org/t/p/w533_and_h300_bestv2/isCrlWWI4JrdLKAUAwFb5cjAsH4.jpg",
140 | "description": "It’s 1982, and Taeko is 27 years old, unmarried, and has lived her whole life in Tokyo. She decides to visit her family in the countryside, and as the train travels through the night, memories flood back of her younger years: the first immature stirrings of romance, the onset of puberty, and the frustrations of math and boys. At the station she is met by young farmer Toshio, and the encounters with him begin to reconnect her to forgotten longings. In lyrical switches between the present and the past, Taeko contemplates the arc of her life, and wonders if she has been true to the dreams of her childhood self.",
141 | "director": "Isao Takahata",
142 | "producer": "Toshio Suzuki",
143 | "release_date": "1991",
144 | "running_time": "118",
145 | "rt_score": "100",
146 | "people": [
147 | "https://ghibliapi.herokuapp.com/people/"
148 | ],
149 | "species": [
150 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
151 | ],
152 | "locations": [
153 | "https://ghibliapi.herokuapp.com/locations/"
154 | ],
155 | "vehicles": [
156 | "https://ghibliapi.herokuapp.com/vehicles/"
157 | ],
158 | "url": "https://ghibliapi.herokuapp.com/films/4e236f34-b981-41c3-8c65-f8c9000b94e7"
159 | },
160 | {
161 | "id": "ebbb6b7c-945c-41ee-a792-de0e43191bd8",
162 | "title": "Porco Rosso",
163 | "original_title": "紅の豚",
164 | "original_title_romanised": "Kurenai no buta",
165 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/byKAndF6KQSDpGxp1mTr23jPbYp.jpg",
166 | "movie_banner": "https://image.tmdb.org/t/p/original/nAeCzilMRXvGaxiCpv63ZRVRVgh.jpg",
167 | "description": "Porco Rosso, known in Japan as Crimson Pig (Kurenai no Buta) is the sixth animated film by Hayao Miyazaki and released in 1992. You're introduced to an Italian World War I fighter ace, now living as a freelance bounty hunter chasing 'air pirates' in the Adriatic Sea. He has been given a curse that changed his head to that of a pig. Once called Marco Pagot, he is now known to the world as 'Porco Rosso', Italian for 'Red Pig.'",
168 | "director": "Hayao Miyazaki",
169 | "producer": "Toshio Suzuki",
170 | "release_date": "1992",
171 | "running_time": "93",
172 | "rt_score": "94",
173 | "people": [
174 | "https://ghibliapi.herokuapp.com/people/"
175 | ],
176 | "species": [
177 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
178 | ],
179 | "locations": [
180 | "https://ghibliapi.herokuapp.com/locations/"
181 | ],
182 | "vehicles": [
183 | "https://ghibliapi.herokuapp.com/vehicles/"
184 | ],
185 | "url": "https://ghibliapi.herokuapp.com/films/ebbb6b7c-945c-41ee-a792-de0e43191bd8"
186 | },
187 | {
188 | "id": "1b67aa9a-2e4a-45af-ac98-64d6ad15b16c",
189 | "title": "Pom Poko",
190 | "original_title": "平成狸合戦ぽんぽこ",
191 | "original_title_romanised": "Heisei tanuki gassen Ponpoko",
192 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/kowo9E1e1JcWLXj9cCvAOFZcy5n.jpg",
193 | "movie_banner": "https://image.tmdb.org/t/p/original/jScPd0u0jeo66l8gwDl7W9hDUnM.jpg",
194 | "description": "As the human city development encroaches on the raccoon population's forest and meadow habitat, the raccoons find themselves faced with the very real possibility of extinction. In response, the raccoons engage in a desperate struggle to stop the construction and preserve their home.",
195 | "director": "Isao Takahata",
196 | "producer": "Toshio Suzuki",
197 | "release_date": "1994",
198 | "running_time": "119",
199 | "rt_score": "78",
200 | "people": [
201 | "https://ghibliapi.herokuapp.com/people/"
202 | ],
203 | "species": [
204 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
205 | ],
206 | "locations": [
207 | "https://ghibliapi.herokuapp.com/locations/"
208 | ],
209 | "vehicles": [
210 | "https://ghibliapi.herokuapp.com/vehicles/"
211 | ],
212 | "url": "https://ghibliapi.herokuapp.com/films/1b67aa9a-2e4a-45af-ac98-64d6ad15b16c"
213 | },
214 | {
215 | "id": "ff24da26-a969-4f0e-ba1e-a122ead6c6e3",
216 | "title": "Whisper of the Heart",
217 | "original_title": "耳をすませば",
218 | "original_title_romanised": "Mimi wo sumaseba",
219 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/5E3Hvbu0bg38ouYf6chGftVGqZ7.jpg",
220 | "movie_banner": "https://image.tmdb.org/t/p/original/fRtaDgmj0CirvqFUG1XN48BDY1l.jpg",
221 | "description": "Shizuku lives a simple life, dominated by her love for stories and writing. One day she notices that all the library books she has have been previously checked out by the same person: 'Seiji Amasawa'. Curious as to who he is, Shizuku meets a boy her age whom she finds infuriating, but discovers to her shock that he is her 'Prince of Books'. As she grows closer to him, she realises that he merely read all those books to bring himself closer to her. The boy Seiji aspires to be a violin maker in Italy, and it is his dreams that make Shizuku realise that she has no clear path for her life. Knowing that her strength lies in writing, she tests her talents by writing a story about Baron, a cat statuette belonging to Seiji's grandfather.",
222 | "director": "Yoshifumi Kondō",
223 | "producer": "Toshio Suzuki",
224 | "release_date": "1995",
225 | "running_time": "111",
226 | "rt_score": "91",
227 | "people": [
228 | "https://ghibliapi.herokuapp.com/people/"
229 | ],
230 | "species": [
231 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
232 | ],
233 | "locations": [
234 | "https://ghibliapi.herokuapp.com/locations/"
235 | ],
236 | "vehicles": [
237 | "https://ghibliapi.herokuapp.com/vehicles/"
238 | ],
239 | "url": "https://ghibliapi.herokuapp.com/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3"
240 | },
241 | {
242 | "id": "0440483e-ca0e-4120-8c50-4c8cd9b965d6",
243 | "title": "Princess Mononoke",
244 | "original_title": "もののけ姫",
245 | "original_title_romanised": "Mononoke hime",
246 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/jHWmNr7m544fJ8eItsfNk8fs2Ed.jpg",
247 | "movie_banner": "https://image.tmdb.org/t/p/original/6pTqSq0zYIWCsucJys8q5L92kUY.jpg",
248 | "description": "Ashitaka, a prince of the disappearing Ainu tribe, is cursed by a demonized boar god and must journey to the west to find a cure. Along the way, he encounters San, a young human woman fighting to protect the forest, and Lady Eboshi, who is trying to destroy it. Ashitaka must find a way to bring balance to this conflict.",
249 | "director": "Hayao Miyazaki",
250 | "producer": "Toshio Suzuki",
251 | "release_date": "1997",
252 | "running_time": "134",
253 | "rt_score": "92",
254 | "people": [
255 | "https://ghibliapi.herokuapp.com/people/ba924631-068e-4436-b6de-f3283fa848f0",
256 | "https://ghibliapi.herokuapp.com/people/ebe40383-aad2-4208-90ab-698f00c581ab",
257 | "https://ghibliapi.herokuapp.com/people/030555b3-4c92-4fce-93fb-e70c3ae3df8b",
258 | "https://ghibliapi.herokuapp.com/people/ca568e87-4ce2-4afa-a6c5-51f4ae80a60b",
259 | "https://ghibliapi.herokuapp.com/people/e9356bb5-4d4a-4c93-aadc-c83e514bffe3",
260 | "https://ghibliapi.herokuapp.com/people/34277bec-7401-43fa-a00a-5aee64b45b08",
261 | "https://ghibliapi.herokuapp.com/people/91939012-90b9-46e5-a649-96b898073c82",
262 | "https://ghibliapi.herokuapp.com/people/20e3bd33-b35d-41e6-83a4-57ca7f028d38",
263 | "https://ghibliapi.herokuapp.com/people/8bccdc78-545b-49f4-a4c8-756163a38c91",
264 | "https://ghibliapi.herokuapp.com/people/116bfe1b-3ba8-4fa0-8f72-88537a493cb9"
265 | ],
266 | "species": [
267 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
268 | "https://ghibliapi.herokuapp.com/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e",
269 | "https://ghibliapi.herokuapp.com/species/f25fa661-3073-414d-968a-ab062e3065f7"
270 | ],
271 | "locations": [
272 | "https://ghibliapi.herokuapp.com/locations/"
273 | ],
274 | "vehicles": [
275 | "https://ghibliapi.herokuapp.com/vehicles/"
276 | ],
277 | "url": "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
278 | },
279 | {
280 | "id": "45204234-adfd-45cb-a505-a8e7a676b114",
281 | "title": "My Neighbors the Yamadas",
282 | "original_title": "ホーホケキョ となりの山田くん",
283 | "original_title_romanised": "Hōhokekyo tonari no Yamada-kun",
284 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/wTGuHmMIBBgKakY80J1D52VvQKI.jpg",
285 | "movie_banner": "https://image.tmdb.org/t/p/original/nDOsicEg4RHDq0t23JKGSb58z6u.jpg",
286 | "description": "The Yamadas are a typical middle class Japanese family in urban Tokyo and this film shows us a variety of episodes of their lives. With tales that range from the humourous to the heartbreaking, we see this family cope with life's little conflicts, problems and joys in their own way.",
287 | "director": "Isao Takahata",
288 | "producer": "Toshio Suzuki",
289 | "release_date": "1999",
290 | "running_time": "104",
291 | "rt_score": "75",
292 | "people": [
293 | "https://ghibliapi.herokuapp.com/people/"
294 | ],
295 | "species": [
296 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
297 | ],
298 | "locations": [
299 | "https://ghibliapi.herokuapp.com/locations/"
300 | ],
301 | "vehicles": [
302 | "https://ghibliapi.herokuapp.com/vehicles/"
303 | ],
304 | "url": "https://ghibliapi.herokuapp.com/films/45204234-adfd-45cb-a505-a8e7a676b114"
305 | },
306 | {
307 | "id": "dc2e6bd1-8156-4886-adff-b39e6043af0c",
308 | "title": "Spirited Away",
309 | "original_title": "千と千尋の神隠し",
310 | "original_title_romanised": "Sen to Chihiro no kamikakushi",
311 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/39wmItIWsg5sZMyRUHLkWBcuVCM.jpg",
312 | "movie_banner": "https://image.tmdb.org/t/p/original/bSXfU4dwZyBA1vMmXvejdRXBvuF.jpg",
313 | "description": "Spirited Away is an Oscar winning Japanese animated film about a ten year old girl who wanders away from her parents along a path that leads to a world ruled by strange and unusual monster-like animals. Her parents have been changed into pigs along with others inside a bathhouse full of these creatures. Will she ever see the world how it once was?",
314 | "director": "Hayao Miyazaki",
315 | "producer": "Toshio Suzuki",
316 | "release_date": "2001",
317 | "running_time": "124",
318 | "rt_score": "97",
319 | "people": [
320 | "https://ghibliapi.herokuapp.com/people/8228751c-bdc1-4b8d-a6eb-ca0eb909568f"
321 | ],
322 | "species": [
323 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
324 | ],
325 | "locations": [
326 | "https://ghibliapi.herokuapp.com/locations/"
327 | ],
328 | "vehicles": [
329 | "https://ghibliapi.herokuapp.com/vehicles/"
330 | ],
331 | "url": "https://ghibliapi.herokuapp.com/films/dc2e6bd1-8156-4886-adff-b39e6043af0c"
332 | },
333 | {
334 | "id": "90b72513-afd4-4570-84de-a56c312fdf81",
335 | "title": "The Cat Returns",
336 | "original_title": "猫の恩返し",
337 | "original_title_romanised": "Neko no ongaeshi",
338 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/avPMO5cnaGHgLaNiAIhy33WoQLm.jpg",
339 | "movie_banner": "https://image.tmdb.org/t/p/original/d4BTZvckFTthyhGX27LZnWxl0tl.jpg",
340 | "description": "Haru, a schoolgirl bored by her ordinary routine, saves the life of an unusual cat and suddenly her world is transformed beyond anything she ever imagined. The Cat King rewards her good deed with a flurry of presents, including a very shocking proposal of marriage to his son! Haru embarks on an unexpected journey to the Kingdom of Cats where her eyes are opened to a whole other world.",
341 | "director": "Hiroyuki Morita",
342 | "producer": "Toshio Suzuki",
343 | "release_date": "2002",
344 | "running_time": "75",
345 | "rt_score": "89",
346 | "people": [
347 | "https://ghibliapi.herokuapp.com/people/"
348 | ],
349 | "species": [
350 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
351 | ],
352 | "locations": [
353 | "https://ghibliapi.herokuapp.com/locations/"
354 | ],
355 | "vehicles": [
356 | "https://ghibliapi.herokuapp.com/vehicles/"
357 | ],
358 | "url": "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
359 | },
360 | {
361 | "id": "cd3d059c-09f4-4ff3-8d63-bc765a5184fa",
362 | "title": "Howl's Moving Castle",
363 | "original_title": "ハウルの動く城",
364 | "original_title_romanised": "Hauru no ugoku shiro",
365 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/TkTPELv4kC3u1lkloush8skOjE.jpg",
366 | "movie_banner": "https://image.tmdb.org/t/p/original/hjlvbMKhQm7N8tYynr8yQ8GBmqe.jpg",
367 | "description": "When Sophie, a shy young woman, is cursed with an old body by a spiteful witch, her only chance of breaking the spell lies with a self-indulgent yet insecure young wizard and his companions in his legged, walking home.",
368 | "director": "Hayao Miyazaki",
369 | "producer": "Toshio Suzuki",
370 | "release_date": "2004",
371 | "running_time": "119",
372 | "rt_score": "87",
373 | "people": [
374 | "https://ghibliapi.herokuapp.com/people/"
375 | ],
376 | "species": [
377 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
378 | ],
379 | "locations": [
380 | "https://ghibliapi.herokuapp.com/locations/"
381 | ],
382 | "vehicles": [
383 | "https://ghibliapi.herokuapp.com/vehicles/"
384 | ],
385 | "url": "https://ghibliapi.herokuapp.com/films/cd3d059c-09f4-4ff3-8d63-bc765a5184fa"
386 | },
387 | {
388 | "id": "112c1e67-726f-40b1-ac17-6974127bb9b9",
389 | "title": "Tales from Earthsea",
390 | "original_title": "ゲド戦記",
391 | "original_title_romanised": "Gedo senki",
392 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/67yYwCPq7NbxSF6BIIXCMD34sY0.jpg",
393 | "movie_banner": "https://image.tmdb.org/t/p/original/j276noIGGmfi66EnCfewsL2OVTX.jpg",
394 | "description": "Something bizarre has come over the land. The kingdom is deteriorating. People are beginning to act strange... What's even more strange is that people are beginning to see dragons, which shouldn't enter the world of humans. Due to all these bizarre events, Ged, a wandering wizard, is investigating the cause. During his journey, he meets Prince Arren, a young distraught teenage boy. While Arren may look like a shy young teen, he has a severe dark side, which grants him strength, hatred, ruthlessness and has no mercy, especially when it comes to protecting Teru. For the witch Kumo this is a perfect opportunity. She can use the boy's 'fears' against the very one who would help him, Ged.",
395 | "director": "Gorō Miyazaki",
396 | "producer": "Toshio Suzuki",
397 | "release_date": "2006",
398 | "running_time": "116",
399 | "rt_score": "41",
400 | "people": [
401 | "https://ghibliapi.herokuapp.com/people/"
402 | ],
403 | "species": [
404 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
405 | ],
406 | "locations": [
407 | "https://ghibliapi.herokuapp.com/locations/"
408 | ],
409 | "vehicles": [
410 | "https://ghibliapi.herokuapp.com/vehicles/"
411 | ],
412 | "url": "https://ghibliapi.herokuapp.com/films/112c1e67-726f-40b1-ac17-6974127bb9b9"
413 | },
414 | {
415 | "id": "758bf02e-3122-46e0-884e-67cf83df1786",
416 | "title": "Ponyo",
417 | "original_title": "崖の上のポニョ",
418 | "original_title_romanised": "Gake no ue no Ponyo",
419 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/mikKSEdk5kLhflWXbp4S5mmHsDo.jpg",
420 | "movie_banner": "https://image.tmdb.org/t/p/original/6a1qZ1qat26mAIK3Lq8iYdGpyHm.jpg",
421 | "description": "The son of a sailor, 5-year old Sosuke lives a quiet life on an oceanside cliff with his mother Lisa. One fateful day, he finds a beautiful goldfish trapped in a bottle on the beach and upon rescuing her, names her Ponyo. But she is no ordinary goldfish. The daughter of a masterful wizard and a sea goddess, Ponyo uses her father's magic to transform herself into a young girl and quickly falls in love with Sosuke, but the use of such powerful sorcery causes a dangerous imbalance in the world. As the moon steadily draws nearer to the earth and Ponyo's father sends the ocean's mighty waves to find his daughter, the two children embark on an adventure of a lifetime to save the world and fulfill Ponyo's dreams of becoming human.",
422 | "director": "Hayao Miyazaki",
423 | "producer": "Toshio Suzuki",
424 | "release_date": "2008",
425 | "running_time": "100",
426 | "rt_score": "92",
427 | "people": [
428 | "https://ghibliapi.herokuapp.com/people/"
429 | ],
430 | "species": [
431 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
432 | ],
433 | "locations": [
434 | "https://ghibliapi.herokuapp.com/locations/"
435 | ],
436 | "vehicles": [
437 | "https://ghibliapi.herokuapp.com/vehicles/"
438 | ],
439 | "url": "https://ghibliapi.herokuapp.com/films/758bf02e-3122-46e0-884e-67cf83df1786"
440 | },
441 | {
442 | "id": "2de9426b-914a-4a06-a3a0-5e6d9d3886f6",
443 | "title": "Arrietty",
444 | "original_title": "借りぐらしのアリエッティ",
445 | "original_title_romanised": "Karigurashi no Arietti",
446 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/oc2OB2KDmSRDMelKEAA1n4YRQL0.jpg",
447 | "movie_banner": "https://image.tmdb.org/t/p/original/7Z7WVzJsSReG8B0CaPk0bvWD7tK.jpg",
448 | "description": "14-year-old Arrietty and the rest of the Clock family live in peaceful anonymity as they make their own home from items 'borrowed' from the house's human inhabitants. However, life changes for the Clocks when a human boy discovers Arrietty.",
449 | "director": "Hiromasa Yonebayashi",
450 | "producer": "Toshio Suzuki",
451 | "release_date": "2010",
452 | "running_time": "94",
453 | "rt_score": "95",
454 | "people": [
455 | "https://ghibliapi.herokuapp.com/people/"
456 | ],
457 | "species": [
458 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
459 | ],
460 | "locations": [
461 | "https://ghibliapi.herokuapp.com/locations/"
462 | ],
463 | "vehicles": [
464 | "https://ghibliapi.herokuapp.com/vehicles/"
465 | ],
466 | "url": "https://ghibliapi.herokuapp.com/films/2de9426b-914a-4a06-a3a0-5e6d9d3886f6"
467 | },
468 | {
469 | "id": "45db04e4-304a-4933-9823-33f389e8d74d",
470 | "title": "From Up on Poppy Hill",
471 | "original_title": "コクリコ坂から",
472 | "original_title_romanised": "Kokuriko zaka kara",
473 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/rRLYX4RZIyloHSJwvZKAhphAjiB.jpg",
474 | "movie_banner": "https://image.tmdb.org/t/p/original/xtPBZYaWQMQxRpy7mkdk5n1bTxs.jpg",
475 | "description": "The story is set in 1963 in Yokohama. Kokuriko Manor sits on a hill overlooking the harbour. A 16 year-old girl, Umi, lives in that house. Every morning she raises a signal flag facing the sea. The flag means “I pray for safe voyages”. A 17 year-old boy, Shun, always sees this flag from the sea as he rides a tugboat to school. Gradually the pair are drawn to each other but they are faced with a sudden trial. Even so, they keep going without running from facing the hardships of reality.",
476 | "director": "Gorō Miyazaki",
477 | "producer": "Toshio Suzuki",
478 | "release_date": "2011",
479 | "running_time": "91",
480 | "rt_score": "83",
481 | "people": [
482 | "https://ghibliapi.herokuapp.com/people/"
483 | ],
484 | "species": [
485 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
486 | ],
487 | "locations": [
488 | "https://ghibliapi.herokuapp.com/locations/"
489 | ],
490 | "vehicles": [
491 | "https://ghibliapi.herokuapp.com/vehicles/"
492 | ],
493 | "url": "https://ghibliapi.herokuapp.com/films/45db04e4-304a-4933-9823-33f389e8d74d"
494 | },
495 | {
496 | "id": "67405111-37a5-438f-81cc-4666af60c800",
497 | "title": "The Wind Rises",
498 | "original_title": "風立ちぬ",
499 | "original_title_romanised": "Kaze tachinu",
500 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/jfwSexzlIzaOgxP9A8bTA6t8YYb.jpg",
501 | "movie_banner": "https://image.tmdb.org/t/p/original/stM3jlD4nSJhlvR2DE7XnB0eN25.jpg",
502 | "description": "A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi, whose storied career includes the creation of the A-6M World War II fighter plane.",
503 | "director": "Hayao Miyazaki",
504 | "producer": "Toshio Suzuki",
505 | "release_date": "2013",
506 | "running_time": "126",
507 | "rt_score": "89",
508 | "people": [
509 | "https://ghibliapi.herokuapp.com/people/"
510 | ],
511 | "species": [
512 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
513 | ],
514 | "locations": [
515 | "https://ghibliapi.herokuapp.com/locations/"
516 | ],
517 | "vehicles": [
518 | "https://ghibliapi.herokuapp.com/vehicles/"
519 | ],
520 | "url": "https://ghibliapi.herokuapp.com/films/67405111-37a5-438f-81cc-4666af60c800"
521 | },
522 | {
523 | "id": "578ae244-7750-4d9f-867b-f3cd3d6fecf4",
524 | "title": "The Tale of the Princess Kaguya",
525 | "original_title": "かぐや姫の物語",
526 | "original_title_romanised": "Kaguya-Hime no Monogatari",
527 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/mWRQNlWXYYfd2z4FRm99MsgHgiA.jpg",
528 | "movie_banner": "https://image.tmdb.org/t/p/original/lMaWlYThCSnsmW3usxWTpSuyZp1.jpg",
529 | "description": "A bamboo cutter named Sanuki no Miyatsuko discovers a miniature girl inside a glowing bamboo shoot. Believing her to be a divine presence, he and his wife decide to raise her as their own, calling her 'Princess'.",
530 | "director": "Isao Takahata",
531 | "producer": "Yoshiaki Nishimura",
532 | "release_date": "2013",
533 | "running_time": "137",
534 | "rt_score": "100",
535 | "people": [
536 | "https://ghibliapi.herokuapp.com/people/"
537 | ],
538 | "species": [
539 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
540 | ],
541 | "locations": [
542 | "https://ghibliapi.herokuapp.com/locations/"
543 | ],
544 | "vehicles": [
545 | "https://ghibliapi.herokuapp.com/vehicles/"
546 | ],
547 | "url": "https://ghibliapi.herokuapp.com/films/578ae244-7750-4d9f-867b-f3cd3d6fecf4"
548 | },
549 | {
550 | "id": "5fdfb320-2a02-49a7-94ff-5ca418cae602",
551 | "title": "When Marnie Was There",
552 | "original_title": "思い出のマーニー",
553 | "original_title_romanised": "Omoide no Marnie",
554 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/vug1dvDI1tSa60Z8qjCuUE7ntkO.jpg",
555 | "movie_banner": "https://image.tmdb.org/t/p/original/axUX7urQDwCGQ9qbgh2Yys7qY9J.jpg",
556 | "description": "The film follows Anna Sasaki living with her relatives in the seaside town. Anna comes across a nearby abandoned mansion, where she meets Marnie, a mysterious girl who asks her to promise to keep their secrets from everyone. As the summer progresses, Anna spends more time with Marnie, and eventually Anna learns the truth about her family and foster care.",
557 | "director": "Hiromasa Yonebayashi",
558 | "producer": "Yoshiaki Nishimura",
559 | "release_date": "2014",
560 | "running_time": "103",
561 | "rt_score": "92",
562 | "people": [
563 | "https://ghibliapi.herokuapp.com/people/"
564 | ],
565 | "species": [
566 | "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
567 | ],
568 | "locations": [
569 | "https://ghibliapi.herokuapp.com/locations/"
570 | ],
571 | "vehicles": [
572 | "https://ghibliapi.herokuapp.com/vehicles/"
573 | ],
574 | "url": "https://ghibliapi.herokuapp.com/films/5fdfb320-2a02-49a7-94ff-5ca418cae602"
575 | },
576 | {
577 | "id": "d868e6ec-c44a-405b-8fa6-f7f0f8cfb500",
578 | "title": "The Red Turtle",
579 | "original_title": "レッドタートル ある島の物語",
580 | "original_title_romanised": "Reddotātoru aru shima no monogatari",
581 | "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/wOBU3SLjQ9358Km9YWYasPZyebp.jpg",
582 | "movie_banner": "https://image.tmdb.org/t/p/original/kjXdW5H3myRBmTMYgKayjphr2FA.jpg",
583 | "description": "A man set adrift by a storm wakes up on a beach. He discovers that he is on a deserted island with plenty of fresh water, fruit and a dense bamboo forest. He builds a raft from bamboo and attempts to sail away, but his raft is destroyed by an unseen monster in the sea, forcing him back to the island. He tries again with another, larger raft, but is again foiled by the creature. A third attempt again ends with the raft destroyed, but this time he is confronted by a giant red turtle, which stares at him, and forces him back to the island.",
584 | "director": "Michaël Dudok de Wit",
585 | "producer": "Toshio Suzuki, Isao Takahata, Vincent Maraval, Pascal Caucheteux, Grégoire Sorlat",
586 | "release_date": "2016",
587 | "running_time": "80",
588 | "rt_score": "93",
589 | "people": [
590 | "https://ghibliapi.herokuapp.com/people/"
591 | ],
592 | "species": [
593 | "https://ghibliapi.herokuapp.com/species/"
594 | ],
595 | "locations": [
596 | "https://ghibliapi.herokuapp.com/locations/"
597 | ],
598 | "vehicles": [
599 | "https://ghibliapi.herokuapp.com/vehicles/"
600 | ],
601 | "url": "https://ghibliapi.herokuapp.com/films/d868e6ec-c44a-405b-8fa6-f7f0f8cfb500"
602 | },
603 | {
604 | "id": "790e0028-a31c-4626-a694-86b7a8cada40",
605 | "title": "Earwig and the Witch",
606 | "original_title": "アーヤと魔女",
607 | "original_title_romanised": "Āya to Majo",
608 | "image": "https://www.themoviedb.org/t/p/w600_and_h900_bestv2/sJhFtY3eHuvvACaPpxpzdCLQqpQ.jpg",
609 | "movie_banner": "https://www.themoviedb.org/t/p/original/qMxpGzmmnY1jLd4p7EhhoW43wWF.jpg",
610 | "description": "An orphan girl, Earwig, is adopted by a witch and comes home to a spooky house filled with mystery and magic.",
611 | "director": "Gorō Miyazaki",
612 | "producer": "Toshio Suzuki",
613 | "release_date": "2021",
614 | "running_time": "82",
615 | "rt_score": "30",
616 | "people": [
617 | "https://ghibliapi.herokuapp.com/people/"
618 | ],
619 | "species": [
620 | "https://ghibliapi.herokuapp.com/species/"
621 | ],
622 | "locations": [
623 | "https://ghibliapi.herokuapp.com/locations/"
624 | ],
625 | "vehicles": [
626 | "https://ghibliapi.herokuapp.com/vehicles/"
627 | ],
628 | "url": "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
629 | }
630 | ]
631 |
632 | export default MockFilmResponse;
--------------------------------------------------------------------------------
/src/mocks/response/people.ts:
--------------------------------------------------------------------------------
1 | const MockPeopleResponse = [
2 | {
3 | "id": "267649ac-fb1b-11eb-9a03-0242ac130003",
4 | "name": "Haku",
5 | "gender": "Male",
6 | "age": "12",
7 | "eye_color": "Green",
8 | "hair_color": "Green",
9 | "films": [
10 | "https://ghibliapi.herokuapp.com/films/dc2e6bd1-8156-4886-adff-b39e6043af0c"
11 | ],
12 | "species": "https://ghibliapi.herokuapp.com/species/e2cfaa78-fb19-11eb-9a03-0242ac130003",
13 | "url": "https://ghibliapi.herokuapp.com/people/267649ac-fb1b-11eb-9a03-0242ac130003"
14 | },
15 | {
16 | "id": "fe93adf2-2f3a-4ec4-9f68-5422f1b87c01",
17 | "name": "Pazu",
18 | "gender": "Male",
19 | "age": "13",
20 | "eye_color": "Black",
21 | "hair_color": "Brown",
22 | "films": [
23 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
24 | ],
25 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
26 | "url": "https://ghibliapi.herokuapp.com/people/fe93adf2-2f3a-4ec4-9f68-5422f1b87c01"
27 | },
28 | {
29 | "id": "598f7048-74ff-41e0-92ef-87dc1ad980a9",
30 | "name": "Lusheeta Toel Ul Laputa",
31 | "gender": "Female",
32 | "age": "13",
33 | "eye_color": "Black",
34 | "hair_color": "Black",
35 | "films": [
36 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
37 | ],
38 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
39 | "url": "https://ghibliapi.herokuapp.com/people/598f7048-74ff-41e0-92ef-87dc1ad980a9"
40 | },
41 | {
42 | "id": "3bc0b41e-3569-4d20-ae73-2da329bf0786",
43 | "name": "Captain Dola",
44 | "gender": "Female",
45 | "age": "60",
46 | "eye_color": "Black",
47 | "hair_color": "Peach",
48 | "films": [
49 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
50 | ],
51 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
52 | "url": "https://ghibliapi.herokuapp.com/people/3bc0b41e-3569-4d20-ae73-2da329bf0786"
53 | },
54 | {
55 | "id": "abe886e7-30c8-4c19-aaa5-d666e60d14de",
56 | "name": "Romska Palo Ul Laputa",
57 | "gender": "Male",
58 | "age": "33",
59 | "eye_color": "Black",
60 | "hair_color": "Brown",
61 | "films": [
62 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
63 | ],
64 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
65 | "url": "https://ghibliapi.herokuapp.com/people/abe886e7-30c8-4c19-aaa5-d666e60d14de"
66 | },
67 | {
68 | "id": "e08880d0-6938-44f3-b179-81947e7873fc",
69 | "name": "Uncle Pom",
70 | "gender": "Male",
71 | "age": "Unspecified/Elderly",
72 | "eye_color": "Black",
73 | "hair_color": "White",
74 | "films": [
75 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
76 | ],
77 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
78 | "url": "https://ghibliapi.herokuapp.com/people/e08880d0-6938-44f3-b179-81947e7873fc"
79 | },
80 | {
81 | "id": "5c83c12a-62d5-4e92-8672-33ac76ae1fa0",
82 | "name": "General Mouro",
83 | "gender": "Male",
84 | "age": "Unspecified/Adult",
85 | "eye_color": "Black",
86 | "hair_color": "None",
87 | "films": [
88 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
89 | ],
90 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
91 | "url": "https://ghibliapi.herokuapp.com/people/5c83c12a-62d5-4e92-8672-33ac76ae1fa0"
92 | },
93 | {
94 | "id": "3f4c408b-0bcc-45a0-bc8b-20ffc67a2ede",
95 | "name": "Duffi",
96 | "gender": "Male",
97 | "age": "Unspecified/Adult",
98 | "eye_color": "Dark brown",
99 | "hair_color": "Dark brown",
100 | "films": [
101 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
102 | ],
103 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
104 | "url": "https://ghibliapi.herokuapp.com/people/3f4c408b-0bcc-45a0-bc8b-20ffc67a2ede"
105 | },
106 | {
107 | "id": "fcb4a2ac-5e41-4d54-9bba-33068db083ca",
108 | "name": "Louis",
109 | "gender": "Male",
110 | "age": "30",
111 | "eye_color": "Dark brown",
112 | "hair_color": "Dark brown",
113 | "films": [
114 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
115 | ],
116 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
117 | "url": "https://ghibliapi.herokuapp.com/people/fcb4a2ac-5e41-4d54-9bba-33068db083ca"
118 | },
119 | {
120 | "id": "2cb76c15-772a-4cb3-9919-3652f56611d0",
121 | "name": "Charles",
122 | "gender": "Male",
123 | "age": "Unspecified/Adult",
124 | "eye_color": "Dark brown",
125 | "hair_color": "Light brown",
126 | "films": [
127 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
128 | ],
129 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
130 | "url": "https://ghibliapi.herokuapp.com/people/2cb76c15-772a-4cb3-9919-3652f56611d0"
131 | },
132 | {
133 | "id": "f6f2c477-98aa-4796-b9aa-8209fdeed6b9",
134 | "name": "Henri",
135 | "gender": "Male",
136 | "age": "Unspecified/Adult",
137 | "eye_color": "Dark brown",
138 | "hair_color": "Reddish brown",
139 | "films": [
140 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
141 | ],
142 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
143 | "url": "https://ghibliapi.herokuapp.com/people/f6f2c477-98aa-4796-b9aa-8209fdeed6b9"
144 | },
145 | {
146 | "id": "05d8d01b-0c2f-450e-9c55-aa0daa34838e",
147 | "name": "Motro",
148 | "gender": "Male",
149 | "age": "Unspecified/Adult",
150 | "eye_color": "Dark brown",
151 | "hair_color": "None",
152 | "films": [
153 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
154 | ],
155 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
156 | "url": "https://ghibliapi.herokuapp.com/people/05d8d01b-0c2f-450e-9c55-aa0daa34838e"
157 | },
158 | {
159 | "id": "b22a684f-1819-40c8-94a6-d40c3b5e18eb",
160 | "name": "Okami",
161 | "gender": "Female",
162 | "age": "50",
163 | "eye_color": "Dark brown",
164 | "hair_color": "Orange",
165 | "films": [
166 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
167 | ],
168 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
169 | "url": "https://ghibliapi.herokuapp.com/people/b22a684f-1819-40c8-94a6-d40c3b5e18eb"
170 | },
171 | {
172 | "id": "ba924631-068e-4436-b6de-f3283fa848f0",
173 | "name": "Ashitaka",
174 | "gender": "Male",
175 | "age": "late teens",
176 | "eye_color": "Brown",
177 | "hair_color": "Brown",
178 | "films": [
179 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
180 | ],
181 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
182 | "url": "https://ghibliapi.herokuapp.com/people/ba924631-068e-4436-b6de-f3283fa848f0"
183 | },
184 | {
185 | "id": "ebe40383-aad2-4208-90ab-698f00c581ab",
186 | "name": "San",
187 | "gender": "Female",
188 | "age": "17",
189 | "eye_color": "Brown",
190 | "hair_color": "Brown",
191 | "films": [
192 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
193 | ],
194 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
195 | "url": "https://ghibliapi.herokuapp.com/people/ebe40383-aad2-4208-90ab-698f00c581ab"
196 | },
197 | {
198 | "id": "34277bec-7401-43fa-a00a-5aee64b45b08",
199 | "name": "Eboshi",
200 | "gender": "Female",
201 | "age": "Unspecified/Adult",
202 | "eye_color": "Hazel",
203 | "hair_color": "Black",
204 | "films": [
205 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
206 | ],
207 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
208 | "url": "https://ghibliapi.herokuapp.com/people/34277bec-7401-43fa-a00a-5aee64b45b08"
209 | },
210 | {
211 | "id": "91939012-90b9-46e5-a649-96b898073c82",
212 | "name": "Jigo",
213 | "gender": "Male",
214 | "age": "Middle age",
215 | "eye_color": "Black",
216 | "hair_color": "Brown",
217 | "films": [
218 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
219 | ],
220 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
221 | "url": "https://ghibliapi.herokuapp.com/people/91939012-90b9-46e5-a649-96b898073c82"
222 | },
223 | {
224 | "id": "20e3bd33-b35d-41e6-83a4-57ca7f028d38",
225 | "name": "Kohroku",
226 | "gender": "Male",
227 | "age": "Adult",
228 | "eye_color": "Black",
229 | "hair_color": "Brown",
230 | "films": [
231 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
232 | ],
233 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
234 | "url": "https://ghibliapi.herokuapp.com/people/20e3bd33-b35d-41e6-83a4-57ca7f028d38"
235 | },
236 | {
237 | "id": "8bccdc78-545b-49f4-a4c8-756163a38c91",
238 | "name": "Gonza",
239 | "gender": "Male",
240 | "age": "Adult",
241 | "eye_color": "Grey",
242 | "hair_color": "Bald, but beard is Brown",
243 | "films": [
244 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
245 | ],
246 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
247 | "url": "https://ghibliapi.herokuapp.com/people/8bccdc78-545b-49f4-a4c8-756163a38c91"
248 | },
249 | {
250 | "id": "116bfe1b-3ba8-4fa0-8f72-88537a493cb9",
251 | "name": "Hii-sama",
252 | "gender": "Female",
253 | "age": "Over 50",
254 | "eye_color": "Brown",
255 | "hair_color": "White",
256 | "films": [
257 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
258 | ],
259 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
260 | "url": "https://ghibliapi.herokuapp.com/people/116bfe1b-3ba8-4fa0-8f72-88537a493cb9"
261 | },
262 | {
263 | "id": "030555b3-4c92-4fce-93fb-e70c3ae3df8b",
264 | "name": "Yakul",
265 | "age": "Unknown",
266 | "gender": "Male",
267 | "eye_color": "Grey",
268 | "hair_color": "Brown",
269 | "films": [
270 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
271 | ],
272 | "species": "https://ghibliapi.herokuapp.com/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e",
273 | "url": "https://ghibliapi.herokuapp.com/people/030555b3-4c92-4fce-93fb-e70c3ae3df8b"
274 | },
275 | {
276 | "id": "ca568e87-4ce2-4afa-a6c5-51f4ae80a60b",
277 | "name": "Shishigami",
278 | "age": "400",
279 | "gender": "Male",
280 | "eye_color": "Red",
281 | "hair_color": "Light Orange",
282 | "films": [
283 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
284 | ],
285 | "species": "https://ghibliapi.herokuapp.com/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e",
286 | "url": "https://ghibliapi.herokuapp.com/people/ca568e87-4ce2-4afa-a6c5-51f4ae80a60b"
287 | },
288 | {
289 | "id": "e9356bb5-4d4a-4c93-aadc-c83e514bffe3",
290 | "name": "Moro",
291 | "gender": "Female",
292 | "age": "300",
293 | "eye_color": "Brown",
294 | "hair_color": "White",
295 | "films": [
296 | "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
297 | ],
298 | "species": "https://ghibliapi.herokuapp.com/species/f25fa661-3073-414d-968a-ab062e3065f7",
299 | "url": "https://ghibliapi.herokuapp.com/people/e9356bb5-4d4a-4c93-aadc-c83e514bffe3"
300 | },
301 | {
302 | "id": "7151abc6-1a9e-4e6a-9711-ddb50ea572ec",
303 | "name": "Jiji",
304 | "gender": "Male",
305 | "age": "NA",
306 | "eye_color": "Black",
307 | "hair_color": "Black",
308 | "films": [
309 | "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
310 | ],
311 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
312 | "url": "https://ghibliapi.herokuapp.com/people/7151abc6-1a9e-4e6a-9711-ddb50ea572ec"
313 | },
314 | {
315 | "id": "986faac6-67e3-4fb8-a9ee-bad077c2e7fe",
316 | "name": "Satsuki Kusakabe",
317 | "gender": "Female",
318 | "age": "11",
319 | "eye_color": "Dark Brown/Black",
320 | "hair_color": "Dark Brown",
321 | "films": [
322 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
323 | ],
324 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
325 | "url": "https://ghibliapi.herokuapp.com/people/986faac6-67e3-4fb8-a9ee-bad077c2e7fe"
326 | },
327 | {
328 | "id": "d5df3c04-f355-4038-833c-83bd3502b6b9",
329 | "name": "Mei Kusakabe",
330 | "gender": "Female",
331 | "age": "4",
332 | "eye_color": "Brown",
333 | "hair_color": "Light Brown",
334 | "films": [
335 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
336 | ],
337 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
338 | "url": "https://ghibliapi.herokuapp.com/people/d5df3c04-f355-4038-833c-83bd3502b6b9"
339 | },
340 | {
341 | "id": "3031caa8-eb1a-41c6-ab93-dd091b541e11",
342 | "name": "Tatsuo Kusakabe",
343 | "gender": "Male",
344 | "age": "37",
345 | "eye_color": "Brown",
346 | "hair_color": "Dark Brown",
347 | "films": [
348 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
349 | ],
350 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
351 | "url": "https://ghibliapi.herokuapp.com/people/3031caa8-eb1a-41c6-ab93-dd091b541e11"
352 | },
353 | {
354 | "id": "87b68b97-3774-495b-bf80-495a5f3e672d",
355 | "name": "Yasuko Kusakabe",
356 | "gender": "Female",
357 | "age": "Adult",
358 | "eye_color": "Brown",
359 | "hair_color": "Dark Brown",
360 | "films": [
361 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
362 | ],
363 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
364 | "url": "https://ghibliapi.herokuapp.com/people/87b68b97-3774-495b-bf80-495a5f3e672d"
365 | },
366 | {
367 | "id": "08ffbce4-7f94-476a-95bc-76d3c3969c19",
368 | "name": "Granny",
369 | "gender": "Female",
370 | "age": "Elder",
371 | "eye_color": "Black",
372 | "hair_color": "Grey",
373 | "films": [
374 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
375 | ],
376 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
377 | "url": "https://ghibliapi.herokuapp.com/people/08ffbce4-7f94-476a-95bc-76d3c3969c19"
378 | },
379 | {
380 | "id": "0f8ef701-b4c7-4f15-bd15-368c7fe38d0a",
381 | "name": "Kanta Ōgaki",
382 | "gender": "Male",
383 | "age": "11",
384 | "eye_color": "Brown",
385 | "hair_color": "Brown",
386 | "films": [
387 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
388 | ],
389 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
390 | "url": "https://ghibliapi.herokuapp.com/people/0f8ef701-b4c7-4f15-bd15-368c7fe38d0a"
391 | },
392 | {
393 | "id": "d39deecb-2bd0-4770-8b45-485f26e1381f",
394 | "name": "Totoro",
395 | "gender": "NA",
396 | "age": "",
397 | "eye_color": "Grey",
398 | "hair_color": "Grey",
399 | "films": [
400 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
401 | ],
402 | "species": "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5",
403 | "url": "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f"
404 | },
405 | {
406 | "id": "591524bc-04fe-4e60-8d61-2425e42ffb2a",
407 | "name": "Chu Totoro",
408 | "gender": "NA",
409 | "age": "",
410 | "eye_color": "Black",
411 | "hair_color": "Blue",
412 | "films": [
413 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
414 | ],
415 | "species": "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5",
416 | "url": "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f"
417 | },
418 | {
419 | "id": "c491755a-407d-4d6e-b58a-240ec78b5061",
420 | "name": "Chibi Totoro",
421 | "gender": "NA",
422 | "age": "",
423 | "eye_color": "Black",
424 | "hair_color": "White",
425 | "films": [
426 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
427 | ],
428 | "species": "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5",
429 | "url": "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f"
430 | },
431 | {
432 | "id": "f467e18e-3694-409f-bdb3-be891ade1106",
433 | "name": "Catbus",
434 | "gender": "Male",
435 | "age": "NA",
436 | "eye_color": "Yellow",
437 | "hair_color": "Brown",
438 | "films": [
439 | "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
440 | ],
441 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
442 | "url": "https://ghibliapi.herokuapp.com/people/f467e18e-3694-409f-bdb3-be891ade1106"
443 | },
444 | {
445 | "id": "89026b3a-abc4-4053-ab1a-c6d2eea68faa",
446 | "name": "Niya",
447 | "gender": "Male",
448 | "age": "NA",
449 | "eye_color": "White",
450 | "hair_color": "Beige",
451 | "films": [
452 | "https://ghibliapi.herokuapp.com/films/2de9426b-914a-4a06-a3a0-5e6d9d3886f6"
453 | ],
454 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
455 | "url": "https://ghibliapi.herokuapp.com/people/89026b3a-abc4-4053-ab1a-c6d2eea68faa"
456 | },
457 | {
458 | "id": "6b3facea-ea33-47b1-96ce-3fc737b119b8",
459 | "name": "Renaldo Moon aka Moon aka Muta",
460 | "gender": "Male",
461 | "age": "NA",
462 | "eye_color": "White",
463 | "hair_color": "Beige",
464 | "films": [
465 | "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81",
466 | "https://ghibliapi.herokuapp.com/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3"
467 | ],
468 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
469 | "url": "https://ghibliapi.herokuapp.com/people/6b3facea-ea33-47b1-96ce-3fc737b119b8"
470 | },
471 | {
472 | "id": "3042818d-a8bb-4cba-8180-c19249822d57",
473 | "name": "Cat King",
474 | "gender": "Male",
475 | "age": "87",
476 | "eye_color": "Emerald",
477 | "hair_color": "Grey",
478 | "films": [
479 | "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
480 | ],
481 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
482 | "url": "https://ghibliapi.herokuapp.com/people/3042818d-a8bb-4cba-8180-c19249822d57"
483 | },
484 | {
485 | "id": "58d1973f-f247-47d7-9358-e56cb0d2b5a6",
486 | "name": "Yuki",
487 | "gender": "Female",
488 | "age": "NA",
489 | "eye_color": "Blue",
490 | "hair_color": "White",
491 | "films": [
492 | "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
493 | ],
494 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
495 | "url": "https://ghibliapi.herokuapp.com/people/58d1973f-f247-47d7-9358-e56cb0d2b5a6"
496 | },
497 | {
498 | "id": "a3d8e70f-46a0-4e5a-b850-db01620d6b92",
499 | "name": "Haru",
500 | "gender": "Female",
501 | "age": "13",
502 | "eye_color": "Brown",
503 | "hair_color": "Brown",
504 | "films": [
505 | "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
506 | ],
507 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
508 | "url": "https://ghibliapi.herokuapp.com/people/a3d8e70f-46a0-4e5a-b850-db01620d6b92"
509 | },
510 | {
511 | "id": "fc196c4f-0201-4ed2-9add-c6403f7c4d32",
512 | "name": "Baron Humbert von Gikkingen",
513 | "gender": "Male",
514 | "age": "NA",
515 | "eye_color": "Green",
516 | "hair_color": "Yellow",
517 | "films": [
518 | "https://ghibliapi.herokuapp.com/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3",
519 | "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
520 | ],
521 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
522 | "url": "https://ghibliapi.herokuapp.com/people/fc196c4f-0201-4ed2-9add-c6403f7c4d32"
523 | },
524 | {
525 | "id": "466bc926-2024-4653-ac63-fe52f2dc8c7b",
526 | "name": "Natori",
527 | "gender": "Male",
528 | "age": "NA",
529 | "eye_color": "Blue",
530 | "hair_color": "Grey",
531 | "films": [
532 | "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
533 | ],
534 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
535 | "url": "https://ghibliapi.herokuapp.com/people/466bc926-2024-4653-ac63-fe52f2dc8c7b"
536 | },
537 | {
538 | "id": "40c005ce-3725-4f15-8409-3e1b1b14b583",
539 | "name": "Colonel Muska",
540 | "gender": "Male",
541 | "age": "33",
542 | "eye_color": "Grey",
543 | "hair_color": "Brown",
544 | "films": [
545 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
546 | ],
547 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
548 | "url": "https://ghibliapi.herokuapp.com/people/40c005ce-3725-4f15-8409-3e1b1b14b583"
549 | },
550 | {
551 | "id": "6523068d-f5a9-4150-bf5b-76abe6fb42c3",
552 | "name": "Porco Rosso",
553 | "gender": "Male",
554 | "age": "47",
555 | "eye_color": "Black",
556 | "hair_color": "Brown",
557 | "films": [
558 | "https://ghibliapi.herokuapp.com/films/ebbb6b7c-945c-41ee-a792-de0e43191bd8"
559 | ],
560 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
561 | "url": "https://ghibliapi.herokuapp.com/people/6523068d-f5a9-4150-bf5b-76abe6fb42c3"
562 | },
563 | {
564 | "id": "a10f64f3-e0b6-4a94-bf30-87ad8bc51607",
565 | "name": "Sosuke",
566 | "gender": "Male",
567 | "age": "5",
568 | "eye_color": "Brown",
569 | "hair_color": "Brown",
570 | "films": [
571 | "https://ghibliapi.herokuapp.com/films/758bf02e-3122-46e0-884e-67cf83df1786"
572 | ],
573 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
574 | "url": "https://ghibliapi.herokuapp.com/people/a10f64f3-e0b6-4a94-bf30-87ad8bc51607"
575 | },
576 | {
577 | "id": "2409052a-9029-4e8d-bfaf-70fd82c8e48d",
578 | "name": "Kiki",
579 | "gander": "Female",
580 | "age": "13",
581 | "eye_color": "Black",
582 | "hair_color": "Brown",
583 | "films": [
584 | "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
585 | ],
586 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
587 | "url": "https://ghibliapi.herokuapp.com/people/2409052a-9029-4e8d-bfaf-70fd82c8e48d"
588 | },
589 | {
590 | "id": "2a1dad70-802a-459d-8cc2-4ebd8821248b",
591 | "name": "Laputian Robot",
592 | "gender": "NA",
593 | "age": "Really Old",
594 | "eye_color": "Black",
595 | "hair_color": "NA",
596 | "films": [
597 | "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
598 | ],
599 | "species": "https://ghibliapi.herokuapp.com/species/",
600 | "url": "https://ghibliapi.herokuapp.com/people/2a1dad70-802a-459d-8cc2-4ebd8821248b"
601 | },
602 | {
603 | "id": "8228751c-bdc1-4b8d-a6eb-ca0eb909568f",
604 | "name": "Chihiro Ogino",
605 | "gender": "Female",
606 | "age": "10",
607 | "eye_color": "Black",
608 | "hair_color": "Brown",
609 | "films": [
610 | "https://ghibliapi.herokuapp.com/films/dc2e6bd1-8156-4886-adff-b39e6043af0c"
611 | ],
612 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
613 | "url": "https://ghibliapi.herokuapp.com/people/8228751c-bdc1-4b8d-a6eb-ca0eb909568f"
614 | },
615 | {
616 | "id": "1c1a8054-3a34-4185-bfcf-e8011506f09a",
617 | "name": "Osono",
618 | "gender": "Female",
619 | "age": "26",
620 | "eye_color": "Black",
621 | "hair_color": "Red",
622 | "films": [
623 | "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
624 | ],
625 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
626 | "url": "https://ghibliapi.herokuapp.com/people/1c1a8054-3a34-4185-bfcf-e8011506f09a"
627 | },
628 | {
629 | "id": "bc838920-7849-43ea-bfb8-7d5e98dc20b6",
630 | "name": "Ursula",
631 | "gender": "Female",
632 | "age": "18",
633 | "eye_color": "Black",
634 | "hair_color": "Reddish brown",
635 | "films": [
636 | "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
637 | ],
638 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
639 | "url": "https://ghibliapi.herokuapp.com/people/bc838920-7849-43ea-bfb8-7d5e98dc20b6"
640 | },
641 | {
642 | "id": "33f5fea9-c21b-490b-90e0-c4051c372826",
643 | "name": "Tombo",
644 | "gender": "Male",
645 | "age": "13",
646 | "eye_color": "Black",
647 | "hair_color": "Dirty Blond",
648 | "films": [
649 | "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
650 | ],
651 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
652 | "url": "https://ghibliapi.herokuapp.com/people/33f5fea9-c21b-490b-90e0-c4051c372826"
653 | },
654 | {
655 | "id": "d1de1c0e-3fcd-4cef-94eb-bb95cc2314aa",
656 | "name": "Madame",
657 | "gender": "Female",
658 | "age": "60",
659 | "eye_color": "Cyan",
660 | "hair_color": "Light Gray",
661 | "films": [
662 | "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
663 | ],
664 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
665 | "url": "https://ghibliapi.herokuapp.com/people/d1de1c0e-3fcd-4cef-94eb-bb95cc2314aa"
666 | },
667 | {
668 | "id": "2d66e2eb-5a74-4721-a5b3-c556497bdb66",
669 | "name": "Earwig",
670 | "gender": "Female",
671 | "age": "10",
672 | "eye_color": "Brown",
673 | "hair_color": "Brown",
674 | "films": [
675 | "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
676 | ],
677 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
678 | "url": "https://ghibliapi.herokuapp.com/people/2d66e2eb-5a74-4721-a5b3-c556497bdb66"
679 | },
680 | {
681 | "id": "56c94dae-3290-472f-ad2e-0a6444956b51",
682 | "name": "Bella Yaga",
683 | "gender": "Female",
684 | "age": "NA",
685 | "eye_color": "Brown",
686 | "hair_color": "Blue",
687 | "films": [
688 | "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
689 | ],
690 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
691 | "url": "https://ghibliapi.herokuapp.com/people/56c94dae-3290-472f-ad2e-0a6444956b51"
692 | },
693 | {
694 | "id": "5caa6a3c-7d44-47d1-afc6-12fffad28889",
695 | "name": "Mandrake",
696 | "gender": "Male",
697 | "age": "NA",
698 | "eye_color": "NA",
699 | "hair_color": "Brown",
700 | "films": [
701 | "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
702 | ],
703 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
704 | "url": "https://ghibliapi.herokuapp.com/people/5caa6a3c-7d44-47d1-afc6-12fffad28889"
705 | },
706 | {
707 | "id": "374561e6-aa8e-4d20-8579-806e589c0f31",
708 | "name": "Scarlet Rose",
709 | "gender": "Female",
710 | "age": "NA",
711 | "eye_color": "Brown",
712 | "hair_color": "Red",
713 | "films": [
714 | "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
715 | ],
716 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
717 | "url": "https://ghibliapi.herokuapp.com/people/374561e6-aa8e-4d20-8579-806e589c0f31"
718 | },
719 | {
720 | "id": "e9177769-9b6b-49c2-9820-d24f50b83ad9",
721 | "name": "Thomas",
722 | "gender": "Male",
723 | "age": "NA",
724 | "eye_color": "Green",
725 | "hair_color": "Black",
726 | "films": [
727 | "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
728 | ],
729 | "species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
730 | "url": "https://ghibliapi.herokuapp.com/people/e9177769-9b6b-49c2-9820-d24f50b83ad9"
731 | },
732 | {
733 | "id": "835f8c7c-2fc6-4f54-b545-c02ab066cd69",
734 | "name": "Custard",
735 | "gender": "Male",
736 | "age": "NA",
737 | "eye_color": "Blue",
738 | "hair_color": "Blonde",
739 | "films": [
740 | "https://ghibliapi.herokuapp.com/films/790e0028-a31c-4626-a694-86b7a8cada40"
741 | ],
742 | "species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
743 | "url": "https://ghibliapi.herokuapp.com/people/835f8c7c-2fc6-4f54-b545-c02ab066cd69"
744 | }
745 | ];
746 |
747 | export default MockPeopleResponse;
--------------------------------------------------------------------------------
/src/mocks/server.ts:
--------------------------------------------------------------------------------
1 | // src/mocks/server.js
2 | import { setupServer } from 'msw/node'
3 | import { handlers } from './handlers'
4 |
5 | // This configures a request mocking server with the given request handlers.
6 | export const server = setupServer(...handlers)
--------------------------------------------------------------------------------
/src/pages/Films.tsx:
--------------------------------------------------------------------------------
1 | import { SimpleGrid, Badge, Card, Group, Image, Text, useMantineTheme } from "@mantine/core";
2 | import Breadcrumbs from "../components/Breadcrumbs";
3 | import useFilmData, { Film } from "../hooks/useFilmData";
4 |
5 | export default function FilmsPage() {
6 | const { data, isLoading } = useFilmData();
7 | const theme = useMantineTheme();
8 |
9 | const secondaryColor = theme.colorScheme === 'dark'
10 | ? theme.colors.dark[1]
11 | : theme.colors.gray[7];
12 |
13 | return (
14 |
15 |
16 |
17 | {!isLoading && data && (
18 | data.map((film: Film) => {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 | {film.title}
27 |
28 | 🗓️ {film.release_date}
29 |
30 |
31 |
32 |
33 |
34 | {film.director}
35 |
36 |
37 |
38 | ⭐ {film.rt_score}
39 |
40 |
41 |
42 |
43 | {film.description}
44 |
45 |
46 | )
47 | })
48 | )}
49 |
50 |
51 | );
52 | }
--------------------------------------------------------------------------------
/src/pages/People.tsx:
--------------------------------------------------------------------------------
1 | import { SimpleGrid, Card, Group, Text, useMantineTheme } from "@mantine/core";
2 | import Breadcrumbs from "../components/Breadcrumbs";
3 | import usePeopleData, { Person } from "../hooks/usePeopleData";
4 |
5 | export default function PeoplePage() {
6 | const { data, isLoading } = usePeopleData();
7 | const theme = useMantineTheme();
8 |
9 | return (
10 |
11 |
12 |
13 | {!isLoading && data && (
14 | data.map((person: Person) => {
15 | return (
16 |
17 |
18 | {person.name}
19 |
20 |
21 |
22 |
23 | {person.gender === "Male" ? "👨" : person.gender === "Female" ? "👩" : "👽"} {person.gender}
24 |
25 |
26 |
27 | )
28 | })
29 | )}
30 |
31 |
32 | );
33 | }
--------------------------------------------------------------------------------
/src/pages/__tests__/Films.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import Films from '../Films';
3 | import { BrowserRouter } from 'react-router-dom';
4 |
5 | test('Should render film page properly', async () => {
6 | render();
7 |
8 | // Expect all Breadcrumbs text
9 | expect(await screen.findByText('Home')).toBeInTheDocument()
10 | expect(await screen.findByText('Films')).toBeInTheDocument()
11 |
12 | // Expect first row of films are visible
13 | expect(await screen.findByText('Castle in the Sky')).toBeVisible();
14 | expect(await screen.findByText('Grave of the Fireflies')).toBeVisible();
15 | expect(await screen.findByText('My Neighbor Totoro')).toBeVisible();
16 | expect(await screen.findByText(`Kiki's Delivery Service`)).toBeVisible();
17 | });
18 |
--------------------------------------------------------------------------------
/src/pages/__tests__/People.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import People from '../People';
3 | import { BrowserRouter } from 'react-router-dom';
4 |
5 | test('Should render people page properly', async () => {
6 | render();
7 |
8 | // Expect all Breadcrumbs text
9 | expect(await screen.findByText('Home')).toBeInTheDocument()
10 | expect(await screen.findByText('People')).toBeInTheDocument()
11 |
12 | // Expect first row of people are visible
13 | expect(await screen.findByText('Haku')).toBeVisible();
14 | expect(await screen.findByText('Pazu')).toBeVisible();
15 | expect(await screen.findByText('Lusheeta Toel Ul Laputa')).toBeVisible();
16 | expect(await screen.findByText(`Captain Dola`)).toBeVisible();
17 | });
18 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
7 | // src/setupTests.js
8 | import { server } from './mocks/server'
9 | // Establish API mocking before all tests.
10 | beforeAll(() => server.listen())
11 |
12 | // Reset any request handlers that we may add during the tests,
13 | // so they don't affect other tests.
14 | afterEach(() => server.resetHandlers())
15 |
16 | // Clean up after the tests are finished.
17 | afterAll(() => server.close())
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------