Skip to content

Commit

Permalink
chore(core): Add core tests (#16)
Browse files Browse the repository at this point in the history
* Add core tests

- Tests cover Form and Controller
- You can check code coverage with "yarn test --coverage"

* A little formatting + form test modifications

* add back console log

Co-authored-by: Yanick Bélanger <yanick.belanger@yahoo.com>
Co-authored-by: Brandon Bayer <b@bayer.ws>
  • Loading branch information
3 people authored Mar 2, 2020
1 parent 8f6d0e0 commit 4113124
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 57 deletions.
1 change: 1 addition & 0 deletions packages/core/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('@testing-library/jest-dom')
7 changes: 7 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"pre-commit": "tsdx lint"
}
},
"jest": {
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.js"
]
},
"engines": {
"yarn": "^1.19.1",
"node": ">=12.16.1"
Expand All @@ -46,6 +51,8 @@
"next": "^9.2.2-canary.21"
},
"devDependencies": {
"@testing-library/react": "9.4.1",
"@testing-library/jest-dom": "5.1.1",
"@types/react": "16.9.23",
"next": "9.2.3-canary.8",
"prisma2": "2.0.0-preview022"
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import Router from 'next/router'
import React from 'react'

export default function({children, action, method, ...props}: {[index: string]: any}) {
const [i, setI] = React.useState(0)
Expand All @@ -22,10 +22,9 @@ export default function({children, action, method, ...props}: {[index: string]:
const form = event.target as HTMLFormElement
const data = new URLSearchParams()

for (const pair of new FormData(form).entries()) {
console.log(pair[0], pair[1])
for (const [key, value] of new FormData(form).entries()) {
// TODO: handle file types
data.append(pair[0], pair[1] as string)
data.append(key, value.toString())
}

const res = await fetch(action, {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {NextApiRequest, NextApiResponse} from 'next'
import {PrismaClient} from '@prisma/client'
import prettyMs from 'pretty-ms'
import {NextApiRequest, NextApiResponse} from 'next'
import permit from 'permit-params'
import {isServer} from './utils'
import prettyMs from 'pretty-ms'
import {ControllerInput, ControllerInstance} from '../types/controller'
import {isServer} from './utils'

export {default as Form} from './components/Form'

Expand Down
7 changes: 0 additions & 7 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,2 @@
export * from './components'
export * from './controller'

export const sum = (a: number, b: number) => {
if ('development' === process.env.NODE_ENV) {
console.log('boop')
}
return a + b
}
7 changes: 0 additions & 7 deletions packages/core/test/blah.test.ts

This file was deleted.

60 changes: 60 additions & 0 deletions packages/core/test/components/Form.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {fireEvent, render, RenderResult, wait} from '@testing-library/react'
import Router from 'next/router'
import React, {FC} from 'react'
import Form from '../../src/components/Form'
jest.mock('next/router')

/**
* Creates a mock implementation of fetch which will resolve asynchronously with the provided `res` param
* @param res The response that will be sent back
*/
function mockFetch(res: any = {ok: true}) {
Object.assign(global, {
fetch: jest.fn().mockImplementation(async () => res),
})
}

describe('Form', () => {
beforeAll(() => {
mockFetch()
})

const TestHarness: FC = () => (
<Form>
<label htmlFor="title">Title</label>
<input id="title" name="title" type="text" defaultValue="test" />
<button type="submit">Submit</button>>
</Form>
)

const submitForm = async (page: RenderResult) => {
const form = await page.findByRole('form')
const button = await page.findByRole('button')
fireEvent.click(button, {target: form})
await wait()
}

it('renders', async () => {
const page = await render(<TestHarness />)
expect(page.getByLabelText('Title')).toHaveValue('test')
})

describe('with redirect', () => {
beforeAll(() => {
const headers = {Location: 'location', 'x-as': 'xas'} as any
mockFetch({
ok: true,
headers: {
get: jest.fn().mockImplementation(key => headers[key]),
},
})
})

it('triggers routing', async () => {
const page = await render(<TestHarness />)
await submitForm(page)

expect(Router.push).toBeCalledWith('location', 'xas')
})
})
})
45 changes: 45 additions & 0 deletions packages/core/test/controller-fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Controller, harnessServerProps} from '../src/controller'

const SimpleController = Controller(() => ({
name: 'SimpleController',

async index() {
return {status: 200, data: {message: 'indexed'}}
},

async show() {
return {status: 200, data: {message: 'shown'}}
},

async create() {
return {
status: 201,
data: {message: 'created'},
}
},

async update() {
return {
status: 200,
data: {message: 'updated'},
}
},

async delete() {
return {
status: 204,
data: {},
}
},
}))

const RedirectController = Controller(() => ({
name: 'RedirectController',

async create() {
return {redirect: {href: 'href', as: 'as'}}
},
}))

export const unstable_getSimpleServerProps = harnessServerProps(SimpleController)
export const unstable_getRedirectServerProps = harnessServerProps(RedirectController)
76 changes: 76 additions & 0 deletions packages/core/test/controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as fixtures from './controller-fixtures'

describe('controller', () => {
const createContext = (opts: any = {}) => ({
query: {},
...opts,
req: {method: 'GET', url: '/', socket: {remoteAddress: 'testAddress'}, ...opts.req},
res: {status: jest.fn(), ...opts.res},
})

const createSimpleRequest = (ctx: any) => fixtures.unstable_getSimpleServerProps(ctx)
const createRedirectRequest = (ctx: any) => fixtures.unstable_getRedirectServerProps(ctx)

it('simple index response', async () => {
const ctx = createContext()
const returning = (await createSimpleRequest(ctx)) as {props: any}
expect(returning.props).toMatchObject({message: 'indexed'})
expect(ctx.res.status).toBeCalledWith(200)
})

it('simple show response', async () => {
const ctx = createContext({query: {id: 123}})
const returning = (await createSimpleRequest(ctx)) as {props: any}
expect(returning.props).toMatchObject({message: 'shown'})
expect(ctx.res.status).toBeCalledWith(200)
})

it('simple create response', async () => {
const ctx = createContext({req: {method: 'POST'}})
const returning = (await createSimpleRequest(ctx)) as {props: any}
expect(returning.props).toMatchObject({message: 'created'})
})

it('simple update response', async () => {
const ctx = createContext({req: {method: 'PATCH'}})
const returning = (await createSimpleRequest(ctx)) as {props: any}
expect(returning.props).toMatchObject({message: 'updated'})
})

it('simple delete response', async () => {
const ctx = createContext({req: {method: 'DELETE'}})
await createSimpleRequest(ctx)
expect(ctx.res.status).toBeCalledWith(204)
})

it('simple head response', async () => {
const ctx = createContext({
req: {method: 'HEAD'},
res: {status: jest.fn().mockImplementation(() => ctx.res), end: jest.fn()},
})
await createSimpleRequest(ctx)
expect(ctx.res.status).toBeCalledWith(204)
expect(ctx.res.end).toBeCalled()
})

it('simple unknown response', async () => {
const ctx = createContext({req: {method: 'UNKNOWN'}, res: {status: jest.fn(), end: jest.fn()}})
await createSimpleRequest(ctx)
expect(ctx.res.status).toBeCalledWith(404)
})

it('simple api response', async () => {
const ctx = createContext({req: {url: '/api/simple'}, res: {status: jest.fn(), json: jest.fn()}})
await createSimpleRequest(ctx)
expect(ctx.res.json).toBeCalledWith({message: 'indexed'})
expect(ctx.res.status).toBeCalledWith(200)
})

it('redirect response', async () => {
const ctx = createContext({req: {method: 'POST'}, res: {setHeader: jest.fn(), end: jest.fn()}})
await createRedirectRequest(ctx)

expect(ctx.res.setHeader).toBeCalledWith('Location', 'href')
expect(ctx.res.setHeader).toBeCalledWith('x-as', 'as')
})
})
Loading

0 comments on commit 4113124

Please sign in to comment.