The tests for each component has been set up. The way on writing tests have been patterned from https://kentcdodds.com/blog/common-mistakes-with-react-testing-library. In addition, the requirements document has been modified to use a consistent formatting which can be translated into code.master
@@ -1,30 +1,26 @@ | |||
# Requirements | |||
- As a client, I want to log in to the service. | |||
- [ ] In the front-end, the client requests login by providing a username and a password to the back-end. | |||
- [ ] In the front-end, the client provides a username, and a password to request login to the back-end. | |||
- [ ] In the back-end, the server processes the login request from the front-end. | |||
- As a client, I want to log out from the service. | |||
- [ ] In the front-end, the client requests logout to the backend. | |||
- [ ] In the back-end, the server processes the logout request from the front-end. | |||
- As a client, I want to search for available ringtones. | |||
- [ ] In the front-end, the client requests ringtones by providing a search keyword to the back-end. | |||
- [ ] In the back-end, the server retrieves the ringtones whose name matches the search keyword provided by the | |||
front-end. | |||
- As a client, I want to download a ringtone. | |||
- [ ] In the front-end, the client requests the data of a ringtone from the back-end. | |||
- [ ] In the front-end, the client provides a ringtone ID to request a ringtone from the back-end. | |||
- [ ] In the back-end, the server sends the ringtone data to the front-end. | |||
- As a client, I want to search for composers. | |||
- [ ] In the front-end, the client requests composers by providing a search keyword to the back-end. | |||
- [ ] In the back-end, the server retrieves the composers whose name matches the search keyword provided by the | |||
front-end. | |||
- As a client, I want to search for composers and ringtones. | |||
- [ ] In the front-end, the client requests provides a search keyword to request for composers and ringtones from the back-end. | |||
- [ ] In the back-end, the server retrieves the composers and ringtones whose name matches the search keyword provided | |||
by the front-end. | |||
- As a composer, I want to create a ringtone. | |||
- [ ] In the front-end, the client inputs ringtone data to the view. | |||
- [ ] In the front-end, the client sends the ringtone data to the back-end. | |||
- [ ] In the back-end, the server stores the ringtone. | |||
- As a composer, I want to update a ringtone. | |||
- [ ] In the front-end, the client modifies the ringtone data loaded on the view. | |||
- [ ] In the front-end, the client modifies the ringtone data retrieved from the back-end and loaded on the view. | |||
- [ ] In the front-end, the client sends the ringtone data to the back-end. | |||
- [ ] In the back-end, the server stores the ringtone. | |||
- As a composer, I want to delete a ringtone. | |||
- [ ] In the front-end, the client requests deletion of a ringtone to the back-end. | |||
- [ ] In the back-end, the server deletes the ringtone. | |||
- [ ] In the front-end, the client provides a ringtone ID to request a ringtone's deletion to the back-end. | |||
- [ ] In the back-end, the server deletes the ringtone if its ID matches the one provided by the front-end. |
@@ -10,5 +10,6 @@ module.exports = { | |||
'src/components/**/*.{jsx,tsx}', | |||
'src/modules/**/*.{js,ts}', | |||
'src/utils/**/*.{js,ts}', | |||
] | |||
], | |||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'] | |||
}; |
@@ -0,0 +1 @@ | |||
import '@testing-library/jest-dom' |
@@ -18,6 +18,7 @@ | |||
"styled-components": "^5.3.0" | |||
}, | |||
"devDependencies": { | |||
"@testing-library/jest-dom": "^5.12.0", | |||
"@testing-library/react": "^11.2.6", | |||
"@types/jest": "^26.0.23", | |||
"@types/node": "^15.0.2", | |||
@@ -1,22 +1,19 @@ | |||
import {cleanup, render} from '@testing-library/react' | |||
import {render, screen} from '@testing-library/react' | |||
import ActionButton from '.' | |||
describe('button component for triggering actions', () => { | |||
afterEach(() => { | |||
cleanup() | |||
}) | |||
it('should render a button element with a no-op action', () => { | |||
const result = render(<ActionButton />) | |||
const input = result.queryByRole('button') | |||
expect(input).not.toBeNull() | |||
render(<ActionButton />) | |||
expect(screen.getByRole('button')).toBeInTheDocument() | |||
}) | |||
describe.each(['button', 'reset', 'submit'] as const)('on %p action', (type) => { | |||
beforeEach(() => { | |||
render(<ActionButton type={type} />) | |||
}) | |||
it('should render a button element with a submit action', () => { | |||
const result = render(<ActionButton type={type} />) | |||
const input = result.queryByRole('button') as HTMLButtonElement | |||
expect(input.type).toBe(type) | |||
expect(screen.getByRole('button')).toHaveAttribute('type', type) | |||
}) | |||
}) | |||
}) |
@@ -1,21 +1,15 @@ | |||
import {render, cleanup} from '@testing-library/react' | |||
import {render, screen} from '@testing-library/react' | |||
import TextInput from '.' | |||
describe('single-line text input component', () => { | |||
afterEach(() => { | |||
cleanup() | |||
}) | |||
it('should contain a text input element', () => { | |||
const result = render(<TextInput label="" name="" />) | |||
const input = result.queryByRole('textbox') | |||
expect(input).not.toBeNull() | |||
render(<TextInput label="" name="" />) | |||
expect(screen.getByRole('textbox')).toBeInTheDocument() | |||
}) | |||
it('should acquire a descriptive label', () => { | |||
const label = 'foo' | |||
const result = render(<TextInput label={label} name="" />) | |||
const input = result.queryByLabelText(label) | |||
expect(input).not.toBeNull() | |||
render(<TextInput label={label} name="" />) | |||
expect(screen.getByRole('textbox', { name: label, })).toBeInTheDocument() | |||
}) | |||
}) |
@@ -1,21 +1,15 @@ | |||
import {render, cleanup} from '@testing-library/react' | |||
import {render, screen} from '@testing-library/react' | |||
import TextInput from '.' | |||
describe('single-line text input component', () => { | |||
afterEach(() => { | |||
cleanup() | |||
}) | |||
it('should contain a text input element', () => { | |||
const result = render(<TextInput label="" name="" />) | |||
const input = result.queryByRole('textbox') | |||
expect(input).not.toBeNull() | |||
render(<TextInput label="" name="" />) | |||
expect(screen.getByRole('textbox')).toBeInTheDocument() | |||
}) | |||
it('should acquire a descriptive label', () => { | |||
const label = 'foo' | |||
const result = render(<TextInput label={label} name="" />) | |||
const input = result.queryByLabelText(label) | |||
expect(input).not.toBeNull() | |||
render(<TextInput label={label} name="" />) | |||
expect(screen.getByLabelText(label)).toBeInTheDocument() | |||
}) | |||
}) |
@@ -0,0 +1,15 @@ | |||
import { render, screen } from '@testing-library/react' | |||
import Link from '.' | |||
describe('link component', () => { | |||
it('should render an anchor element', () => { | |||
render( | |||
<Link | |||
href={{ | |||
pathname: 'foo', | |||
}} | |||
/> | |||
) | |||
expect(screen.getByRole('link')).toBeInTheDocument() | |||
}) | |||
}) |
@@ -0,0 +1,33 @@ | |||
import {render, screen} from '@testing-library/react' | |||
import CreateRingtoneForm from '.' | |||
describe('form for creating ringtones', () => { | |||
beforeEach(() => { | |||
render( | |||
<CreateRingtoneForm | |||
labels={{ | |||
form: 'Create Ringtone', | |||
name: 'Name', | |||
data: 'Data', | |||
cta: 'Post', | |||
}} | |||
/> | |||
) | |||
}) | |||
it('should render a form', () => { | |||
expect(screen.getByRole('form', { name: 'Create Ringtone' })).toBeInTheDocument() | |||
}) | |||
it('should render a textbox for the ringtone name', () => { | |||
expect(screen.getByRole('textbox', { name: 'Name' })).toBeInTheDocument() | |||
}) | |||
it('should render a textbox for the ringtone data', () => { | |||
expect(screen.getByRole('textbox', { name: 'Data' })).toBeInTheDocument() | |||
}) | |||
it('should render a button for submitting the ringtone data', () => { | |||
expect(screen.getByRole('button', { name: 'Post' })).toBeInTheDocument() | |||
}) | |||
}) |
@@ -10,25 +10,38 @@ const Form = styled('form')({ | |||
}) | |||
type Props = { | |||
onSubmit?: FormEventHandler | |||
onSubmit?: FormEventHandler, | |||
action?: string, | |||
labels: Record<string, string>, | |||
} | |||
const CreateRingtoneForm: FC<Props> = ({ | |||
onSubmit, | |||
action, | |||
labels, | |||
}) => { | |||
return ( | |||
<Form | |||
onSubmit={onSubmit} | |||
method="post" | |||
action="/api/a/create/ringtone" | |||
action={action} | |||
aria-label={labels['form']} | |||
> | |||
<TextInput label="Name" name="name" block /> | |||
<TextArea label="Data" name="data" block /> | |||
<TextInput | |||
label={labels['name'] || 'Name'} | |||
name="name" | |||
block | |||
/> | |||
<TextArea | |||
label={labels['data'] || 'Data'} | |||
name="data" | |||
block | |||
/> | |||
<ActionButton | |||
type="submit" | |||
block | |||
> | |||
Post | |||
{labels['cta'] || 'Post'} | |||
</ActionButton> | |||
</Form> | |||
) | |||
@@ -0,0 +1,27 @@ | |||
import {render, screen} from '@testing-library/react' | |||
import OmnisearchForm from '.' | |||
describe('form for searching everything', () => { | |||
beforeEach(() => { | |||
render( | |||
<OmnisearchForm | |||
labels={{ | |||
form: 'Search', | |||
query: 'Query', | |||
}} | |||
/> | |||
) | |||
}) | |||
it('should render a form', () => { | |||
expect(screen.getByRole('form', { name: 'Search' })).toBeInTheDocument() | |||
}) | |||
it('should render a textbox for the search query', () => { | |||
expect(screen.getByRole('textbox', { name: 'Query' })).toBeInTheDocument() | |||
}) | |||
it('should render a button for submitting the search query', () => { | |||
expect(screen.getByRole('button', { name: 'Search' })).toBeInTheDocument() | |||
}) | |||
}) |
@@ -0,0 +1,38 @@ | |||
import {FC, FormEventHandler} from 'react' | |||
import TextInput from '../../../molecules/forms/TextInput' | |||
import ActionButton from '../../../molecules/forms/ActionButton'; | |||
type Props = { | |||
onSubmit?: FormEventHandler, | |||
action?: string, | |||
labels: Record<string, string>, | |||
} | |||
const OmnisearchForm: FC<Props> = ({ | |||
onSubmit, | |||
labels, | |||
action, | |||
}) => { | |||
return ( | |||
<form | |||
method="get" | |||
onSubmit={onSubmit} | |||
action={action} | |||
aria-label={labels['form']} | |||
> | |||
<TextInput | |||
label={labels['query'] || 'Query'} | |||
name="q" | |||
block | |||
/> | |||
<ActionButton | |||
type="submit" | |||
block | |||
> | |||
Search | |||
</ActionButton> | |||
</form> | |||
) | |||
} | |||
export default OmnisearchForm |
@@ -2,16 +2,29 @@ import {FC, FormEventHandler} from 'react' | |||
import { LeftSidebarWithMenu } from '@tesseract-design/viewfinder' | |||
import CreateRingtoneForm from '../../organisms/forms/CreateRingtone' | |||
import Link from '../../molecules/navigation/Link' | |||
import OmnisearchForm from '../../organisms/forms/Omnisearch' | |||
type Props = { | |||
onSearch?: FormEventHandler, | |||
onSubmit?: FormEventHandler | |||
} | |||
const CreateRingtoneTemplate: FC<Props> = ({ | |||
onSearch, | |||
onSubmit, | |||
}) => { | |||
return ( | |||
<LeftSidebarWithMenu.Layout | |||
topBarCenter={ | |||
<OmnisearchForm | |||
labels={{ | |||
form: 'Search', | |||
query: 'Query', | |||
}} | |||
onSubmit={onSearch} | |||
action="/api/a/search" | |||
/> | |||
} | |||
linkComponent={({ url, icon, label, }) => ( | |||
<Link | |||
href={url} | |||
@@ -68,6 +81,13 @@ const CreateRingtoneTemplate: FC<Props> = ({ | |||
<LeftSidebarWithMenu.ContentContainer> | |||
<CreateRingtoneForm | |||
onSubmit={onSubmit} | |||
action="/api/a/create/ringtone" | |||
labels={{ | |||
form: 'Create Ringtone', | |||
name: 'Name', | |||
data: 'Data', | |||
cta: 'Post', | |||
}} | |||
/> | |||
</LeftSidebarWithMenu.ContentContainer> | |||
</LeftSidebarWithMenu.Layout> | |||
@@ -0,0 +1,62 @@ | |||
import {URLSearchParams} from 'url'; | |||
enum Method { | |||
HEAD = 'HEAD', | |||
GET = 'GET', | |||
POST = 'POST', | |||
PUT = 'PUT', | |||
PATCH = 'PATCH', | |||
DELETE = 'DELETE', | |||
} | |||
type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response> | |||
type CreateFetchClientOptions = { | |||
baseUrl: string, | |||
headers?: HeadersInit, | |||
fetch: Fetch, | |||
} | |||
export type FetchClientParams = { | |||
url: string, | |||
method: Method, | |||
body?: BodyInit, | |||
query?: URLSearchParams | string | NodeJS.Dict<string | ReadonlyArray<string>> | Iterable<[string, string]> | ReadonlyArray<[string, string]>, | |||
headers?: HeadersInit, | |||
fetch?: Fetch, | |||
} | |||
type FetchClient = (params: FetchClientParams) => Promise<Response> | |||
type CreateFetchClient = (options: CreateFetchClientOptions) => FetchClient | |||
export const createFetchClient: CreateFetchClient = ({ | |||
baseUrl, | |||
headers: defaultHeaders = {}, | |||
fetch: f = fetch, | |||
}) => { | |||
return ({ | |||
url, | |||
method, | |||
body, | |||
query, | |||
headers = {}, | |||
fetch: ff = f, | |||
}) => { | |||
const theUrl = new URL(url, baseUrl) | |||
if (Boolean(query as unknown)) { | |||
theUrl.search = new URLSearchParams(query).toString() | |||
} | |||
return ff(theUrl.toString(), { | |||
method, | |||
body, | |||
// TODO handle any kind of headers init | |||
headers: { | |||
...defaultHeaders, | |||
...headers, | |||
}, | |||
}) | |||
} | |||
} | |||
export const createMockFetch = (body: BodyInit, response: ResponseInit): Fetch => async () => new Response(body, response) |
@@ -1,7 +1,12 @@ | |||
{ | |||
"extends": "./tsconfig.json", | |||
"compilerOptions": { | |||
"jsx": "react-jsx", | |||
"types": ["jest"] | |||
}, | |||
"extends": "./tsconfig.json", | |||
"include": [ | |||
"./jest.setup.ts" | |||
], | |||
"compilerOptions": { | |||
"jsx": "react-jsx", | |||
"types": [ | |||
"jest" | |||
] | |||
} | |||
} |
@@ -280,7 +280,7 @@ | |||
dependencies: | |||
regenerator-runtime "^0.13.4" | |||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5": | |||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2": | |||
version "7.14.0" | |||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" | |||
integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== | |||
@@ -697,6 +697,20 @@ | |||
lz-string "^1.4.4" | |||
pretty-format "^26.6.2" | |||
"@testing-library/jest-dom@^5.12.0": | |||
version "5.12.0" | |||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz#6a5d340b092c44b7bce17a4791b47d9bc2c61443" | |||
integrity sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow== | |||
dependencies: | |||
"@babel/runtime" "^7.9.2" | |||
"@types/testing-library__jest-dom" "^5.9.1" | |||
aria-query "^4.2.2" | |||
chalk "^3.0.0" | |||
css "^3.0.0" | |||
css.escape "^1.5.1" | |||
lodash "^4.17.15" | |||
redent "^3.0.0" | |||
"@testing-library/react@^11.2.6": | |||
version "11.2.6" | |||
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" | |||
@@ -782,7 +796,7 @@ | |||
dependencies: | |||
"@types/istanbul-lib-report" "*" | |||
"@types/jest@^26.0.23": | |||
"@types/jest@*", "@types/jest@^26.0.23": | |||
version "26.0.23" | |||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.23.tgz#a1b7eab3c503b80451d019efb588ec63522ee4e7" | |||
integrity sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA== | |||
@@ -853,6 +867,13 @@ | |||
"@types/react" "*" | |||
csstype "^3.0.2" | |||
"@types/testing-library__jest-dom@^5.9.1": | |||
version "5.9.5" | |||
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz#5bf25c91ad2d7b38f264b12275e5c92a66d849b0" | |||
integrity sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ== | |||
dependencies: | |||
"@types/jest" "*" | |||
"@types/yargs-parser@*": | |||
version "20.2.0" | |||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" | |||
@@ -1516,6 +1537,14 @@ chalk@^1.0.0, chalk@^1.1.3: | |||
strip-ansi "^3.0.0" | |||
supports-color "^2.0.0" | |||
chalk@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" | |||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== | |||
dependencies: | |||
ansi-styles "^4.1.0" | |||
supports-color "^7.1.0" | |||
chalk@^4.0.0, chalk@^4.1.0: | |||
version "4.1.1" | |||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" | |||
@@ -1841,11 +1870,20 @@ css-to-react-native@^3.0.0: | |||
css-color-keywords "^1.0.0" | |||
postcss-value-parser "^4.0.2" | |||
css.escape@1.5.1: | |||
css.escape@1.5.1, css.escape@^1.5.1: | |||
version "1.5.1" | |||
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" | |||
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= | |||
css@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" | |||
integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== | |||
dependencies: | |||
inherits "^2.0.4" | |||
source-map "^0.6.1" | |||
source-map-resolve "^0.6.0" | |||
cssnano-preset-simple@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/cssnano-preset-simple/-/cssnano-preset-simple-2.0.0.tgz#b55e72cb970713f425560a0e141b0335249e2f96" | |||
@@ -2840,6 +2878,11 @@ indent-string@^3.0.0: | |||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" | |||
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= | |||
indent-string@^4.0.0: | |||
version "4.0.0" | |||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" | |||
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== | |||
inflight@^1.0.4: | |||
version "1.0.6" | |||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" | |||
@@ -3847,7 +3890,7 @@ lodash.sortby@^4.7.0: | |||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" | |||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= | |||
lodash@4.x, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: | |||
lodash@4.x, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: | |||
version "4.17.21" | |||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" | |||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== | |||
@@ -3997,6 +4040,11 @@ mimic-fn@^2.1.0: | |||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" | |||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== | |||
min-indent@^1.0.0: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" | |||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== | |||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: | |||
version "1.0.1" | |||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" | |||
@@ -4777,6 +4825,14 @@ readdirp@~3.5.0: | |||
dependencies: | |||
picomatch "^2.2.1" | |||
redent@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" | |||
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== | |||
dependencies: | |||
indent-string "^4.0.0" | |||
strip-indent "^3.0.0" | |||
regenerator-runtime@^0.13.4: | |||
version "0.13.7" | |||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" | |||
@@ -5139,6 +5195,14 @@ source-map-resolve@^0.5.0: | |||
source-map-url "^0.4.0" | |||
urix "^0.1.0" | |||
source-map-resolve@^0.6.0: | |||
version "0.6.0" | |||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" | |||
integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== | |||
dependencies: | |||
atob "^2.1.2" | |||
decode-uri-component "^0.2.0" | |||
source-map-support@^0.5.6: | |||
version "0.5.19" | |||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" | |||
@@ -5408,6 +5472,13 @@ strip-final-newline@^2.0.0: | |||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" | |||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== | |||
strip-indent@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" | |||
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== | |||
dependencies: | |||
min-indent "^1.0.0" | |||
styled-components@^5.3.0: | |||
version "5.3.0" | |||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.0.tgz#e47c3d3e9ddfff539f118a3dd0fd4f8f4fb25727" | |||