mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Added IoC example (#3595)
* Added `with-ioc` example * pre-compile deps until we get nextjs magic working
This commit is contained in:
parent
4b143fc232
commit
e401e2cf5f
16
examples/with-ioc/.babelrc
Normal file
16
examples/with-ioc/.babelrc
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"env": {
|
||||
"development": {
|
||||
"presets": ["next/babel"],
|
||||
"plugins": ["transform-decorators-legacy"]
|
||||
},
|
||||
"production": {
|
||||
"presets": ["next/babel"],
|
||||
"plugins": ["transform-decorators-legacy"]
|
||||
},
|
||||
"test": {
|
||||
"presets": [["next/babel", { "preset-env": { "modules": "commonjs" } }]],
|
||||
"plugins": ["transform-decorators-legacy"]
|
||||
}
|
||||
}
|
||||
}
|
43
examples/with-ioc/README.md
Normal file
43
examples/with-ioc/README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-ioc)
|
||||
# Dependency Injection (IoC) example ([ioc](https://github.com/alexindigo/ioc))
|
||||
|
||||
## How to use
|
||||
|
||||
### Using `create-next-app`
|
||||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-ioc with-ioc-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
||||
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-ioc
|
||||
cd with-ioc
|
||||
```
|
||||
|
||||
Install it and run:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||
|
||||
```bash
|
||||
now
|
||||
```
|
||||
|
||||
## The idea behind the example
|
||||
|
||||
This example uses [ioc](https://github.com/alexindigo/ioc) for dependency injection, which lets you create decoupled shared components and keep them free from implementation details of your app / other components.
|
||||
|
||||
It builds on top of [with-next-routes](https://github.com/zeit/next.js/tree/master/examples/with-next-routes) example and makes use of dependency injection to propagate custom `Link` component to other components.
|
||||
|
||||
Also, it illustrates ergonomics of testing using dependency injection.
|
|
@ -0,0 +1,91 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`With Snapshot Testing Blog renders components 1`] = `
|
||||
<div>
|
||||
<h1>
|
||||
Hi There!
|
||||
</h1>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"border": "1px dotted #ff0000",
|
||||
"marginTop": "5px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3>
|
||||
Component1
|
||||
</h3>
|
||||
Knows nothing about any custom \`Link\` or \`Router\` components or prop
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"border": "1px dashed #0000ff",
|
||||
"marginTop": "5px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3>
|
||||
Component2
|
||||
</h3>
|
||||
Knows nothing about any custom \`Link\` or \`Router\` components or prop
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"border": "1px dashed #00ff00",
|
||||
"marginTop": "5px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3>
|
||||
Endpoint
|
||||
</h3>
|
||||
Uses injected \`Link\` component without direct dependency on one
|
||||
<br />
|
||||
<a
|
||||
href="/about-us/baz"
|
||||
onClick={[Function]}
|
||||
>
|
||||
About: foo baz
|
||||
</a>
|
||||
<br />
|
||||
<a
|
||||
href="/"
|
||||
onClick={[Function]}
|
||||
>
|
||||
go Home
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"border": "1px dashed #00ff00",
|
||||
"marginTop": "5px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3>
|
||||
EndButton
|
||||
</h3>
|
||||
Uses injected \`Router\` component without direct dependency on one
|
||||
<br />
|
||||
<button
|
||||
onClick={[Function]}
|
||||
>
|
||||
Route to About foo bar
|
||||
</button>
|
||||
<br />
|
||||
<button
|
||||
onClick={[Function]}
|
||||
>
|
||||
go Home
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,34 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`With Snapshot Testing Blog renders components 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"border": "1px dashed #00ff00",
|
||||
"marginTop": "5px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h3>
|
||||
Endpoint
|
||||
</h3>
|
||||
Uses injected \`Link\` component without direct dependency on one
|
||||
<br />
|
||||
<div
|
||||
comment="mocked Link component"
|
||||
>
|
||||
<a>
|
||||
About: foo baz
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div
|
||||
comment="mocked Link component"
|
||||
>
|
||||
<a>
|
||||
go Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,44 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`With Snapshot Testing App shows "Menu" 1`] = `
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="/blog/hello-world"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Blog: Hello world
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/blog/another-blog-post"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Blog: Another blog post
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/blog/non-existant"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Blog: Not found
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onClick={[Function]}
|
||||
>
|
||||
About foo bar
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onClick={[Function]}
|
||||
>
|
||||
About foo baz
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
35
examples/with-ioc/__tests__/blog.page_with_provide.test.js
Normal file
35
examples/with-ioc/__tests__/blog.page_with_provide.test.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* eslint-env jest */
|
||||
|
||||
/*
|
||||
* Testing pages with @provide decorator:
|
||||
*
|
||||
* Snapshots – as usual
|
||||
*
|
||||
* Shallow rendering – need to `.dive()` one level deep,
|
||||
* as with any High Order Component.
|
||||
* Also `.html()` may cause havoc when it'd try to expand the render
|
||||
* but won't inject context since top level co,ponent has been rendered already.
|
||||
* This problem is not unique to IoC though, anything that relies on context (i.e. Redux)
|
||||
* is facing the same issue. Use `.debug()` or `mount()` instead
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme'
|
||||
import React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
|
||||
import App from '../pages/blog.js'
|
||||
|
||||
describe('With Enzyme', () => {
|
||||
it('Blog renders components', () => {
|
||||
const app = shallow(<App post={{title: 'Hi There!'}} />).dive()
|
||||
expect(app.find('h1').text()).toEqual('Hi There!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('With Snapshot Testing', () => {
|
||||
it('Blog renders components', () => {
|
||||
const component = renderer.create(<App post={{title: 'Hi There!'}} />)
|
||||
const tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,36 @@
|
|||
/* eslint-env jest */
|
||||
|
||||
/*
|
||||
* Individual component testing is pretty simple
|
||||
* just provide your dependencies as props
|
||||
* and add `.dive()` step to your shallow render,
|
||||
* as with any High Order Component.
|
||||
*
|
||||
* Remarks about `.html()` may apply,
|
||||
* depending if any of the children components
|
||||
* expect anything from the context
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme'
|
||||
import React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
|
||||
import Component from '../components/endpoint.js'
|
||||
|
||||
describe('With Enzyme', () => {
|
||||
it('Component renders with props', () => {
|
||||
// no need to mock Link component much for shallow rendering
|
||||
const injected = shallow(<Component Link={() => {}} />)
|
||||
const component = injected.dive()
|
||||
expect(component.find('h3').text()).toEqual('Endpoint')
|
||||
expect(component.find('Link').first().find('a').text()).toEqual('About: foo baz')
|
||||
})
|
||||
})
|
||||
|
||||
describe('With Snapshot Testing', () => {
|
||||
it('Blog renders components', () => {
|
||||
const component = renderer.create(<Component Link={(props) => <div comment={'mocked Link component'}>{props.children}</div>} />)
|
||||
const tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
/* eslint-env jest */
|
||||
|
||||
/*
|
||||
* Testing pages without @provide decorator as usual
|
||||
*/
|
||||
|
||||
import { shallow } from 'enzyme'
|
||||
import React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
|
||||
import App from '../pages/index.js'
|
||||
|
||||
describe('With Enzyme', () => {
|
||||
it('App shows "Menu"', () => {
|
||||
const app = shallow(<App />)
|
||||
expect(app.find('li a').first().text()).toEqual('Blog: Hello world')
|
||||
})
|
||||
})
|
||||
|
||||
describe('With Snapshot Testing', () => {
|
||||
it('App shows "Menu"', () => {
|
||||
const component = renderer.create(<App />)
|
||||
const tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
10
examples/with-ioc/components/component1.js
Normal file
10
examples/with-ioc/components/component1.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import Component2 from './component2'
|
||||
|
||||
export default () => (
|
||||
<div style={{ marginTop: '5px', border: '1px dotted #ff0000', padding: '10px' }}>
|
||||
<h3>Component1</h3>
|
||||
Knows nothing about any custom `Link` or `Router` components or prop
|
||||
<Component2 />
|
||||
</div>
|
||||
)
|
12
examples/with-ioc/components/component2.js
Normal file
12
examples/with-ioc/components/component2.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
import Endpoint from './endpoint'
|
||||
import EndButton from './endbutton'
|
||||
|
||||
export default () => (
|
||||
<div style={{ marginTop: '5px', border: '1px dashed #0000ff', padding: '10px' }}>
|
||||
<h3>Component2</h3>
|
||||
Knows nothing about any custom `Link` or `Router` components or prop
|
||||
<Endpoint />
|
||||
<EndButton />
|
||||
</div>
|
||||
)
|
23
examples/with-ioc/components/endbutton.js
Normal file
23
examples/with-ioc/components/endbutton.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from 'react'
|
||||
import { inject } from 'ioc'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
@inject({
|
||||
Router: PropTypes.object
|
||||
})
|
||||
export default class extends React.Component {
|
||||
render () {
|
||||
const { Router } = this.props
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: '5px', border: '1px dashed #00ff00', padding: '10px' }}>
|
||||
<h3>EndButton</h3>
|
||||
Uses injected `Router` component without direct dependency on one
|
||||
<br />
|
||||
<button onClick={() => Router.pushRoute('about', { foo: 'bar' })}>Route to About foo bar</button>
|
||||
<br />
|
||||
<button onClick={() => Router.pushRoute('/')}>go Home</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
29
examples/with-ioc/components/endpoint.js
Normal file
29
examples/with-ioc/components/endpoint.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from 'react'
|
||||
import { inject } from 'ioc'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
@inject({
|
||||
// keep it `isRequired`-free to allow mock injection via props
|
||||
Link: PropTypes.func
|
||||
})
|
||||
export default class extends React.Component {
|
||||
static propTypes = {
|
||||
// you can add `isRequired` to the component's propTypes definition
|
||||
Link: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { Link } = this.props
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: '5px', border: '1px dashed #00ff00', padding: '10px' }}>
|
||||
<h3>Endpoint</h3>
|
||||
Uses injected `Link` component without direct dependency on one
|
||||
<br />
|
||||
<Link route='about' params={{ foo: 'baz' }}><a>About: foo baz</a></Link>
|
||||
<br />
|
||||
<Link route='/'><a>go Home</a></Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
4
examples/with-ioc/jest.config.js
Normal file
4
examples/with-ioc/jest.config.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
setupFiles: ['<rootDir>/jest.setup.js'],
|
||||
testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/']
|
||||
}
|
4
examples/with-ioc/jest.setup.js
Normal file
4
examples/with-ioc/jest.setup.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { configure } from 'enzyme'
|
||||
import Adapter from 'enzyme-adapter-react-16'
|
||||
|
||||
configure({ adapter: new Adapter() })
|
26
examples/with-ioc/package.json
Normal file
26
examples/with-ioc/package.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "with-ioc",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "NODE_ENV=test jest",
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV=production node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ioc": "1.0.3",
|
||||
"next": "latest",
|
||||
"next-routes": "^1.0.17",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-transform-decorators-legacy": "1.3.4",
|
||||
"enzyme": "3.3.0",
|
||||
"enzyme-adapter-react-16": "1.1.1",
|
||||
"jest": "22.1.3",
|
||||
"react-addons-test-utils": "15.6.2",
|
||||
"react-test-renderer": "16.2.0"
|
||||
}
|
||||
}
|
1
examples/with-ioc/pages/about.js
Normal file
1
examples/with-ioc/pages/about.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default props => <h1>About foo {props.url.query.foo} – no `Link` ({typeof props.Link}) available here</h1>
|
41
examples/with-ioc/pages/blog.js
Normal file
41
examples/with-ioc/pages/blog.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React from 'react'
|
||||
import { provide, types } from 'ioc'
|
||||
import { Link, Router } from '../routes'
|
||||
import Component1 from '../components/component1'
|
||||
|
||||
const posts = [
|
||||
{ slug: 'hello-world', title: 'Hello world' },
|
||||
{ slug: 'another-blog-post', title: 'Another blog post' }
|
||||
]
|
||||
|
||||
@provide({
|
||||
@types.func.isRequired
|
||||
Link,
|
||||
|
||||
@types.object
|
||||
Router
|
||||
})
|
||||
export default class extends React.Component {
|
||||
static async getInitialProps ({ query, res }) {
|
||||
const post = posts.find(post => post.slug === query.slug)
|
||||
|
||||
if (!post && res) {
|
||||
res.statusCode = 404
|
||||
}
|
||||
|
||||
return { post }
|
||||
}
|
||||
|
||||
render () {
|
||||
const { post } = this.props
|
||||
|
||||
if (!post) return <h1>Post not found</h1>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{post.title}</h1>
|
||||
<Component1 />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
11
examples/with-ioc/pages/index.js
Normal file
11
examples/with-ioc/pages/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Link, Router } from '../routes'
|
||||
|
||||
export default () => (
|
||||
<ul>
|
||||
<li><Link route='blog' params={{ slug: 'hello-world' }}><a>Blog: Hello world</a></Link></li>
|
||||
<li><Link route='blog' params={{ slug: 'another-blog-post' }}><a>Blog: Another blog post</a></Link></li>
|
||||
<li><Link route='blog' params={{ slug: 'non-existant' }}><a>Blog: Not found</a></Link></li>
|
||||
<li><button onClick={() => Router.pushRoute('about', { foo: 'bar' })}>About foo bar</button></li>
|
||||
<li><button onClick={() => Router.pushRoute('about', { foo: 'baz' })}>About foo baz</button></li>
|
||||
</ul>
|
||||
)
|
5
examples/with-ioc/routes.js
Normal file
5
examples/with-ioc/routes.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const nextRoutes = require('next-routes')
|
||||
const routes = module.exports = nextRoutes()
|
||||
|
||||
routes.add('blog', '/blog/:slug')
|
||||
routes.add('about', '/about-us/:foo(bar|baz)')
|
17
examples/with-ioc/server.js
Normal file
17
examples/with-ioc/server.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
const { createServer } = require('http')
|
||||
const next = require('next')
|
||||
const routes = require('./routes')
|
||||
|
||||
const port = parseInt(process.env.PORT, 10) || 3000
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
const handler = routes.getRequestHandler(app)
|
||||
|
||||
app.prepare()
|
||||
.then(() => {
|
||||
createServer(handler)
|
||||
.listen(port, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue