Skip to content

Commit

Permalink
add first jest unit tests and fix another bug in loadInitialProps (ja…
Browse files Browse the repository at this point in the history
…redpalmer#178)

* add first jest unit tests and fix bug in loadInitialProps

* remove unused dep

* fix typo for test Component

* fix typo in mock NonDymamicImport

* remove unnecessary cast in loadInitialProps

* revert Async -> After name changes
  • Loading branch information
dagda1 authored and jaredpalmer committed Oct 2, 2018
1 parent 4bf6dfe commit 3e4f2f4
Show file tree
Hide file tree
Showing 17 changed files with 4,517 additions and 420 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ node_modules
*.log
package-lock.json
build
.vscode
.vscode
*.map
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ immediately in all the examples.
- `yarn run clean`: Clean up all `node_modules` and remove all symlinks from packages and examples.
- `yarn run bootstrap`: Run `yarn` on all examples and packages. Automatically symlinks inter-dependent modules.
- `yarn run e2e`: Runs end-to-end tests
- `yarn test`: Run the jest tests in watch mode
- `yarn test:ci`: Run the tests without watch and probably in a CI environment

### Updating your fork

Expand Down
4 changes: 2 additions & 2 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"@jaredpalmer/after": "^0.5.2",
"express": "^4.16.2",
"raf": "^3.4.0",
"razzle": "^2.0.0-alpha.5",
"razzle": "^2.4.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-helmet": "^5.2.0",
"react-router-dom": "^4.2.2"
}
}
}
36 changes: 20 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"prepare": "tsc -p tsconfig.build.json",
"start": "tsc -p tsconfig.build.json -w",
"format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'",
"precommit": "lint-staged"
"precommit": "lint-staged",
"test": "razzle test --env=jsdom --no-cache",
"test:ci": "CI=true yarn test"
},
"lint-staged": {
"*.js": [
Expand All @@ -30,19 +32,21 @@
]
},
"jest": {
"testPathIgnorePatterns": [
"<rootDir>/coverage/",
"<rootDir>/node_modules/",
"<rootDir>/examples/"
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testMatch": [
"<rootDir>/src/test/**/*test.ts?(x)"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json"
],
"collectCoverageFrom": [
"**/*.js"
],
"coveragePathIgnorePatterns": [
"<rootDir>/node_modules/",
"<rootDir>/packages/*/node_modules/",
"<rootDir>/examples/",
"<rootDir>/coverage/"
]
},
"peerDependencies": {
Expand All @@ -54,6 +58,7 @@
},
"devDependencies": {
"@types/express": "^4.16.0",
"@types/jest": "^23.3.2",
"@types/node": "^10.9.2",
"@types/react": "^16.4.14",
"@types/react-dom": "^16.0.7",
Expand All @@ -62,19 +67,18 @@
"@types/serialize-javascript": "^1.3.2",
"express": "^4.16.2",
"husky": "^0.14.3",
"jest": "^22.2.1",
"jest": "^23.6.0",
"lint-staged": "^6.1.0",
"razzle": "^2.4.0",
"react": "^16.5.0",
"react-dom": "^16.5.0",
"react-helmet": "^5.2.0",
"react-router-dom": "^4.2.2",
"serialize-javascript": "^1.5.0",
"ts-jest": "^22.0.3",
"ts-jest": "^23.1.4",
"tslint": "^5.9.1",
"tslint-config-prettier": "^1.7.0",
"tslint-react": "^3.4.0",
"typescript": "^2.8.1"
},
"dependencies": {
}
}
}
4 changes: 2 additions & 2 deletions src/ensureReady.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { matchPath } from 'react-router-dom';
import { AsyncRouteProps } from './types';
import { isAsyncComponent } from './utils';
import { isLoadableComponent } from './utils';

/**
* This helps us to make sure all the async code is loaded before rendering.
Expand All @@ -9,7 +9,7 @@ export async function ensureReady(routes: AsyncRouteProps[], pathname?: string)
await Promise.all(
routes.map(route => {
const match = matchPath(pathname || window.location.pathname, route);
if (match && route && route.component && isAsyncComponent(route.component) && route.component.load) {
if (match && route && route.component && isLoadableComponent(route.component) && route.component.load) {
return route.component.load();
}
return undefined;
Expand Down
4 changes: 2 additions & 2 deletions src/loadInitialProps.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { matchPath } from 'react-router-dom';
import { AsyncRouteProps, InitialProps, AsyncRouteComponentType, CtxBase } from './types';
import { AsyncRouteProps, InitialProps, CtxBase } from './types';
import { isAsyncComponent } from './utils';

export async function loadInitialProps(routes: AsyncRouteProps[], pathname: string, ctx: CtxBase): Promise<InitialProps> {
Expand All @@ -9,7 +9,7 @@ export async function loadInitialProps(routes: AsyncRouteProps[], pathname: stri
const match = matchPath(pathname, route);

if (match && route.component && isAsyncComponent(route.component)) {
const component = route.component as AsyncRouteComponentType<any>;
const component = route.component;

promises.push(
component.load
Expand Down
19 changes: 19 additions & 0 deletions src/test/components/AsyncGetInitialProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

export interface AsyncGetInitialPropsProps {
stuff: string;
}

const api = () => Promise.resolve({stuff: 'async call'})

class AsyncGetInitialProps extends React.Component<AsyncGetInitialPropsProps> {
static async getInitialProps({ req, res, match, history, location, ...ctx }) {
return await api();
}

render() {
return <h1>AsyncGetInitialProps</h1>
}
}

export default AsyncGetInitialProps;
17 changes: 17 additions & 0 deletions src/test/components/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';

class Home extends React.Component {
static displayName = 'Assessments';

static async getInitialProps({ req, res, match, history, location, ...ctx }) {
return { stuff: 'home stuffs' };
}

render() {
return (
<h1>Home</h1>
);
}
}

export default Home;
3 changes: 3 additions & 0 deletions src/test/components/NoGetInitialProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react';

export const NoGetInitialProps = () => <h1>No getInitialProps</h1>
15 changes: 15 additions & 0 deletions src/test/components/NoNDefaultExport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

export class NonDefaultExport extends React.Component {
static displayName = 'NonDefaultExport';

static async getInitialProps({ req, res, match, history, location, ...ctx }) {
return { stuff: 'non default export' };
}

render() {
return (
<h1>Non Default Export</h1>
);
}
}
17 changes: 17 additions & 0 deletions src/test/components/NonDynamicExport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';

class NonDymamicImport extends React.Component {
static displayName = 'NonDynamicExport';

static async getInitialProps({ req, res, match, history, location, ...ctx }) {
return { stuff: 'non dynamic export' };
}

render() {
return (
<h1>Non Dynamic Import</h1>
);
}
}

export default NonDymamicImport;
65 changes: 65 additions & 0 deletions src/test/loadInitialProps.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { loadInitialProps } from '../loadInitialProps';
import routes from './routes';
import createMemoryHistory from 'history/createMemoryHistory';
import { History } from 'history'

describe('loadInitialProps', () => {
let history: History;

beforeEach(() => {
history = createMemoryHistory();
})

it('should find matched component and call getInitialProps', async () => {
const url = '/'

const matched = await loadInitialProps(routes, url, { history });

const expected = routes.find(r => r.path === url);

expect(matched.match).toEqual(expected);

expect(matched.data).toEqual({ stuff: 'home stuffs' });
});


it('should retrieve initial props from async call', async () => {
const url = '/async-get-initial-props';

const matched = await loadInitialProps(routes, url, { history });

expect(matched.match.path).toBe(url);

expect(matched.data).toEqual({ stuff: 'async call' });
});

it('should call getInitialProps for non dynamic import component', async () => {
const url = '/non-dynamic-import';

const matched = await loadInitialProps(routes, url, { history });

expect(matched.match.path).toBe(url);

expect(matched.data).toEqual({ stuff: 'non dynamic export' });
});

it('should call getInitialProps for non default export component', async () => {
const url = '/non-default-export';

const matched = await loadInitialProps(routes, url, { history });

expect(matched.match.path).toBe(url);

expect(matched.data).toEqual({ stuff: 'non default export' });
});

it('should load component with no getInitialProps', async () => {
const url = '/no-get-initial-props';

const matched = await loadInitialProps(routes, url, { history });

expect(matched.match.path).toBe(url);

expect(matched.data).toBeUndefined();
});
})
44 changes: 44 additions & 0 deletions src/test/routes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';

import { asyncComponent } from '../asyncComponent';
import NonDymamicImport from './components/NonDynamicExport';
import { NoGetInitialProps } from './components/NoGetInitialProps';

const Placeholder = () => <div>...LOADING...</div>

export default [
{
path: '/',
exact: true,
component: asyncComponent({
loader: () => import('./components/Home'),
Placeholder
}),
},
{
path: '/async-get-initial-props',
exact: true,
component: asyncComponent({
loader: () => import('./components/AsyncGetInitialProps'),
Placeholder
}),
},
{
path: '/non-dynamic-import',
exact: true,
component: NonDymamicImport
},
{
path: '/non-default-export',
exact: true,
component: asyncComponent({
loader: () => import('./components/NoNDefaultExport').then((module) => module.NonDefaultExport),
Placeholder
}),
},
{
path: '/no-get-initial-props',
exact: true,
component: NoGetInitialProps
},
];
7 changes: 6 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export const isObject = (obj: any) => obj !== null && typeof obj === 'object';
export const isPromise = (value: any): boolean =>
isObject(value) && isFunction(value.then);

/** @private Guard cluase to narrow the AsyncRouteableComponent union type */
/** @private Guard cluase to narrow the AsyncRouteableComponent union type on getInitialProps */
export function isAsyncComponent(Component: AsyncRouteableComponent): Component is AsyncRouteComponentType<any> {
return (<AsyncRouteComponentType<any>>Component).getInitialProps !== undefined;
}

/** @private Guard cluase to narrow the AsyncRouteableComponent union type on load */
export function isLoadableComponent(Component: AsyncRouteableComponent): Component is AsyncRouteComponentType<any> {
return (<AsyncRouteComponentType<any>>Component).load !== undefined;
}
5 changes: 3 additions & 2 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"config",
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/**/*.story.tsx"
"src/**/*.story.tsx",
"src/test"
]
}
}
10 changes: 8 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
"strict": true,
"pretty": true,
"jsx": "react",
"lib": ["es2015", "dom"],
"lib": [
"es2015",
"dom"
],
"suppressImplicitAnyIndexErrors": true
}
},
"exclude": [
"src/test"
]
}
Loading

0 comments on commit 3e4f2f4

Please sign in to comment.